Person-view split — v0.6
Version. 0.6
Date. 2026-06-16
Author. Marvin Percival (DUNIN7 Operator) with Claude.ai (scoping) on DUNIN7-M4.
Status. Steps 1–3.3 built, pushed, green on branch stele-phase-6b-person-view-split (b83deea). This version corrects a propagated error in v0.5 (the false "field-equal to Principal" claim) and finalizes the Step 3.7 plan against the 3.7 inspect. Markdown primary (technical consumer: CC executes).
Against. branch stele-phase-6b-person-view-split b83deea. Suite 2933 / 46 / 0.
What changed from v0.5. Two things. (1) Correction: v0.5 stated the Person view is "field-equal to Principal." That is false — confirmed by the 3.7 inspect. Person is {id, display_name, created_at, updated_at}; Principal adds first_login_at and last_presence_proof_at. So Person ⊂ Principal, a strict subset. The Person → Principal swap is still safe, but for a different reason than v0.5 gave: it is superset-safe (Principal adds two Optional fields, and zero code reads them off any view var), not field-equal. (2) 3.7 finalized: the inspect resolved 3.7's one care-point — the host-vs-identity split of the shim consumers came back 0 host-needing / 68 identity-only, so the sweep is uniform.
Decision needed. One small grouping call: fold the ~5 function renames into 3.7, or split them into a 3.7b follow-on. CR lean: fold in (so Person is genuinely retired in one commit). Confirmable in a word.
Out of scope (contract phase). Drop persons host columns · re-target the 12 host FKs · retire the mirror triggers · rename PersonRow.
Carries v0.1–v0.5 (recorded there). This version adds:
Person view as "field-equal to Principal." Claude.ai took that at face value and propagated it into v0.5 (build-state table and 3.7 scope) and into the framing of 3.7 as a "clean rename, no reconciliation." The 3.7 inspect showed it is false: Person = {id, display_name, created_at, updated_at} is a strict subset of Principal = {id, display_name, first_login_at, last_presence_proof_at, created_at, updated_at}. The swap remains safe — superset-safe, because Principal's two extra fields are Optional and an audit found zero reads of .first_login_at / .last_presence_proof_at off any view var — but the reason in v0.5 ("field-equal, clean rename") was wrong. This is a drift-surface failure: a fact from an earlier tool output was treated as authoritative without re-verification and written into a CR. The source Person view docstring carries the same false "equals Principal" claim and is fixed in 3.7. Recorded rather than silently corrected.b83deea) — corrected
| Step | Commit | State |
|---|---|---|
| 1 | (in branch) | Principal + HostAccount views authored |
| 2 | 23d4bb5 | stele host-reads re-homed |
| 3.0 | 73846d4 | CurrentUser delivery path (additive) |
| 3.1 | 1396552 | companion_name stripped; HostAccount cascade thread established |
| 3.2 | f49c63f | personal_engagement_id stripped; shared test helper proven; host classification confirmed |
| 3.3 | b83deea | email/mobile/exhaustion_preference/spend_pause stripped — strip phase complete |
Person view now carries: {id, display_name, created_at, updated_at} — a strict subset of Principal (which also has first_login_at, last_presence_proof_at). Not field-equal (correcting v0.5). Person survives solely as the get_current_person shim.
Twin state (on the branch, never to reach main): Person (subset) and Principal (superset). 3.7 collapses Person → Principal; the swap is superset-safe (the two extra Principal fields are Optional and unread off any view var).
Unchanged from v0.4. Summary: M1 combined get_current_user delivery (two cached loads, host-assembled in deps.py); M2 reader re-home by site class; M3 the HostAccount cascade thread; M4 shared test helper; M5 builder↔view anti-drift; M6 commit discipline relaxes once machinery exists; M7 API shape constant.
The last view-layer step. The inspect resolved the one care-point: 0 host-needing, 68 identity-only — the host-vs-identity split has already collapsed to one bucket (3.1–3.3 did the dangerous work). Proven type-level, not just observed: Person is frozen=True with host fields gone, so any surviving host read would AttributeError, and the suite is green. So the sweep is uniform — every get_current_person site goes to the identity resolver, no per-site judgment.
The genuinely new work (not a rename):
get_current_principal → Principal (resolve_session → resolved.principal, no host load). ~15-line analogue of get_current_person. ResolvedSession already carries both person and principal, so the body is a one-field swap — no new DB load.get_current_person_optional re-points (does not fully retire; its nullable behavior is still needed by assert_engagement_creator and Phase-25 draft creation) to return Principal | None (get_current_principal_optional).The mechanical sweep (uniform, superset-safe):
Depends(get_current_person) targets → get_current_principal.: Person / -> Person) and 33 imports (from ...registry import Person) → Principal.deps.py:204 person: "Optional[Person]" = None (the inline twin of get_current_person_optional) — hand-edit to the optional principal form.Depends sites (require_founder, engagements.py ×4, seed_conversation.py ×5) — swap the dependency target only, no annotation.Retirement + collapse:
get_current_person and ResolvedSession.person (re-point the shim reads to .principal).Principal view and the now-subset Person view into one (Principal). No twin reaches main.Person view's "equals Principal" claim is wrong (it's a subset); correct or remove it as the view collapses.Function renames (~5 sites) — [GROUPING: fold into 3.7 per CR lean; confirmable]:
get_person_by_id → get_principal_by_id (caller auth_dev.py:82); get_person_by_email (already returns Principal, just misnamed) → get_principal_by_email; create_person, _row_to_person → principal-named; get_principal_and_person_by_id collapses to get_principal_by_id (sole caller session.py:161).
Out of scope in 3.7: get_current_contributor — resolves a Contributor (engagement-scoped, not person-host), does not depend on get_current_person. Leave alone.
PersonRow (ORM row) keeps its name — contract-phase. Verify against playground_dev. Suite green. Commit. Halt.
Step 3.8 — boundary + invariant verification. stele/ imports nothing from persons/; no migration; triggers/columns untouched; full suite green; commit; halt; then merge to main, record, retire branch.
No migration · triggers untouched (0085, 0086) · persons keeps all 23 columns · stele/ imports nothing from persons/ · API shape constant (M7; Person is never a response_model — confirmed tree-wide, zero OpenAPI drift) · suite green at every commit, halt before push, playground_dev per touched route · corrections preserved.
Principal view (Stele); HostAccount view (host-side); all readers re-homed.Person name retired (view + functions); no twin on main; shim retired; 68 sites on get_current_principal; get_current_person_optional → principal-optional; get_current_contributor untouched.PersonRow survives (contract-phase).playground_dev clean.main, recorded, branch retired.PersonRow host-side; drop persons host columns; re-target 12 host FKs; retire mirror triggers as an ordered pair.persons/host_account_view.py imports HostAccountRow from stele/models.py — resolves at row relocation.personal_engagement_id= kwarg to local builders. Follow-on consolidation onto the shared helper — out of 6b scope.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 personal_engagement_id's FK is re-targeted.personal_engagement_id classification — closed (confirmed host).deps.py, not a generic resolve_session rewrite.
DUNIN7 — Done In Seven LLC — Miami, Florida
CR-2026-110 — Stele Extraction Phase 6b — Person-view split — v0.6 — 2026-06-16