Version. 0.1
Date. 2026-08-03
Brief executed. inspection-briefs/loomworks-b41-step-0-inspection-brief-v0_3.md
Charter. standing-notes/dunin7-standing-authorization-charter-v0_1. R-5 inspection run.
Build-list item. B-41 Step 0.
Status. Read-only. No fix, no change request, no recommendation. This document establishes ground for a scoping decision the Operator makes elsewhere.
| Repository | Branch | SHA | Tag | Tree state |
|---|---|---|---|---|
| /Users/dunin7/loomworks-engine | main | ffc29afbe07bc917f3460f32491389db2fe7e713 | — | clean (git status --porcelain empty) |
| /Users/dunin7/stele | main | 689de4192f68383edac2d0590ffa7ce195393e5c | v0.4.0 (exact match) | clean (git status --porcelain empty) |
Both SHAs match the brief's header exactly. Both were confirmed by git rev-parse HEAD, not read off the brief.
No database was connected during this session. playground_dev was not touched, and no other database — throwaway or otherwise — was connected to. No migration was run, in either direction, against anything. Every claim below about what a migration does, or what it would require, comes from reading the migration's source file, never from executing it.
The migration chain has never been exercised end-to-end from an empty database, and there is direct, positive evidence — not merely an absence of proof — that a route other than the chain has always supplied at least one row every later migration in the chain depends on.
/Users/dunin7/loomworks-engine/src/loomworks/api/app.py:65-98 defines _lifespan, the FastAPI startup/shutdown handler wired to create_app() (the module docstring, app.py:1-9, states this factory is "called by the uvicorn entrypoint and by tests" — i.e. this is the real deploy/boot path, not a test-only stub). Its docstring (app.py:67-73) states outright:
> "Startup: ... 2. Open a bootstrap session; run the Phase 2 administrative-engagement and seed-requirements bootstraps (idempotent); commit."
At app.py:85-98, inside the startup transaction, four bootstrap calls run in sequence:
ensure_administrative_engagement(session) — app.py:87ensure_seed_requirements(session) — app.py:88ensure_e2e_test_engagement(session) — app.py:91ensure_credit_engagements(session) — app.py:98
ensure_administrative_engagement is defined at /Users/dunin7/loomworks-engine/src/loomworks/engagement/bootstrap.py:88-177. Read in full: it does SELECT id FROM engagements WHERE id = :id for the deterministic UUID 00000000-0000-0000-0000-000000000001 (bootstrap.py:26, :96-100), and if the row is absent, issues INSERT INTO engagements (id) VALUES (:id) (bootstrap.py:104-107), then writes a bootstrap Seed and Engagement memory object through append_event (bootstrap.py:109-161). It is explicitly idempotent (bootstrap.py:7-9, :92-93): "subsequent calls find state already present and return without changing it."
ensure_e2e_test_engagement (bootstrap.py:180-278) is the same pattern for the E2E sandbox engagement (00000000-0000-0000-0000-0000000000e2), and its own docstring says "Bootstrapped at app startup" (bootstrap.py:40).
ensure_credit_engagements lives in /Users/dunin7/loomworks-engine/src/loomworks/credit/bootstrap.py. It creates two further deterministic engagement rows — CREDIT_MANAGEMENT_ENGAGEMENT_ID (credit/bootstrap.py:53-54) and ACCOUNTING_ENGAGEMENT_ID (credit/bootstrap.py:56-57) — via the same INSERT INTO engagements (id) VALUES (:id) pattern (credit/bootstrap.py:138), and the docstring at app.py:92-94 calls it a mirror of the ensure_administrative_engagement precedent.
This is the route. None of these four rows — the administrative engagement, the E2E test engagement, the credit-management engagement, the accounting engagement — is created by any Alembic migration (§3.1 and §3.2 below confirm the migration chain only ever reads and assumes the administrative engagement; it never inserts it). They are created by application code that runs every time the process boots, against whatever database DATABASE_URL points at, independent of whether the migration chain has ever been run to completion.
bootstrap.py:1-9's own docstring calls this "Per R-A12" and "the Phase 2 convention" — i.e. this bootstrap has existed since early in the project's history. Migration 0034_phase_15_loomworks_engagement_induction.py (Phase 15, a much later phase — see §3.1) is written on the assumption that the administrative engagement row already exists (it raises RuntimeError if not, 0034:276-280) and never creates it. The only place in the entire repository that creates that row is the app-boot bootstrap in §2.1.
The plain conclusion: **the migration chain has never been the sole source of truth for the schema's data, and probably never even been asked to be.** Every real database this project has ever run against — including, almost certainly, playground_dev itself — got its administrative-engagement row (and the three other bootstrap rows) from the application booting at least once, not from a migration. Migration 0034 was authored later, against a world where that row was already guaranteed present by the boot-time bootstrap, so its author had no occasion to notice the chain itself doesn't produce it. On a database where the app has never booted — a genuinely fresh Alembic-only build, which is exactly what B-9's throwaway attempted — the assumption is exposed.
This makes B-41 not a regression in a chain that once worked end-to-end, but a gap that was structurally invisible for as long as every real database was born by booting the app before (or interleaved with) applying migrations, rather than by running migrations to completion in isolation first.
/Users/dunin7/loomworks-engine/scripts/bootstrap_platform_founder.py — a standalone script (not run by the app, not run by any migration) that sets system_config.platform_founder_person_id via direct DB write (bootstrap_platform_founder.py:60-77). Its own docstring calls it "the ONLY path to set the first founder" (:5-6) and instructs it be run "by DUNIN7 authority against the database directly" (:7). It assumes the schema (including system_config) already exists; it does not create tables./Users/dunin7/loomworks-engine/tests/test_stele_isolation.py:79-94 — a pytest fixture (stele_db) that calls Base.metadata.create_all (:89) against settings.database_url_test directly from Stele's own SQLAlchemy metadata, bypassing Alembic entirely. The docstring is explicit that this is deliberate isolation proof, not a deployment path (:24-28): "checkfirst (default) makes this a no-op on the migrated test DB, but it proves the metadata is self-sufficient." This is a create_all route, but it is scoped to one isolated test file's fixture, rolls back every use (:93), and is explicitly framed as a proof, not a way to stand up a real database./Users/dunin7/loomworks-engine/tests/conftest.py:35-55 — the session-scoped engine fixture used by the rest of the suite does not create schema itself. It checks for three expected tables and raises RuntimeError("Expected 3 tables, found {count}. Run: ALEMBIC_URL=<test-url> uv run alembic upgrade head") if they are absent (:39-53). This is evidence the test suite's own authors expect alembic upgrade head to be the route for a test database — but nothing in the repository shows that command succeeding from empty, and per §3.1 it cannot, without the app having booted at least once against the same database first to plant the administrative-engagement row (or that row having been created by some other means, e.g. a copied/dumped database).README.md, deployment doc, or script instructs a founding sequence that reconciles the two — see Question four (§5) for the documentation gap this leaves.
Whether playground_dev's actual current schema and data match what the chain, run end-to-end today, would produce, is unread — that comparison needs a live connection to the production database, which this session does not have and did not open (see §7).
0034
File: /Users/dunin7/loomworks-engine/migrations/versions/0034_phase_15_loomworks_engagement_induction.py. Revision 0034, down_revision = "0033" (:42-43).
Read in full, not from any error message or summary. The migration is not inserting the administrative engagement — it reads it and raises if absent:
admin_version_row = conn.execute(
text(
"SELECT current_engagement_version FROM engagements "
"WHERE id = :eid"
),
{"eid": str(ADMINISTRATIVE_ENGAGEMENT_ID)},
).one_or_none()
if admin_version_row is None:
raise RuntimeError(
"administrative engagement not present — required for "
"seed_requirements_engagement_version_at_induction"
)
(0034:267-280)
ADMINISTRATIVE_ENGAGEMENT_ID = uuid.UUID("00000000-0000-0000-0000-000000000001") (0034:52). This is neither an INSERT of that row nor an ALTER TABLE — it is a SELECT used only to read the administrative engagement's current version number (seed_requirements_version, 0034:281), which then gets embedded in the seed payload it does insert (0034:305). The migration's own inserts (INSERT INTO engagements for the Loomworks engagement at 0034:260-265, plus the memory-event writes via _append_event_sync, 0034:148-249) all target rows the migration itself creates or the administrative engagement's event log — never the administrative engagement row itself.
Confirmed: 0034 assumes a pre-existing row rather than creating one. No migration anywhere in the 103-file chain creates engagements row 00000000-0000-0000-0000-000000000001 (confirmed by exhaustive grep across migrations/versions/.py for both the literal UUID and ADMINISTRATIVE_ENGAGEMENT_ID =; the only definitions found are in 0034, 0037, and 0043, all of which only reference* the constant, never insert that specific row). Per §2.1, the row is created solely by application boot code, outside the chain entirely.
The sweep covered the full 103-file chain (0001 through 0102), not just the segment up to the first break, using three independent passes: (a) a grep for every INSERT/UPDATE/op.execute occurrence across all 103 files (48 files matched), (b) a Python AST-free script matching every op.add_column(...) / batch_op.add_column(...) / op.alter_column(...) / batch_op.alter_column(...) call whose block contains nullable=False without also containing server_default, and (c) a targeted grep for raise RuntimeError / is None: guard patterns signalling an assumed-present row, followed by reading each hit.
Revisions that assume rows exist without creating them in-chain (downstream consequences of the same break, not new independent breaks):
0035_phase_15_founding_memory.py:221-223 — _append_event_sync raises RuntimeError(f"append_event_sync: engagement {engagement_id} not found") if the target engagement row is absent. The engagement it targets is the Loomworks engagement created by 0034 immediately prior in the chain — so this only holds if 0034 already succeeded. Not an independent break; a chained consequence.0037_phase_15_seed_engagement_name.py:81 — checks v1_payload is None for a seed object that 0034 creates; same dependency chain.0043_phase_21_e2e_test_engagement.py:103-105 — same _append_event_sync guard, targeting the administrative engagement (referenced again at :207, :331, :375, :389); depends on 0034 having succeeded (which itself depends on the app-boot bootstrap).0058_phase_41_personal_engagement_induction.py:220-222 — same guard pattern, same dependency chain.None of 0035, 0037, 0043, or 0058 is a second, independent break — each is reachable only after 0034 already passed, so on a genuinely fresh database (no app boot ever run) the chain never reaches them; it halts at 0034. They are named here because §3 of the brief asks for every revision assuming existing data, not only the independent breaks.
Revisions requiring an environment variable, not a row, to succeed — a distinct assumption class, found via the same sweep and worth naming even though it isn't "existing data" in the strict sense:
0062_phase_47_credit_substrate.py:90-102, 0063_phase_48_evaluator_state_and_smtp_config.py:70-82, 0064_phase_49_persons_columns_and_thresholds.py:73-85 — each defines a _fernet_encrypt helper that raises RuntimeError if LOOMWORKS_SECRET_KEY is not resolvable from the environment or .env (e.g. 0062:96-102: "LOOMWORKS_SECRET_KEY is not set; migration 0062 cannot encrypt the system_config seeds..."). Each migration's own comment (0062:83-84, mirrored at 0063/0064) notes the seed rows land via ON CONFLICT DO NOTHING, so this is recoverable by re-running after setting the key — a different failure shape from 0034's hard, un-recoverable-in-chain block.
Revisions tightening a column to NOT NULL — checked for a missing backfill, none found unguarded:
The AST-free sweep for add_column/alter_column with nullable=False and no server_default in the same call returned four hits, all read in full:
0056_phase_37_render_compositions_view_and_epr_extension.py:194-199 — alter_column("external_production_records_view", "render_job_id", existing_nullable=False, nullable=True). This relaxes nullability (False → True); not a break.0056:253-260 — the mirror of the above, tightening back to nullable=False, but this is inside downgrade() (0056:236), not the forward path, and its own comment says it "succeeds only if no composition-linked rows exist" — an acknowledged downgrade-only risk, not a forward-chain break.0065_engagement_addressing_primitives.py:159-161 — alter_column("memberships", "operator_sequence_number", nullable=False). Traced upward in the same file: the column is added nullable at :113, backfilled by two UPDATE memberships SET operator_sequence_number = ... statements at :130-131 and :139-148 (the second using a windowed subquery keyed off WHERE operator_sequence_number IS NULL, :148), and only then tightened. Backfill precedes tightening; not a break.0073_credit_flows_cleanup.py:138-141 — alter_column("flows", "event_kind", nullable=False, schema="credit"). Traced: added nullable at :81, backfilled by UPDATE credit.flows SET event_kind = COALESCE(...) at :101-102 plus two further UPDATE statements (:122, :131), tightened only afterward at :137-141 ("Tighten event_kind to NOT NULL after backfill", :30, :137). Backfill precedes tightening; not a break.
No unguarded NOT NULL-without-default or without-backfill revision was found anywhere in the chain.
alembic.ini's version_locations, re-verified at source, this session, not inherited from the brief:
/Users/dunin7/loomworks-engine/alembic.ini — grep -n "version_locations" returns exactly one match, at line 49: # version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions, commented (the line begins with #). Stripping all commented lines from the file (grep -vE "^\s*#" alembic.ini) leaves script_location = %(here)s/migrations (:3) as the only location-relevant directive; no other version_locations line, active or otherwise, exists in the file. Confirmed: only migrations/versions (103 files, counted by ls | wc -l) is scanned.
Which tables/columns the engine's ORM/models expect that only Stele's own chain creates — re-derived from source, not from the brief's or the grounding notes' naming of totp_last_step alone:
The engine's own migration 0085_stele_phase_6_principals_table_expand.py (revision 0085, down_revision = "0084", :41-42) itself does op.create_table("principals", ...) (:56-77) with exactly 7 columns: id, display_name, totp_secret, first_login_at, last_presence_proof_at, created_at, updated_at (_PRINCIPAL_COLUMNS, :48-51, matching the sa.Column list at :58-76). This means the principals table's existence does not depend on Stele's own chain running — the engine's chain builds it independently as part of an app-level "persons → principals + host_account" physical split (CR-2026-109, per 0085:1-9).
Comparing this to Stele's own ORM model, /Users/dunin7/stele/src/stele/models.py, class PrincipalRow (:38-47 for __tablename__ and columns): it declares 8 columns, adding totp_last_step: Mapped[Optional[int]] (:57) to the same 7. Stele's own migration 0002_totp_last_step.py (down_revision = "0001_baseline") is what adds this column (op.add_column("principals", sa.Column("totp_last_step", sa.Integer(), nullable=True)), 0002:27-30) — and only Stele's chain runs it, since the engine's alembic.ini never scans Stele's versions directory (confirmed above).
totp_last_step is the only gap. The other two Stele-model tables, webauthn_credentials and recovery_codes (models.py:75, :105), are both created by the engine's own chain at migrations/versions/0029_phase_14_person_layer.py (confirmed by grep -ln "webauthn_credentials\|recovery_codes" *.py, which returns only 0029, 0082_recovery_code_invalidated_at.py, and 0085) — well before the Stele-extraction migrations (0084 onward) existed as a concept. Diffing each table's full column list against models.py's declarations for WebauthnCredentialRow and RecoveryCodeRow found no further discrepancy in this session's read. So: one column, principals.totp_last_step, is the sole point where the engine's own chain builds a table shape that Stele's ORM model (imported and used live via the stele editable dependency, pyproject.toml:48, :85) disagrees with — and it is a gap, not a superset, in the engine-built table.
No shared version table, and this is deliberate on Stele's side, not accidental. Stele's env.py (/Users/dunin7/stele/src/stele/migrations/env.py:49-54, :61-64) explicitly passes version_table="stele_alembic_version" to context.configure(...) in both the offline and online branches — a non-default table name. The engine's own migrations/env.py (/Users/dunin7/loomworks-engine/migrations/env.py:26-37) passes no version_table argument at all, so it uses Alembic's default table name, alembic_version. The two chains, if pointed at the same database, would not collide on version bookkeeping — this is confirmed working as intended, not merely absence of collision by chance.
No conflicting base revisions, because neither chain references the other at all. Engine migrations/versions/0001_phase_1_substrate_events.py:12-13 — revision = "0001", down_revision = None. Stele src/stele/migrations/versions/0001_baseline.py:26-27 — revision = "0001_baseline", down_revision = None. Both chains independently start from down_revision = None; there is no depends_on or cross-chain reference in either.
Documentation of how they compose does exist — but only on Stele's side, and nothing in the engine repository or the loomworks record links to it, cites it, or appears to have consulted it. /Users/dunin7/stele/README.md, section "Consuming Stele alongside an existing schema" (README.md:82-99), reads in full:
> "Stele's migration chain tracks its own progress in stele_alembic_version (CR-2026-150), distinct from the default alembic_version — so it can run independently of a host's own migration chain against the same database. But if the host's own migrations already created Stele's tables (e.g. an engine that owned principals before adopting Stele, or otherwise pre-built Stele's schema through its own chain), Stele's 0001_baseline will try to create tables that already exist and fail. In that case, stamp the baseline instead of running it, then upgrade forward for anything after it:
> ```
> alembic stamp 0001_baseline
> alembic upgrade head
> ```
> A genuinely fresh database (nothing built yet, by either chain) doesn't need this — a plain alembic upgrade head from a clean start works as shown above."
This is, word for word, the loomworks-engine situation: engine migration 0085 (§3.3 above) is exactly "an engine that owned principals before adopting Stele." Stele's own README names the composition path (alembic stamp 0001_baseline against Stele's own alembic.ini, pointed at the shared database, then alembic upgrade head to pick up 0002_totp_last_step) that would resolve the second failure the B-9 workaround hit.
Nothing in /Users/dunin7/loomworks-engine or /Users/dunin7/loomworks references this. An exhaustive grep for "stamp", "stele_alembic_version", and "alembic stamp" across both repositories returned zero matches. The implementation notes at /Users/dunin7/loomworks/docs/phase-crs/cr-2026-164-implementation-notes-v0_1.md:126 describe the totp_last_step failure and its workaround ("Worked around in the throwaway database only... Engine work, outside this CR (C-3)") without citing Stele's README section that names the documented fix.
So the answer to 3.4 is layered, not a flat "undocumented": the two chains have no defined technical relationship (no shared version table by design, no cross-chain revision reference) — that part genuinely has no composition mechanism. But the procedure for running them together despite that has been written down, in Stele's own README, and the engine side of the project shows no evidence of having found or used it.
Reported without recommendation, per the brief's §5 and §7.
version_locations names Stele's directory)
What it would touch. /Users/dunin7/loomworks-engine/alembic.ini:49 — uncomment and populate version_locations to include Stele's src/stele/migrations/versions path. Alembic would then see revisions from both directories as one merged history (subject to Alembic's multi-directory revision resolution, which requires either a single linear history or branch labels).
What it forecloses. It puts Stele's revision files under the engine's script_location scanning, which means the engine's Alembic history now directly includes Stele-authored revision IDs (0001_baseline, 0002_totp_last_step) inside what is otherwise the engine's own numeric sequence (0001...0102) — two different ID-naming conventions in one logical chain. It also means every consumer of the Stele package that is not loomworks-engine (any other host per Stele's own "mountable... no host framework dependency" framing, README.md:15-19) would need the same version_locations wiring done independently in their own alembic.ini, since this change lives in the engine's config file, not in Stele's package. It does not by itself resolve the version-table question (§3.4) — Stele's chain still writes to stele_alembic_version unless further reconfigured, so the merged scan would need reconciling with two separate version-tracking tables, which Alembic's standard single-config model does not straightforwardly support without customization.
What it would touch. No code change to either alembic.ini. A deployment doc (currently absent, see §5) would need to specify: run Stele's alembic upgrade head (pointed at the shared database, using Stele's own alembic.ini and its stele_alembic_version table) either before or after the engine's own alembic upgrade head, and — per Stele's own README (§3.4 above) — use alembic stamp 0001_baseline first if the engine's chain already built principals (which, per §3.3, it does, at 0085).
What it forecloses. Nothing in the schema or code; it is the option Stele's own README already anticipates and names a procedure for. Its cost is operational, not architectural: every environment stand-up (second deployment, recovery, customer's own deployment, contributor's laptop — the exact list the brief's motivating paragraph names) becomes a two-command sequence instead of one, and that sequence must be documented somewhere a person or agent will actually read it before running either chain — which, per §3.4, has not yet happened on the engine side of this project despite the sequence being written down on Stele's side.
What it would touch. A new engine migration (or an edit to 0034, which the charter's read-only fence and general migration-immutability practice would likely rule out in favor of a new revision) that inserts the administrative-engagement row, mirroring what ensure_administrative_engagement (engagement/bootstrap.py:88-177) already does at app boot — and, separately, a mechanism to run Stele's 0002_totp_last_step (or replicate its effect) inside the engine's own chain, since Option C read narrowly only closes the 0034 gap, not the totp_last_step gap, unless it also absorbs Stele's migration content into an engine-authored revision.
What it forecloses. It creates two independent code paths that both write the same bootstrap rows — the existing idempotent ensure_* functions at app boot, and a new migration doing the equivalent insert — which is exactly the "two implementations carry the same shape and any future drift between them is a maintenance hazard" problem migration 0058 already documents having accepted once, deliberately, elsewhere in this chain (0058_phase_41_personal_engagement_induction.py:300-306: "Synchronous mirror of `loomworks.persons.personal_engagement.create_personal_engagement... If you change one, change the other."). For the totp_last_step half, folding Stele's own migration content into the engine's chain re-creates Option A's problem (an engine migration reaching into and reproducing Stele-owned schema decisions) without Option A's at-least-explicit version_locations` wiring — it would be a silent duplication instead of a declared one.
What it would touch. Documentation only — most plausibly the currently-empty "Setup" section of /Users/dunin7/loomworks-engine/README.md:28-34 (which today reads only "This project is managed with uv... uv sync --all-extras" and says nothing about the database at all, see §5) — naming that a fresh install requires (1) alembic upgrade head up to the point it can go, or a full run if the gap is closed some other way, and (2) at least one app boot (or an equivalent one-shot script) to run the ensure_ bootstraps before migrations that assume the administrative engagement (0034 and its downstream dependents, §3.2) can proceed — which, as written today, is not even orderable, since 0034 comes before* any app boot could plant the row it needs, in the only sequence a fresh Alembic run would attempt (migrations 0001 through head, in order, with nothing running between them).
What it forecloses. This is closest to describing what has actually happened historically (per §2), so it forecloses the least architecturally — but as a "fix" it does not actually resolve the ordering problem in the previous paragraph unless paired with either a migration-chain change (splitting 0034 so the bootstrap can run between two migration steps) or a code change to how the bootstrap functions are invoked (e.g. a standalone script callable independently of app boot, which does not currently exist — the only invocation site found in this session is _lifespan in app.py, §2.1).
Stele being a separately-released, independently-versioned package (v0.4.0, its own CHANGELOG.md, pyproject.toml) most directly constrains Option A and, to a lesser extent, Option C. Option A embeds a path reference (version_locations) from the engine's config into Stele's internal package layout (src/stele/migrations/versions); if Stele's packaging ever changes where its migrations directory sits inside the installed package (a decision entirely within Stele's own release cycle and explicitly out of this brief's scope per §7 / B-22), the engine's alembic.ini breaks silently until someone notices. Option C, to the extent it means copying or re-deriving Stele's migration content into the engine's own chain, creates exactly the drift-hazard pattern 0058's own docstring already names as a known cost elsewhere in this codebase — and that drift-hazard would specifically be against a package that can move out from under the engine on its own release schedule. Option B and Option D do not reach into Stele's package internals at all — they treat Stele's chain as an opaque command to run (alembic upgrade head against Stele's own alembic.ini), which is exactly the shape Stele's own README already assumes when it describes "a host's own migration chain" as a separate, coordinating-but-independent thing (README.md:84-85). No option was found to be foreclosed outright by the separate-package fact; Options A and C are the two whose ongoing maintenance cost that fact raises.
/Users/dunin7/loomworks-engine/README.md's only setup instruction is:
> "## Setup
> This project is managed with uv.
> ```bash
> uv sync --all-extras
> ```"
(README.md:28-34)
This is the entirety of the README's setup guidance. A grep of the whole file for alembic, migrat, stele, database, createdb, and create_all (case-insensitive) returned zero matches. The README does not instruct a reader to run any migration at all, let alone name the sequencing problem in §4/Option D above. There is no CONTRIBUTING.md in the repository (find -maxdepth 1 -iname "CONTRIBUTING*" returned nothing) and no AGENTS.md at the repository root (only /Users/dunin7/loomworks/AGENTS.md, in the separate frontend repository, exists — loomworks-engine itself has none at -maxdepth 2).
The only written instruction to run the chain at all is inside the test suite, not the docs. /Users/dunin7/loomworks-engine/tests/conftest.py:35-53, the session-scoped engine fixture, raises on missing tables with the literal message "Run: ALEMBIC_URL=<test-url> uv run alembic upgrade head". This is a runtime error message encountered mid-test-run, not a setup document a new contributor or agent would read before starting.
Verdict: the README's setup instruction does not produce a working database, because it does not attempt to. uv sync --all-extras installs dependencies only; it makes no database claim, so it cannot be said to fail one — but its omission of any database step is itself the gap named in Question two and Question three above: there is no single documented command, anywhere in this repository, that would take a fresh Postgres instance to a state where the app or the test suite runs. The nearest thing to an instruction (conftest.py's error message, alembic upgrade head) is demonstrably insufficient by itself, per §3.1 — it stops at 0034 on a database the app has never booted against.
Stele's own README is materially more complete and — as far as this session can establish by reading, without running anything — internally consistent for Stele's own clone-and-run case: README.md:71-80 gives pip install -e ., sets two env vars, and alembic upgrade head, stated to build "The three tables... against a fresh database with no external dependency." Nothing found in this session contradicts that claim for a database Stele's chain has never touched — 0001_baseline (src/stele/migrations/versions/0001_baseline.py) is a single, self-contained revision building all three tables from nothing, with no assumed pre-existing row (read in full; no SELECT/guard pattern present, only three op.create_table calls, :35-113).
Stele's README also correctly anticipates, and names a working-as-far-as-can-be-read-here procedure for, the exact composed scenario this inspection found (§3.4, README.md:82-99, alembic stamp 0001_baseline then alembic upgrade head). Whether that stamp-and-upgrade procedure actually succeeds against loomworks-engine's specific database shape (built by engine migration 0085 rather than a fresh Stele run) is unread — this session did not, and per the fences could not, run it. It is a documented, plausible-by-reading procedure, not a confirmed-working one.
Per the brief's discipline clause (§2, item 3: "Do not inherit. The two facts above are the drafting session's reads; re-verify them"), both facts named in the brief's grounding section were independently re-derived by this session rather than taken on the brief's word:
loomworks-engine/alembic.ini leaves version_locations commented out. Re-verified by direct grep of the file this session: the only occurrence of the string version_locations in an uncommented context is absent; the sole textual occurrence, at line 49, begins with #. Confirmed at §3.3 above.stele ships its own chain at src/stele/migrations/versions. Re-verified by directory listing: ls /Users/dunin7/stele/src/stele/migrations/versions returns 0001_baseline.py and 0002_totp_last_step.py (plus __pycache__), and stele/alembic.ini:4 sets script_location = src/stele/migrations, confirmed by direct read this session, not by trusting the brief's assertion. Confirmed at §3.3/§3.4 above.Both facts held as stated in the brief. No correction to either was required — but both were re-derived from the file system and git-tracked source in this session, independent of the brief's prior claim, per the standing discipline.
The following could not be established by reading alone, and are named here rather than estimated, per the brief's fences and discipline clause:
playground_dev's actual current schema matches what the migration chain, run to completion today, would produce. This requires a live connection to the production database, which was neither opened nor attempted (§1, §2.4).playground_dev's administrative-engagement row (and the other three app-boot-bootstrapped rows named in §2.1) was in fact created by an app boot, as opposed to some other route this session did not find (a manual SQL script run once and not committed to the repository, a database dump/restore, etc.). This session established that an app-boot route exists and is sufficient to explain the row's presence without the chain ever needing to create it — not that it is the only possible explanation for how playground_dev specifically came to have that row. Confirming which route actually ran, historically, against playground_dev would require either a database connection (to inspect created_at timestamps or audit trails) or access to deployment history/logs outside both repositories' source trees, neither of which this session has.alembic stamp 0001_baseline / alembic upgrade head procedure (§3.4, §5) actually succeeds when pointed at loomworks-engine's specific database shape — i.e. whether Stele's 0001_baseline migration's create_table column definitions for principals, webauthn_credentials, and recovery_codes are byte-for-byte compatible with what the engine's own migrations (0029, 0085, and the intervening 0086-0093 CR-B/CR-C revisions) actually produced, such that stamping and then running 0002_totp_last_step alone would succeed with no further conflict. This requires running Alembic against a database, which the fences forbid.playground_dev (a staging environment, a customer's own deployment, if any exist) has ever had the migration chain run against it from empty, and if so what happened. No such environment or its history is visible from either repository's source tree.LOOMWORKS_SECRET_KEY (the environment-variable dependency found in migrations 0062/0063/0064, §3.2) has ever actually been absent during a real migration run, causing one of those three migrations to halt in practice. This is an operational/environment-configuration fact, not something the source establishes either way.DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — B-41 Step 0 inspection findings — v0.1 — 2026-08-03 The chain reads as a chain a database that has already been touched once, not as a chain that builds one from nothing.