Skip to main content

Module tenants

Module tenants 

Source
Expand description

Multi-tenant tenants/<id>.toml registry for the revalidate receiver.

Part of R446 — the relay annotation lives at the top of crate::revalidate; this module carries the implementation.

§Why

crate::revalidate::serve hosts one workload dir + publish config on a process. The cloud-tier runner hosts many surfaces (yah.dev’s releases page today; more tenants later) on one mesofact serve --revalidate process. Each tenant has its own revalidate identity (a bearer the receiver checks) and its own render/publish target (its built workload + its publish config). This module routes an inbound poke to the right tenant by bearer, then runs the existing crate::revalidate::revalidate_once against that tenant’s workload — the render/publish half is unchanged, only multiplied.

§Boundary (deliberate)

A tenant references its own mesofact_publisher mesofact.config.toml (publish_config), not yah’s .yah/services/<svc>/mirrors/<env>.toml. mesofact is an independently-exportable workspace and mesofact_publisher::PublishConfig is deliberately yah-agnostic (S3 endpoint + env-named creds, zero yah types). Composing the publish target from a yah mirror is a yah-side concern: a yah reconciler generates each tenant’s mesofact.config.toml from the mirror toml. Keeping that translation on the yah side preserves the export boundary while still letting yah stay DRY.

§Routing contract

Inbound POST /revalidate {route, mirror_key, data_inputs?}:

  • mirror_key absent/empty, or matching no tenant → 403 (a tenant with no configured bearer is unroutable — reject, never open; multi-tenant has no “open” mode because the bearer is the tenant selector).
  • an explicit route outside the matched tenant’s routes allowlist → 403 (scoping, not authentication — see TenantFile::routes).
  • a data_inputs key that escapes the workload → 400.
  • matched → enqueue a TenantJob for that tenant’s workload + publish_config → 202.

The poke’s carried data_inputs (yah R330-F33) matter more here than in the single-tenant case: a multi-tenant runner is exactly the box that gets replicated for capacity, and the payload is what makes any replica able to service any tenant’s poke. See crate::revalidate’s module docs.

Secrets never live in tenants/<id>.toml: the bearer is named by mirror_key_env and resolved from the environment at load, mirroring PublishConfig’s *_env credential convention.

Structs§

ResolvedTenant
A tenant with its bearer resolved for the running process.
TenantFile
On-disk shape of one tenants/<id>.toml file.
TenantJob
A validated poke routed to a specific tenant, handed from the HTTP handler to the render/publish worker.
TenantRegistry
The resolved multi-tenant routing table. Immutable for the process lifetime (a config change is a redeploy).

Functions§

load_tenants
Load every *.toml under dir as a TenantFile, deterministically (sorted by path). A missing directory is “no tenants” (empty, not an error). Each file’s id must equal its filename stem. Bearer resolution is the caller’s next step (resolve_tenants).
resolve_tenants
Resolve mirror_key_env names to bearers via a caller-supplied lookup. The production path passes |name| std::env::var(name).ok(); tests pass an in-memory map, keeping load_tenants free of process-env coupling.
router
Build the multi-tenant receiver router: POST /dawn routes by bearer, plus the crate::health probes. Decoupled from the render/publish worker via tx so it is unit-testable without V8 or a network publish — same split the single-tenant crate::revalidate receiver uses.
router_with_health
serve
Run the multi-tenant revalidate receiver: bind port, serve the router, and drain routed TenantJobs through revalidate_once one at a time (renders serialized — one V8 boot at a time bounds the footprint). Runs until a hard I/O error.