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

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

Version. 0.1 Date. 2026-06-15 Author. Marvin Percival (DUNIN7 Operator) with Claude.ai (scoping/CR) on DUNIN7-M4; CC executing on DUNIN7-M4. Status. Completion record. Phase 5 (CR-2026-108 v0.2) of the Stele extraction is built, verified against playground_dev, merged to engine main at 8f2665a, and pushed. This record is the full Phase 5 story — what moved, the decision trajectory, the two build-caught deviations, the real-database proof, and the lessons carried forward. Markdown primary (technical consumer).

Governing CR. cr-2026-108-stele-extraction-phase-5-v0_2.md (loomworks-record change-requests/; archived in engine at docs/phase-crs/ commit a877c90). Governing scoping note. loomworks-stele-phase-5-scoping-note-v0_1.md (loomworks-record 53733db).


Plain-language summary

What Phase 5 did. Completed the credential-lifecycle surface of Stele. Two operations were genuinely missing and got built — passkey-revoke (with a last-passkey guard) and person-scoped TOTP-rotate (a new Stele twin). One small gap on the already-built additional-passkey path got closed — display_name now threads through enrollment. Second-passkey enrollment and recovery-code surfacing were already shipping (the latter via the §10.3 amendment) and were not rebuilt.

The build, in order. 5C (display_name) → 5A (passkey-revoke) → 5B (TOTP-rotate). Five commits, fast-forwarded onto main: archive → 5C → gitignore → 5A → 5B. No schema change — Phase 5 stayed at alembic 0084.

What this record preserves. Three decisions and the trajectory to each — including one correction the Step 0 grounding forced (the revoke guard predicate). Two build deviations CC caught. The real-database verification. And one grounding-gap lesson for future phases that introduce a delete.

Final state. main at 8f2665a, suite 2933/46/0, alembic 0084, origin in sync. Branch held (deletion is a separate step after this record files).


1. What moved

New Stele primitives:

New host routes (me_security.py), all get_current_person-gated (inherit the totp_verified second-factor gate):

Repo hygiene: .wrangler/ added to .gitignore (its own commit e21e261).

2. The build (five commits, faithful order)

| Commit | Step | What | |---|---|---| | a877c90 | 1.1 | Archive CR-2026-108 v0.2 into engine docs/phase-crs/ | | 4d5091e | 1.2 (5C) | Thread display_name through the passkey-complete route (source + test only) | | e21e261 | — | Ignore .wrangler/ (own commit, in response to the 5C credential-sweep catch) | | 9477618 | 2 (5A) | passkey-revoke: remove_credential + last-passkey guard + DELETE route | | 8f2665a | 3 (5B) | person-scoped TOTP-rotate twin (stele/person_totp.py + routes) |

Per-step commits, suite green at every commit, halt before every push. The gitignore landed between 5C and 5A — faithful to how the work ran (the .wrangler/ catch happened at the 5C commit, fixed before 5A began), not reordered for tidiness.

3. The decision trajectory (three decisions; one is a correction)

Phase 5's value is mostly in what Step 0's real-code grounding overturned. The scoping note locked decisions from a design-level model; the grounding read corrected one of them against the real login flow. The corrected position and the superseded position are both recorded.

Decision 1 — the revoke guard predicate. CORRECTED at Step 0.

Decision 2 — delete mechanism. Hard-delete, no migration.

Decision 3 — TOTP-rotate primitive. Person-scoped twin, not generalization. (The planned Step 0 decision.)

4. Two build deviations CC caught (both surfaced and resolved, not silently absorbed)

Deviation 1 — the .wrangler/ credential sweep (at the 5C commit). The CR's commit instruction used git add -A, which swept .wrangler/cache/wrangler-account.json (the Operator's Cloudflare account id/name) into the staged 5C commit. CC caught it, amended it out (so 4d5091e is clean), and recommended a gitignore. Nothing was pushed, so the credential never left the machine. Two fixes followed: .wrangler/ was gitignored (e21e261), and from 5A onward the CR's commit instructions staged explicit paths, not git add -A — the class-fix for the footgun, not just the instance-fix.

Deviation 2 — the non-erasure architectural-guard collision (at 5A). The locked hard-delete decision (Decision 2) put the first .delete( in stele/, which tripped tests/test_no_delete_statements.py — the R-A28/R-B21 non-erasure guard that fails the build if .delete( appears in source. The CR did not anticipate this (see §5 — the grounding gap). CC resolved it the guard's designed way: added credentials.py to the file-level allowlist with a rationale matching the six existing precedents (hard_delete.py, workspaces.py, etc.) and the sibling OVA-credentials module's own documented discipline — credentials live outside the memory event log and are deletable without violating non-erasure. The allowlist entry argues the classification (a passkey is operational auth, not a MemoryObject), names the replacement access control (the last-passkey guard), cites the CR, and adds a scope note (unique basename — only stele/credentials.py) tighter than the precedents.

5. The grounding-gap lesson (carried forward)

The Step 0 grounding read the placement site (the credential module) and the call-sites (the login flow) — which is what caught the Decision 1 guard correction. But it did not read the repo-wide architectural-guard suite for what polices the kind of change being made. Introducing the first delete into a non-erasure codebase is exactly the kind of change those invariants exist to catch, and the collision surfaced at build (5A) rather than at grounding.

The lesson, for future delete-introducing (or invariant-adjacent) phases: at Step 0, grep the architectural-guard suite for what polices the operation being added, not just the code being touched. This was applied immediately in 5B — CC's first action in Step 3 was a read-only scan of the guard suite for anything policing a new stele/ file or a PersonRow.totp_secret write (came back clean: rotate overwrites a column, no delete, so the non-erasure guard didn't apply). The discipline moved from "caught at build" toward "surfaced at grounding" within the same phase.

This extends the Stele-extraction grounding discipline: ground the function signatures and placements (Phase 4), and ground the architectural invariants that police the kind of change (Phase 5).

6. Verification — proven in the real database (§7 of the CR)

All six cases passed against playground_dev through the real production HTTP path (real get_db_session, real transaction semantics, real WebAuthn config wired from settings as _lifespan does), stubbing only the WebAuthn crypto verify exactly as the suite does. Run on the local branch before push, so any real-path divergence would have been fixed locally — none was.

Cleanup: five throwaway persons deleted, zero orphan credentials (FK integrity intact), playground_dev still 0084, branch untouched, no commit.

7. Final state

8. What Phase 5 did NOT do (boundaries, for the next phase)

9. Carried-forward items for the manifest fold-in


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