DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path substrate/cleanup/phase-63-implementation-notes-v0_1.md

Phase 63 Implementation Notes — v0.1 (Closing Document of the Cleanup Arc)

Version. 0.1

Date. 2026-05-25

Author. Claude Code

Engine HEAD. 573abe1 (merge of phase-63-credit-substrate-cleanup)

Tag. phase-63-credit-substrate-cleanup

Companion documents.


0. Closing document framing

CR-C is the third and final sibling Change Request from the substrate hygiene scoping note v0.2. When CR-C closes — with this commit — Phase A of the development assessment is complete. The FORAY-shape substrate hygiene cleanup arc is done.

These implementation notes serve a dual purpose:

  1. CR-C close — what landed, what surprised, what's preserved. Standard implementation-notes role.
  2. Cleanup arc closure — what the three-CR sequence accomplished, what methodology candidates emerged, what the trajectory looks like from end to end. This is the closing artifact for the entire arc.

§9 carries the arc-closure summary. §§1-8 cover CR-C specifically.


1. What landed (CR-C specifically)

Phase 63 closed in five commits on the engine, merged as 573abe1:

  1. 64516f5 — Migration 0073 (in-place transform) + CreditFlowRow ORM rename
  2. fa26163 — Refactor three downstream readers to typed-column predicates
  3. 7a13132 — Update 12 constructor sites with typed columns + reserved-emit (includes inline reader 3 refactor)
  4. c894f9b — Documentation sweep: credit.foray_action_flows → credit.flows
  5. bad7ea7 — Update tests for credit.flows rename and typed-column queries

The cleanup proceeds as the scoping note v0.2 anticipated: CR-A audit cleanup (smallest, validates pattern) → CR-B memory-events Phase 25 wiring (scale-down to 1 site) → CR-C credit substrate (scale-up to 12 sites, the architectural test).

1.1 Substrate result

credit.foray_action_flows no longer exists. credit.flows carries the 2 historical rows (preserved through ALTER TABLE ... RENAME) plus the new typed-column schema:

| Column | Type | Status | |---|---|---| | id | UUID PK | unchanged | | transaction_id | UUID NOT NULL | unchanged (actually groups multi-row transactions) | | event_kind | VARCHAR(64) NOT NULL | NEW — 9-value taxonomy (issuance/consumption_token/consumption_credit/suspension/reactivation/deletion/balance_zeroing/referral_credit/corrective; legacy_unknown for historical rows that fall outside) | | asset_id | VARCHAR(64) NOT NULL | unchanged | | quantity | BigInt NOT NULL | unchanged | | from_party | VARCHAR(128) NOT NULL | unchanged | | to_party | VARCHAR(128) NOT NULL | unchanged | | occurred_at | TIMESTAMPTZ NOT NULL | renamed from timestamp | | turn_event_id | UUID NULL | NEW — extracted from JSONB for consumption flows | | converted_person_id | UUID NULL | NEW — extracted from JSONB for referral_credit flows | | extra_metadata | JSONB NOT NULL | renamed from metadata (retires SQLAlchemy reserved-name workaround) |

Two new indexes serve the typed-column reader queries:

1.2 Module + writer result

src/loomworks/credit/models.py: class renamed ForayActionFlowRowCreditFlowRow. ORM model carries the three new typed columns; extra_metadata direct mapping (no SQLAlchemy reserved-name workaround); AssetBalanceRow.last_flow_id FK updated to credit.flows.id (intra-credit, same DeclarativeBase — survived the rename transparently).

src/loomworks/credit/flows.py: 11 constructor sites updated. Each now:

src/loomworks/credit/proposal_applier.py:399: 12th constructor site updated identically with event_kind="corrective".

1.3 Reader refactor result

Three downstream readers refactored from JSONB-extract to typed-column predicates:

All three readers now indexable. The architectural upside the design promised: queries become readable and indexable; the typed columns make the identification explicit.

1.4 Test result

| Metric | CR-B baseline (ead29a8) | Final (CR-C close) | |---|---|---| | Collected | 2705 | 2708 (+3) | | Passed | 2670 | 2673 | | Skipped | 33 | 33 | | Failed | 2 (pre-existing) | 2 (same two) |

The +3 is test_foray_reserved_emit_for_credit.py (issuance + referral_credit + suspension smoke tests covering the per-call-site discipline at the writer surface).

