DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path phases/stele-extraction-cr-b/loomworks-stele-cr-b-completion-record-v0_1.md

Loomworks — Stele Extraction CR-B Completion Record — v0.1

Version. 0.1 Date. 2026-06-19 Author. Marvin Percival (DUNIN7 Operator) with Claude.ai (CR drafting) on DUNIN7-M4; CC executing on DUNIN7-M4. Status. Completion record. CR-2026-113 (Stele Extraction CR-B) — host-FK re-target, mirror retire, persons drop — is built, verified on playground_dev, and merged + pushed to engine main at fe7c3f1 (a --no-ff merge of the three-commit branch; origin/main confirmed d0f302e pre-merge, no drift; branch cr-2026-113-stele-cr-b retired local + origin). Markdown primary (technical consumer).

Governing CR. change-requests/cr-2026-113-stele-extraction-cr-b-v0_2.md (as-built, §0 records five deltas); v0.1 preserved alongside as the planned trajectory.


Plain-language summary

What CR-B did. The last Stele arc before Phase 7 (packaging). After CR-C, principals was the canonical identity table and persons stood only as a reversed mirror (trigger 0090) feeding 11 host foreign keys. CR-B moved those 11 FKs onto principals.id, then removed everything that existed only to keep persons alive — the reverse mirror trigger, the 4 _PersonRow registration stubs, and the persons table itself.

What CR-B did NOT do. It did not begin Phase 7 packaging, did not touch identity reads/writes (canonical on principals since CR-C), and did not re-open the FK target (principals.id, settled). It is the clean clearing of the last persons coupling.

The end-state. persons is gone. All 11 host FKs reference principals.id, each with its original delete behavior preserved (3 ON DELETE CASCADE, 8 NO ACTION). The reverse mirror trigger and function are retired. The 4 _PersonRow stubs are removed and the 8 ORM ForeignKey("persons.id") declarations they targeted are now DB-only references to principals.id (the host_account.id → principals.id cross-base pattern). principals carries all 15 inbound host/credential FKs (the 11 + the 4 pre-existing).

The build was three commits, as planned — but a wider surface than planned. The destination held exactly (zero drift on the §3 FK table, the §4 trigger body, the §5 stub list). What the plan under-counted was three adjacent surfaces: the mirror's non-FK consumers (4 production raw-SQL reads), the ORM FK strings the stubs were the target for (8), and the data half of Migration C's down-path. All caught at halt points or by the suite, all resolved on the merits. Recorded in the CR's v0.2 §0 and summarized below.


1. What was built

All 11 host foreign keys now reference principals.id. Each re-target was a pure constraint swap (every FK column was already uuid — zero column-type migrations) preserving that FK's own delete behavior verbatim: person_settings, saved_filters, workspaces keep ON DELETE CASCADE (now anchored on the principals row, the intended end-state); the other 8 keep NO ACTION; all 11 are ON UPDATE NO ACTION. Migration A asserts persons.id ⊆ principals.id at build time before swapping — no backfill implied, but the invariant is checked, not assumed.

The reverse mirror trigger stele_principals_mirror_to_persons and its function stele_mirror_principals_to_persons() are dropped. The 4 id-only _PersonRow stubs (files, notifications, orchestration, persons models) are removed; the persons table is dropped. The 8 ORM ForeignKey("persons.id") columns those stubs served are converted to DB-only columns — the DB constraints from Migration A carry referential integrity.

2. The build (merged at fe7c3f1)

| # | Commit | What | |---|---|---| | 1 | 47d544d | Migration A (0091) — re-target 11 host FKs persons(id)principals(id); per-FK delete behavior preserved (3 CASCADE / 8 NO ACTION); build-time subset assertion | | 2 | bd1f18a | Migration B (0092) — retire reverse mirror trigger; + 4 production-read repoints (Delta 2) + 1 test repoint (Delta 5) | | 3 | f55386a | Migration C (0093) — drop persons (down recreates+refills); remove 4 _PersonRow stubs; 8 ORM FKs → DB-only; 2 test repoints |

Three migrations 0090 → 0091 → 0092 → 0093. Suite 2928 / 0 / 46 skipped at every commit and at head. Ground-checked before Commit 1: origin/main at d0f302e, working tree clean, both DBs at 0090, no drift. Merged --no-ff into main at fe7c3f1 (16 files, +359/-89); origin/main re-confirmed d0f302e immediately before the merge.

3. The five as-built corrections (caught at halt points / by the suite, each on the merits)

Three of the five are grounding gaps — Step 0 inventoried one kind of persons coupling and missed another. This is the notable shape of CR-B: the enumerated surface (FKs, trigger, stubs) was exact, but persons had couplings the inventory's lenses didn't sweep.

3a — migration path (Delta 1). §7 named src/loomworks/migrations/versions/; the real script_location is migrations/ at repo root. Path error in the plan; staged correctly as migrations/versions/0091–0093.

