Third section of #23. Two runtime paths worked only because the console's login is a superuser; once it runs as console_runtime, both would fail. Creating a partition requires owning the parent table, so the boot step and the maintenance schedule could not create a webhook_events partition as the runtime user, and inserts would start failing once the lookahead ran out. Migration 00031 gives core.webhook_events and each of its partitions to core_owner (partitions do not follow their parent's owner) and adds core.ensure_webhook_event_partitions, a SECURITY DEFINER function owned by core_owner, with a pinned search_path, EXECUTE only for console_runtime and a window of 0 to 12 months, that computes month bounds in UTC whatever the session's time zone. EnsureWebhookEventPartitions calls it. The core baseline's initial partition block now computes its bounds in UTC too (a quick fix listed in the change): in a non-UTC session it ended the first partitions at local midnight, which the next UTC partition would overlap. The plan-act obligation move locked its row with SELECT ... FOR UPDATE on core.plan_act_obligations, which the enclosure leaves the writer only SELECT on; Postgres requires UPDATE for a row lock, so every move would fail as the runtime user. Migration 00032 adds core.lock_plan_act_obligation, owned by core_owner, which takes the same lock and returns the same row, and the LockPlanActObligation query calls it. The lock lasts until the caller's transaction ends, as before; docs/database-locks.md says where it is taken now. schemaSnapshot compares ACLs as sets, so the ownership move's down-then-up round trip compares equal on a correct Down, which re-grants core_owner. TestPlanActOwnerRuleIsStatedOnce changed: LockPlanActObligation leaves its list of queries that call core.plan_act_owns_org_type, because it now calls core.lock_plan_act_obligation, and that function joins the list of functions whose definitions must call it. TestBaselinePartitionBoundsAreUTC checks the bounds on any day, but it can observe the UTC month computation only within the 14 hours before a UTC month start, because the baseline block reads the real NOW(); a run that crosses a UTC month start between the migration and the test skips. The function's session-zone break is caught by the partition-exists assertion (the gap test's now is 2119-12-31 12:00Z, already 2120 in Pacific/Kiritimati); the last-hour insert catches bounds rendered without AT TIME ZONE 'UTC'. Full suite on reset databases: 4215 pass, 15 skip; one failure, TestEntitlementSetsWalkthrough's missing toast (#222), passed when run alone. make lint passes. Deliberate breaks, scripts/mutation-check -side new, all CAUGHT (33), among them: the month or the bounds in the session's time zone; no EXECUTE grant to console_runtime; SECURITY INVOKER; no SET search_path; EXECUTE left with PUBLIC; the partition ownership loop removed; a window of 13 allowed; monthsAhead - 1 passed; the baseline block in the session zone, or restored whole; the Down giving the parent or the partitions to the wrong owner, or without the core_owner re-grant; ACLs compared in stored order; the direct-DDL test without its SET LOCAL ROLE; the lock query as the bare FOR UPDATE again; the lock function without FOR UPDATE, without the owner rule, as SECURITY INVOKER, or returning a composite row; the owner-rule test's function list without the lock function.
title, audience, summary
| title | audience | summary | |
|---|---|---|---|
| Domain Model Cards |
|
One card per domain model: purpose, invariants, dimensions, drift traps, and open questions, each claim cited to the migration, file, or decision that enforces it. |
Domain Model Cards
Each card in this directory describes one domain model as it is actually built, so a maintainer can modify that model without re-deriving its rules from migrations, specs, and decision documents. The cards are the descriptive home for the as-built models; the normative record — the designed schema, the decision log, the numbered design documents, and the glossary — lives in the synced design/ directory (Decision 141, design/documents/doc-43-schema-topology-and-documentation-division.md). Migrations are the ground truth for as-built claims.
The catalog was motivated by a 2026-07 inventory of the console's domain models, which traced the majority of a 56-finding UX audit to model-attributable causes: rules that lived only in prose, invariants enforced nowhere, and dimensions that were easy to conflate because no single page named them side by side.
The cards
- Product / catalog — how capabilities are packaged, priced, and sold; the dissolved kind taxonomy and the shape views that replaced it.
- Payments and Billing — billing accounts, subscriptions, invoices and payments projected from Stripe, scheduled changes as intent, and the reconciler.
- Plan Ladders and Transitions — ranked tiers, who occupies which rung, the append-only audit, and the conferral functions as sole writers.
- Entitlements — resource keys, sets and rules, grants as decrees, provisions as delivery, and the materializer.
- Acts — the act record for actions that write several rows, the plan acts' details, the obligations a default change or reorder owes its organizations, and the functions that are the only writers of its tables.
- Resource Pools — the per-organization container: workspace assignments, the two resolution directions, and pool-grain usage.
- Providers and Integrations — the registry and its connections, boot-time manifests, resource-key ownership, desired-state delivery, and the shared substrate.
- Domains Registry — claims as subtree ownership, placements as the only thing that serves, and the anti-abuse ledger.
- Identity, Organizations, and Workspaces — users vs persons, the containment ladder, org types as policy, and first-login provisioning.
A new domain model is not considered documented until its card exists and is listed here.
Card anatomy
Every card has four sections in this order:
- Purpose — what the model owns and why it exists, in plain present tense.
- Where this lives — a map of the model's pieces: each table, view, function, and code file, with one line saying what it is. Locations live here, not scattered through the body.
- Invariants — the rules the model guarantees, one per numbered entry so reviews can cite "invariant 3". Each entry carries an enforcement tag matching the actual mechanism: [db] means the database schema rejects violations outright; [db-code] means the database functions are written to uphold the rule, so reviews must protect it when they change; [app] means only application code upholds the rule, so new code paths must uphold it too.
- Dimensions — the orthogonal axes a maintainer must hold simultaneously, each defined where it is named.
Cards describe the model as it is today, for a reader who knows nothing yet. That means: no history, no decision numbers, no change narrative in the body — a card introduces every table, column, view, and concept before or where it first uses it. Drift between design and code, open questions, and other bookkeeping live on the forge tracker, not in the cards.
Style contract
Card prose follows these rules, and edits are reviewed against them:
- Write for someone with zero context: never assume the reader knows a term, a table, or an internal codename you haven't introduced.
- Name the agent: state what acted and what it acted on.
- Give each verb its own object; do not let one object serve two verbs.
- Connect clauses with explicit causal or temporal words (because, so, when, after) — never with a bare comma.
- Replace every metaphor or compressed phrase with the literal claim it stands for.
- Give every count or statistic its denominator.
A change that alters an invariant, dimension, or map entry recorded on a card must update that card in the same change (see openspec/specs/model-catalog/spec.md once this change archives).