Skip to content

Cross-cutting invariants — the rules that span subsystems

  • Fail-closed authorization, everywhere. Every BFF entity-proxy read/write resolves grants under application_id=admin-bff; UMS unreachable → 503 (fail-closed); no grants returned → 1=0 deny-all. The public reader is twin-gated (Gate A surface admissibility + Gate B UMS row-RLS) and fails closed (EmptyPublishedStatesError). Action gates reuse the same criteria at read-time availability and submit-time enforcement, so CTAs never drift.

  • Tenant identity is derived, never trusted from inbound headers. Tenant comes from the JWT claim (Step 3a) and is forwarded as X-Tenant-Id on outbound ontology calls. The sole cross-plane authoring bridge is X-Author-Tenant, honored only for callers holding the right UMS tier grant (authoring_update_allowed, auth.py:275 — a raw SpiceDB tier-group read, independent of data-plane rule resolution, to prevent confused-deputy amplification).

  • Declare-not-code. Policy, actions, surfaces, and notifications are ontology rows compiled per request — not Python branches. The admin_entity_config.actions JSONB cache is dead; the rows ARE the runtime contract (#292 S2). Adding a capability = adding a row + (if new) a registry key.

  • Fork fidelity by copy-and-rewire. A new tenant comes up fully configured via one-shot full-catalog fork keyed on metadata.forkable (fork_plan.py:91-101); copied rows are FK-rewired into the tenant plane. Same editors serve every tenant because each endpoint keys on the caller's resolved tenant. Copy-on-create, no live inheritance.

  • Presentation-only design system. The renderer (@aiaiai-pt/design-system) dispatches Block descriptors to widgets via registry keys and carries no editing metadata; authoring field-schema lives in the host. TH-08: only known-good registry-key literals reach the DOM.

  • Views, not endpoints, for aggregates. Every count/tally/group-by is an ontology VIEW (the only aggregate primitive), surfaced as a Metabase card (staff) or source_kind:view public feed (citizen). Never a bespoke BFF stats route. A view bakes its creator's ACL (and every JOINed type's ACL) into a frozen compiled_sql snapshot, so X-Tenant-Id never narrows a view.

  • Dedup ledgers are written AFTER the action they guard. Idempotency rows (RecurrenceNotificationLog, ConflictNotificationLog, the action_submissions status guard) are created after the guarded action succeeds, with get_or_create (not create) under UniqueConstraint, and composite keys sorted for order-independence — so a failed dispatch can still retry.

  • Schema evolves additively only. The ontology never deletes a column; the sheet diff is no-delete; round-trip-to-zero holds (re-importing an applied sheet is a no-op). tenant_scoped is REQUIRED-EXPLICIT in the sheet grammar — but note the engine's own default is False (repo.py:208,245), the divergence named below.

  • Plane-agnostic engine. The ontology engine sees only ordinary tenant-scoped rows; all vocabulary/template/tenant and public-surface semantics live BFF-side. This boundary is deliberate and, per the open questions, possibly permanent.


Atelier — Platform Specification. Internal canonical reference.