3b — the mirror had non-FK consumers (Delta 2, the richest correction). §4 reasoned the mirror's only job was the 11 FKs. Step 0 swept FK references and ORM stubs but not raw-SQL reads of persons. Four production sites read persons directly (2 approval-card recipient lists in credit/, 2 notification JOINs in notifications/listener.py), silently fed by the mirror. Retiring it broke 21 tests; in production it would have returned empty recipient lists for any post-CR-C identity. Repointed all four to principals; folded into Commit 2 per Operator decision (causally inseparable from the retire). Falsifies §4's premise.

3c — stub removal strands 8 ORM FK strings (Delta 3). The 4 _PersonRow stubs were the in-metadata FK target for 8 ForeignKey("persons.id") declarations across 5 modules. Removing the stubs alone breaks configure_mappers. Resolution: convert all 8 to DB-only → principals.id, matching the host_account.id → principals.id cross-base pattern (principals is on Stele's separate MetaData; a string FK can't resolve across bases). Verified no relationship() / host create_all depends on them.

3d — Migration C down-path is schema + data (Delta 4). §7 said "recreate persons (7 columns)." But the chained downgrade re-points the 11 FKs back onto persons (A-down), and an empty recreated persons fails the constraint against live host rows. At drop time, persons was a 1:1 mirror of principals, so the faithful inverse recreates and refills from principals. Caught only by the full-chain down test, not the suite. Follows the CR-A/CR-C schema+data down-path discipline.

3e — empirical test surface = 3 repoints (Delta 5). All stale references to the dropped identity table, none an assertion against the mirror: test_automatic_membership.py back-fill query (Commit 2), conftest.py TRUNCATE list, test_stepup_organization_structure.py org-less-identity query (Commit 3). WHY verified each time before repointing.

4. Verification — proven on the real database

On playground_dev (41 principals, live host data — 322 conversation_turns, 360 companion_notifications, etc.), rolled back where mutating:

Full suite green on the branch (2928/0/46).

5. End-state

6. Closing sequence (all done)

  1. Merged to engine main at fe7c3f1 (--no-ff; origin/main confirmed d0f302e pre-merge). ✓
  2. This completion record — filed to loomworks-record at phases/stele-extraction-cr-b/. ✓
  3. CR v0.2 (as-built)change-requests/cr-2026-113-stele-extraction-cr-b-v0_2.md; v0.1 preserved as planned trajectory. ✓
  4. Manifest fold-incurrent-status-manifest-v0_64 records CR-B complete, alembic 0090 → 0093, the three grounding-gap deltas, Phase 7 next. ✓
  5. Branch retiredcr-2026-113-stele-cr-b deleted local + origin (was f55386a). ✓

7. Carry-forwards (recorded, landing where they can land cleanly)

  1. Phase 7 (packaging) — the standalone repo, SDK surface, mountable router, reference app, the clone-and-run bar. CR-B was its last identity-side precondition; Phase 7 is the next Stele arc.
  2. The persons.spend_pause / host-column lineage — already off persons since CR-2026-111 M2; no CR-B action, noted for completeness.
  3. Stele's standalone principals ownershipprincipals is now the sole identity table with no persons shadow; the standalone repo inherits a clean single-table identity model.

8. Methodology lessons banked from CR-B (for the next consolidation, not for Phase 7 execution)

  1. An inventory's lenses define its blind spots. Step 0 swept FK references and ORM stubs exactly (zero drift) but missed raw-SQL reads of the same table and the ORM FK strings the stubs targeted. When grounding a "remove table X" change, the sweep must cover every accessor class — FK constraints, ORM FK declarations, raw-SQL reads/writes, dynamic access — not just the one the prior CR's framing made salient. (Echoes the CR-A "model strip is a tripwire grep cannot replace" lesson, generalized: each accessor class needs its own sweep.)
  2. "The mirror's only job is the FKs" is a premise to test, not assume. A mirrored table can have non-FK consumers (raw-SQL reads) that the FK inventory never sees; retiring the mirror silently breaks them. Falsified §4 here; the failure mode (empty recipient lists) was silent, not loud.
  3. Removing an FK-target stub is not free — it strands the FK declarations that named it. The dual-job lesson from CR-C (a class is identity-model and registration) extends: the registration stub is itself the target of N ORM FK strings, which must convert to DB-only (cross-base) or repoint when the stub goes.
  4. A drop migration's down-path is schema + data when the dropped table fed live FKs. Recreating the table empty is not the inverse of dropping a populated one; the chained downgrade's FK re-point fails on live data. Reconstruct the data state that existed at drop time (here: refill from the mirror source). The full-chain down test, not the suite, is what catches this.
  5. Fold-vs-separate-commit for a discovered surface is the Operator's call, surfaced at the moment of discovery. The 4 production repoints (a grounding gap expanding into production code, falsifying a CR premise) were surfaced before committing, not absorbed silently — and folded into Commit 2 only on Operator decision. Halt-and-surface beats absorb-silently, applied to scope expansion.

DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — Stele Extraction CR-B Completion Record — v0.1 — 2026-06-19