Version. 0.1
Date. 2026-06-15
Author. Marvin Percival (DUNIN7 Operator) with Claude.ai, against engine origin/main 677c46f (post-Phase-2).
Status. Scoping note — precedes CR-2026-106. Markdown primary (technical consumer). Drafted against CC's read-only pre-flight at 677c46f, not from memory.
Phase 3 moves the sign-in resolution logic — the work of turning a session cookie into "this is who is acting" — into Stele. The function get_current_person stays in deps.py as a thin stub that calls Stele, so its 146 callers don't move. The phase also draws one boundary: **Stele answers who you are; the host keeps *what you're allowed to do here*** (engagement membership, can_commit). And it routes the one place that still builds a person-actor by hand through the actor_from_person constructor Phase 2 just moved.
This is the integration design's session.resolve operation (§3, §4): "the operation a host calls on every request to learn who is acting … the host keeps this dependency's name and signature, so the sites that depend on it do not change; only its body now calls Stele."
677c46f)get_current_person (deps.py:565-618) — the only definition. Already imports from Stele (registry + session, repointed in Phase 1). It reads the cookie → decodes the session → checks TOTP → loads the person → returns a Person. The function body still lives in deps.py, not in Stele — that's what Phase 3 relocates.get_current_person across src + tests (1 definition + 145 Depends(...) call sites). These are the "~119 callers don't move" the brief names. None move._resolve_cookie_actor (:996-1070) — builds a person ResolvedActor inline at :1063-1067, hand-rolling _ActorRef(kind="person", …) instead of calling the moved stele.actor.actor_from_person. This is the route-it-through target.get_resolved_actor (:912-993) / get_committing_resolved_actor (:1073-1096) — engagement-scoped resolution. These mix Stele concerns (decode session, load person) with host concerns (engagement membership, can_commit, the bearer contributor/agent paths). The boundary cut runs through here.0083.The resolution functions blend two jobs. Phase 3 splits them:
session.resolve (integration design §3.4).can_commit, the bearer contributor/agent paths. These are Loomworks/OVA territory — "what may this principal do in this engagement." They stay host-side and do not cross into Stele.This is not a new decision — it is the host seam the integration design already commits to (§4: "Host-specific concerns never cross into Stele: engagements, memory events, credit/account lifecycle"). Phase 3 applies it to the resolution path.
Example of the cut. Today _resolve_cookie_actor does, in one function: decode session → load person → check the person's membership in the engagement → compute can_commit → build the actor. After Phase 3: Stele's session.resolve does "decode → load person → return principal"; the host code takes that principal and does "check membership → compute can_commit → build the engagement-scoped actor." Stele hands back Marvin; the host decides Marvin can commit in E4729.
The inline person-ActorRef build in _resolve_cookie_actor:1063-1067 routes through stele.actor.actor_from_person — the constructor Phase 2 moved. Reason: leaving a hand-rolled duplicate of a just-extracted constructor is exactly the drift the extraction removes.
Scope limit: person path only. The bearer-path _ActorRef(kind="contributor") (:975) and kind="agent") (:981) constructions stay inline and untouched — those are host/agent concerns, and the agent constructor is the "Later" build (no constructor exists to route to).
session.resolve (the integration design's named operation). deps.py:get_current_person becomes a thin delegator — same name, same signature, same return — so its 146 callers don't move._resolve_cookie_actor / get_resolved_actor: Stele resolves the principal; the engagement/membership/can_commit layer stays host-side.stele.actor.actor_from_person (replacing the inline _ActorRef(kind="person") build).get_current_person callers — do not move; the stub keeps name and signature.:975, :981) — host/Later concerns.agent.authenticate / actor.for_agent — the agent effort, after the main phases.
Where does session.resolve land in stele/? Two candidates:
stele/session.py (default) — the session module already owns decode_session, COOKIE_NAME, SessionInvalid; "resolve token → principal" is the natural next operation there, and the integration design names it session.resolve.stele/resolve.py — if the resolution is better as its own contract surface.
Lean: stele/session.py — resolve is a session operation, and the module already holds the session primitives it needs. CC confirms at Step 0 pre-flight whether folding into session.py collides or fits, same as the Phase 2 actor.py destination check.
Step 0 pre-flight (read-only, archive the CR, confirm coordinates/importers/baseline) → halt → execute with per-step commits, each move bundled with its repoints in one commit (the Phase 2 lesson) → halt-before-push → verify against playground_dev, not the suite alone → completion record + manifest bump.
Carry the Phase 2 lesson: model import topology, not just caller count, when drafting per-step suite expectations. Here the 146 callers depend on get_current_person by name — as long as the stub keeps that name and signature, relocating its body should not abort collection. The CR's Step 0 confirms this against the live tree.
DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — Stele Extraction Phase 3 Scoping Note — v0.1 — 2026-06-15