Version. 0.1
Date. 2026-08-04
Author. Claude Code (executing session), DUNIN7-M4.
Brief. inspection-briefs/loomworks-b49-step-0-inspection-brief-v0_1.md — confirmed present and the only B-49 brief in the directory (numeric sort: no v0_2 or higher exists).
Charter. standing-notes/dunin7-standing-authorization-charter-v0_1 — R-5 inspection run.
Status. Read-only, as fenced. Produces no change, no recommendation on gate design.
/Users/dunin7/loomworks-engine, branch main, HEAD = 7bdbf8b07ff25b8e43b4b8e6123414eb1fa4538c, tag stele-mount-test-fix-v0_1 points at it. Tree clean (git status --short empty) both before and after the session. Confirmed: has not moved./Users/dunin7/loomworks, branch main, HEAD = 7a9adc95592685ba3252c01c50543e85e784d58e, tag clean-report-surface-v0_1 points at it. Tree clean both before and after. Confirmed: has not moved.DUNIN7/loomworks-engine via gh pr list --state all), no manual workflow run. GitHub Actions run history was read via gh api (a read-only call) — no run was dispatched.playground_dev and playground_test were never touched — not connected to, not referenced in any command run this session. A throwaway database, b49_gate_walkaudit, was created for the run (CREATE DATABASE b49_gate_walkaudit OWNER playground) and dropped at the end (DROP DATABASE b49_gate_walkaudit) — confirmed by rerunning psql postgres -c '\l' conceptually via the same drop command succeeding with no error.
Engine — exactly one: .github/workflows/api-docs.yml, job check-api-docs.
on: push: branches: [main] and on: pull_request (no branch filter on the latter — every PR against every base).uv sync --extra dev, then uv run python scripts/check_api_docs_fresh.py.scripts/check_api_docs_fresh.py) regenerates docs/api/api-reference.md and docs/api/api-detail-blocks.html in-memory from the live create_app().openapi() schema and compares byte-for-byte against the committed files; a mismatch prints the stale path(s) and exits 1.
Surface — zero workflow files. find /Users/dunin7/loomworks/.github -type f returned nothing; there is no .github directory at all. Confirmed independently via gh api repos/DUNIN7/loomworks/actions/workflows, which returned an empty workflow list, and gh api repos/DUNIN7/loomworks/actions/runs, whose total_count is 0. Nothing has ever run in CI for the surface, at any time.
The engine's job triggers on pull_request as well as push: main — by the YAML alone, it would run before a merge, if a merge arrived as a PR. It never does. gh pr list --repo DUNIN7/loomworks-engine --state all --limit 5 returned zero PRs. git log --oneline --merges -5 shows the actual mechanism: local git merge --no-ff producing a merge commit (e.g. 7bdbf8b Merge CR-2026-166 (engine) into main: the clean-report cluster, D-1), pushed directly to main. The pull_request trigger has no branches to fire on — it is not dead configuration in the abstract, it is configuration for a merge path this project does not use. The only trigger that ever fires is push: main, which by construction runs after the code is already on main.
Answer to 3.2's question directly: nothing runs before code reaches main, in either repository. The engine's workflow fires only after the merge; the surface has no workflow to fire at all.
Checked in both repositories: no Makefile, no .pre-commit-config.yaml, no .husky directory, no lint-staged config, no active git hook (core.hooksPath unset in both; .git/hooks/ contains only the stock .sample files, none installed). Branch protection on main was checked via gh api repos/<owner>/<repo>/branches/main/protection for both repositories — both return 404 Branch not protected. There is no gate of any kind, CI or otherwise, in front of either repository's main.
This is the central finding of this inspection, and it corrects an assumption in the brief itself.
The brief's Section 5.1 frames the doc-freshness job as the candidate for a vacuous pass — "it runs, it is green." It is not green. Run locally at HEAD (7bdbf8b), with dependencies synced exactly as CI syncs them (uv sync --extra dev):
$ uv run python scripts/check_api_docs_fresh.py
API docs are STALE — run scripts/generate_api_docs.py and commit:
- docs/api/api-reference.md
- docs/api/api-detail-blocks.html
Exit code 1. Diffing the generated content against the committed files (in-process, no file written) shows real, substantive drift — not a whitespace artifact: the committed docs/api/api-reference.md describes 206 operations · 289 schemas; the live schema has 228 operations · 318 schemas. Fields such as voice_interpretation_disabled, alternatives, requires_operator_comment, operator_comment, and a widened source_mode enum (discovery, external_credential) exist in the live schema and are absent from the committed docs.
git log --oneline 9265c97..HEAD -- src/loomworks/api/ — 9265c97 being the last commit to touch docs/api/ (2026-06-28) — counts 46 commits touching the API surface since, up to and including HEAD.
This was checked against GitHub's own run history, not inferred from the local run alone. Via gh api repos/DUNIN7/loomworks-engine/actions/workflows/291008209/runs:
conclusion: success: 0.HEAD itself (7bdbf8b, created 2026-08-04T23:42:43Z) — failure.failure on every single run.
So: does it fail when it should? Yes — it is failing right now, correctly, on real drift, and has apparently never once passed since it was created two months ago. But the brief's framing needs correcting: the hazard here is not a check that passes vacuously. It is a check that has been red for its entire life, on every run, and that redness has not stopped a single merge, because nothing consumes its result — no branch protection, no PR gate (no PRs exist), nothing blocking push: main. A red check that blocks nothing is functionally identical to a vacuous green one: neither has ever prevented anything from landing. The corrected framing for whatever gate design follows: this is not "install a gate over what's clean," for this specific check — it is "a check already exists, has already caught real and repeated drift, and has been ignored 112 times in a row." Grandfathering here does not mean tolerating debt accrued before a new rule; the debt (docs/api/ two months and 46 commits stale) already exists under the current rule, unenforced.
Four commands, each run twice, locally, against the throwaway database where a database was needed. Both runs of every command produced identical pass/skip/fail counts; no test flipped between runs; no output differed once non-deterministic content (elapsed-time strings) was stripped.
| Command | Run 1 | Run 2 | Deterministic |
|---|---|---|---|
| Engine suite (uv run pytest -q) | 3522 passed, 68 skipped, 486.73s | 3522 passed, 68 skipped, 496.19s | Yes — identical counts, grep -c FAILED\|ERROR = 0 both logs |
| Surface lint (npm run lint) | 0 findings, 5.29s wall | 0 findings, 4.52s wall | Yes — byte-identical output |
| Surface tests (npm run test, vitest) | 112 files / 696 tests passed, 17.37s wall | 112 files / 696 tests passed, 17.31s wall | Yes — identical file/test counts |
| Surface build (npm run build) | Compiled + typechecked + 21 routes generated, 6.35s wall | Same, 5.69s wall | Yes — outputs identical once timing numbers stripped |
4.1 — does it currently pass? All four: yes, as shown above.
4.2 — how long? The engine suite is the outlier at roughly 8 minutes; every surface command is under 20 seconds. A gate combining all four is dominated entirely by the engine suite's wall-clock, not the surface's.
4.3 — what does each require?
DATABASE_URL_TEST, built by the exact five-command sequence in the engine's own README ("Database" section), run in order against a fresh database:ALEMBIC_URL=... uv run alembic upgrade 0033 (engine chain to the pre-break revision — required first; skipping to head from empty fails at revision 0034 and rolls back the whole invocation, per the README's own documented trap).DATABASE_URL=... uv run python scripts/bootstrap_engagements.py (plants the administrative-engagement row and three siblings; the chain cannot pass 0034 without them).ALEMBIC_URL=... uv run alembic upgrade head (engine chain to head).STELE_DATABASE_URL=... uv run alembic stamp 0001_baseline, run from Stele's own checkout (/Users/dunin7/stele) with Stele's own alembic.ini — uv run --project from the engine's directory is documented as not equivalent.STELE_DATABASE_URL=... uv run alembic upgrade head, same checkout.
All five ran clean against the throwaway database this session, with no deviation from the documented sequence and no failure at any step. A fresh runner needs: a Postgres instance with a role holding CREATEDB (or a database pre-created by one), a checkout of the Stele repository alongside the engine's (path-dependent — the engine's pyproject.toml sources Stele via { path = "../stele", editable = true }), and LOOMWORKS_SECRET_KEY — either as an environment variable or via .env fallback (three migrations, 0062–0064, raise a RuntimeError naming the fix if neither is present). This session's .env already carried the key; a genuinely fresh runner (CI) would need it supplied as a secret.
One environment trap surfaced during this session that is itself a finding worth carrying into gate design: setting LOOMWORKS_ENV=test to run the suite is actively wrong. src/loomworks/api/app.py calls create_app() at module import time, and create_app() invokes a posture guard (enforce_startup_posture) that refuses to boot when loomworks_env looks like a dev posture (which "test" triggers) and the configured WebAuthn origins are public (.env's WEBAUTHN_RP_ORIGIN=https://app.dunin7.com, a leftover of the real deployment config this repo's .env otherwise carries). The result was 18 test-collection errors, all the same PostureConfigurationError, naming the 2026-07-19 dev-endpoint-exposure incident. Removing that override and leaving LOOMWORKS_ENV unset (falling back to .env's production, which the posture guard accepts) and setting only DATABASE_URL_TEST is what actually ran clean. Any CI job standing up this suite needs to know this, or it will fail on a posture guard rather than on a real test failure — a failure mode indistinguishable from a real one without reading the traceback.
npm run lint and npm run test need only node_modules (already present, not reinstalled this session — a fresh runner would need npm install first, untimed here since it wasn't run). npm run build (next build) runs a full TypeScript check as part of its own pipeline ("Running TypeScript ... Finished TypeScript in 3.5s") — this is relevant to Section 6 below.4.4 — determinism. Established above: every command, run twice, produced identical results. No flake surfaced in either repository's suite.
5.1 — a gate that passes vacuously. Addressed at length in §1 above (3.4): the engine's one existing check is not this hazard's example — it fails, correctly, on real drift, every time it has ever run. The hazard as stated (a check that is green and checks nothing anyone worried about) was not found in either repository's current configuration, because the surface has no checks at all and the engine's one check is red, not green. For any new candidate gate (the four commands in §2), the construction check is: each one currently exits non-zero on a real defect by design — pytest fails a test, eslint fails a lint rule, vitest fails an assertion, next build fails a type error or a build error — none of these is a check that structurally cannot fail. No candidate gate examined here is a check with no failure mode.
5.2 — a gate that hardcodes a moving number. None of the four commands in §2 needs to encode a count to function — pytest, eslint, vitest, and next build all gate on exit code, not on a printed total. The one place a moving number appears adjacent to a gate is inside the doc-freshness check's own output: docs/api/api-reference.md's header line states 206 operations · 289 schemas, a committed, generated figure, not a hardcoded assertion in the check itself — the check compares full file content, not this one line in isolation, so a schema count changing does not by itself require touching the check's logic. Nothing in the four commands, or in the one existing workflow, hardcodes a test count, a coverage percentage, or any other figure that would break the next time a test or route is added.
Not decided here (B-48's ground, and this brief's own fence) — reported as options only, per the brief's instruction to choose none.
What a truthful statement of current coverage would need to say, if written today:
ruff, does not run mypy (B-48's subject). A reader seeing this workflow's badge would need it to say no more than "docs are checked against the schema" — and, honestly, "and this check is currently failing."next build's embedded TypeScript pass is the only type-checking that happens anywhere in this repository's pipeline today, and it happens only when a human runs npm run build locally — nothing runs it in CI, because nothing runs anything in CI.Options for making coverage legible, none chosen:
api-docs.yml's existing header comment already explains what the job does — extending that pattern to also state its limits).
The surface's Playwright config (playwright.config.ts, read in full) states plainly, in its own header comment: "E2E tests run against the live substrate at localhost:8000 and the dev server at localhost:3001. Neither server is started by Playwright — bring them up manually." fullyParallel: false, workers: 1, single Chromium project. It cannot run in CI as configured — there is no step, in any workflow (there are none in this repo) or in the config itself, that starts either the engine (which itself needs the five-step database stand-up from §2) or the Next dev server. Making it CI-capable would require authoring that orchestration from scratch; this brief does not do that, and this session did not run npm run test:e2e.
npm install / a fresh node_modules was not timed — the surface's dependencies were already installed at session start; a fresh-runner cost for this step is unestablished.ruff and mypy were not run — explicitly out of scope (B-48).npm run test:e2e was not run, per the brief's explicit instruction (§7). Its CI-feasibility gap is reported in §5 above from reading the config alone.DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — B-49 Step 0 findings — v0.1 — 2026-08-04 The doc-freshness check is not the gate design's vacuous-pass worry. It is the gate design's already-ignored-112-times worry.