Tenants live in a new tenants.yaml in panto-cloud-hq, not in registry.yaml
rank: decision outranks: []
Section titled “rank: decision outranks: []”Tenants live in a new tenants.yaml in panto-cloud-hq, not in registry.yaml
Section titled “Tenants live in a new tenants.yaml in panto-cloud-hq, not in registry.yaml”Date: 2026-08-18 Status: open Supersedes: none Superseded-by: none — current Upholds: MR-13
Context
Section titled “Context”panto-cloud admits tenant ORGS. petrova-codes/registry.yaml admits REPOS. The question is whether they are the same object.
registry.schema.json describes itself as a list of repositories. slug must equal the workspace directory name; contract_sha is the 12-hex join key to state/
The genuine counter-argument, which must be stated before it is dismissed: registry.schema.json ALREADY represents org-level, App-installed, repo-less entities. Line 44 makes url optional and line 53 defines role: placeholder as ‘an org has the Petrova-act App installed but no governance repo yet’, rendering as not_onboarded on /console/phases. That is an org record wearing a repo schema, and it is the strongest reason to reuse.
Decision
Section titled “Decision”Create tenants.yaml and tenants.schema.json in panto-cloud-hq. Do not extend registry.yaml.
The counter-argument loses at 20-100 tenants for five reasons:
- Scale swamps the host. registry.yaml holds 38 human-reviewed rows with multi-paragraph notes. Adding 100 near-empty rows makes the file majority-noise and degrades the human review that is its entire purpose.
- Different admission semantics. Admitting a repo grants WRITE verbs gated by profile and fleets_allowed. Admitting a tenant org grants READ on a third party’s property. Same file, two meanings of admitted, one refusal code — that is how a misconfiguration becomes an incident.
- Different blast radius, therefore different repo. A bad registry.yaml merge misgoverns the operator’s own repos; a bad tenants.yaml merge reads someone else’s. These must not share a review queue.
- Different committers. registry.yaml is human-PR-only. tenants.yaml is PROPOSED by the broker webhook on installation.created and merged by a human — a mixed path registry.yaml deliberately lacks.
- Cross-org coupling. registry.yaml lives in petrova-codes; putting tenants there would make panto-core’s broker depend on a different GitHub org’s repo at request time.
Schema mirrors registry.schema.json’s discipline deliberately: additionalProperties false, version as a const integer, shard with default global, absolute dates, notes free text.
Editing: humans merging PRs in panto-cloud-hq only. The broker may OPEN PRs and may never merge. Auto-demotion (active -> suspended on installation.deleted) is enforced at token-mint time regardless of file state, so a demotion never waits on a merge; the PR follows for the record. Promotion always requires the merge — App installation is necessary and not sufficient, the same asymmetry as probes never auto-promoting.
Follow-up, not decided here: whether existing role: placeholder rows in registry.yaml migrate to tenants.yaml or survive alongside. That is a decision, not a refactor.
Alternatives considered
Section titled “Alternatives considered”Reuse registry.yaml with tenant orgs as additional rows
Section titled “Reuse registry.yaml with tenant orgs as additional rows”Rejected: Repo-shaped schema leaves six of twelve fields meaningless for an org; 100 added rows would swamp a 38-row human-reviewed file; and it would deepen the role: placeholder seam by making one refusal code cover both write-verb admission and third-party read admission.
tenants.yaml inside panto-core or panto-state
Section titled “tenants.yaml inside panto-core or panto-state”Rejected: The runtime would govern its own admission — the component that harvests tenants deciding which tenants exist. panto-cloud-hq is governance-only with no runtime, which keeps the separation.
A database or Vercel Edge Config so the broker reads without a git fetch
Section titled “A database or Vercel Edge Config so the broker reads without a git fetch”Rejected: Puts admission outside PR review and breaks the machine-surface-bound-to-sources property. The broker can read a build-time-baked copy exactly as scripts/vercel-build.sh already stages registry.yaml and state/ into _render.func for /api/rpc to read at runtime.
References
Section titled “References”registry.schema.jsonregistry.yamlcli/src/registry.ts:88-93scripts/vercel-build.shdocs/decisions/2026-05-10-rpc-into-astro.md
Sign-off
Section titled “Sign-off”Human merge required — this PR is opened with the awaiting-human-merge
label and does not auto-merge.