DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path change-requests/cr-2026-110-stele-extraction-phase-6b-v0_7.md

CR-2026-110 — Stele Extraction Phase 6b — Person-view split — v0.7 (CLOSE-OUT)

Version. 0.7 Date. 2026-06-16 Author. Marvin Percival (DUNIN7 Operator) with Claude.ai (scoping) on DUNIN7-M4. Status. COMPLETE — merged to main, recorded. Phase 6b is closed. Merge commit 129d124 on engine main (--no-ff), pushed; branch stele-phase-6b-person-view-split retired (local + remote). Suite 2933 / 46 / 0 on merged main. This version is the close-out record and the contract-phase handoff. Markdown primary. Against. engine main 129d124 (merge of branch from base def8b04).


Plain-language summary

Phase 6b is done. Stele's fused Person view is split into a pure Principal view (identity/auth, Stele surface) and a host-side HostAccount view (host account state). Every reader is re-homed; the Person name is retired at the view and function layer; the get_current_person shim is gone, replaced by get_current_principal (identity) and get_current_user (identity + host). No twin reached main — the branch collapsed PersonPrincipal before merge, verified on the merged tree.

This was the application-layer (expand) half of the physical split. 6a split the tables; 6b re-homed the reads. The DB columns, the 12 host FKs, and the two mirror triggers are all untouched — they are the contract phase, the next CR.

What 6b did NOT do (contract phase, separate CR): drop any persons column · re-target the 12 host FKs · retire the mirror triggers · rename/relocate PersonRow. Handoff list below.


Final state (engine main 129d124)

| Invariant | Verified on merged main | |---|---| | Person view | GONE | | get_current_person shim | GONE | | ResolvedSession.person | GONE (only .principal) | | Principal view | exactly one (registry.py:28) | | Suite | 2933 / 46 / 0 | | Boundary — stele/ imports nothing from persons/ | clean (docstring prose only) | | Migration | none introduced; alembic head 0086 unchanged | | Triggers (0085, 0086) | untouched | | persons columns | all 23 intact | | Branch | retired local + remote (-d, merge-verified) |

Merge scope: 128 files, +1466 / −1114. New files: src/loomworks/persons/host_account_view.py, tests/helpers/identity.py.


What 6b delivered

Views. Principal (Stele surface, stele/registry.py) — identity/auth: id, display_name, first_login_at, last_presence_proof_at, created_at, updated_at. HostAccount (host-side, persons/host_account_view.py) — the full host column set.

Delivery. get_current_principalPrincipal (identity-only, no host load). get_current_userCurrentUser(.principal, .host_account) (host-side assembly in deps.py; two cached PK loads in expand, the contract reality regardless). Optional variants for both.

The method (proven, reusable for future extraction seams):

Commit trail on main (--no-ff, 8 commits): Step 1 (views) · Step 2 (stele host-reads re-homed) · Step 3.0 (CurrentUser delivery) · Step 3.1 (companion_name + the thread) · Step 3.2 (personal_engagement_id + the shared test helper) · Step 3.3 (email/mobile/exhaustion_preference/spend_pause) · Step 3.7 (retire Person + shim) · merge 129d124.


Correction trajectory (full, preserved across v0.1–v0.7)

The method above was not the plan — it was earned. Key corrections, preserved:

  1. Delivery cost. "zero added query" (v0.2) → corrected to one cached PK lookup, expand-phase (v0.3); the boundary forbids Stele exposing loaded host data. Bypass and injected-projector single-query options rejected (the projector's win is expand-only, evaporates at contract).
  2. 3.0 "additive" contradiction — replace/rename verbs (3.7's work) were pasted into 3.0's additive slot; caught at build (would have broken ~256 sites); corrected to add-alongside.
  3. Strip order — hottest-first → easiest-first → field order is not the lever; the thread + shared test helper is. companion_name's "zero test edits" was a benign-default dodge, not proof. personal_engagement_id sequenced next not last, to face the test-helper redesign on the field that forces it.
  4. personal_engagement_id host classification — Operator-questioned; confirmed host (FK into the host engagements substrate; a standalone principal cannot carry it). Carry-forward closed.
  5. Field-equality error (a propagated drift-surface failure) — "Person field-equal to Principal" (from a 3.3 report) was propagated into v0.5; the 3.7 inspect showed Person ⊂ Principal (strict subset). The swap is superset-safe (two extra Optional fields, unread), not field-equal. Corrected in v0.6; the false source docstring fixed in 3.7; verified empirically (the field-set tests pass on the superset).
  6. me_create_engagement leaf→hub — misclassified as leaf pre-flight; build found it fans the object to a helper; corrected to the hub tactic.

Contract phase — handoff (the next CR's starting inventory)

The contract phase completes the physical split. Its scope, assembled across 6b:

  1. Drop the persons host columns — now that nothing reads them off a view (6b re-homed all reads). Includes the mint host-column cleanup (mint stops writing email/mobile/onboarding_complete to persons).
  2. Re-target the 12 host FKs to host_account.
  3. Retire the two mirror triggers as an ORDERED PAIR (0085principals, 0086host_account). They fire in name-sort order; retire them so one never fires without the other (6a completion record §5).
  4. Rename/relocate PersonRow host-side. Resolves the standing inversion: persons/host_account_view.py currently imports HostAccountRow from stele/models.py (the row still physically lives in Stele) — fine during expand, resolved at relocation.
  5. The delivery cost becomes the joinget_host_account_by_id reads from host_account (already does, chosen in 3.0 for exactly this durability); when persons drops host columns, the two-load shape is the natural principals + host_account read. No 6b rework needed.

Non-blocking cleanups (carry, do not let them gate the contract CR):


Definition of done — met


DUNIN7 — Done In Seven LLC — Miami, Florida CR-2026-110 — Stele Extraction Phase 6b — Person-view split — v0.7 (CLOSE-OUT) — 2026-06-16