Version. v0.2
Date. 2026-07-31
Supersedes. v0.1 at record b36a676, which stands as a sibling, unaltered. Unexecuted at the time of this bump.
Changes from v0.1. Two, both from the executing session's citation audit at v0.1's filing, neither altering the work. §6.4's open [CC verifies] determination is closed — the equivalence holds in both backfill modules and the anchors are recorded, so the execution session confirms rather than investigates. §5 D-5's phrasing corrected: it implied the module docstring both asserts and undercuts its own claim, which it does not — two different docstrings are involved.
Author. Claude.ai (drafting session). Approving: Marvin Percival.
Charter. standing-notes/dunin7-standing-authorization-charter-v0_1, ratified 2026-07-30. This is a drafting session's output; no session executes a change request it drafted (§1).
Target. /Users/dunin7/loomworks-engine on DUNIN7-M4. No work in loomworks, loomworks-ui, or stele.
Baseline. Engine main a317051. Tag registry-completeness-v0_1 sits on c4014d6, the merge commit one before — git describe returns registry-completeness-v0_1-1-ga317051. Alembic head 0102 as declared in migrations/versions/ (single head, 102 revisions). Working tree carried M uv.lock unstaged at inspection time.
Build-list item. B-29.
Priority. Standard, with one ordering constraint that is not negotiable — see §4.
Confidential. Internal DUNIN7.
CR number. CR-2026-159. Highest present in the record at drafting is CR-2026-158; CR-2026-157 is filed and unexecuted. [CC verifies at Step 0 against change-requests/ and docs/phase-crs/; advance if taken.]
Companion to. inspection-briefs/loomworks-b29-b27-b5-engine-inspection-findings-v0_1 (the grounding read, record e037a37); inspection-briefs/loomworks-b25-sizing-sweeps-findings-v0_1 (the original sizing, corrected by the note below); inspection-briefs/loomworks-inspection-corrections-note-v0_1 (C-3, C-4, C-5 apply here); current-status/dunin7-status-brief-v0_2 §3 D-1 (the seam decision and its reasoning).
Status. Pre-execution. Ready for execution by a session that did not draft it.
Provenance.wasGeneratedBy is declared at src/loomworks/memory/base.py:147 with its meaning in a trailing comment: the memory event id that produced this version. It is required — a bare UUID annotation with no default and no default factory, unlike the four fields around it.
Every write path constructs provenance before the event exists, so every caller fills the field with a freshly minted UUID that points at nothing. Eighty-three sites across thirty-seven files do this. Three sites in persons/personal_engagement.py do it correctly, by pre-minting the identifier and passing the same value to both the provenance and the insert.
append_event mints its own event identifier at memory/events.py, inside the MemoryEventRow(...) constructor, after the object has already been serialised — and never back-patches. The stored value can therefore never be the real one.
This CR stamps the field inside append_event, before serialisation. One file, one reordering. The eighty-three sites are not touched; their values simply stop reaching the database. Two raw-SQL paths that bypass append_event entirely get a one-line repair each, because no seam change reaches them.
Why this is low risk. Nothing reads the field. No query filters on it, no projector derives from it, no response schema exposes it, no test asserts its value, and nothing hashes it. The surface is write-side only, so no downstream consumer can break.
Why it should not wait. The FORAY reserved emitter is a no-op today and provenance is not in its payload. When integration opens, whatever sits in the payload and provenance JSONB columns becomes the anchored composition. Fabricated provenance inside an anchored history is materially worse than fabricated provenance outside one.
append_event mints the event identifier before serialising, rebuilds the object's provenance with the real value, and serialises from the corrected object — so both the payload JSONB and the provenance JSONB columns carry the true identifier.engagement/wasderivedfrom_backfill.py and engagement/redirect_edge_backfill.py, one line each.memory/events.py, which currently asserts something false.provenance.wasGeneratedBy equals the row's event_id, on both the seam path and the two repaired backfills.relocate_memory_object. It preserves object_id and object_version and rewrites only the row's home; it does not touch provenance and is not part of this surface.main at a317051 or later, working tree clean apart from any pre-existing M uv.lock..env untouched. playground_dev is the live production database and is not touched.
C-1. This CR lands before B-25 is built. At three sites the two items are the two keyword arguments of a single Provenance(...) expression on consecutive lines:
| Site | Lines |
|---|---|
| api/routers/seed_extraction.py | :258-259 — wasAttributedTo=extraction_actor, wasGeneratedBy=uuid.uuid4() |
| api/routers/seed_commit_from_brief.py | :305-306 — wasAttributedTo=draft_actor, wasGeneratedBy=uuid.uuid4() |
| api/routers/me_create_engagement.py | :841-842 — wasAttributedTo=person_actor, wasGeneratedBy=uuid.uuid4() |
Because this CR does not touch those sites, the collision does not arise — but only if B-25 is not built concurrently. B-25's change request may be drafted while this builds.
C-2. This CR lands before FORAY integration opens at src/loomworks/foray/reserved_emit.py. The emitter is a no-op today and its payload carries only event_id and anchor_priority; provenance is not in it and nothing hashes the JSONB columns. After integration, changing the stamped value changes payload composition across the anchor boundary.
C-3. This CR lands before the completion arc's engine rider (CR-B). Same engine, same ruling.
D-1. Seam option B — append_event stamps the field itself. Recorded with full reasoning in current-status/dunin7-status-brief-v0_2 §3. In summary: both options are correct and neither can break a consumer; option A touches all 83 sites and guarantees a line-level collision with B-25; option B touches one file B-25 never opens. Reversal cost before build starts is one redraft.
D-2. The rebuild uses model_copy, nested. Provenance is frozen (base.py:144) and MemoryObject is frozen (base.py:161), so the corrected provenance cannot be assigned in. The object is copied with a copied provenance.
> A tension to state rather than leave implicit. memory/base.py's module docstring says frozen models "enforce non-erasure structurally — a memory object cannot be mutated, only superseded by a new version." This change copies an object before it is persisted, not after. Nothing recorded is altered, and no version is superseded; the copy exists only to carry a value that could not be known when the caller built it. The invariant the docstring protects is about the record, and the record is untouched. The implementation carries a comment saying so, so the next reader does not have to re-derive it.
D-3. The eighty-three dead stampers are not swept in this CR. After the seam lands they are harmless but misleading — a reader sees wasGeneratedBy=uuid.uuid4() and reasonably assumes it is stored. Sweeping them requires making the field optional at base.py:147, which is a declaration change touching 37 files, and it collides with B-25 at the three sites in C-1. The sweep is queued behind B-25 and recorded on the build list under Part 4. This is a deliberate deferral, not an oversight.
D-4. No historical backfill. Every object written before this change carries a fabricated value in the field. Because the damage is structurally knowable — every object outside the three correct sites — the honest remediation is one recorded statement in the record, not a per-record marking pass. This CR files that statement as part of its implementation notes. Quantifying how many live records are affected needs a query against the serving perimeter and stays parked as a decision of a different kind.
D-5. The module docstring is corrected in the same change. memory/events.py currently opens: "append_event is the single code path for writing to the memory event log. No other function in the codebase writes to memory_events directly." Three functions do — persons/personal_engagement.py::_append_event, engagement/wasderivedfrom_backfill.py, engagement/redirect_edge_backfill.py — plus relocate_memory_object in the same module.
> Corrected at v0.2. v0.1 said relocate_memory_object is acknowledged in "the docstring's own later text", which reads as though the module docstring both asserts the claim and undercuts it. It does not. Two different docstrings are involved: the module docstring is five lines and never mentions relocation; the "one sanctioned exception" language lives in relocate_memory_object's own function docstring, further down the file. The substance was right — the exception is acknowledged somewhere in that file — but the module docstring is uncontradicted on its own terms, and simply false.
A false docstring at the seam is how the "one seam" sizing error happened; correcting it is cheap and prevents the next one.
src/loomworks/memory/events.py
Today, inside append_event, in this order:
payload = object.model_dump(mode="json")
event = MemoryEventRow(
event_id=uuid.uuid4(),
...
payload=payload,
provenance=object.provenance.model_dump(mode="json"),
...
)
The identifier is minted inside the constructor, after payload has been serialised from the un-corrected object. Both the payload JSONB (which contains a nested provenance) and the dedicated provenance JSONB column therefore carry the caller's fabricated value.
After, mint first and correct the object before either serialisation:
# 4. Mint the event identifier, then stamp it into the object's
# provenance before anything is serialised. wasGeneratedBy means "the
# memory event id that produced this version" (memory/base.py:147), and
# the caller cannot know that value — it does not exist until this line.
# Both Provenance and MemoryObject are frozen, so this is a nested copy,
# not an assignment. Nothing recorded is mutated: the copy happens
# before persistence, so the non-erasure invariant the frozen models
# protect is untouched.
event_id = uuid.uuid4()
object = object.model_copy(
update={
"provenance": object.provenance.model_copy(
update={"wasGeneratedBy": event_id}
)
}
)
payload = object.model_dump(mode="json")
event = MemoryEventRow(
event_id=event_id,
...
payload=payload,
provenance=object.provenance.model_dump(mode="json"),
...
)
Why both columns are fixed by one change. payload is dumped from the whole object, which includes provenance; the provenance column is dumped from object.provenance. Both read the corrected object. Correcting payload after the fact would have fixed only one of the two.
A note the implementation carries. model_copy(update=...) skips validation in Pydantic. Here the update is a UUID into a UUID-annotated field, so it is safe; the CR records this so a future reader does not have to establish it.
A coherence gain worth naming. The FORAY reserved emit at the end of append_event already sends {"event_id": str(event.event_id), ...} — the correct identifier. After this change, the value in the emit and the value in provenance are the same value. They disagree today.
[CC determines] whether the local name object is rebound or a new name introduced. Rebinding keeps the diff minimal and every downstream reference correct; a new name is clearer to read. Either is acceptable; pick one and be consistent within the function.
Replace the module docstring's false claim. The corrected text names the sanctioned exceptions rather than asserting there are none:
append_event is the primary write path and the only one that maintains the six projections.relocate_memory_object, in this module, moves existing rows and writes no new event.memory_events directly and maintain their own locking, version advance, and projection upsert: persons/personal_engagement.py::_append_event, engagement/wasderivedfrom_backfill.py, engagement/redirect_edge_backfill.py. Any change to the event-write contract must reach all four sites.src/loomworks/engagement/wasderivedfrom_backfill.pyThe module mints the row's real event identifier, then on the next line mints a second, different UUID and stamps that into provenance. The correct value is already in scope one line above the wrong one.
Repair: assign the provenance variable from the event identifier rather than minting again. One line.
src/loomworks/engagement/redirect_edge_backfill.py
Same defect, wider apart. event_id = str(uuid.uuid4()) is minted, and eleven lines later "wasGeneratedBy": str(uuid.uuid4()) mints a fresh throwaway.
Repair: "wasGeneratedBy": event_id. One line.
Note — determination closed at v0.2. One repair covers both columns in both modules. v0.1 left this open as [CC verifies]; it was verified at v0.1's filing and the anchors are recorded here so the execution session confirms rather than investigates.
Both modules build a single provenance dict, embed that same object in the relationship payload, and serialise the same object again for the dedicated column — so correcting the dict at its construction corrects both destinations.
| Module | Builds the dict | Embeds it in the payload | Serialises for the column |
|---|---|---|---|
| engagement/wasderivedfrom_backfill.py | :78 | :97 | :142 |
| engagement/redirect_edge_backfill.py | :142 | :164 | :206 |
The execution session re-confirms these five anchors at Step 0 alongside the site sweep, and halts if any has moved — a shifted line here means the module changed after this CR's evidence base was taken.
src/loomworks/persons/personal_engagement.pyNo change. Its three sites pre-mint, stamp, and pass the same identifier to the insert. They are correct, and they are the in-tree proof that the field means what its comment says.
None. No table, no column, no constraint, no index. The change is entirely to what is written into two existing JSONB columns going forward. Alembic head stays at 0102.
The field currently has zero value assertions anywhere in the tree — 312 test-tree occurrences of the string are either construction boilerplate or the unrelated Relationship vocabulary term of the same name. This CR establishes the first.
append_event and assert the stored row's provenance["wasGeneratedBy"] == str(row.event_id), on both the provenance column and the nested payload["provenance"]. Assert it holds even when the caller passed a deliberately wrong value.wasderivedfrom_backfill. Run the backfill and assert every written row satisfies the same equality.redirect_edge_backfill. Same.personal_engagement. A characterisation test asserting the three sites still satisfy the equality — they do today, and this locks it.
Verification environment. Throwaway database on the walk-audit pattern. No connection to playground_dev.
Per-step commits. Suite green at each. Halt on anomaly per charter §6.
Step 0 — pre-flight and archival.
Verify the CR number is free. Verify engine HEAD, working tree, and alembic head against §Baseline; report any divergence and halt if the tree has moved in a way this CR's evidence base does not anticipate (charter C-3). Re-run the fabricating-site sweep and confirm 83 sites / 37 files. Archive this CR to docs/phase-crs/.
Commit: CR-2026-159 step 0: pre-flight + CR archival
Step 1 — the seam.
§6.1 and §6.2. Suite green.
Commit: CR-2026-159 step 1: stamp wasGeneratedBy at the seam; correct the events.py docstring
Step 2 — the two bypasses.
§6.3 and §6.4. Suite green.
Commit: CR-2026-159 step 2: repair the two backfill provenance fabrications
Step 3 — tests.
§8 items 1–5. Full suite green.
Commit: CR-2026-159 step 3: provenance equality regression tests
CHECKPOINT A — halt. Report: suite counts, the five new tests passing, a live verification on the walk-audit dev stack showing a freshly appended event whose stored provenance equals its event_id, and confirmation that no pre-existing test required editing. Operator confirms before merge.
Step 4 — implementation notes.
docs/phase-impl-notes/cr-2026-159-implementation-notes-v0_1.md, carrying D-4's recorded statement: every memory object written before this commit carries a fabricated value in provenance.wasGeneratedBy, outside the three personal_engagement.py sites; no backfill was performed; the count of affected live records is unquantified and parked.
Commit: CR-2026-159 step 4: implementation notes
CHECKPOINT B — merge and tag. --no-ff merge to main, annotated tag provenance-seam-v0_1, push. Authorized under charter R-2 including the push. Deployment is never autonomous (F-1) — the launchd restart is the Operator's act and batches to cadence.
provenance.wasGeneratedBy equals the row's event_id on the seam path, in both the provenance column and the nested payload.personal_engagement.py sites still satisfy it.memory/events.py's docstring names all four write paths.0102.loomworks-engine. No database touched except a throwaway.
CR-2026-159 — B-29, the provenance seam. Execution session.
CR: loomworks-record/change-requests/cr-2026-159-provenance-seam-v0_2.md
Grounding read: inspection-briefs/loomworks-b29-b27-b5-engine-inspection-findings-v0_1.md
Corrections that apply: inspection-briefs/loomworks-inspection-corrections-note-v0_1.md — C-3, C-4, C-5
Read the CR in full before starting. Charter dunin7-standing-authorization-charter-v0_1 governs.
Target: /Users/dunin7/loomworks-engine at main a317051 or later.
The engine belongs to this CR for its duration. Confirm no other session holds it.
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.
Steps 0 through 3 auto, per-step commits, suite green at each.
CHECKPOINT A halts for Operator confirmation.
Step 4 auto. CHECKPOINT B merges, tags provenance-seam-v0_1, and pushes.
Deployment is never yours — the restart batches to the Operator's cadence.
Two things that are anomalies, not adjustments:
- any pre-existing test needing an edit (nothing asserts the old behaviour)
- any divergence between the CR's evidence base and the live tree
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.2 — 2026-07-31 One seam, two bypasses, one false docstring. Eighty-three sites untouched by design.