Doctrine
ORA-2026-0116 — Stub-to-enriched world-model advancement
ORA-2026-0116 — Stub-to-enriched world-model advancement
Rule
Imported rows that exist in the world-model schema but do not yet make faithful project claims MUST advance on the enrichment ladder, or surface their dormancy. Importing a row creates a stub — a database object that occupies the namespace of a real-world thing without yet carrying source-backed evidence of it. Stubs that sit indefinitely are invisible permanent half-truths: they look like world-model rows to readers, queries, and downstream surfaces, but they cannot be sourced, cited, or rendered to a client without lying.
The ladder has four rungs. Every row in a world-model table MUST sit on exactly one rung, and every row that sits on a non-final rung MUST have a visible advancement path (a ticket, a backfill plan, or a tombstone candidate flag).
The ladder
stub → fact-backed → narrative-backed → surface-ready
↘ terminal-historical
| Rung | Definition | Test (mechanical) | Example (Heartwood projects) |
|---|---|---|---|
| stub | Row exists; primary identifier present (project name, person name, claim subject); no source-backed facts attached | SELECT * FROM projects WHERE address IS NULL OR job_type IS NULL OR contract_id IS NULL OR county IS NULL returns the row | "Newton Farmhouse Addition" exists with name only; no address, no county, no contract, no job_type |
| fact-backed | Core invariant facts (address, dates, parties, type, scope) present AND each carries source_kind + source_path proof | All required FK/NOT-NULL columns populated; provenance row exists in *_source_proof table | Newton has full address, county, contract id, job type, all sourced from BT contract scrape with stored URI |
| narrative-backed | Project narrative atoms (design intent, notable feature, completion story, design context) exist as source-backed claims, NOT as marketing prose mixed with facts | At least N narrative claims in project_narrative_claims table with non-null source_kind + confidence ≥ 0.6 | Newton has a sourced design-intent claim (from architect's spec doc), a sourced notable-feature claim (from photo metadata), a sourced completion-story claim (from Zack's project notes) |
| surface-ready | Visibility-eligible for public/portfolio surfaces. Has photo evidence with provenance, has consent/permission for any quoted client voice, has been reviewed within last 90 days | surface_ready_at IS NOT NULL AND last_review_at > now() - interval '90 days' AND visibility_decision IN ('public', 'portfolio') | Newton has imported StoriePro photos with hashes, has been reviewed by Zack, is approved for HCB public portfolio |
| terminal-historical | Row will not advance further. Source evidence does not exist (project too old, photos lost, narrative unrecoverable) and the row is preserved only for historical / financial / FK-integrity reasons | terminal_decision_at IS NOT NULL AND terminal_reason IS NOT NULL (e.g., terminal_reason = 'pre-2018 project, no surviving photos or narrative') — explicit operator decision required to enter this state | A 2015 small-job that exists in BT financial records but has zero photos, zero narrative source, no testimonial possibility. Preserved for cost-history; never appears on public surfaces. |
Naming discipline: these are SHAPE names per ORA-2026-0044. Borrow from the discipline that already does this work — provenance/evidence (audit), enrichment (ETL), staging (build pipelines), narrative (history), preservation (archive). Avoid _complete, _done, *_final — they describe terminal state, not the shape of the row's claim.
Why terminal-historical is a peer rung not a child of stub: dormancy alarm rules treat stub as a row that should advance. Terminal-historical rows have an explicit will not advance decision attached, so they exit the dormancy alarm path. Without this rung, every un-enrichable historical row would either be deleted (losing financial / FK integrity) or trip dormancy alarms forever.
Materialization vs. computed rungs
The rung is computed, not materialized. Implementation options (in order of preference):
1. DB view or generated column that derives current_rung from underlying invariant tests at read time. Cannot drift from data because the data is the source. 2. Materialized column with a strict CHECK constraint that enforces the rung's invariants — e.g., CHECK (current_rung != 'surface-ready' OR (photo_count > 0 AND last_review_at > now() - interval '90 days')). Acceptable when read frequency justifies caching. 3. Materialized column without a CHECK constraint: forbidden. Drifts silently when underlying facts are deleted or downgraded; produces the exact "claims-without-evidence" failure mode this doctrine is designed to prevent.
The doctrine prefers option 1 unless query latency demands otherwise. Option 2 is a deliberate engineering trade-off, not a default.
When the doctrine fires
Any of the following triggers an enrichment-state obligation:
1. Bulk import lands rows into a world-model table (FLT-0082-style): the import PR MUST file the *_enrichment_state row alongside each imported row, marking initial rung as stub with a backfill ticket reference. No imports without enrichment-state rows. 2. A surface query joins to a world-model row that is below surface-ready: the query MUST either (a) filter out non-surface-ready rows, or (b) annotate them with a "stub — not surface-ready" marker. Silent rendering of stubs as if they were enriched is a violation. 3. A row sits at stub for >30 days with no advancement ticket: shepherd tick promotes a STALE_STUB ticket to the lane backlog as a candidate for tombstone or backfill prioritization. 4. A row jumps rungs without source proof: the advance MUST carry source_kind + source_path for each fact added. Advancing stub → fact-backed by typing in facts without source attribution is a violation — the row remains a stub-with-typing.
Why
FLT-0633 StoriePro enrichment scope, 2026-04-29. Heartwood DB had 40 projects, 22 of which were closed (completed) but missing core facts: no address, no county, no job type, no contract. photo_assets had 9 rows total. testimonials table — missing entirely. project_series table — missing entirely. project_narrative_claims columns on projects — zero. walkthrough_cards had 97 rows but all 97 were Woodbery (active project), not the 22 closed projects.
These 22 rows were stubs: they existed in the projects table, they had names, they were marked as closed — but they could not be cited as faithful world-model claims. A naive query joining projects to a marketing surface would render "Newton Farmhouse Addition" as if HCB had complete narrative for it. The row's existence implied a faithfulness it did not carry.
This is ORA-2026-0036's pattern (Dormant Capability) extended into the data row dimension. The library author's parallel — code that exists but has no consumer — is the import author's parallel: rows that exist but have no source-backed enrichment. Both look like signal until queried. Both compound silently.
Relationship to ORA-2026-0036 (parent pattern)
ORA-2026-0036 names "Dormant Capability" as the parent class — assets that exist in the codebase but propagate no signal about their existence or state. ORA-2026-0066 named one child (specs without prior art). FLT-0633 surfaces the second child:
Invisible Existing Work — three known children:
1. Dormant Capability (ORA-2026-0036): code exists, nobody knows to call it.
2. Specs Without Prior Art (ORA-2026-0066): spec is written, nobody knows the code already exists.
3. Stub-as-Enriched (this doctrine, ORA-2026-0116): row exists, nobody knows it has no source-backed claim.
All three are visibility failures. The countermeasure shape is the same: make dormancy visible. ORA-2026-0036 mandates the FOLLOW-ONS activation-ticket bullet for code. This doctrine mandates the *_enrichment_state row + ladder for data.
Relationship to ORA-2026-0062 (world-model fidelity)
ORA-2026-0062 is the tacit contract — does this make the world model more faithful to reality? A stub row sitting unmarked as a stub is a fidelity violation: the schema asserts presence of a project; the row carries no faithful claim about that project. The ladder is the operationalization of fidelity for imported rows. Coverage without fidelity is map-confabulation — importing 22 closed projects without enrichment is the data equivalent of confabulating coverage.
The 4th implicit Marquet pillar lens applies here: did this output serve world-model fidelity? — if not, was the gap named? Naming the gap = filing the enrichment-state row at stub with a backfill ticket. Not naming = silent confabulation.
How to apply
Schema obligation
- Every world-model table that accepts imports MUST have a paired
*_enrichment_staterow per primary entity (one row per project, one per person, one per claim — not one per import batch). The state row carries:current_rung,last_advanced_at,advancement_ticket_id,missing_field_summary,last_reviewed_at. - Tables that hold derived narrative (testimonials, series, narrative claims) MUST carry per-row
source_kind+source_path+confidence+correction_historycolumns.
Import obligation
- Every import PR creates
*_enrichment_staterows alongside imported rows. Initial rung =stubunless the import source carries source-backed proof (rare). - The PR's DONE post FOLLOW-ONS line names the backfill ticket per import batch, OR names a rung-advancement plan, OR names an explicit tombstone-after-N-days flag.
Surface obligation (the surface-read contract)
- Default client-facing reads — any surface that can reach Zack, Aleah, Grace, or external clients (Heartwood site, Dollhouse, public portfolio, vendor-shared docs, marketing surfaces) — MUST filter
WHERE current_rung = 'surface-ready'. No exceptions, no opt-out. - Internal/admin surfaces (operator console, fleet dashboards, debug tools, agent diagnostics) MAY render lower-rung rows but MUST visually distinguish them (e.g., greyed, prefix with
[stub], badge with[fact-backed]). Silent rendering of stubs in any reader-facing surface is the violation this doctrine exists to prevent. - Code review gate: any new surface that joins to a world-model table is reviewed for surface-read contract compliance before merge. Reviewer asks "would this surface render a stub as if it were enriched?" — if yes, the join carries an explicit rung filter or annotation.
Advancement obligation
- Advancing a row a rung requires source attribution. The advancement edit MUST carry
source_kind+source_pathfor each fact added. Untraceable advances are reverted. - Skipping rungs is allowed only when the source proof covers all intermediate rungs simultaneously (rare — usually means the source is comprehensive, e.g., a single signed contract that establishes all core facts).
Dormancy alarm (decoupled from shepherd)
- Filing happens at the import boundary, not at the shepherd boundary. When an import lands stubs, the import pipeline files the enrichment-backfill ticket(s) into the lane backlog as part of the same PR — one ticket per import batch, scoped to the rows it created. This decouples fleet operational infrastructure from business data schemas; the shepherd does not query
heartwood-dborcamber-dbto discover dormant stubs. - Backlog aging is the alarm. Standard ORA-2026-0029 / Standing Directive §11 tombstone ladder applies to the enrichment-backfill ticket itself: 30 days dormant =
STALE_SINCE, 60 days =_deprecated_<original>, 90 days =_archive/. The shepherd does its existing tick on backlog tickets; no schema-aware logic required. - Optional secondary signal (lane-internal, not fleet-wide): a per-lane periodic SQL probe (e.g., a Heartwood-internal cron that runs
SELECT count(*) FROM project_enrichment_state WHERE current_rung = 'stub' AND last_advanced_at < now() - interval '30 days') MAY emit a lane-feed observation. This is lane infrastructure, not fleet shepherd infrastructure — different blast radius, different ownership. - Terminal-historical exemption: rows in
terminal-historicalare exempt from dormancy alarms regardless of age. They're not dormant; they're done.
When this is a DB/schema concern vs. backlog/proof concern
| Concern type | Symptom | Owner |
|---|---|---|
| DB/schema | Table cannot represent enrichment state at all (no *_enrichment_state table, no source-proof columns) | CODEX-STRAT — schema migration |
| Backlog/proof | Schema supports it, but rows aren't being advanced or stubs are accumulating | STRAT shepherd — backlog promotion + tombstone ladder |
| Surface/render | Query joins to a stub and renders it as enriched | CODEX-STRAT — query gate update |
| Doctrine/policy | Whether to admit stubs at all, OR whether a particular enrichment requires Zack/operator review before advancement | CLAUDE-STRAT — render decision (this doctrine) |
The first move on encountering a stub-as-enriched bug is to identify which of the four concern types it belongs to. Only one of them is a doctrine question; the other three are owner-clear once classified.
Boot/bundle parity
This doctrine joins the additional-doctrines block in ~/.claude/CLAUDE.md, ~/.codex/AGENTS.md, ~/.gemini/GEMINI.md per ORA-2026-0029 boot parity. Stamping handled by doctrine-parity-check --reconcile post-merge.
Operator note
Chad framing 2026-04-29 (FLT-0629 directive): "every byte HCB financial data from Gmail in heartwood-db by 6am ET; long-tail vision = predictive job costing/estimates/cost-to-completes/takeoffs/vendor RFIs." The long-tail vision is impossible without faithful rows — predictive cost-to-completes built on stub projects would be confabulation pretending to be foresight. The enrichment ladder is the discipline that makes the long-tail vision tractable.
See also
/Users/chadbarlow/gh/heartwood/docs/proofs/FLT-0633_storiepro_enrichment_scope_2026-04-29/README.md— the proximate case- ORA-2026-0036 — Dormant Capability (parent pattern)
- ORA-2026-0062 — World Model is the Tacit Contract (anchor lens)
- ORA-2026-0029 — Dying Well / Generative Ticket Transitions (tombstone ladder integration)
- ORA-2026-0044 — Names Carry Contracts (ladder rung naming)
feedback_primary_source_required.md— adjacent doctrine: every receipt row MUST carry primary-source PDF (the same fidelity-over-coverage shape, applied to the receipts surface)