#312. The Stripe outbox retried a product or price create that Stripe answered with its own server error for a day, under an idempotency key Stripe answers from its saved 500 every time, so the product could not be sold and the readiness panel said the price was syncing all day. After the dead-letter, Retry put the same row back and sent the same key again. A 500 does not say whether Stripe created the object, so a new key alone could create a second one. A keyed create (product, price, customer) that Stripe answers with its own error object at 500 or above is now dead-lettered after that one attempt, with the reason "Stripe answered with a server error; whether it created the price is unknown" (product, customer). A gateway's error page, which carries no Stripe error object, and a request that gets no answer stay retried. reconcile_subscription, which sends no key of its own, keeps retrying. Retry counts each requeue (migration 00039: core.outbox.requeues), and a requeued entry's key gains the count, so its create is a new request. Before that create, a requeued product or price entry lists what Stripe made since an hour before the entry was written, through Stripe's list rather than its lagging search, and maps the oldest object whose metadata carries the console's id (a price also with the same amount, currency, compared without case, and interval, and on the same product); it creates only when none matches, and logs the other matches. An entry never requeued keeps its key. The three creates carry the attempt's context, so none outlives its attempt beside a Retry. Copy, approved: the panel's failed-sync row ends at the reason with a full stop, without "Use Retry to run it again."; the line under the control is gone; the control reads "Find or create in Stripe" in place of "Retry Stripe sync". The reader-columns golden lists the new column, and the dead-lettered sync golden loses the two removed lines. docs/stripe.md and docs/models/payments-billing.md say what the outbox does. Filed from the work: #314 (Stripe's event for an object made after a 500 is never recorded) and #315 (a checkout can leave a second Stripe customer); the panel's share of #149 and the public-details reads' share of #313 go there as comments. Deploy: no data change. Before it, a read-only count of the Stripe create entries by status; after it, an operator presses Find or create in Stripe for each product whose panel shows a failed sync. A deployment using the console_reader role re-runs scripts/db-reader-role.sql as the schema owner. Break list: 52 records, 52 CAUGHT. Named without a record: the migration's default (a break of it fails every database test at the migration, before any test's claim) and the comments of items 1.2, 2.2 and 2.5. Full suite on reset databases: 5652 passed, 16 skipped, none failed. Screens: the product detail shots lose the two removed lines; the other changed shots are timestamps and regenerated ids, as before.
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).