Version. 0.1 Date. 2026-08-08 Status. STANDING. In force until the log-reconstruction item (B-70) closes. Author. Claude Code (execution session). Operator: Marvin Percival. Found by. CR-2026-190, while re-measuring B-54's fixture debt.
rebuild_shape_events_view and rebuild_render_events_view do not faithfully reproduce the projection for any engagement with events written before Phase 36. The rebuilt rows come back with display_number NULL.
This is true today, and has been true since Phase 36. It is not caused by any constraint, proposed or landed. CR-2026-190 discovered it while preparing to add NOT NULL, and the constraint's only role was to make an existing silent defect loud.
> State it that way whenever it comes up. "The constraint breaks rebuild" is the wrong sentence and leads to the wrong remedy — dropping the constraint, and calling it fixed. The constraint exposes it. Rebuild was already lossy.
Migration 0054 wrote the numbers into the projection and never into the event log.
Phase 36 introduced display_number and backfilled existing rows with ROW_NUMBER() over each (engagement, declared type) scope. That backfill was an UPDATE against shape_events_view and render_events_view. The memory_events payloads were not touched — correctly, because the log is append-only and rewriting a historical payload is not available.
The consequence follows directly. apply_event_to_*_events_view reads payload.get("display_number"). For a pre-Phase-36 event that key does not exist, so the projector has nothing to write.
So the view holds a fact the log cannot reproduce. That is the whole finding, and it is worth stating in the substrate's own terms: the derived thing carries information its source does not. Everything else here is consequence.
Incremental projection is unaffected. The upsert is:
display_number = COALESCE(shape_events_view.display_number,
EXCLUDED.display_number)
Replaying an event onto a row that already carries a number preserves it. A later shape_confirmed or render_retired on a legacy object does not null the number out.
Only rebuild-from-scratch loses it, because rebuild does DELETE FROM … WHERE engagement_id = :eid first and then replays from the log — and the log does not know.
> Note the shape of the risk. Rebuild is documented as operational recovery. It is the tool you reach for when a projection is already damaged. The failure mode is that the repair silently discards data the damaged copy still held.
playground_dev, 2026-08-08:
| Object type | Events whose payload has no display_number | Total |
|---|---|---|
| shape_event | 63 | 64 |
| render_event | 27 | 28 |
Nearly the entire log. One event of each type postdates Phase 36. Current view contents are complete — 0 NULL of 33 shape rows and 0 of 21 render rows — because 0054's backfill filled them and incremental projection has preserved them since.
Both facts are true at once, and the pairing is the point: the view is currently correct, and cannot currently be rederived.
Do not run a full rebuild on an engagement with pre-Phase-36 events without accepting that display numbers will be lost. If a projection genuinely needs repair before B-70 lands, the numbers must be captured first and restored after — read them out of the view, then rebuild, then put them back.
Do not make the projector assign numbers when the payload lacks one. Ruled out firmly by the Operator, 2026-08-08, for three reasons: it makes the projector a writer of facts rather than a reader; two rebuilds could disagree with each other; and it inverts the log-is-truth / view-is-derived relationship the substrate rests on. The convenience is real and the cost is the substrate's central guarantee.
The remedy is B-70 — compensating events carrying the numbers 0054 computed, appended to the log so the log holds what the view holds. Append-only, invents nothing, and makes rebuild correct rather than making it safe to avoid.
display_number NOT NULL cannot land until B-70 closes. A rebuild of a legacy engagement would fail at the database level rather than silently returning NULLs — which is a better failure, but still a failure, and it would take the recovery path out of service entirely.
B-54 therefore closes at its code-level guarantee. Every production path assigns a number (CR-2026-176), and every fixture now does too (CR-2026-190). The database half is deferred pending B-70, not judged unnecessary — the distinction matters, because a deferred item is one somebody still owes.
DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — standing note: legacy engagements cannot be rebuilt from the log — v0.1 — 2026-08-08