DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path substrate/cleanup/phase-62-cr-memory-events-cleanup-v0_1.md

Phase 62 Change Request — Memory events cleanup (CR-B of substrate hygiene)

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.

Baseline. loomworks-engine 616bab9 (CR-A merge). Alembic head 0071. Test count 2674 pytest passing at baseline.


1. Background and motivation

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_eventsaudit.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:

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.


2. Scope

2.1 In-scope

Engine-only work. The full set:

2.2 Out-of-scope

The following do not ship in CR-B:

If CC finds itself implementing any of the above, it has crossed a scope boundary — halt and surface per §10.

2.3 Scope clarification — 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.


3. Architectural decisions

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: attestationcommit_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.


4. Substrate changes

4.1 Alembic migration 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.

4.2 No indexes affected

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.

4.3 No PostgreSQL trigger

None added by CR-B. Memory events stay append-only; projector maintenance stays in the application layer.

4.4 Data preservation


5. Code changes

5.1 New file — 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.

5.2 Update src/loomworks/foray/__init__.py

Add 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"]

5.3 src/loomworks/memory/events.py

Substantive simplification:


# 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.

5.4 src/loomworks/engagement/commit_orchestration.py

stamp_commit_attestation at lines 109-149:

5.5 No other production code changes

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.


6. Test changes

6.1 Delete tests/test_phase_25_content_hash_and_foray.py (3 tests)

All three tests cover removed functionality:

Use git rm so the deletion is tracked as a deletion.

6.2 Rename + update tests/test_phase_25_commit_attestation.pytests/test_commit_webauthn_attestation.py (8 tests)

git mv to preserve blame history. Inside the renamed file:

6.3 New test — tests/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.

6.4 Incidental references in 10 other test files

Files (per design §2.7):

For each: grep attestation\|content_hash\|foray_tx_ref\|_ANCHOR_PRIORITY and classify:

Expected delta: ~5-15 line changes across the 10 files.

6.5 tests/conftest.py

No change. TRUNCATE chain references memory_events table only.


7. Migration plan

7.1 Pre-migration check (Step 0)

Before running alembic upgrade head:

  1. Confirm engine baseline at 616bab9 (the CR-A merge).
  2. Confirm alembic head is 0071.
  3. Verify materialized view independence:

```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).

  1. Run baseline pytest; record passing count (~2674).

7.2 Apply migration on both dev and test DBs


.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).

7.3 Verify post-upgrade

7.4 Round-trip verification

alembic downgrade -1alembic upgrade head cycle. Confirms the migration is reversible and re-applicable. CR-A established this as a discipline.

7.5 Rollback plan

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.


8. Order of operations

CC executes the following sequence, gated by checkpoints.

Step 0 — Pre-flight

  1. Pull loomworks-engine main; confirm HEAD at 616bab9 (the CR-A merge).
  2. Confirm Alembic head is 0071.
  3. Run baseline test suite: .venv/bin/pytest -q; record count (expected ~2674).
  4. Verify the design's findings still hold:
  1. Materialized-view independence check (§7.1).
  2. Create branch phase-62-memory-events-cleanup from main.

If any check fails, CC halts and surfaces.

Step 1 — Migration scaffold

Write migrations/versions/0072_memory_events_phase_25_wiring_cleanup.py per §4.1. Do not run yet.

Step 2 — Move _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.

Step 3 — Update 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.

Step 4 — Update stamp_commit_attestation

Apply the changes in §5.4 to src/loomworks/engagement/commit_orchestration.py.

Step 5 — Apply migration


.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.

Step 6 — Update tests

Apply test changes per §6:

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.

Step 7 — Documentation sweep

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.

Step 8 — Commit chain

Commit chain (3-5 commits) on branch phase-62-memory-events-cleanup:

  1. "Phase 62 CR-B — Move _ANCHOR_PRIORITY to foray module + migration 0072"
  2. "Phase 62 CR-B — Drop FORAY-readiness columns from memory_events; rename attestation → commit_webauthn_attestation"
  3. "Phase 62 CR-B — Add reserved-emit at append_event write site; remove _foray namespace injection"
  4. "Phase 62 CR-B — Update tests for column rename + Phase 25 wiring removal"
  5. (optional) "Phase 62 CR-B — Documentation sweep"

Checkpoint A — Operator confirms

CC reports back:

Operator reviews; either approves close or requests adjustments.

Close — Tag and merge

On Operator approval:

  1. Tag phase-62-memory-events-cleanup at the branch HEAD
  2. Push branch + tag to origin
  3. Merge to main per branch-protection convention
  4. Land implementation notes per the direct-to-record discipline

9. Acceptance criteria

At Checkpoint A, all of the following must be true:


10. Risk assessment

10.1 Materialized-view dependency

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.

10.2 ORM-level reference to renamed column slips past sweep

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.

10.3 _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.

10.4 Reserved-emit placement breaks "event landed" semantics

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.

10.5 Reserved-slot budget

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.

10.6 The 1 attestation row

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.


11. Carry-forward

11.1 To CR-C (credit substrate cleanup)

11.2 Deferred from CR-B explicitly

11.3 Methodology notes

Two pattern validations from CR-B:

  1. The reserved-emit discipline scales from 3 sites (CR-A) to 1 site (CR-B) without friction. The pattern is "one emit per business event" — three writes meant three emits; one write means one emit. Will validate scale-up to 12 in CR-C.
  2. The "test file reflects what it tests, not where it came from" discipline. CR-A renamed test_voice_tune_setting_foray_audit.pytest_voice_tune_setting_audit.py. CR-B renames test_phase_25_commit_attestation.pytest_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.


12. Document trailer

This CR is drafted by Claude Code on 2026-05-24 from:

No 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