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_keyabsent/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
routeoutside the matched tenant’sroutesallowlist → 403 (scoping, not authentication — seeTenantFile::routes). - a
data_inputskey that escapes the workload → 400. - matched → enqueue a
TenantJobfor 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§
- Resolved
Tenant - A tenant with its bearer resolved for the running process.
- Tenant
File - On-disk shape of one
tenants/<id>.tomlfile. - Tenant
Job - A validated poke routed to a specific tenant, handed from the HTTP handler to the render/publish worker.
- Tenant
Registry - The resolved multi-tenant routing table. Immutable for the process lifetime (a config change is a redeploy).
Functions§
- load_
tenants - Load every
*.tomlunderdiras aTenantFile, deterministically (sorted by path). A missing directory is “no tenants” (empty, not an error). Each file’sidmust equal its filename stem. Bearer resolution is the caller’s next step (resolve_tenants). - resolve_
tenants - Resolve
mirror_key_envnames to bearers via a caller-supplied lookup. The production path passes|name| std::env::var(name).ok(); tests pass an in-memory map, keepingload_tenantsfree of process-env coupling. - router
- Build the multi-tenant receiver router:
POST /dawnroutes by bearer, plus thecrate::healthprobes. Decoupled from the render/publish worker viatxso it is unit-testable without V8 or a network publish — same split the single-tenantcrate::revalidatereceiver uses. - router_
with_ health - serve
- Run the multi-tenant revalidate receiver: bind
port, serve the router, and drain routedTenantJobs throughrevalidate_onceone at a time (renders serialized — one V8 boot at a time bounds the footprint). Runs until a hard I/O error.