CR identifier. CR-2026-XXX (next number in sequence; CR drafter confirms against engine repo docs/phase-crs/ listing before archival).
Version. 0.1
Date. 2026-05-24
Status. Drafted; awaiting Operator approval. No pre-flight Step 0 yet — Step 0 runs at CR execution time.
Author. Claude Code (CR drafter) on direction from Marvin Percival (DUNIN7 Operator).
Phase number. 62 (Phase 61 CR-A closed and tagged phase-61-audit-cleanup at engine commit 616bab9; Phase 62 is the next available number).
Companion documents.
loomworks-cr-b-memory-events-cleanup-design-v0_1.md — design document; this CR's substrate-design source of truth.phase-61-cr-audit-cleanup-v0_1.md — CR-A; CR-B inherits its patterns.phase-61-implementation-notes-v0_1.md — what CR-A actually shipped, including the D5 FK deviation.loomworks-substrate-hygiene-scoping-note-v0_2.md (record substrate/cleanup/) — scoping note authorizing the three-sibling-CR shape.loomworks-foray-integration-locations-v0_1.md (record substrate/cleanup/) — Step 1 inventory.
Baseline. loomworks-engine 616bab9 (CR-A merge). Alembic head 0071. Test count 2674 pytest passing at baseline.
CR-A (Phase 61 — audit cleanup) closed at engine tag phase-61-audit-cleanup. The reserved-location pattern was validated; the src/loomworks/foray/ module foundation exists; the audit.foray_events → audit.events rename shipped clean (one design deviation around FKs, captured in Phase 61 implementation notes §3).
CR-B is the second of the three sibling CRs from the substrate hygiene scoping note v0.2. It addresses the Phase 25 readiness wiring on memory_events:
content_hash column — TEXT, written on every memory event since Phase 25 (494 non-null rows in dev), never read by application code. Pure FORAY-readiness with zero downstream consumers.foray_tx_ref column — TEXT, never written (0 non-null rows). Pure readiness for an anticipated future FORAY-transaction-reference column.attestation column — JSONB, 1 non-null row carrying a real WebAuthn cryptographic proof. Memory-essential — survives renamed to commit_webauthn_attestation._ANCHOR_PRIORITY registry — 19 entries across Phases 25/31/53/54/55/58/59 mapping event kinds to anchor-priority labels. Lives at src/loomworks/memory/events.py:112-165. Relocates to src/loomworks/foray/anchor_priority.py._foray namespace injection logic — at events.py:236-255, mutates payloads to include a _foray block on anchor-priority event kinds. Removed entirely; replaced with the per-call-site reserved-emit pattern.The discovery trajectory: scoping note v0.1 → CC's Step 1 inventory → scoping note v0.2 (absorbed the corrections, including the attestation-survives-renamed finding) → CR-A close → this CR.
Engine-only work. The full set:
0072 that drops content_hash, drops foray_tx_ref, renames attestation → commit_webauthn_attestation. Atomic single migration.src/loomworks/foray/anchor_priority.py holding the relocated _ANCHOR_PRIORITY dict; updated src/loomworks/foray/__init__.py to re-export.events.py rewrite. Drop the _ANCHOR_PRIORITY dict; drop compute_content_hash; drop the _foray namespace injection block at lines 236-255; drop the FORAY-readiness ORM columns; rename the ORM column; add reserved-emit call at the end of append_event.commit_orchestration.py update. Rename column in the stamp_commit_attestation UPDATE SQL.tests/test_phase_25_content_hash_and_foray.py (tests removed functionality); rename and update tests/test_phase_25_commit_attestation.py → tests/test_commit_webauthn_attestation.py; update incidental references in 10 other test files; add one new test for the reserved-emit hook.The following do not ship in CR-B:
credit.foray_action_flows replacement; 12 constructor sites; PostgreSQL trigger replacement. Separate sibling CR.ForayClient wrapper is downstream work._ANCHOR_PRIORITY rename to _FORAY_INTEGRATION_PRIORITY — recommendation §3.2 of the design is keep-as-is; module path provides FORAY context.commit_webauthn_attestation — no query exercises a search by attestation. Add when needed.compute_content_hash with new semantics — speculative; revisit if a concrete use emerges._ANCHOR_PRIORITY entries — the 19 entries come along verbatim; no entry adds or removes during CR-B.commit_attestation.py's parameter name — the function-level attestation variable is not a column reference; stays.attestation — they're API shape, not column names.If CC finds itself implementing any of the above, it has crossed a scope boundary — halt and surface per §10.
tests/conftest.py
The TRUNCATE chain at tests/conftest.py:69-81 already references memory_events (not a column-specific reference). No conftest update needed.
All decisions trace to the design document (loomworks-cr-b-memory-events-cleanup-design-v0_1.md). CC consumes them; does not relitigate.
| Decision | Setting | Source |
|---|---|---|
| D1 | Column rename: attestation → commit_webauthn_attestation. True rename via op.alter_column(..., new_column_name=...). Preserves the 1 non-NULL row. | Design §3.1 |
| D2 | Columns dropped: content_hash (494 write-only rows lost; no consumer); foray_tx_ref (0 rows; never written). | Design §3.1 |
| D3 | Registry move + no rename. _ANCHOR_PRIORITY moves to src/loomworks/foray/anchor_priority.py; symbol name preserved (module path provides FORAY context). | Design §3.2 |
| D4 | compute_content_hash removed entirely. Zero callers after column drop. Future hashing requirement gets a fresh function with concrete semantics. | Design §3.5 |
| D5 | _foray namespace injection removed; replaced by reserved-emit at end of append_event. Per the per-call-site discipline CR-A validated. Memory has one write site; one reserved-emit call. | Design §3.3 |
| D6 | Reserved-emit placement: at the very end of append_event, after fire_post_append_hooks returns. Semantics: emit only when the event fully landed and post-append hooks completed. | Design §3.3 + Risk §9.3 |
| D7 | Reserved-emit payload: {"event_id": str(event.event_id), "anchor_priority": _ANCHOR_PRIORITY.get(event_kind)}. Anchor priority is data in the payload, not a gate on emit. Every event emits; future FORAY can decide which to anchor. | Design §3.3 |
| D8 | stamp_commit_attestation function name preserved. Describes the operation, not the column. Only the column reference in its UPDATE SQL changes. | Design §3.4 |
| D9 | Test file test_phase_25_content_hash_and_foray.py deleted entirely. All 3 tests cover functionality being removed. Don't refactor; delete. | Design §6.1 |
| D10 | Test file test_phase_25_commit_attestation.py renamed → test_commit_webauthn_attestation.py. Drops the "Phase 25" framing; aligns with the new column name. Test logic preserved. | Design §6.2 |
| D11 | Single Alembic migration 0072. Atomic upgrade: rename first, then drop two columns. Reversible downgrade. | Design §4 |
| D12 | One new test added: test_foray_reserved_emit_called_on_memory_event_write. Asserts the hook fires; protects against accidental removal. Single test, not extensive. | Design §6.5 |
| D13 | Reserved-slot count: 2. Most likely consumption: registry import-path drift not caught by sweep; materialized-view dependency on dropped columns. | Design §9.6 |
If a decision feels wrong during build, CC halts per §10 — the chat doesn't override; it surfaces.
0072_memory_events_phase_25_wiring_cleanup.py
Single migration file. Revision 0072, revises 0071.
Upgrade:
op.alter_column(
"memory_events", "attestation",
new_column_name="commit_webauthn_attestation",
)
op.drop_column("memory_events", "content_hash")
op.drop_column("memory_events", "foray_tx_ref")
Order matters: rename first (preserves the 1 row); drops follow. Single transaction.
Downgrade:
op.add_column(
"memory_events",
sa.Column("foray_tx_ref", sa.Text(), nullable=True),
)
op.add_column(
"memory_events",
sa.Column("content_hash", sa.Text(), nullable=True),
)
op.alter_column(
"memory_events", "commit_webauthn_attestation",
new_column_name="attestation",
)
The downgrade re-adds the two dropped columns as NULL. The 494 content_hash values are not regenerated; production rollback path is fail-forward.
Three indexes on memory_events today (engagement_time, engagement_version, object_type); none reference the affected columns. The uniqueness constraints (uq_memory_events_engagement_version, uq_memory_events_object_version) and the FK to engagements(id) are unaffected.
None added by CR-B. Memory events stay append-only; projector maintenance stays in the application layer.
ALTER TABLE ... RENAME COLUMN is metadata-only in PostgreSQL).content_hash data lose the column; no consumer reads it.foray_tx_ref data — trivial.src/loomworks/foray/anchor_priority.py
The relocated _ANCHOR_PRIORITY dict + the 19 entries + their phase-origin comments, moved verbatim from events.py:107-165. Module docstring explains the move came from CR-B and the dict's role.
src/loomworks/foray/__init__.pyAdd the re-export so callers can import either from the submodule or from the package:
from loomworks.foray.anchor_priority import _ANCHOR_PRIORITY
from loomworks.foray.reserved_emit import _foray_reserved_emit
__all__ = ["_ANCHOR_PRIORITY", "_foray_reserved_emit"]
src/loomworks/memory/events.pySubstantive simplification:
hashlib, json (no longer needed after compute_content_hash removal)_ANCHOR_PRIORITY dict literal (moves to foray module)compute_content_hash function entirelyMemoryEventRow:content_hash: Mapped[str | None] columnattestation: Mapped[dict | None] → commit_webauthn_attestation: Mapped[dict | None]foray_tx_ref: Mapped[str | None] column# Phase 25 — FORAY readiness columns comment block above the columns# Phase 25 — content hash + optional _foray namespace. block (current lines 236-255)content_hash=content_hash kwarg in the MemoryEventRow(...) constructionfrom loomworks.foray import _foray_reserved_emit at topappend_event, right before return event:
# FORAY_RESERVED_LOCATION: memory.<event_kind>
from loomworks.foray import _ANCHOR_PRIORITY
_foray_reserved_emit(
f"memory.{event_kind}",
{
"event_id": str(event.event_id),
"anchor_priority": _ANCHOR_PRIORITY.get(event_kind),
},
)
return event
Net diff: ~80 lines removed, ~10 lines added.
src/loomworks/engagement/commit_orchestration.py
stamp_commit_attestation at lines 109-149:
SET attestation = :att → SET commit_webauthn_attestation = :attattestation JSONB column" → "Updates the commit_webauthn_attestation JSONB column"
commit_attestation.py's attestation parameter is a variable name, not a column reference: unchanged.
api/routers/engagements.py's six attestation references are about request-body fields and kwarg pass-through: unchanged.
api/schemas.py's Pydantic field is API shape, not column: unchanged.
tests/test_phase_25_content_hash_and_foray.py (3 tests)All three tests cover removed functionality:
test_event_content_hash_computed — column droppedtest_foray_metadata_in_payload — injection removedtest_critical_priority_for_engagement_committed — registry's lookup behavior is now tested by reading the dict directly, which is over-testing static data
Use git rm so the deletion is tracked as a deletion.
tests/test_phase_25_commit_attestation.py → tests/test_commit_webauthn_attestation.py (8 tests)
git mv to preserve blame history. Inside the renamed file:
memory_events.attestation → memory_events.commit_webauthn_attestationtests/test_foray_reserved_emit_for_memory.py (1 test)Single test asserting the reserved-emit hook fires:
async def test_foray_reserved_emit_called_on_memory_event_write(db, monkeypatch):
from loomworks.foray import reserved_emit as re_module
calls = []
monkeypatch.setattr(re_module, "_foray_reserved_emit",
lambda kind, payload: calls.append((kind, payload)))
# ... trigger one event write ...
assert len(calls) == 1
assert calls[0][0].startswith("memory.")
assert calls[0][1]["event_id"]
assert "anchor_priority" in calls[0][1]
Protects against accidental removal of the reserved-emit call. ~25 lines.
Files (per design §2.7):
tests/test_phase_31_seed_conversation.pytests/test_phase_48_reconciliation_evaluator.pytests/test_phase_53_discovery_to_seed.pytests/test_phase_54_seed_commit_from_brief.pytests/test_phase_55_engagement_creation_assistance.pytests/test_phase_58_storage.pytests/test_phase_58_upload_pathway.pytests/test_phase_59_fallback_endpoint.pytests/test_phase_59_upload_event_persistence.py
For each: grep attestation\|content_hash\|foray_tx_ref\|_ANCHOR_PRIORITY and classify:
attestation=... in calls to stamp_commit_attestation → unchanged (parameter name preserved per §5.4)body.attestation → unchanged (API shape preserved per §5.5)Expected delta: ~5-15 line changes across the 10 files.
tests/conftest.py
No change. TRUNCATE chain references memory_events table only.
Before running alembic upgrade head:
616bab9 (the CR-A merge).0071.
```sql
SELECT viewname, definition FROM pg_views
WHERE schemaname IN ('public', 'audit', 'credit')
AND (definition ILIKE '%memory_events.attestation%'
OR definition ILIKE '%memory_events.content_hash%'
OR definition ILIKE '%memory_events.foray_tx_ref%');
```
If any rows return, surface — the migration's column drops will fail until the view is updated or dropped. Expected: 0 rows (the materialized views (current_memory_objects, etc.) project event payloads, not these specific columns).
.venv/bin/alembic upgrade head
DATABASE_URL='postgresql+asyncpg://playground@localhost:5432/playground_test' .venv/bin/alembic upgrade head
CR-A surfaced that test DB needs its own migration apply (not automatic).
audit.events row count unchanged (CR-A artifact; this CR doesn't touch).memory_events.commit_webauthn_attestation column exists; non-null row count is 1 (the preserved attestation).memory_events.content_hash does not exist.memory_events.foray_tx_ref does not exist.
alembic downgrade -1 → alembic upgrade head cycle. Confirms the migration is reversible and re-applicable. CR-A established this as a discipline.
Same shape as CR-A: production fail-forward via new migration; alembic downgrade for local-dev iteration; mid-migration failure recoverable manually since the migration is atomic.
CC executes the following sequence, gated by checkpoints.
loomworks-engine main; confirm HEAD at 616bab9 (the CR-A merge).0071..venv/bin/pytest -q; record count (expected ~2674).memory_events schema matches design §2.1 (no later changes)compute_content_hash, _ANCHOR_PRIORITY, the injection block all at their design-named locationssrc/loomworks/foray/ exists with __init__.py + reserved_emit.py from CR-A0072 is the next available numberphase-62-memory-events-cleanup from main.If any check fails, CC halts and surfaces.
Write migrations/versions/0072_memory_events_phase_25_wiring_cleanup.py per §4.1. Do not run yet.
_ANCHOR_PRIORITY to src/loomworks/foray/anchor_priority.py
Create the new file. Move the dict + comments verbatim from events.py:107-165. Update src/loomworks/foray/__init__.py re-exports per §5.2.
src/loomworks/memory/events.py
Apply all the changes in §5.3: drop imports, drop dict, drop function, drop columns, rename column, drop injection block, drop content_hash kwarg, add reserved-emit call at end of append_event.
stamp_commit_attestation
Apply the changes in §5.4 to src/loomworks/engagement/commit_orchestration.py.
.venv/bin/alembic upgrade head
DATABASE_URL='postgresql+asyncpg://playground@localhost:5432/playground_test' .venv/bin/alembic upgrade head
Verify per §7.3. Run round-trip per §7.4.
Apply test changes per §6:
tests/test_phase_25_content_hash_and_foray.py (use git rm)tests/test_phase_25_commit_attestation.py to tests/test_commit_webauthn_attestation.py (use git mv) and update column referencestests/test_foray_reserved_emit_for_memory.py
Run full test suite: .venv/bin/pytest -q. Expected count: baseline ± a few (2674 minus 3 deleted tests plus 1 new test = 2672).
If tests fail, CC halts and surfaces.
Search the engine repo for remaining content_hash / foray_tx_ref / unrenamed attestation column references:
grep -rn "memory_events.attestation\|memory_events\.content_hash\|memory_events\.foray_tx_ref" --include="*.py" --include="*.md" .
grep -rn "compute_content_hash" --include="*.py" .
grep -rn "from loomworks.memory.events import _ANCHOR_PRIORITY\|from loomworks.memory.events import compute_content_hash" --include="*.py" .
Each hit should be either in the migration 0072 (appropriate historical reference) or already updated. Anything else gets corrected.
Commit chain (3-5 commits) on branch phase-62-memory-events-cleanup:
CC reports back:
0072 applied on both dev + test DBs; schema verifiedcontent_hash / foray_tx_ref / unrenamed attestation referencesOperator reviews; either approves close or requests adjustments.
On Operator approval:
phase-62-memory-events-cleanup at the branch HEADAt Checkpoint A, all of the following must be true:
memory_events.commit_webauthn_attestation column exists; non-null row count is 1 (the preserved row)memory_events.content_hash column does not existmemory_events.foray_tx_ref column does not existsrc/loomworks/foray/anchor_priority.py exists with 19 entries matching the original _ANCHOR_PRIORITY dictsrc/loomworks/foray/__init__.py re-exports _ANCHOR_PRIORITY and _foray_reserved_emitsrc/loomworks/memory/events.py no longer contains the _ANCHOR_PRIORITY dict, the compute_content_hash function, or the _foray namespace injection blockappend_event ends with the reserved-emit call (# FORAY_RESERVED_LOCATION: memory.<event_kind> marker present)stamp_commit_attestation references commit_webauthn_attestation (not attestation) in its UPDATE SQLtests/test_phase_25_content_hash_and_foray.py does not existtests/test_commit_webauthn_attestation.py exists; tests/test_phase_25_commit_attestation.py does not existtests/test_foray_reserved_emit_for_memory.py exists with 1 test.venv/bin/pytest -q passes with no new failures (the 2 pre-existing failures from CR-A may still surface)grep -rn "memory_events.attestation\|memory_events.content_hash\|memory_events.foray_tx_ref" src/ tests/ returns no hitsgrep -rn "compute_content_hash" src/ returns no hits0072 on both dev + test DBs
Risk. current_memory_objects or another projector view references the dropped columns; DROP COLUMN fails.
Mitigation. Step 0 §7.1 explicit check. If positive, halt and surface for view-update plan.
Risk. Some code uses getattr(MemoryEventRow, "attestation") or similar dynamic access.
Mitigation. Step 7 documentation sweep + test runs catch the dynamic-access cases. Grep finds the literal references.
_ANCHOR_PRIORITY import-path drift
Risk. Test or runtime code imports _ANCHOR_PRIORITY from loomworks.memory.events.
Mitigation. Currently grep -rn "_ANCHOR_PRIORITY" --include="*.py" returns 4 hits: 3 in events.py (the dict + 1 use), 1 in the dedicated test file (test_phase_25_content_hash_and_foray.py — to be deleted). After CR-B: events.py references move to foray module; the test file goes away. No external importers.
Risk. If fire_post_append_hooks is changed in the future to re-raise hook exceptions instead of swallowing, the reserved-emit would no longer fire — surprising future maintainers.
Mitigation. D6 documents the placement intent: "emit only when the event fully landed and post-append hooks completed." If future work changes hook-exception handling, the new behavior should consciously decide whether to keep the emit at the end or move it earlier.
Risk. A surprise consumes more than the 2 reserved slots.
Mitigation. Per D13, halt after the second slot is consumed and surface for v0.2.
Risk. Concurrent test or fixture process deletes the engagement carrying the 1 real attestation row during migration.
Mitigation. Migration is atomic. Production is single-instance. Test DB has no concurrent migration runs. Not a real risk.
src/loomworks/foray/ is now populated with two helpers (_foray_reserved_emit from CR-A, _ANCHOR_PRIORITY from CR-B). CR-C extends with value-flow emit support._ANCHOR_PRIORITY symbol rename to _FORAY_INTEGRATION_PRIORITY. Recommendation §3.2 is keep-as-is. If CR-C surfaces a clearer name from a value-flow-side angle, methodology candidate.commit_webauthn_attestation. Add when a query surface needs it.anchor_priority payload field is read by no one. The data is there so future FORAY integration has the priority hint without re-deriving. If integration arrives and doesn't need it, drop later.Two pattern validations from CR-B:
test_voice_tune_setting_foray_audit.py → test_voice_tune_setting_audit.py. CR-B renames test_phase_25_commit_attestation.py → test_commit_webauthn_attestation.py AND deletes test_phase_25_content_hash_and_foray.py outright. The principle: when a test's framing in its filename is about an origin that has been cleaned up, rename or delete to match the current substrate.If CR-C surfaces a third instance of "rename to match the cleanup," this becomes a methodology candidate for the next consolidation.
This CR is drafted by Claude Code on 2026-05-24 from:
substrate/cleanup/loomworks-substrate-hygiene-scoping-note-v0_2.md)substrate/cleanup/loomworks-foray-integration-locations-v0_1.md)substrate/cleanup/)substrate/cleanup/loomworks-cr-b-memory-events-cleanup-design-v0_1.md)616bab9: src/loomworks/memory/events.py, src/loomworks/engagement/commit_orchestration.py, src/loomworks/engagement/commit_attestation.py, src/loomworks/api/routers/engagements.py, src/loomworks/api/schemas.py, migrations/versions/0047_phase_25_engagement_creation_ui.py, 12 test filesNo CR-execution work begins until the Operator approves both this CR and the companion design document.
DUNIN7 — Done In Seven LLC — Miami, Florida Phase 62 Change Request — Memory events cleanup (CR-B) — v0.1 — 2026-05-24