Pre-existing failures unchanged: test_api_assertion_retract (JSON-encoding bug at app.py:461) + test_phase_44_evaluator::test_concern_scan_does_not_re_fire_within_dedup_window. Both confirmed against main baseline; both unrelated to CR-C.

8 test files updated mechanically:


2. Trigger continuity empirical evidence (load-bearing for CR-C)

The kickoff §2 framed trigger preservation as "the main unknown going in." This was the load-bearing claim of CR-C's in-place RENAME strategy: PostgreSQL's ALTER TABLE ... RENAME is metadata-only; the trigger binds to the table OID rather than the table name; trigger body references column names CR-C does not change.

After Step 2 (migration applied), synthetic-insert verification:

Test setup:

Synthetic INSERT executed against credit.flows (the renamed table):


INSERT INTO credit.flows
  (id, transaction_id, event_kind, asset_id, quantity,
   from_party, to_party, occurred_at, extra_metadata)
VALUES (..., 'issuance', test_asset, 100,
        'dunin7', test_party, now(), '{}'::jsonb);

Result observed in credit.asset_balances:

| party_id | balance | last_flow_id | |---|---|---| | dunin7 | -100 | a327ea09-4ee1-... | | test_trigger_d28a8a05 | +100 | a327ea09-4ee1-... |

The trigger fired. Both sides of the double-entry maintained atomically. The trigger's binding to the table OID survived the ALTER TABLE ... RENAME transparently. The trigger body's NEW.to_party, NEW.from_party, NEW.asset_id, NEW.quantity, NEW.id column references all resolved correctly against the renamed table's columns (none of which were touched by CR-C).

Decision D3 + D8 validated empirically. The "don't touch the trigger" call was correct. The "rename in place rather than drop-and-recreate" call was correct. This was CR-C's load-bearing architectural bet and it paid off.


3. legacy_unknown row count

The 9-value event_kind taxonomy plus legacy_unknown fallback covers all production patterns the design anticipated. The backfill query:


UPDATE credit.flows
   SET event_kind = COALESCE(
         extra_metadata->>'event',
         CASE
           WHEN extra_metadata->>'pipeline_stage' IN ('classify','respond')
             THEN 'consumption_token'
           WHEN extra_metadata->>'pipeline_stage' = 'credit_debit'
             THEN 'consumption_credit'
           WHEN extra_metadata ? 'grant_id'
             THEN 'issuance'
           WHEN extra_metadata ? 'shape_event_id'
             THEN 'corrective'
           ELSE 'legacy_unknown'
         END
       );

Dev DB result: 2 rows → both issuance (both had grant_id in metadata). legacy_unknown count: 0.

Production data shape: unknown at CR-C close time. When CR-C's migration runs against production, the legacy_unknown count will be empirically established. If it's non-zero, that's not a problem — legacy_unknown is a designated bucket and application code never writes it (new rows always carry an explicit event_kind). Future analytics or compliance work can reclassify if needed.

Recommendation for the Operator: when the migration runs against production, capture the post-migration distribution:


SELECT event_kind, COUNT(*) FROM credit.flows GROUP BY 1 ORDER BY 2 DESC;

The result will be small and informative. Worth recording in production-deploy notes when that ship happens.


4. Reserved-slot consumption — both slots used; four execution-time discoveries

Both slots were consumed by execution-time discoveries that the design didn't fully anticipate. They are evidence for Principle 4 (inventory enumeration vs cognitive density per reference) of the methodology candidates the arc surfaced.

4.1 Slot 1: four execution-time discoveries within test scope

The design anticipated test scope expansion (CR-B's slot 1 surfaced 4 phase-specific tests beyond the inventory's "10 incidental"). For CR-C, the test surface count was correct (8 files) but the per-file density produced four discrete discoveries:

Discovery 4.1.1 — Multi-line JSONB predicate the sed-script's regex didn't catch.

The sed-script regex matched CreditFlowRow.extra_metadata["event"].astext\s\n\s== "value" patterns. Inside test_phase_48_conversion_detection.py (lines 486-489), one predicate was split across more lines than the regex anticipated:


CreditFlowRow.extra_metadata[
    "converted_person_id"
].astext
== str(referee.id),

Required manual edit. Lesson: Mechanical sed-sweeps work for ~85% of patterns; the remaining 15% need manual review. The cognitive cost is in the review, not the editing.

