DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path inspection-briefs/loomworks-b70-log-reconstruction-findings-v0_1.md

Loomworks — B-70 log-reconstruction findings — v0.1

Version. 0.1 Date. 2026-08-08 Author. Claude Code (execution session). Operator: Marvin Percival. Charter. standing-notes/dunin7-standing-authorization-charter-v0_1. Produced by. change-requests/cr-2026-191-loomworks-b70-log-reconstruction-v0_1, Step 1. Read-only. No code written. Baseline. Engine 3bcf4f6, tag cr-2026-190-b54-fixtures.


1. The headline

The remedy CR-2026-191 §1 treats as settled is not this project's precedent. Three migrations have already faced this exact situation and resolved it a different way, with the reasoning written down.

Four migrations have backfilled an operational identifier into existing objects. 0054 is the only one that did not also write the log.

| Migration | What it backfilled | Wrote memory_events? | Wrote the projection? | |---|---|---|---| | 0041 (Phase 16) | assertion display_number | yes | yes | | 0055 (Phase 38) | specification grammar | yes | yes | | 0094 (CR-2026-121) | held-assertion display_number | yes | yes | | 0054 (Phase 36) | shape/render display_number | no | yes |

0041 is the direct ancestor — the same identifier, on assertions, thirteen migrations earlier — and its docstring states the rationale:

> "Data immutability: this writes display_number into existing JSONB payloads in place. R-A28's non-erasure discipline governs ongoing operation — the assertion's content / state / commit facts are never mutated. display_number is an operational identifier that didn't exist before this migration; surfacing it after the fact is a one-time data evolution, not an audit-trail rewrite."

0094 restates the same posture and adds determinism and reversibility: a documented ordering, a WHERE clause that skips already-numbered rows, and a downgrade that strips the key.

So 0054 looks less like a considered choice than an omission — the one member of its family that skipped a step its siblings took deliberately.

> This does not settle the question. In-place payload mutation is the thing append-only discipline exists to forbid, and three prior instances make a practice, not a permission. What it does settle is that the remedy was chosen without this on the table.


2. §2.1 — what a compensating event asserts, and who asserts it

The structural problem, independent of the philosophy

memory_events carries UNIQUE (object_id, object_version). append_event derives the payload from a complete MemoryObject and takes the row's timestamp from object.created_at.

A compensating event must therefore bump the object's version. There is no way to append a fact about an existing version without minting a new one.

What that writes into the record:

> That is noise written into the record in the append-only discipline's name. A person reading that engagement afterwards sees 52 revisions that never happened. The discipline exists so the record tells the truth about what occurred; a remedy that satisfies its letter by fabricating history is not obviously serving it.

No honest author exists

ActorKind is contributor | agent | person | companion. All four are in live use — 599 person, 299 contributor, 187 agent, 77 companion events in playground_dev. None of them is the system.

A compensating event has no truthful actor. Writing one requires either misattributing a repair to a party who did not perform it, or inventing a fifth actor kind — which is a substrate decision, not a change-request decision, exactly as §2.1 anticipated.

Dating

timestamp comes from object.created_at, which the caller supplies. Backdating is mechanically available and should not be used — but note that the safeguard is convention, not schema. Nothing in the substrate prevents an event from claiming an earlier moment than the one it was written in.


3. §2.2 — the numbers are recoverable, and undrifted

Recoverable: completely. Of the objects whose log carries no number, 0 of 32 shapes and 0 of 20 renders lack a number in the projection. Every object that needs one has one available.

Undrifted: exactly. Recomputing 0054's own ROW_NUMBER() formula against today's views reproduces all 33 shape and all 21 render numbers with zero mismatches. Nothing has modified them since the migration ran.

> So the compensation would record what 0054 computed, not a number that has since moved — which was the risk §2.2 raised. It is not present.


4. Scope, across all engagements

| | events missing the key | events total | |---|---|---| | shape events | 63 | 64 | | render events | 27 | 28 |

Across two engagements: 59/60 and 20/21 in the live one, 4/4 and 7/7 in a second.

Those are event counts. The distinct objects are 32 shapes and 20 renders — the gap is versions, because a number must ride every version of an object, as 0041 established when it updated all versions of each assertion.

The key is absent, never JSON-null, in all 90 events. That matters for idempotency: NOT (payload ? 'display_number') is unambiguous here, with no third state to disambiguate.

Idempotency is available on either path. 0041 and 0094 both key on the same predicate and are safe to re-run; a compensating-event path would need an equivalent guard, and has one available.


5. Seed-mutability impact — Kind C, checked, no contact

All 64 shape events carry seed_version_at_production; no render event does; no seed_ref appears in either type.

The field stamps the seed version in force when the shape was produced. Numbering never reads it, and nothing about it changes if the seed moves. Compensation on either path would carry it forward untouched and would introduce no new seed dependency.

> Seed-adjacent, not seed-dependent — B-69's worked example holding exactly as written, on the first entry recorded after it was adopted.


6. What the decision actually is

Not "compensating events versus nothing." The live alternative is finishing what 0054 started — the same in-place backfill its three siblings performed.

| | compensating events | in-place backfill (0041's pattern) | |---|---|---| | Append-only letter | satisfied | violated | | Record afterwards | 52 fabricated revisions | unchanged | | Author | none exists honestly | not applicable | | Precedent | none | three migrations | | Reversible | hard | yes (0041, 0094 both downgrade) | | Size | large | small |

Underneath both sits a question neither answers, and it is larger than B-70: is an operational identifier that did not exist when the object was created an audit-trail fact at all?

0041 claims it is not. If that claim holds, three migrations applied it correctly and 0054 skipped a step. If it does not, three migrations need answering for, and B-70 is the smallest part of the problem.

That question has never been ratified. It has only been acted on. It is put to the Operator in CR-2026-191 v0.2.


DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — B-70 log-reconstruction findings — v0.1 — 2026-08-08