DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path phases/stele-extraction-phase-0/loomworks-stele-phase-0-completion-record-v0_1.md

Loomworks — Stele Extraction Phase 0 Completion Record — v0.1

Version. 0.1 Date. 2026-06-14 Author. Marvin Percival (DUNIN7 Operator) with CC (executing agent) on DUNIN7-M4, supervised from Claude.ai. Status. Completion record. Phase 0 of the Stele extraction is complete and pushed to engine origin/main. Markdown primary (technical consumer). Records what moved, how, the two mid-flight boundary corrections, the isolation proof, the database-truth result, the named verification gap carried forward, and the Phase 1 handoff. Governing CR. CR-2026-103 v0.3 (loomworks-record main a9f1095; supersedes v0.1, v0.2 — all three present).


Plain-language summary

Stele — the principal/identity layer, the fourth Loomworks product — now has the start of a clean module boundary inside the engine. Phase 0 stood up the stele/ module and moved the three identity files that no subsystem outside the identity surface imports (recovery codes, the WebAuthn ceremony, session encode/decode). Each move was a pure file rename with every importer repointed to the new path — no logic changed anywhere; sign-in, sign-up, and get_current_person behave exactly as before, they just import from stele/ now. An isolation test harness proves the moved logic runs without booting the rest of the engine. The full suite is green and a real-database sign-in was verified. Nothing load-bearing was rewritten; the identity records, the credential row models, the person-TOTP internals, and the actor constructors do not move until later phases. This is the safe first slice, done.

Two boundary corrections happened mid-flight and are recorded below: the original plan assumed each piece was a separable file (two were not — fixed in CR v0.2), and it assumed moving the three clean files touched no load-bearing code (they had ~113 importers including load-bearing modules — resolved in CR v0.3 by distinguishing forbidden logic edits from in-scope import repoints).


1. What Phase 0 closed

Phase 0 gave Stele a named module (src/loomworks/stele/) and relocated the three identity files that have zero callers outside the identity set:

Every move was a pure rename (git R, zero content change) with all importers repointed (import-line rewrites only; no importer's logic, control flow, or behaviour altered). An isolation harness was added proving the moved logic exercises without engagements, memory, or credit. No behaviour changed anywhere in the engine. The KEK did not move — stele/session.py references it in credentials/, unchanged.

This is the boundary-proving slice: it proved the module structure, proved the test-in-isolation requirement (the Operator's "minor ecosystem"), and proved a load-bearing file (session.py, 85 importers) can be relocated with the import graph repointed and the suite staying green — the pattern Phases 1–5 inherit.

2. State after Phase 0 close

Engine repo (DUNIN7/loomworks-engine).

Phase 0 commit series (all on origin/main):

| Commit | Step | |---|---| | 60e7cfa | 0.1 — empty stele/ skeleton | | 3d96508 | 0.2 — move recovery.py, repoint 8 importers (3 prod / 5 test) | | e2ab1c6 | 0.2 — move webauthn.py, repoint 20 importers (10 prod / 10 test) | | 3b2c585 | 0.2 — move session.py, repoint 85 importers (7 prod / 72 test / 6 script) | | e4656b4 | 0.4 — isolation harness (+3 tests) |

Step 0.3 (KEK reference) required no commit — stele/session.py references the unmoved KEK in credentials/, confirmed.

Record repo (DUNIN7/loomworks-record).

3. What moved, and the move mechanics

The defining discipline of this phase: move, not duplicate (locked decision). Each of the three files is a single implementation that changed location; there is never a second copy and no re-export shim. A move is complete only when every importer points at the new path — so each commit bundles the file rename and all its import repoints together, making each commit a complete, revertible move.

Repoint totals: 113 import sites across the three files (≈20 production, 87 test, 6 script). The only non-import edits in the entire phase were 4 docstring path references (module/test docstrings naming the now-moved module), corrected for accuracy — behaviour-neutral, verified by diffing all changed lines outside the moved files. No logic, control flow, or signature changed anywhere.

RecoveryCodeRow, WebauthnCredentialRow, and PersonRow imports were left pointing at persons.models — those rows do not move this phase.

4. The two mid-flight boundary corrections

Both corrections were surfaced by tree-truth verification before any file moved, and both are recorded canonically in the CR's amendment notes. They are preserved here per corrections-not-smoothed discipline.

Correction 1 (CR v0.1 → v0.2) — file-separability. v0.1's §1 listed five pieces as moving in Phase 0. The tree at 60e7cfa showed two were not separable files: (a) WebauthnCredentialRow and RecoveryCodeRow are defined in shared persons/models.py alongside PersonRow under one Base with ForeignKey("persons.id") — moving them is a metadata/Base-split decision, not a file move; (b) the person-scoped TOTP internals are not a file (persons/totp.py does not exist) but inline logic inside signup.py and login.py. And persons/credentials.py's functions import WebauthnCredentialRow, so they cannot precede it. v0.2 reassigned the row models + credentials.py to Phase 1, and the TOTP internals to Phases 3/4 — each to the phase that already owns the module it lives in. Phase 0 held three clean file-moves.

Correction 2 (CR v0.2 → v0.3) — import repoints vs. logic edits. v0.2 described Phase 0 as "no edits to load-bearing code," but the three remaining files have ~113 importers including persons/login.py, persons/signup.py, and api/deps.py (get_current_person). Under move-not-duplicate, relocating a file means repointing every importer — so v0.2 as written had no executable path (repoint → forbidden edits; don't repoint → broken suite). v0.3 resolved it by distinguishing a logic/behaviour edit (forbidden in Phase 0; deferred to Phases 3/4) from an import-path repoint (the mechanical completion of a move; in scope). The "zero external callers" premise was made precise: zero callers outside the identity set, which is what made the files safe to move; importers within the identity surface are repointed.

The lesson for later phases: "zero external callers" must always be read as "zero outside the identity set," and "don't touch load-bearing code" means "don't change its behaviour" — its import lines will change whenever a file it imports relocates.

5. Verification — and the named gap

Suite (Step 0.5 part 1): 2920 / 46 / 0, identical to the post-0.4 baseline — no regression.

Database-truth sign-in (Step 0.5 part 2) — against the real playground_dev database (not the test DB, not a rollback fixture), session token never printed:

Named gap (carried forward, not a Phase 0 blocker): a full cryptographic WebAuthn ceremony (verify_registration / verify_authentication) against the database was not exercised — it requires a real browser/hardware authenticator to produce a signed attestation/assertion, infeasible headless. This gap is acceptable for Phase 0 because the verify functions are byte-identical renames (a rename cannot break crypto; it can only break import resolution, which the server booting and serving disproves), and because the load-bearing half every authenticated request runs on — session decode through get_current_person — got the full real-DB end-to-end check. The live browser ceremony is carried forward to the Stele standalone / Claude Code Miami UI milestone, where a reference UI drives a real authenticator end to end — that is the natural and correct place to close it.

6. Handoff — Phase 1 is next

Phase 1 — identity records and registry. Moves PersonRow and the registry (Person, get_person_by_id) into stele/, and — per the v0.2 correction — now also carries the credential row models (WebauthnCredentialRow, RecoveryCodeRow) and persons/credentials.py, because those were reassigned out of Phase 0 to the phase that owns the shared persons/models.py. Phase 1 makes the shared-Base / ForeignKey("persons.id") decision once, for all the identity rows together — this is the metadata/Base-split decision Phase 0 deliberately did not make.

Carry-forward cautions for the Phase 1 CR:

Operational note already closed: CC's dev-env memory was updated post-push — programmatic session-minting now imports loomworks.stele.session; webauthn/recovery now at loomworks.stele.; old persons. paths gone (move, not duplicate).


DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — Stele Extraction Phase 0 Completion Record — v0.1 — 2026-06-14