DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path change-requests/cr-2026-159-provenance-seam-v0_10.md

DUNIN7-M4 — INFRASTRUCTURE CHANGE REQUEST

CR-2026-159 — B-29: the provenance seam — v0.10

Version. v0.10 Date. 2026-07-31 Author. Claude.ai (drafting session). Approving: Marvin Percival. Charter. standing-notes/dunin7-standing-authorization-charter-v0_1. No session executes a change request it drafted (§1). Target. /Users/dunin7/loomworks-engine, branch cr-2026-159-option-a. Supersedes. v0.9, drafted and never filed — halted at pre-flight. v0.8 at record 9c6f9a5, v0.6 at c53476b, v0.5 at 6165bf6, v0.1–v0.4. All filed versions stand as siblings, unaltered. The record holds no v0.7 — drafted, halted at pre-flight, never filed. Status. RESUME. src/ is complete at branch head f3a1bbf. The remaining work is the test tree.

Changes from v0.9. One stale anchor, no design change. §4 told the executing session to re-derive the test-tree counts "at Step 0" — a step this run does not contain, since §5 begins at Step 3 and §8 forbids performing Step 0. The instruction was right and its anchor was stale, so §4 and §8 gave the same instruction pointing at two different places, one of them prohibited. Second occurrence of the same defect — v0.7 left §9 anchored to a renumbered sequence in the same way. The generator is renumbering a sequence without sweeping for references to the old numbers. (v0.9 was halted at pre-flight and never filed.)

Changes from v0.8, carried. The design is confirmed and the test tree is scoped. v0.8 sized the work at 83 sites in src/ and was silent on the test tree, which holds a reported 322 append_event calls across 88 files that a required parameter breaks. Option A is confirmed by Operator ruling (2026-07-31). This version adds the invariant that makes option A correct, the constraint on pass-through that v0.8 stated nowhere, two test helpers that make the work mechanical rather than 322 hand edits, and the second shape-three site the build found.


1. The Operator ruling, and the alternatives set aside

The build halted at a real fork. Finishing option A required deciding something no prior version had: what happens when a model_copy advances an object's version without re-minting its provenance. Under the old fabricated-value regime that was silently wrong and worked. Under option A it is a hard IntegrityError — the build session reproduced 292 of them, all duplicate key value violates unique constraint "substrate_events_pkey".

Three alternatives were put to the Operator. He ruled: finish option A.

Set aside — drop the field. Provenance loses wasGeneratedBy; a provenance walk joins on memory_events.event_id, which is already the primary key with a uuid4 default. The drafting session leaned this way, on the ground that a field required to equal a column in the row carrying it is a denormalisation, and that this arc's whole cost is maintaining it. Set aside by the Operator. Recorded here in full because it is the alternative most likely to be revisited, and because the reasoning against maintaining the duplicate does not disappear by being overruled — it becomes a known, accepted cost.

Set aside — option C, stamp-and-return. append_event stamps the field and returns the corrected object, repairing option B's one defect. Cheapest to build; the test tree would need no change at all. Set aside. It keeps the duplicate permanently and leaves 83 sites stamping a discarded value.

Chosen — option A, finished. The caller mints; the object the caller holds and the row written carry one value by construction. Highest build cost, and the only option in which no value anywhere in the system is fabricated, corrected, or derived after the fact.


2. The invariant

One object version is one event is one freshly minted identifier.

This was implicit in every prior version and is why the build broke. Stated plainly:

2.1 The pass-through constraint, stated for the first time

v0.6 introduced shape three — the constructor mints, the append passes through event_id=obj.provenance.wasGeneratedBy. It did not say when that is safe, and the build found the boundary the hard way.

Pass-through is valid only where the object was freshly constructed and is appended exactly once.

It is invalid for any object obtained by model_copy from an already-appended version, because that object carries the prior version's identifier. A pass-through at such a site is the 292-error case.

[EXECUTING SESSION: every pass-through site must satisfy both conditions — freshly constructed, appended once. Name the constructor in a comment per acceptance item 9. Any site where either condition cannot be established halts.]


