Data model stewardship runbook

Status: procedure reference. The rules — schema audit before large features, Organization as canonical identity, use the shared primitives — live in AGENTS.md §11 and stay always-on. This file holds the primitive inventory, helper boundaries and audit indicators. Relocated by BI-0020D511 Phase 1; no rule was dropped.

Before adding any large feature, audit the existing schema for refactoring opportunities. Indicators that refactoring is needed: a domain model being reused as a shared concept; the same logical data appearing in two+ existing models; a new feature needing meta-data with no canonical home. → kernel principle

Organization is the canonical platform identity model. Any feature needing org name, slug, logo, address, or contact info reads from Organization — not from BrandingConfig, env vars, or bespoke fields elsewhere. → kernel principle

The Organization.address JSON has one canonical shape + helpers in apps/web/lib/shared/org-address.ts (OrgAddress, parseOrgAddress / serializeOrgAddress / formatOrgAddressLines, resolveTimezoneFromAddress). Read and write the address through those — do not hand-roll a parallel address field or shape. It is captured at setup via the business-context step (/storefront/settings/business) and is the precise source for state-accurate timezone derivation (BI-AAAA0691).

Shared micro-primitives (BET-6, BI-6A505BFF). Cross-cutting helpers that were hand-inlined at hundreds of sites now have one home each — import them, do not re-copy:

Route-segment helpers vs shared domain modules (BI-IMP-BC5AA87E). Page-local helpers under a route segment (e.g. apps/web/app/(shell)/…/_helpers.ts or a colocated *.ts next to page.tsx) may format, adapt, or present canonical domain data for that surface only. They must not invent a second home for reusable agent/delegation policy, tool metadata, grant maps, persisted outcome contracts, or identifiers that other routes need — those live in shared modules under apps/web/lib/ (or packages). If a helper is imported from a second route or encodes policy that would change coworker behavior platform-wide, promote it to a shared canonical module in the same PR.

Metadata governance (BI-IMP-FA900452, BI-IMP-52761525). JSON metadata columns (e.g., unstructured payload fields) are reserved for optional, unstructured, or rapidly-evolving context. They must not be used as primary query or reporting sources. Any property that becomes frequently filtered, queried, or joined must be promoted to a typed schema field (via a database migration). When reading or writing JSON metadata in code, use typed accessor helpers and centralized key constants rather than raw string indexing. A provider publication/thread/conversation id is normalized through the owning domain’s canonical identity or relationship mapping before a report joins on it; an incidental metadata key is never an implicit foreign key. → kernel principle · reporting boundary

Principal convergence (2026-05-09). Per the addendum on docs/superpowers/specs/2026-04-22-enterprise-auth-directory-federation-design.md, any new identity-bearing entity introduced after 2026-05-09 must be modeled as a PrincipalAlias linked to a single Principal, not as a parallel identity table. The convergence target covers User, CustomerContact, Agent, EdgeNode, MobileDevice, and ServiceAccount. Authorization decisions resolve on the Principal; alias kind tells the platform which surface authenticated the request. → kernel principle

Record lifecycle convention

The ONE “not active” convention (BI-C357FA5A, Simplify & Strengthen W20, architecture pass 2026-08-16 §3.2-d). The pass measured “not active” said six ways across the schema — archivedAt, supersededById, mergedIntoId, quarantinedAt/overlapQuarantinedAt/conflictQuarantinedAt, retiredAt, and status="quarantined" — so every reader had to know which convention applies per table. The unified convention is:

Semantics per state: archived = kept for reference, excluded from operational reads; retired = permanently withdrawn from service; superseded = replaced by a successor record (pointer required); merged = collapsed into a survivor record (pointer required); quarantined = excluded pending integrity review (the record is suspect, not the data subject).

Enforcement. scripts/check-no-new-notactive-conventions.mjs blocks NEW legacy-convention columns schema-wide; the 29 existing carriers live in the shrink-only owned baseline scripts/notactive-conventions-baseline.json (owner platform-architecture). New String status columns carrying quarantine vocabulary are already blocked by scripts/check-no-new-closed-set-strings.mjs.

Pilot family. The W19 unified resource-scheduling models (Resource, ResourceAvailability, ResourceCapacityPool, ResourceCapacityAllocation) are born on the convention — zero-data adoption. Existing carriers migrate per the operator-reviewed plan: docs/superpowers/plans/2026-08-18-w20-lifecycle-convention-migration-plan.md. Business-state machines (e.g. an allocation’s reserved→confirmed→released flow) remain domain enums — the record lifecycle says whether the ROW is live, not where the BUSINESS process stands.