Version. 0.1
Date. 2026-06-19
Author. Marvin Percival (DUNIN7 Operator) with Claude.ai (scoping-note authoring) on DUNIN7-M4.
Status. Phase 7 scoping note. Step 0 (the accessor-class inventory + extraction-blocker triage) is complete and filed; the import-graph is proven clean — no de-coupling sub-arc. This note resolves the one open decision (the §E fork), names what the Phase 7 CR(s) commit to under that resolution, and sequences the deliverable set. Operator-facing — HTML primary with this Markdown source preserved alongside. Lands in loomworks-record at phases/stele-extraction-phase-7/.
Grounding line. engine main = fe7c3f1 (CR-B merge), alembic head 0093, suite 2928/0/46. record main = 2ed8d9e (Step 0 brief + report filed; manifest v0.64). Drafted from the Step 0 inspection report v0.1 — not from the handoff's shorthand.
What this note does. Names what Stele Phase 7 — the arc that takes Stele out of the engine monorepo into a standalone, clone-and-run package — commits to. It resolves the single open decision Step 0 surfaced (the §E fork: does Stele own signup/login orchestration, or ship primitive-level routes only?), then names the deliverable set and how it sequences into CRs.
The decision, resolved. Fork (B) — Stele ships primitive-level routes only — for the first packaging arc. Stele packages its primitives (ceremony, mint, session, credential, recovery, TOTP) and a thin primitive-level router in the me_security/auth_dev shape. The marquee signup/login orchestration stays a host composition over those primitives; the reference app demonstrates wiring it. Fork (A) — Stele owning the /auth/signup/ + /auth/login/ routes via a host-supplied onboard callback seam — is preserved as a named later arc, not built now.
Why (B). Three reasons, all already on the record. First, seed-alignment: the architecture spec's settled framing is "the Engine is the product; the Companion is a subordinate access path" — Stele is a substrate primitive, not the product surface, and (B) ships exactly that. Second, blast radius: (B) is import-pure and introduces no new seam; (A) introduces a Stele→host onboard callback contract that has to be designed, and designing a seam before the primitive surface is proven in a stranger's hands inverts the safe order. Third, the cut line already exists — Phase-4's mint↔onboard split is the Stele↔host boundary (mint_principal is Stele, onboard is host-pure), so (B) packages along a boundary the code already drew, while (A) reaches across it.
What's settled and not re-opened. Stele is a pure library today (10 .py files, no router of its own). The import-graph is clean — no de-coupling sub-arc. principals is the sole identity table; Stele's MetaData is self-contained with zero outward FK. None of this is a Phase 7 decision; it is the state Phase 7 starts from.
What this note does NOT do. It does not draft the CR(s) — that is the next chat. It does not design the (A) callback contract — deferred. It does not run or specify migrations beyond naming that Stele's migration set must become independently runnable.
The decision the Operator confirms here. Fork (B)-first, as stated. If the Operator wants (A) in the first arc instead, that reverses the central commitment below and the deliverable sequencing changes — raise it before CR drafting.
Step 0 §E surfaced the one decision Phase 7 turns on. Restated from the report's own framing, then resolved.
Option A — Stele owns signup/login orchestration. Relocate the orchestration functions (begin_signup, complete_passkey, complete_totp_verify, begin_login, complete_login, verify_totp_for_partial_session, verify_recovery_for_partial_session) from persons/signup.py + persons/login.py into Stele; Stele ships full /auth/signup/ + /auth/login/ routes. The stranger's app gets working sign-up / sign-in out of the box.
onboard, which is host-side (builds host_account, the credit grant, membership). For Stele to own them, it must call back into a host-supplied onboarding hook — a new Stele→host callback seam Stele defines and the host fills. Not an upward import (the mountable-router injection pattern keeps it clean), but a new contract to design, document, and version.
Option B — Stele ships primitive-level routes only. The me_security / auth_dev shape: direct primitive-level routes (passkey begin/complete/list/revoke, recovery-code regenerate/status, TOTP rotate begin/confirm, dev session-issue). signup/login stay host compositions over Stele primitives; the reference app wires ceremony → mint → session → onboard.
Fork (B). Stele's first packaged form ships primitive-level routes only. signup/login orchestration stays host-side; the reference app demonstrates the composition. Fork (A) is a named later arc.
The three reasons, each tied to a settled source:
mint↔onboard boundary and introduces no callback contract. (A) introduces the Stele→host onboard callback seam — a contract that must be designed before it can be packaged. Designing that seam before the primitive surface has been mounted and run by a stranger inverts the safe order: prove the primitives clone-and-run, then decide whether the orchestration belongs in the package.mint↔onboard split already is the Stele↔host cut line. mint_principal (+ ceremony / session / credential / recovery / TOTP) is Stele; onboard (host_account, credit, membership) is host-pure, importing exactly one Stele symbol (the PrincipalRow type) and calling zero Stele functions. (B) packages Stele along that existing boundary. (A) reaches across it via the callback. Packaging along a boundary the code already drew is lower-risk than building a new seam across it.
(B) means the package, on its own, does not expose /auth/signup and /auth/login as mountable routes. A stranger who clones Stele gets the primitives and the primitive-level routes, plus a reference app that wires them into working signup/sign-in. The clone-and-run bar is met through the reference app, not through the package's own route surface. This is a real cost and the Operator is accepting it deliberately: the headline flows are demonstrated, not shipped-as-routes, in the first arc.
(A) is not rejected — it is sequenced after. Once the primitive surface is proven mountable in a stranger's hands, a later CR can design the onboard callback contract and lift signup/login orchestration into Stele as first-class routes. The conditions that would call for (A): the reference-app wiring proves to be the same boilerplate every host re-writes (a sign the orchestration belongs in the package), or the Miami testers report that primitives-only is too low-level to evaluate. Recorded so the trajectory is walkable, not smoothed.
The deliverable set, from the seed's strategic direction and the manifest's Phase 7 framing (standalone repo, SDK surface, mountable router, docs-with-runnable-examples, reference app, thin config generator), resolved against fork (B):
loomworks-engine into its own repo — the 10 stele/ modules, no residual DUNIN7/Loomworks import. The Step 0 import-graph finding (clean, no Stele→engine imports) is what makes this a lift, not an untangling.pyproject/requirements — the third-party deps the 10 modules actually use (WebAuthn lib, SQLAlchemy, the crypto for the encrypted-secret pattern), nothing inherited from the engine's manifest.principals + the credential/recovery tables.stele/registry, stele/webauthn, stele/session, stele/credentials, stele/recovery, stele/person_totp functions a host calls, with their session/config injection contract made explicit (Step 0 category 6: how Stele receives its Session and its config — e.g. the loomworks_secret_key the encrypted-secret pattern reads — formalized as the mount contract, not an engine import).APIRouter — Step 0 §A confirmed Stele has none today, so this is built, not relocated — carrying the me_security/auth_dev-shape routes (passkey begin/complete/list/revoke, recovery-code regenerate/status, TOTP rotate begin/confirm, the dev session-issue). The host mounts it; the router receives session/config by injection.ceremony → mint → session → onboard into working signup and sign-in. This is the clone-and-run proof. Under (B) it carries more weight than it would under (A) — name it as a first-class deliverable, not a sample.Step 0 proved no de-coupling sub-arc is needed (the import-graph is clean). Whether the packaging is one CR or a sequence is a scoping call, driven by the deliverable-set size — which is large (8 deliverables, two of them — the migration set and the reference app — substantial in their own right).
Recommended: a packaging sub-arc, not a single CR. The deliverable set splits cleanly along "what must exist before what":
Each slice has a clean done-bar a stranger could check. The sub-arc is the recommendation; the Operator may collapse P7-2 and P7-3 if the reference app proves thin, or split P7-1's migration extraction out further if it proves heavy. The sequencing is a recommendation for the CR-drafting chat to absorb, not a settled partition.
For the CR-drafting chat — these are real costs the CRs must plan around, none a scoping blocker:
persons/signup.py is doubly-bound. It serves the signup flow and me_security's passkey-enrollment (add_passkey_begin / add_passkey_complete). Under (B), the primitive router needs the enrollment endpoints, so the passkey-enrollment primitives must come across into Stele while the signup orchestration stays host-side. The split runs through this module — treat it as a shared module, not a signup-only one. (Step 0 §F.2.)stele/webauthn is the load-bearing binding — 4 ceremony functions (begin_registration, verify_registration, begin_authentication, verify_authentication) across both flows. Already inside stele/, settled; named because whatever the reference app demonstrates, the ceremony engine is the core, and the reference-UI WebAuthn-ceremony gap closes against it. (Step 0 §F.3.)COOKIE_NAME is Stele's; the host currently owns the cookie mechanics (set on the FastAPI Response in signup/login handlers, clear via the host _clear_session_cookie). A standalone Stele must decide whether it ships the cookie set/clear helpers or leaves them host-side. Under (B), the primitive router issues sessions; the cookie-set helper is a small thing the package could ship or the reference app could own — name it in the P7-2 CR. (Step 0 §F.4.)The fork resolution was checked against the seed before this note committed to it:
No conflict surfaced. If CR drafting finds the (B) reference-app wiring contradicts a seed commitment not caught here, raise it before proceeding.
Two-role division holds: scoping / CR-drafting (Claude.ai) separate from execution (CC). Fresh chat per role.
loomworks-record and fold the resolution into the manifest.
Read-write. File the Phase 7 scoping note pair to loomworks-record.
Confirm record main is at 2ed8d9e and working tree clean before staging. If not, stop and report.
Place these two files (from Downloads) at loomworks-record/phases/stele-extraction-phase-7/:
loomworks-stele-phase-7-scoping-note-v0_1.md
loomworks-stele-phase-7-scoping-note-v0_1.html
Stage by explicit path — git add the two file paths, not git add -A.
Commit message:
Stele Phase 7 scoping note v0.1 — resolves §E fork (B-first: primitives-only), names deliverable set + P7-1/2/3 sub-arc sequencing
Then halt before push and report the staged state (git status, git diff --cached --stat) for confirmation. Do not push until I authorize.
The manifest fold-in (recording the fork resolution + the P7-1/2/3 sequencing at v0.65) is a separate edit — surface it after this note files, so the manifest bump references the filed note's commit.
DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — Stele Phase 7 Scoping Note — v0.1 — 2026-06-19