Discovery 4.1.2 — UUID/str comparison after typed-column conversion.

Test assertion at test_phase_48_conversion_detection.py:535 compared converted_person_id values as set against a set of string UUIDs:


converted_ids = {f[0].converted_person_id for f in flows}
assert converted_ids == {str(referee_a.id), str(referee_b.id)}

After CR-C, f[0].converted_person_id is a UUID object (typed column), not a string. The comparison failed because {UUID(x)} != {str(x)}. Fix: drop the str() calls on the RHS.

Lesson: Typed-column conversions change Python-side type semantics in ways the JSONB-extract version's .astext predicate hid. Tests that compared f.extra_metadata["x"].astext with str(other) were comparing string-to-string. Tests that compare f.typed_column need to compare UUID-to-UUID. The semantic shift is real and tests must adapt.

Discovery 4.1.3 — Two missed raw-SQL metadata->> queries in test_phase_49_proposal_applier.py.

The design §6.3 explicitly called out the test_phase_48_reconciliation_evaluator.py:128 raw-SQL INSERT helper (CR-B's slot-2 finding equivalent). It did not enumerate raw-SQL read queries in other test files. test_phase_49_proposal_applier.py had two WHERE metadata->>'source_proposal_id' = :pid queries at lines 354 and 471 that needed updating to WHERE extra_metadata->>'source_proposal_id'.

These were trivial sed-fixable once found, but the design's enumeration didn't surface them.

Lesson: "Raw-SQL helpers" in the design's scope-warning grep ≠ all "raw-SQL queries against the affected table." Read sites are as important as write sites for the grep enumeration.

Discovery 4.1.4 — Mangled import in test_phase_48_conversion_detection.py:39.

The pre-CR-C import was:


from loomworks.credit.flows import write_issuance_flow
from loomworks.credit.models import ForayActionFlowRow

Two separate lines. The test-sweep sed script's pattern from loomworks.credit.flows import CreditFlowRowfrom loomworks.credit.models import CreditFlowRow collapsed two related lines incorrectly, producing:


from loomworks.credit.models import CreditFlowRow, write_issuance_flow

But write_issuance_flow lives in loomworks.credit.flows, not loomworks.credit.models. The import error surfaced at test collection time.

Lesson: Multi-line import re-arrangements need more careful sed patterns. The fix was to split the import back into two lines, one per module.

4.2 Slot 2: inline reader-writer interleaving in write_referral_credit_flow

Design §3 D9 said "the 3rd reader is inside the writer." In execution, refactoring this required interleaving the typed-column predicate refactor (the reader part) with the writer's typed-column kwargs addition (the writer part). The two changes couldn't be cleanly separated — the function's idempotency check uses the same converted_person_id value the writer later passes to the new row.

The result is one larger function rewrite (one commit, function body coherent) rather than two separable edits (reader commit + writer commit). The final code is cleaner — the function reads as a single coherent flow rather than two awkwardly-bridged sections.

The cost was cognitive overhead beyond the per-site estimate the design anticipated. No halt produced. The interleaving was working with the function's existing structure, not against it.

Lesson: When a "reader" and "writer" are interleaved in one function, refactor them together rather than trying to separate by site. The cognitive overhead is upfront; the resulting code is cleaner.

4.3 Both slots consumed within budget; no third surprise

Per the CR §10.7 reservation, two slots were the budget. Both consumed. No third surprise emerged. The reserved-slot discipline held.


5. Methodology candidate validation status

The CR-A → CR-B → CR-C arc validated several patterns. After three CRs, the patterns have enough evidence to lift to methodology canon at the next consolidation pass.

5.1 Principle 1 — Per-call-site reserved-emit discipline (validated 3 → 1 → 12)

| CR | Sites | Emits | Held? | |---|---|---|---| | CR-A audit | 3 | 3 | yes | | CR-B memory | 1 | 1 | yes | | CR-C credit | 12 | 12 | yes |

Three-instance evidence across the full range Loomworks will encounter. Pattern is settled. Ready for methodology canon lift.

5.2 Principle 2 — Test-filename-to-current-substrate alignment (validated 4 instances, positive + negative)

| Instance | Application | Form | |---|---|---| | 1 | CR-A: rename test_voice_tune_setting_foray_audit.pytest_voice_tune_setting_audit.py | Positive (rename) | | 2 | CR-B: rename test_phase_25_commit_attestation.pytest_commit_webauthn_attestation.py | Positive (rename) | | 3 | CR-B: delete test_phase_25_content_hash_and_foray.py outright (both framing and tested functionality gone) | Positive (delete) | | 4 | CR-C: no renames needed because credit test files were already named after their phase rather than after the FORAY framing | Negative (no-op) |

Instance 4 is the negative case — the principle correctly predicted "no renames needed here" because the credit test files (test_phase_47_credit_substrate.py, etc.) describe their phase-origin which remains historically correct. The principle holds in both directions: rename or delete when filename framing references a cleaned-up origin; leave alone when the existing name still describes what the file tests.

The negative case is methodologically important — a principle that only predicts positive actions is less useful than one that predicts both. Principle 2 is now validated in both forms. Ready for methodology canon lift.

5.3 Principle 3 — Rename-when-bindings-exist vs recreate-when-independent

| CR | Strategy | Why | |---|---|---| | CR-A audit | Drop-and-recreate | Independent table, no FK to other rows, no trigger | | CR-B memory | In-place ALTER COLUMN | One column rename within an existing table; FK to engagements survives | | CR-C credit | In-place ALTER TABLE + ALTER COLUMN | Trigger AND FK on the table; both survive transparent rename |

The rule emerging: rename when downstream bindings exist (trigger, FK, indexes); recreate when independent. Three-instance evidence. Ready for methodology canon lift.

5.4 Principle 4 — Inventory enumeration vs cognitive density per reference

| CR | Inventory accuracy | Per-reference density | |---|---|---| | CR-A | Exact (3 sites, 4 test files) | Matched inventory | | CR-B | Undercounted by 4 (test functions inside "incidental" files testing the wiring directly) | Significantly higher per-reference work | | CR-C | Exact count (8 files) | Four execution-time discoveries within those files (§4) |

The pattern: inventories enumerate references but undercount the cognitive density per reference. A grep that returns "X test files" doesn't capture (a) the per-file work density, (b) the multi-line patterns that mechanical sed misses, (c) the type-shift semantics after typed-column conversion, (d) the raw-SQL queries beyond the obvious INSERT helpers. Three-instance evidence. Ready for methodology canon lift.


6. Decision retrospectives (CR-C specific)

D2 — In-place RENAME strategy

Held cleanly. The ALTER TABLE ... RENAME + ALTER COLUMN ... RENAME operations preserved every downstream binding (trigger, FK, indexes) transparently. The migration is 158 lines (vs CR-B's ~85 for column-rename-with-drops), but operational complexity is lower because the in-place transform avoids the drop-and-recreate ceremony entirely.

The trigger continuity verification (§2) was the moment that locked this decision in.

D3 — Trigger preservation (no DDL touches the trigger)

Validated empirically. The synthetic-insert test confirmed the trigger fires correctly on the renamed table. The trigger body's column references (NEW.id, NEW.to_party, NEW.from_party, NEW.asset_id, NEW.quantity) all resolved against the renamed table's columns, none of which were touched by CR-C.

The kickoff §2 framed the trigger as "the main unknown going in." In execution, it was the smoothest piece — exactly because the design's call to leave it alone was correct.

D4 — 9-value event_kind taxonomy + legacy_unknown fallback

Held cleanly in dev. The 2 dev rows had grant_id in metadata; both backfilled as issuance. legacy_unknown count: 0 in dev. Production may produce some legacy_unknown rows; the design accepts this as a designated bucket.

D9 — Three readers (inventory said 2; grep found 3)

Validated. The third reader inside write_referral_credit_flow was correctly identified at design time. In execution, refactoring it produced slot-2 consumption (interleaving with the writer) but no halt. The interleaving produced cleaner final code; design D9 (count the inline reader explicitly) was correct.

D12 — Per-call-site reserved emit at 12 sites

Held cleanly. Twelve 5-line emit blocks across two files did not feel like ceremony. The pattern (one emit per business event) read naturally even at scale-up. The discipline scales 3 → 1 → 12 without friction. Pattern settled. See §5.1.


7. What CR-C ships forward

To future FORAY integration

When FORAY anchoring eventually opens:

To Phase B (Memory-environment cleanup)

To Phase C (Memory work)

Deferred from CR-C explicitly


8. Future ledger items

Carried forward from CR-A and CR-B, unchanged at CR-C close:

  1. test_api_assertion_retract JSON-encoding bug at src/loomworks/api/app.py:461TypeError: Object of type ValueError is not JSON serializable in _log_request_validation. Pre-existing through three CRs. Worth a small fix at next infra ship.
  1. test_phase_44_evaluator::test_concern_scan_does_not_re_fire_within_dedup_window dedup-window flake — pre-existing through three CRs. Worth investigation when next touching evaluator surfaces.
  1. Test DB engagement_display_identifier_seq overflow — known follow-on, workaround applied every CR; conftest still doesn't reset the sequence between runs. Worth a test-infra ship eventually.

New items surfaced at CR-C:

  1. Production deploy of migration 0073 should capture the event_kind distribution post-backfill for the legacy_unknown count (§3). Worth recording in deploy notes when production migration runs.
  1. Methodology consolidation pass — the four principles §5 have three-instance evidence each. A separate consolidation ship (not part of any CR) should lift them into methodology canon. CR-C's implementation notes are evidence-rich enough that the consolidation can draw directly from them.

9. Cleanup arc closure summary

The three-CR arc began with loomworks-substrate-hygiene-scoping-note-v0_2.md and closes with these notes.

9.1 What the arc accomplished

Three FORAY-shaped substrate surfaces replaced:

| Before | After | CR | Engine tag | |---|---|---|---| | audit.foray_events | audit.events | CR-A | phase-61-audit-cleanup (616bab9) | | memory_events.attestation JSONB + 2 FORAY-readiness columns + injection logic | memory_events.commit_webauthn_attestation + _ANCHOR_PRIORITY relocated | CR-B | phase-62-memory-events-cleanup (ead29a8) | | credit.foray_action_flows + JSONB-extract readers | credit.flows + typed-column readers + 2 new indexes | CR-C | phase-63-credit-substrate-cleanup (573abe1) |

16 reserved-emit call sites with # FORAY_RESERVED_LOCATION: <namespace>.<event_kind> markers: 3 in audit + 1 in memory + 12 in credit. The future-FORAY-integration substrate is in place.

src/loomworks/foray/ module foundation:

Phase A of the development assessment complete. The substrate is clean of speculative FORAY framing.

9.2 The trajectory

The trajectory is visible at record.dunin7.com/sections/substrate/cleanup/ and will remain visible for as long as the record stands.

9.3 What surprised across the arc

The cleanup arc surfaced more methodology than any single CR could have. The three-CR sequence's value is in the principle accumulation as much as the substrate cleanup.

9.4 Methodology candidates ready for canonical lift

Four principles validated across three CRs (§5):

  1. Per-call-site reserved-emit discipline — three-instance evidence at 3 / 1 / 12 sites
  2. Test-filename-to-current-substrate alignment — four instances including the negative case
  3. Rename-when-bindings-exist vs recreate-when-independent — three CR strategies validated
  4. Inventory enumeration vs cognitive density per reference — three CRs of evidence

Worth a separate consolidation ship to lift these into methodology canon. CR-D does not exist; the consolidation is not a "CR-D" — it's a methodology-only ship distinct from the substrate-change CR series.

9.5 What the substrate looks like at arc close

The three previously-FORAY-shaped surfaces:

The future-FORAY hook surface:

The substrate is ready. When the Operator decides to open FORAY integration, the hooks are in place. Until then, the substrate is operationally clean of speculative framing.


10. Confidence at arc close

Cleanup arc closed cleanly.

CR-C landed clean. Five clean commits. Trigger continuity verified empirically (§2). Both reserved slots consumed by execution-time discoveries documented as evidence for Principle 4. The methodology candidates §5 have three-instance evidence each and are ready for canonical lift.

The substrate is clean. The reserved-emit hook surface is in place. The foundation module exists. Phase A is complete. Phase B and Phase C horizons are open.

The trajectory across scoping note v0.1 → inventory → scoping note v0.2 → three sibling CRs → these closing notes is preserved at record.dunin7.com/sections/substrate/cleanup/ for as long as the record stands. Future maintainers — human or otherwise — can read the full discovery record and understand what was learned, what changed, and why.

This is the closing artifact of the cleanup arc. Phase A done.


DUNIN7 — Done In Seven LLC — Miami, Florida Phase 63 Implementation Notes — v0.1 — Closing the FORAY-shape Substrate Hygiene Cleanup Arc — 2026-05-25