Ownership Boundary
What the Registry owns (schema + default UI), what the Portal owns (tenant runtime data), and what the Tenant owns (their brand profile values).
Ownership Boundary
The single most important rule of the brand system is the separation between governed code distribution and runtime client state. Getting this boundary right is what lets the Registry be a canonical source for the Portal, Marketing, and Remotion without becoming a dumping ground for operational state.
Non-goal (PRD): the Registry does not store client operational state, uploaded production assets as registry items, or Remotion render outputs. Per-tenant governed config (brand tokens, guidelines, components) is in scope; operational / app state is not.
Three owners
| Owner | Owns | Examples |
|---|---|---|
| Registry | Schema + default UI + governed config | The token contract (brand-requirements), default UI components, the {ns}-brand-system registry:base, versioned snapshots |
| Tenant | Their brand profile values | The per-brand values for the contract variables (their brand-system.css instance), their curated component set, their assets |
| Portal | Tenant runtime data + approvals | What a client actually signed off on (approved operational snapshots), live client state, the Guideline Viewer UI |
Registry owns: schema + default UI
The decision (execution recommendation §4, gate 2, resolved 2026-06-03): Registry ships the TypeScript/Zod contract for brand requirements plus default UI components; the Portal injects tenant data at runtime. No tenant runtime state lives in the Registry.
Concretely:
brand-requirements— the shipped JSON-Schema for the contract (the token contract).{ns}-brand-system— a shippedregistry:baseitem that is a complete instance of the contract for one tenant (governed config — values authored and git-reviewed, not runtime state); Vandoko's isvandoko-brand-system.portal-brand-workbench(planned) — UI that will consume the contract with placeholder / default data only, carrying no tenant runtime state. Not yet in the registry.
Tenant owns: their values
A tenant (an internal Vandoko brand or an Agency customer) owns the values of the contract — their registries/{ns}/brand-system.css instance, their curated component sources, and their assets. Because each tenant ships a complete instance of the contract (not a partial override that falls through to a shared Vandoko base), a customer install is self-contained and brand-isolated — Vandoko's identity never leaks into a customer's bundle (ADR 0002 §3).
Portal owns: runtime data
The Portal holds tenant-approved operational snapshots — what a client actually signed off — in its own database. The Registry serves governed, versioned packages; the Portal pins a version and renders the brand for the client. The Portal consumes the Registry through:
- the shadcn CLI for code/components, and
- the runtime API (
/api/brand-system/*) for brand packages and the normalizedguidelines.jsonsummary (ADR 0002 §7).
See Multi-Tenant Model for how isolation is enforced and Versions & Snapshots for the runtime API.
Why this boundary
- Auditable & reviewable. Governed config is git-committed CSS/JSON; every brand change is a reviewable diff with a changelog entry.
- Bounded blast radius. No customer-operational state in the Registry means a compromised Registry credential exposes governed packages, not live client data.
- Clean migration story. The Registry can become the governed Registry app in a future Vandoko platform without dragging runtime state across the boundary.
Token Contract
The canonical CSS-variable contract every brand instance must satisfy — 67 tokens, a date-based contract version, the exact-match rule, and pnpm brand:lint.
Multi-Tenant Model
Governed distribution, file-based serving namespaces, per-tenant {ns}-brand-system instances of one contract, and cross-tenant isolation.