Skip to content

Lifecycle

End-to-end lifecycle — author → import → fork → seed → run → diagnose → evolve

1. Author a sheet. An operator writes a Vertical Sheet (YAML) — or edits live in the admin editors, which round-trip to the same rows. The sheet declares one application across all three functional planes under its identity.code (== application_id): entities: (data), actions: (execution), public_surfaces:/layers:/portal_pages: (surface), plus views:, notifications, acl_rules:, and admin_configs:.

2. Import. prepare_sheet_plan (sheet_apply.py:49) runs the four-stage shared pipeline — parse → validate → diff → apply — and walks the FK-ordered APPLY_ORDER (yaml_import.py), creating ontology rows under tenant_id = the target plane. Two front doors exist: direct YAML import (CLI / boot) and the authoring-hub round-trip. Idempotency is find-or-create / 409-converge. Rows land on the vocabulary plane (types, registries) and the template plane (the worked example).

3. Fork. A new municipality comes up via the one-shot 5-phase fork_tenant: it copies every row whose type carries metadata.forkable from template_municipality and FK-rewires them into the new tenant's plane (fork_plan.py:91-101). No fork.py edit is needed to onboard a new forkable type — declare the type with metadata.forkable:true and it forks. This is copy-on-create: there is no live inheritance.

4. Seed. seed_initial_tenant_admin provisions the first staff principal (fork itself seeds no staff). register_entity_types provides a floor of platform types; derive_tenant_wide_plan materializes tenant-wide config. Data rows (not config) are seeded separately.

5. Run. The generated experiences serve traffic:

  • A citizen opens the portal (generated from portal_page rows); a public read passes Gate A (surface admissibility) + Gate B (UMS row-RLS) + a disclosure projection.
  • A citizen submits: /public/submit writes a durable action_submissions ledger row (the 202 is durable before any entity exists), emits a CloudEvent to shared Martha, which calls back POST /submissions/{id}/create-entity to materialize the row.
  • A staff user opens the admin console; OccurrenceAdmin-style views and dashboard_config render through the shared renderer. An action fires: the BFF compiles the action_type rows into an ActionDefinition (30s TTL, loader.py) and runs the uniform 8-step pipeline — validate, gate (UMS apply + preconditions), mutate (PATCH + compensating creates), audit (edit_event), fan out (notification / CloudEvent / Martha).
  • A side-effect emits a named notification_event; the dispatcher resolves notification_rule rows (tenant-scoped, over HTTP), applies D2 filters then D3 tenant precedence, and fans out one Temporal NotificationWorkflow per rule on the admin-notifications queue.

6. Diagnose. When the portal shows "no data" or "nav blank," it is almost always a provisioning gap, not a code bug: a fail-open boot import that silently 422'd, an unrooted portal route, an unresolved placement binding, or a missing nav_section row. Diagnosis scripts and manifest build guards (advisory today) catch these classes.

7. Evolve. The tenant admin edits THEIR forked copy with the same editors (act-as not required — they own their plane). A platform author edits the template plane via X-Author-Tenant; preview==live and compile-on-save mean publishing a portal_page synchronously emits its graduated UMS ACL rules so reader and writer agree by construction. The unsolved evolution edge: a fleet-wide template change does not propagate to already-forked tenants — there is no re-sync. This is the north-star gap.


Atelier — Platform Specification. Internal canonical reference.