DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path scoping-notes/loomworks-b41-scoping-note-v0_2.md

Loomworks — B-41 scoping note — standing the system up from its own sources — v0.2

Version. 0.2 Date. 2026-08-03 Supersedes. v0.1, drafted and never filed — halted at pre-flight. The record holds no v0.1. Changes from v0.1. One count dropped, and a correction to the grounding document recorded at §8. v0.1 said "the hundred-and-three-file chain", inherited from the findings. The chain is 102 files. The count earns nothing here — the point is that no migration creates the row, whatever the chain's length. Author. Claude.ai (drafting session). Operator: Marvin Percival. Charter. standing-notes/dunin7-standing-authorization-charter-v0_1. Build-list item. B-41. Grounding. inspection-briefs/loomworks-b41-step-0-findings-v0_1every fact below comes from it, and the findings were read in full before this note was drafted. Status. Scoping. Decides direction; does not authorize a build. One thing must be verified first — see §4.


1. What the inspection established

B-41 is two problems wearing one name. They have different causes, different fixes, and different costs, and treating them as one item is why the workaround needed two separate hacks.

Problem one — the chain assumes a row it never creates. Migration 0034 reads the administrative engagement and raises if it is absent. No migration anywhere in the chain creates it. The only thing that does is ensure_administrative_engagement, called from _lifespan when the application boots.

Problem two — one column the engine's chain does not build. principals.totp_last_step is declared by Stele's ORM model and added by Stele's own migration, and the engine's alembic.ini never scans Stele's versions directory. The engine's chain builds the principals table itself, at 0085, with the other seven columns.

And the finding that reframes both: this is not a regression. The chain has never been the sole source of truth for data, and probably was never asked to be. Every real database this project has run against got its bootstrap rows from the app booting. 0034 was authored later, in a world where that row was already guaranteed, so its author had no occasion to notice. The gap was structurally invisible until something tried to build a database without ever booting the app — which is exactly what B-9's live verification did.

What is not wrong. The sweep found no unguarded NOT NULL without a backfill anywhere in the chain, and the four other data-assuming revisions are downstream of 0034 rather than independent breaks. The chain is otherwise sound. Three migrations require LOOMWORKS_SECRET_KEY and fail recoverably without it — a different failure shape, worth documenting, not worth fixing.


2. The decision, with reasoning and reversal cost

Two decisions, because two problems.

D-1 — the Stele column: Option B. Run Stele's chain separately, as a documented step.

Stele's own README already names the procedure for this exact case"an engine that owned principals before adopting Stele" — and prescribes alembic stamp 0001_baseline followed by alembic upgrade head against Stele's own config. That is word-for-word this situation, and nothing in either loomworks repository references it. A grep for stamp, alembic stamp and stele_alembic_version across both returns nothing.

Why B over A. Option A would point the engine's version_locations at Stele's internal package layout. Stele is a separately released package with its own version and changelog — if it ever moves its migrations directory, the engine's config breaks silently. Option B treats Stele's chain as an opaque command, which is what a separately released dependency should be, and is the shape Stele's README already assumes.

And the two chains already compose correctly. Stele writes to stele_alembic_version and the engine to the default alembic_version — different tables, deliberately, and neither chain references the other's revisions. The mechanism exists; only the procedure was unwritten on this side.

Reversal cost: none before building. This is documentation plus a verification.

D-2 — the bootstrap: make it invocable on its own. Option D, with the piece Option D was missing.

Option D as the findings state it cannot work, and the findings say why: 0034 sits before any app boot could plant the row, in the only sequence a fresh run attempts. Boot the app, then migrate is not orderable when the thing that breaks is in the middle of the migration.

The findings also name the missing piece"a standalone script callable independently of app boot, which does not currently exist." That is the fix. The ensure_* functions already exist, are already idempotent, and are already the single implementation. What is missing is a way to call them without booting a web server.

Then the sequence is expressible: migrate to 0033, run the bootstrap, migrate to head.