3. What is already done

Branch cr-2026-159-option-a at f3a1bbf. main unmoved at a317051.

A second shape-three site the build found, not listed in v0.8: reconciliation_evaluator.surface_proposal_to_accounting_room, whose constructor detect_reconciliation_drift mints one identifier per proposal in a loop sized by the number of drifting accounts. Same structure as the Finding case, same treatment, constructor named in a comment. Recorded so the count of shape-three sites is two, not one.

Nothing in src/ is revised by this version.


4. The test tree

Reported by the build session and to be re-derived at the resume re-confirmation (§8), not inherited: roughly 322 append_event calls across 88 files, and roughly 54 version-advancing model_copy sites. [EXECUTING SESSION: report the actual counts alongside the other re-confirmations, before Step 3. If they diverge materially from these, say so — these are one session's numbers, not a verified baseline.] (Anchor corrected at v0.10. v0.9 said "at Step 0", which this run does not contain — §5 begins at Step 3 and §8 forbids performing Step 0. The instruction was right and its anchor was stale.)

4.1 Two helpers, so the work is mechanical

Hand-editing 322 sites is where a transform pass produced invalid Python once already. Add two helpers to the test support module and route the sites through them.

Helper one — mint and stamp at construction.


def with_event_id(obj: MemoryObject) -> tuple[MemoryObject, UUID]:
    """Mint a fresh event id, stamp it into provenance, return both.

    The object returned is the one to append; the id returned is the one
    to pass. One value, two destinations — the same guarantee src/ gives.
    """

Helper two — advance a version and re-mint.


def next_object_version(obj: MemoryObject, **updates) -> MemoryObject:
    """model_copy that advances version AND re-mints provenance.

    Copying provenance forward attaches the prior version's event id to a
    new version. Under the option-A invariant that is an IntegrityError,
    not a silent wrong value. Use this instead of model_copy wherever the
    result will be appended.
    """

Helper two is the fix for the 292 failures, and it is the reason the test tree is a mechanical pass rather than a redesign.

4.2 What must not be done

Do not pass event_id=uuid.uuid4() at test append sites. It restores the prior behaviour exactly: the row's event_id and the object's wasGeneratedBy diverge, and the fabrication this CR exists to remove persists in every fixture. (This was alternative 1 in the build session's halt report; it is rejected here rather than left open.)

Do not pass through from a model_copy result. §2.1.

4.3 Assertions

Some tests assert on provenance values. Where an assertion compares a copied-forward identifier, it encodes the defect. Update the assertion to the invariant — the appended row's event_id equals that object version's wasGeneratedBy — rather than to whatever the new value happens to be.


5. Order of operations

Resume at Step 3. Steps 0, 1 and 2 are done and are not repeated.

Step 3 — the helpers. §4.1, in the test support module. No call site changed yet. Commit: CR-2026-159 step 3: test helpers for minting and version advance

Step 4 — the version-advancing sites first. Route every model_copy that advances a version and will be appended through next_object_version. This before the append sites — it is the smaller set and the one that causes the integrity failures, so doing it first means Step 5's failures are missing-argument errors only. Commit: CR-2026-159 step 4: version-advancing copies re-mint provenance

Step 5 — the append sites, in tranches. By directory or module cluster, suite run at each tranche against the Step 0 baseline set. Commit per tranche. Commit per tranche: CR-2026-159 step 5/N: <cluster> test append sites

Step 6 — the two backfills. wasderivedfrom_backfill.py:75, redirect_edge_backfill.py:148. Unchanged from v0.4 §6.3–§6.4; neither calls append_event. Commit: CR-2026-159 step 6: repair the two backfill provenance fabrications

Step 7 — regression tests. Row-and-object agreement at a mechanical site, a shape-two site, and a shape-three site. Plus one asserting that a version-advancing copy re-mints — the invariant that broke this build. Commit: CR-2026-159 step 7: provenance equality and invariant regression tests

CHECKPOINT A — report, then proceed. Failure set versus the Step 0 baseline; the regression tests; live verification on a throwaway database showing row, both JSONB columns, and the returned object agreeing. No Operator confirmation. Halt and queue on: any failure outside the baseline set; any pass-through site failing §2.1's two conditions; any charter §6 anomaly.

Step 8 — implementation notes, carrying D-4's recorded statement unchanged.

CHECKPOINT B — merge and tag. --no-ff to main, tag provenance-seam-v0_1, push. Authorized under R-2 including the push. Deployment is never autonomous (F-1).


6. Acceptance gate

  1. No failure outside the Step 0 baseline set, at every step.
  2. append_event cannot be called without an event identifier.
  3. Stored provenance.wasGeneratedBy equals the row's event_id, in both the provenance column and the nested payload.
  4. The object the caller holds after the append carries the same value.
  5. No appended object version shares a wasGeneratedBy with any other.
  6. No version-advancing model_copy whose result is appended copies provenance forward.
  7. Both repaired backfills satisfy the equality.
  8. The three personal_engagement.py sites still satisfy it.
  9. Every shape-three site names its minting constructor in a comment, and satisfies §2.1's two conditions.
  10. No uuid.uuid4() as a direct argument to wasGeneratedBy anywhere — src/ or tests/.
  11. Regression tests cover a mechanical, a shape-two and a shape-three site, plus the version-advance invariant.
  12. memory/events.py's docstring names all four write paths.
  13. Alembic head unchanged at 0102.
  14. No change outside loomworks-engine; no database touched except a throwaway.
  15. Implementation notes carry D-4's statement.
  16. The status brief is appended, per charter §7.

7. What this CR does not do


8. Claude Code kickoff

This block names the resume point and the fences. It does not restate §5's steps — §5 is the sequence, §8 says where to enter it.


CR-2026-159 — B-29, the provenance seam. RESUMING session.

CR: loomworks-record/change-requests/cr-2026-159-provenance-seam-v0_10.md
Confirm v0_10 is the highest version present. v0_1 through v0_9 are superseded.

Read the CR in full. Section 5 is the step sequence; follow it there, not from
this block. Section 2 is the invariant — read it before touching any test.
Charter dunin7-standing-authorization-charter-v0_1 governs.

THIS IS A RESUME.

  Branch cr-2026-159-option-a holds Steps 0-2 with src/ COMPLETE at f3a1bbf.
  loomworks-engine main is unmoved at a317051.

  Enter at Section 5, Step 3 — the test helpers.

  Do NOT perform Steps 0, 1 or 2. Do NOT re-touch src/. Do NOT delete any
  branch. The option-B branch was deleted two runs ago and no longer exists.

Re-confirm and derive nothing else: HEAD; working tree; that
cr-2026-159-option-a is at f3a1bbf; that the baseline failure set is still the
single entry test_stele_router_mount.py::test_stele_router_mounts_and_begin_
resolves_end_to_end. Then report the ACTUAL test-tree counts — appends, files,
version-advancing copies. Section 4's numbers are one session's report, not a
verified baseline.

Operator ruling 2026-07-31: finish option A. Dropping the field and the
stamp-and-return alternative are both set aside — do not reopen them.

Commit to the branch, never to main. Check the current branch before the first
commit of the session.

playground_dev is the live production database and is not touched. Verification
runs on the walk-audit dev pattern: spare port, inline environment, throwaway
database, live processes and .env untouched, teardown after.

"Suite green" is not the criterion; "no new failures against the Step 0 baseline
set" is.

Halt and queue on: any failure outside the baseline set; any pass-through site
that is not freshly constructed and appended exactly once; any charter section 6
anomaly.

Append the outcome to current-status/dunin7-status-brief at close.

DUNIN7 — Done In Seven LLC — Miami, Florida CR-2026-159 — B-29: the provenance seam — v0.10 — 2026-07-31 One object version, one event, one identifier. The database enforces it now.