Version. 0.2 · Date. 2026-08-09 · Author. Claude.ai (drafting) / Marvin Percival (approving and amending).
Target. /Users/dunin7/loomworks-engine, branch main.
Baseline. Engine ae58d34. CC confirms exact HEAD at Step 0.
Companion to. CR-2026-181's Step 1 report, where this was found while adding spreadsheet and slide-deck ingestion; standing-notes/dunin7-build-list-v0_49 (B-60).
Halt discharged. completion-records/loomworks-cr-2026-194-b60-halted-at-step-1-v0_1.md — v0.1 halted at Step 1 on a real constraint. This version answers it and authorises the build. §5 is corrected; §4 gains the capture; §8 gains a gate item.
Charter. Autonomous regime. Status. Ready for CC execution. Step 1 is done and reported; execution resumes at Step 2.
v0.1's §5 said "do not enrich." That was aimed at scope creep, and it was over-broad.
Step 1 found that PerStepProvenance requires started_at and completed_at, and that nothing in the pathway has ever observed either — the executor does not import datetime. Under v0.1's wording the CR asked for something unsatisfiable: carry what extraction produces into a model that extraction does not produce enough to satisfy.
That gap is not a scope question. It is a defect in the pathway, and closing it is what B-60 actually is. A field that cannot be populated honestly is not a field being protected from scope creep.
The decision: capture the timestamps. Two datetime.now(UTC) calls around the skill invocation.
Why capture rather than relax the schema. (The reasoning, for the record — the two options were not close.)
The schema is not wrong. A record of how something entered Memory reasonably includes when each step ran. PerStepProvenance asked for the right thing; the pathway simply never supplied it.
Relaxation is permanent and it spreads. Making the fields optional lowers what provenance claims to be — not once, but for every future consumer, each of which then handles a maybe-known field forever. That is a permanent reduction in what the record asserts, paid to avoid two lines. Capture adds a real observation. Relaxation subtracts a guarantee.
The third option is refused, and it is the important refusal. Synthesising both timestamps from the upload moment is the shortest path, produces a green test, and fabricates an observation into an append-only log for every upload from that day forward. It is rejected outright — not deferred, not conditioned.
succeeded is derived, and that is not the same thing. No skill emits it, but a skill that returned rather than raised succeeded, and the executor already distinguishes those paths. That restates an observed fact rather than inventing one — the same test §2 applies to backfill, applied here and passed.
The upload pathway captures structural provenance during extraction — which heading a passage sat under, which page, which sheet — and returns it on the HTTP response. The Memory event then writes an empty list. So the structure reaches the screen that uploaded the document and is discarded before it reaches Memory.
This affects .docx today and .xlsx / .pptx from the moment they landed. The dropping is deliberate: the code comment calls it a v1 gap and names itself a follow-on. This is that follow-on.
Sourced, not asserted. The two sites — the response at uploads.py:468 returning list(result.per_step_provenance), and the Memory write at :956 passing [] — come from CR-2026-181's Step 1 report. CC verifies both against the code at Step 1 rather than trusting this paragraph.
Historical uploads cannot gain provenance, and this CR must not try.
The ratification settled that an operational identifier which is derived, verifiable, and determined by facts already in the log may be backfilled — because the backfill computes what was always true. Extraction provenance fails every part of that test. It was observed at ingestion, from a file that may no longer exist, by an extractor whose version may have changed. Nothing in the log determines it, and recomputing it would produce a fresh observation presented as an old one.
So: this CR fixes the path forward only. Documents already in Memory keep no structure, and that is the correct outcome, not a shortfall.
CC establishes and reports:
PerStepProvenance.details is a free-form dict, already carrying per-page PDF markers and OCR path metadata. Confirm the event schema can hold the same shape, and what it costs in payload size for a realistically large document.[] suggests the pattern may repeat. Report what you find; fix only what is in this path.
Carry per_step_provenance through to the Memory event instead of writing an empty list.
Including the capture that makes it possible (v0.2). Record started_at and completed_at around each skill invocation in the executor, and carry them into PerStepProvenance. skill_name and skill_version come from the skill's own dict — every one of the ten emits both. succeeded is derived from whether the skill returned or raised. details carries the skill's dict as-is.
The capture is an observation made from this day forward. It says nothing about any upload that has already happened.
Tests. An upload of each supported type — at minimum .docx, .xlsx, .pptx, PDF — results in a Memory event whose provenance matches what the extraction produced. Not "is non-empty": matches, since an empty-vs-populated assertion is the vacuity shape this project keeps catching.
Observe the failure. Per the standing note, revert the change and confirm the tests fail, with messages naming the invariant.
PerStepProvenance requires them. Anything the model does not require — new fields, richer extraction, per-skill metrics nobody asked for — remains out. See §0 for why the original wording was wrong rather than merely inconvenient.B-69 applies. Extraction provenance is a record of how something entered Memory; if any part of it reads or derives from seed state, record the contact point with its Kind label. Likely Kind C — record it as one if so.
| Step | What | Mode |
|---|---|---|
| 0 | Pre-flight. | Auto |
| 1 | ~~Read per §3.~~ DONE at v0.1 — halted, reported, discharged by §0. All four answered in the halt record. | Complete |
| 1b | Capture started_at / completed_at around skill invocation in the executor. | Auto |
| 2 | Carry provenance through to the Memory event. | Auto |
| 3 | Tests per §4, observed failing first. | Auto |
| 4 | Full engine suite; ruff 0; mypy at/below baseline. | Auto |
| A | Checkpoint. | Checkpoint |
| 5 | Tag cr-2026-194-b60-provenance-to-memory. Push. | Auto |
1b. The timestamps are observed, not synthesised. A test must distinguish a captured moment from the upload moment — i.e. it fails if started_at / completed_at are copied from the event's now rather than measured around the skill. This is the refusal in §0 made into a check, because it is the failure that would otherwise pass every other test in this CR.
Execute CR-2026-194 v0.1 at ~/Downloads/cr-2026-194-loomworks-b60-
provenance-to-memory-v0_1.md. Confirm the CR number against the ledger
first.
B-60: extraction captures structural provenance, the HTTP response
returns it, and the Memory write passes an empty list. So the structure
reaches the uploading screen and is dropped before Memory. Affects
.docx today and .xlsx/.pptx since they landed.
§1's two file references come from your own CR-2026-181 Step 1 report.
Verify them against the code rather than trusting my paragraph.
Step 1 asks why the write is empty. The comment calls it a v1 gap —
find out whether that's the whole story. HALT if a real constraint sits
behind it (payload size, schema shape, serialization) rather than
working around one.
§2 matters: B-70's ratification does NOT cover this. Provenance is not
derived-and-recomputable — it was observed at ingestion, from a file
that may not exist now, by an extractor whose version may have changed.
Historical uploads keep no structure and that's correct, not a
shortfall. Do not backfill.
Tests assert provenance MATCHES what extraction produced, not that it's
non-empty. Non-empty is the vacuity shape.
Report if nothing downstream reads provenance from Memory. That's fine
but it should be known — it means this makes data available with no
reader yet, which is a build-list item rather than a reason to stop.
DUNIN7 — Done In Seven LLC — Miami, Florida CR-2026-194 — B-60 provenance to Memory — v0.2 — 2026-08-09