Why this beats Option C. C would have a migration insert the administrative engagement. But 0034 does not merely need the row — it reads the row's current_engagement_version, which the bootstrap sets by writing seed and engagement memory objects through append_event. A migration would have to replicate all of that, which is the 0058 mirror hazard the codebase has already accepted once and documented as a cost: "If you change one, change the other." D-2 duplicates nothing — it exposes the existing implementation through a second entry point.

One consequence to accept honestly. A fresh stand-up becomes three commands rather than one. That is a real cost and it is smaller than a second implementation of the bootstrap drifting against the first.

Reversal cost: one command's worth of code, before anything depends on it.


3. What both decisions leave undone, deliberately

The engine's README says nothing about the database at all — zero matches for alembic, migrat, stele, database or create_all. The only written instruction to run the chain anywhere in the repository is an error message inside conftest.py, encountered mid-test-run.

Whatever B-41 builds, the documentation is the deliverable. A three-command sequence nobody can find is the same defect in a new place — and this project's governing rule is that a thing states only what a read supports. A README that omits the database entirely does not state something false; it states nothing, on the one subject a new environment most needs.


4. What must be verified before a change request is drafted

One thing, and the findings named it as unread.

Does Stele's stamp-and-upgrade procedure actually succeed against the database shape the engine's own chain produces? Stele's 0001_baseline creates principals, webauthn_credentials and recovery_codes from nothing. The engine's chain built those same tables at 0029 and 0085, plus intervening revisions. Stamping asserts they match. Whether they do is unread, and it needs Alembic run against a database.

This is a verification task, not an inspection, and it needs a throwaway database on the walk-audit dev pattern. If the shapes do not match, D-1 collapses and the Stele half reopens with Option A back on the table — which is precisely why it is verified before a change request rather than during one.

A second thing worth establishing in the same run, cheaply: whether the full sequence at D-2 — migrate to 0033, bootstrap, migrate to head, then Stele's stamp-and-upgrade — actually produces a working database. That is the claim B-41 exists to make true, and asserting it without running it once would be the defect this project keeps correcting.


5. What this note does not do


6. Sequence from here

  1. The verification at §4, on a throwaway database. Its own brief.
  2. The change request, drafted against what the verification found — the bootstrap entry point, the documented sequence, and the README.
  3. B-41 closes when a fresh Postgres instance can be taken to a working database by following written instructions, and someone has done it.

7. A note on the inspection itself

The findings answered a question the brief did not think to ask. The brief asked whether the chain had ever been the source of truth and expected an absence-of-evidence answer. The findings produced positive evidence instead — they found the route, named it, and explained why the gap was invisible for as long as it was.

They also corrected the framing of the problem: two problems, not one, with the smaller half already solved in a README nobody on this side had read.

Recorded because the difference between we could not find evidence the chain works and we found the specific thing that has always done this instead is the difference between a scoping note that guesses and this one.


8. A correction to the grounding document

inspection-briefs/loomworks-b41-step-0-findings-v0_1 states the migration chain is 103 files. It is 102.

The findings say it twice — once as "the full 103-file chain (0001 through 0102)", which is 102 by its own construction, and once as "103 files, counted by ls | wc -l", which is a claim to have counted. A direct recount of the directory returns 102.

Nothing in the findings' conclusions depends on it. The sweep covered the whole chain either way, 0034 is still the only independent break, and no unguarded NOT NULL was found. This is an off-by-one in a chain length, not an error in what the chain does.

Recorded here rather than by editing the findings, which are filed and pushed — per the record's corrections-preserved discipline, a superseded figure stands where it was written and the correction sits alongside. It rides into the next manifest bump.

> Why the drafting session did not catch it. Its own count sweep asked is this number a finding or is it ornament? — and 103 was a finding, so it passed. The executing session's sweep asked a different question: count the thing. Only the second catches an inherited error, and this is the third time a miscount has crossed from a findings document into a document drafted from it. The rule that follows: a count carried from another document is not verified by establishing that it is load-bearing. It is verified by counting.


DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — B-41 scoping note — v0.2 — 2026-08-03 Two problems, two answers, one verification before either is built.