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.
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.
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.
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.
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.
On playground_dev (41 principals, live host data — 322 conversation_turns, 360 companion_notifications, etc.), rolled back where mutating:
principals; 0 reference persons; 15 inbound on principals (11 + 4 pre-existing). CASCADE proven (deleting a principals row cascades a person_settings dependent); NO ACTION proven (a principals delete with a conversation_turns dependent is blocked). Down restores all 11 to persons(id) with original behaviors; re-up clean.principals write no longer touches persons; after down, the trigger is restored and a principals write mirrors into persons (display_name carried, created_at immutable on conflict); re-up clean.persons is gone and the host-FK class resolves against principals (a signup writes principals; person_settings.person_id resolves; a bogus id is rejected by the FK→principals). Down recreates persons (DDL byte-faithful to the original) and refills it from principals.0093 → 0090: persons exists, trigger present, 11 FKs → persons, persons rows = 41 = principals rows (mirror reconstructed). Re-up to head clean. The arc is fully reversible.Full suite green on the branch (2928/0/46).
persons is dropped. The reverse mirror trigger + function are retired.principals.id, each with its original delete/update behavior. principals carries 15 inbound FKs._PersonRow stubs are removed; the 8 ORM FKs they served are DB-only → principals.id.persons coupling is cleared — the Phase-7 (packaging) precondition on the identity side.main at fe7c3f1 (merge commit), alembic 0093, suite 2928/0/46. Merged + pushed; branch retired.main at fe7c3f1 (--no-ff; origin/main confirmed d0f302e pre-merge). ✓loomworks-record at phases/stele-extraction-cr-b/. ✓change-requests/cr-2026-113-stele-extraction-cr-b-v0_2.md; v0.1 preserved as planned trajectory. ✓current-status-manifest-v0_64 records CR-B complete, alembic 0090 → 0093, the three grounding-gap deltas, Phase 7 next. ✓cr-2026-113-stele-cr-b deleted local + origin (was f55386a). ✓persons.spend_pause / host-column lineage — already off persons since CR-2026-111 M2; no CR-B action, noted for completeness.principals ownership — principals is now the sole identity table with no persons shadow; the standalone repo inherits a clean single-table identity model.DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — Stele Extraction CR-B Completion Record — v0.1 — 2026-06-19