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).
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 Person → Principal 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.
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.
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_principal → Principal (identity-only, no host load). get_current_user → CurrentUser(.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):
get_current_user; hub → keep the object + in-body host load; non-handler helper → host value as a param (never import host types into Stele).HostAccount cascade thread — one param carries all host fields through the converse cascade; no per-field param explosion; boundary-clean (orchestration is host-side).tests/helpers/identity.py) — make_person_and_host (in-memory) + create_person_with_personal_engagement (DB-backed, asserts mirror sync); replaced ~9 per-file builder copies; coverage preserved (real values asserted, never None-to-compile).Person/Principal construction in the same commit it leaves the view.Person string literals and title="Person" (OpenAPI / M7) were never corrupted. A blanket \bPerson\b regex would have broken the API-docs surface.
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.
The method above was not the plan — it was earned. Key corrections, preserved:
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.personal_engagement_id host classification — Operator-questioned; confirmed host (FK into the host engagements substrate; a standalone principal cannot carry it). Carry-forward closed.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).me_create_engagement leaf→hub — misclassified as leaf pre-flight; build found it fans the object to a helper; corrected to the hub tactic.The contract phase completes the physical split. Its scope, assembled across 6b:
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).host_account.0085 → principals, 0086 → host_account). They fire in name-sort order; retire them so one never fires without the other (6a completion record §5).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.get_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):
persons/migration.py imports Principal, _row_to_principal unused (the 3.7 sweep renamed pre-existing dead imports). Harmless; remove at contract phase (that file is touched there anyway).personal_engagement_id= kwarg to local builders; consolidate onto the shared helper.persons.personal_engagement_id → engagements FK isolation issue (passes once any engagement-loading module is in the run; full suite green). Pre-existing; relevant when the FK is re-targeted.deps.py (host-side), not a generic resolve_session rewrite. Until profiled, the two-load shape stands.Principal view (Stele); HostAccount view (host-side); all readers re-homed.Person name retired (view + functions); no twin on main; shim retired; readers on get_current_principal / get_current_user.PersonRow survives (contract-phase).playground_dev clean.main (129d124), recorded (this document), branch retired.
DUNIN7 — Done In Seven LLC — Miami, Florida
CR-2026-110 — Stele Extraction Phase 6b — Person-view split — v0.7 (CLOSE-OUT) — 2026-06-16