Freeze — MUSEHUB-F7a: HTTP-coverage test spec for overseer-run provenance
Date: 2026-08-11
Step: MUSEHUB-F7a (freeze) → MUSEHUB-F7b (Auto build)
Model: Thinking (this artifact) → Auto (F7b builds against it, no redesign)
Repo: musehub (MUSE_HUB workspace) — regime muse-only, canonical Muse
Authority: docs/MUSEHUB-OVERSEER-HANDOVER.md § "F7 correction"; proposal
sha256:01e6a0d4… (changes requested); SD-14; staging deploy remains Tier 3
This document freezes WHAT F7b must assert and HOW it must assert it. It contains no implementation — no test body is written in this session.
Freeze-contract declaration
phase: musehub-f7a-http-coverage
outputs:
- id: f7-http-coverage-spec
path: docs/reviews/2026-08-11-f7-http-coverage-freeze.md
frozen: true
frozen_inputs: []
review_stamp:
reviewed_at: '2026-08-11T20:39:13Z'
verdict: pass
reviewer_mode: agent
reviewer_model: thinking-high
reviewer_provider: local
kit_version: 0.1.0
artifact_digest: sha256:ecf635ee90e6ea4622232a0d414a3e5012056895b5c3b36b8384965ffdb5b420
Document conventions
| Convention | Reason |
|---|---|
URL paths are written with a leading {base} placeholder — e.g. {base}/api/overseer-run-provenance/{run_ref} |
{base} is the empty string. The prefix keeps URL templates from being mis-read as absolute filesystem paths by the C4 heuristic in kit tools/freeze_reviewer/providers/base.py:15 |
Code citations are repo-relative path:line |
Freeze-review findings must cite file+line (SPEC §6) |
Line numbers marked (F7) are as of Muse branch feat/9a-4-f7-overseer-provenance @ e5530c6… |
The F7 route/service/settings do not exist on other branches; F7b carries them forward |
Problem being frozen
Phase 9A-4 F7 implemented the enrichment endpoint plus six unit tests
(tests/unit/test_overseer_provenance_f7.py, F7) on an unmerged Muse feature branch whose
proposal has changes requested. F7 is not merged to dev, not deployed, and not live —
tools/verify/check_consumer_contract.py:97-103 still carries the path as pending, and
docs/MUSEHUB-OVERSEER-HANDOVER.md:198 records it as review-blocked. Every one of those
six tests calls the service function directly, so no test crosses the HTTP boundary —
the app's route table is unobserved by the suite. That gap is exactly what the reviewer
identified, and it is valid.
The reviewer's proposed remedy — adding an explicit app.include_router(...) — is
rejected on evidence (see next section). F7b closes the coverage gap and changes no
registration.
Verified ground truth (do not re-derive in F7b)
Verified 2026-08-11 by running the app on feat/9a-4-f7-overseer-provenance (recorded in
docs/MUSEHUB-OVERSEER-HANDOVER.md:35-52), plus the placement and lifespan facts verified
in this session against the working tree:
| Fact | Evidence |
|---|---|
The endpoint is already registered at {base}/api/overseer-run-provenance/{run_ref} |
app.openapi() paths matching overseer returned exactly that one template |
| Registration happens by auto-discovery, not an explicit call | musehub/api/routes/musehub/__init__.py:70-79 imports every sibling module exposing router; the aggregate is mounted with the /api prefix at musehub/main.py:456 (F7) |
overseer_provenance is not exempted from auto-discovery |
musehub/api/routes/musehub/__init__.py:37-66 (_DIRECT_REGISTERED) does not list it |
| Wildcard ordering is safe by construction | sort key at musehub/api/routes/musehub/__init__.py:70-72 pins repos last unconditionally; overseer_provenance sorts at index 17 and is therefore registered before the wildcard. The total module count is branch-dependent (52 on the F7 branch) and is not load-bearing — the sort key is |
Adding the requested explicit include would double-register at root without the /api prefix (duplicate operationId), which the Scooling adapter rejects by throwing |
tools/verify/check_consumer_contract.py:1-24, 84-104; consumer pin src/adapters/museHubRepoTransport.ts:466,481 in the Scooling repo |
| The unprefixed path returns 422, not 404 | the /{owner}/{repo_slug} wildcard already claims that namespace |
The route declares the ref segment with the path converter and a fixed operation_id |
musehub/api/routes/musehub/overseer_provenance.py:21-25 (F7) — OpenAPI renders the template without the converter suffix |
| Anonymous requests never touch the DB session | musehub/auth/request_signing.py:162-171 returns None before db is used when no Authorization header is present and the dependency is optional |
| …but the session is still declared, so it is still resolved | musehub/auth/request_signing.py:298-300 declares the session via Depends(get_db); musehub/auth/dependencies.py:24 aliases optional_token to that dependency |
| An unoverridden session dependency fails the request | musehub/db/database.py:145 raises RuntimeError("Database not initialized. Call init_db() first.") |
| App startup would require Postgres | musehub/main.py:138-152 — the lifespan handler calls init_db() |
Tests at tests/ root cannot run without Postgres |
session-scoped autouse fixture tests/conftest.py:203-247 connects via psycopg2 to the URL at tests/conftest.py:178-184. Verified this session with Postgres down: tests/test_api_contracts.py → ERROR … psycopg2.OperationalError: connection … port 5434 failed: Connection refused |
Tests under tests/unit/ can run without Postgres |
tests/unit/conftest.py:15-18 overrides that fixture with a no-op. Verified this session with Postgres down: tests/unit/test_phase2_inMemory_walk_dag.py → 9 passed in 0.09s |
| The posture gate reads the settings singleton at call time | musehub/services/musehub_overseer_provenance.py:39-44 (F7) compares the settings attribute to the string enabled |
| The posture field defaults to disabled and is env-aliased | musehub/config.py:168-171 (F7): default disabled, validation_alias MUSEHUB_OVERSEER_PROVENANCE_ENRICHMENT |
Branch baseline for F7b (frozen)
The F7 route, service, settings field, and existing unit tests are absent from every
other branch — they exist only on feat/9a-4-f7-overseer-provenance @ e5530c6…. F7b
therefore has three steps, in this order, on a new feature branch cut from origin/dev
(docs/MUSEHUB-OVERSEER-HANDOVER.md:111):
| # | Step | Notes |
|---|---|---|
| B1 | Carry the F7 commits forward onto the new branch and resolve the single known conflict, config.py::Settings (docs/MUSEHUB-OVERSEER-HANDOVER.md:117) |
Conflict resolution must preserve the field at musehub/config.py:168-171 (F7) verbatim — default disabled and validation_alias MUSEHUB_OVERSEER_PROVENANCE_ENRICHMENT. Re-check the conflict count; dev has moved since it was measured |
| B2 | Carry the seven verify-tooling files verbatim from feat/musehub-overseer-governance |
See § "Verify tooling must travel with F7b" below. Copied unmodified; not authored in F7b |
| B3 | Add the one new test file specified below | The only net-new file authored in F7b |
B1 and B2 must each land as their own Muse commit, separate from B3, and both commit
refs must be recorded in docs/MUSEHUB-OVERSEER-HANDOVER.md in the closing governance sync.
Without distinct commits there is no diff boundary, and exit criterion 2 becomes a judgment
call instead of a mechanical check. The baseline for criterion 2 is the recorded B2 ref.
Verify tooling must travel with F7b
Verified this session by enumerating each branch's tree: tools/verify/ contains three files
on feat/musehub-overseer-governance and zero on dev, on main, and on
feat/9a-4-f7-overseer-provenance. A branch cut from origin/dev therefore has no verify
tooling at all, so exit criterion 5 could not run, and the L1 checkpoints that guard the
cross-repo /api contract would be absent from the very branch that touches it.
What the carried gates do and do not cover. Established empirically in review round 7 by
adding a duplicate root-level include_router and re-running both gates:
| Property | Gated? |
|---|---|
| Every declared router module is reachable in the app route table | Yes — tools/verify/check_routes_registered.py:151 |
The templates the Scooling adapter re-derives are served, /api-prefixed |
Partly. The two required templates fail the gate when missing (tools/verify/check_consumer_contract.py:142-148). The overseer template is pending (:97-103), so its absence is reported but does not fail (:149-150, and the checker's own docstring at :31-33) — verified by running the gate on a tree without the F7 route: it printed the PENDING: line and still exited 0 with 2/3. Exit criterion 5 covers that endpoint instead, by pinning the NOTE: line |
| No second, root-level registration of the same endpoint | No. Both gates exited 0 with byte-identical ARTIFACT_SHA256 digests after a duplicate root mount was added. The consumer gate is a presence test over three hardcoded templates (:84-104) and never enumerates extra paths; the routes gate is a reachability test (:151), and a second mount only adds reachability. FastAPI's duplicate operationId raises a warning, not an exception, so app.openapi() at tools/verify/check_consumer_contract.py:112 still succeeds |
The assertion set cannot cover uniqueness either: A1 exercises the /api path, which keeps
returning 200 while a duplicate root mount exists.
Therefore, stated plainly: on the F7b branch there is no mechanical guard against the
specific change the external reviewer requested. The only control is the frozen prohibition
in § "Registration is NOT modified". Extending check_consumer_contract.py to gate
registration uniqueness is real, worthwhile work — and it is out of scope for F7b, which
may not edit a carried gate (see the prohibition table). It belongs in its own step.
What the carry does buy is that all three L1 checkpoints exist and run on this branch at all, which is a precondition for gating uniqueness later.
F7b carries all seven files, unmodified, as B2:
| File | Role |
|---|---|
tools/verify/check_routes_registered.py |
Route-table reachability gate — compares endpoint callable identity, not path strings, and does not inspect OpenAPI; deliberately immune to prefix drift (tools/verify/check_routes_registered.py:11-15) |
tools/verify/check_consumer_contract.py |
Asserts the paths the Scooling adapter re-derives, including the /api prefix |
tools/verify/check_app_imports.py |
Import-time gate; both checkers' failure messages point at it, so omitting it would leave a dangling instruction |
scripts/verify/_python_env.sh |
Shared interpreter resolution, sourced by all three wrappers; fails closed with exit 3 rather than reporting success when no interpreter can import the app (scripts/verify/_python_env.sh:7-11, :41-58) |
scripts/verify/verify_routes_registered.sh |
Wrapper — execs the checker so its ARTIFACT_SHA256 stays the last stdout line |
scripts/verify/verify_consumer_contract.sh |
Same, for the consumer-contract gate |
scripts/verify/verify_app_imports.sh |
Same, for the import gate |
The wrappers are carried, not just the checkers, because _python_env.sh resolves
MUSEHUB_VERIFY_PYTHON → .venv/bin/python → an importing python3
(scripts/verify/_python_env.sh:30-39). That is what makes exit criterion 5 robust rather
than dependent on a bare python that is not on PATH in this environment.
Carrying F7 forward is not a registration change. It reproduces the already-reviewed F7
diff — including musehub/api/routes/musehub/overseer_provenance.py — byte-for-byte apart
from the Settings conflict resolution. The prohibitions in the next section and exit
criterion 2 are measured against the post-B2 baseline, not against dev: after B1 and
B2, the F7b diff must add exactly one file and nothing else.
Registration is NOT modified — frozen prohibitions
F7b is a test-only change. The following are hard prohibitions; violating any of them fails build verification regardless of test results:
| Must not | Why |
|---|---|
Add app.include_router(...) for overseer_provenance in musehub/main.py |
Double-registers at root without the /api prefix → duplicate operationId and a path the Scooling adapter refuses |
Edit musehub/api/routes/musehub/__init__.py |
Header at line 7 is an explicit DO NOT EDIT instruction; auto-discovery already works |
Add overseer_provenance to _DIRECT_REGISTERED |
It is registered by auto-discovery; exempting it would unmount it |
Change the route decorator path, the converter, or operation_id |
{base}/api/overseer-run-provenance/{run_ref} is a cross-repo consumer contract |
Edit tests/conftest.py or tests/unit/conftest.py |
The DB-free property comes from the existing no-op override; changing shared conftest scope is out of scope for F7b |
Add or modify a conftest.py anywhere |
Same reason — no shared-fixture surface change |
| Override the auth dependencies themselves (the signed-request dependencies) | That would mask the auth path instead of exercising it; only the DB-session dependency may be overridden |
| Edit any of the seven carried verify-tooling files, or relax a gate to make it pass | They are carried verbatim as B2. A failing gate is a finding about the code, not licence to weaken the gate — that is the greenwash this freeze exists to prevent. Report and stop |
Touch KD-6b, python-multipart, or the vault UI |
Separate lane (docs/MUSEHUB-OVERSEER-HANDOVER.md:141-167) |
| Deploy, push to staging, or merge | Tier 3 — operator only |
Frozen HOW — mechanism
| Item | Frozen decision |
|---|---|
| New file | tests/unit/test_overseer_provenance_f7_http.py — exactly one new authored file (B2 additionally carries seven files verbatim; see § "Branch baseline") |
| Placement rationale | tests/unit/ inherits the no-op _db_schema override at tests/unit/conftest.py:15-18; any other location inherits the Postgres-connecting autouse fixture at tests/conftest.py:203-247 and the "runs anywhere" property is lost |
| Client | fastapi.testclient.TestClient over the app imported from musehub.main |
| Lifespan | The client must not be entered as a context manager. Entering it runs the lifespan at musehub/main.py:138-152, which calls init_db() and requires Postgres. Plain construction skips the lifespan |
| Server exceptions | Leave raise_server_exceptions at its default. Do not set it to False — that would convert a genuine unhandled error into an indistinguishable 500 response |
| DB-session override | app.dependency_overrides[get_db], with get_db imported from musehub.db.database — the same object the auth dependency depends on. Required because the optional-auth dependency declares it at musehub/auth/request_signing.py:298-300; without the override the request fails at musehub/db/database.py:145 |
| Override shape | An async-generator override yielding a sentinel (no engine, no connection). Legitimate because anonymous requests return before the session is used (musehub/auth/request_signing.py:162-171); all four assertions are anonymous requests |
| Override teardown | Remove only the single key that was added; do not call dependency_overrides.clear() — that would stomp overrides owned by other fixtures (e.g. tests/conftest.py:154-161) |
| Posture toggle | monkeypatch.setattr on the settings attribute musehub_overseer_provenance_enrichment_enabled, reached through the service module — the identical mechanism already used at tests/unit/test_overseer_provenance_f7.py:33-49 (F7). Set the string enabled for A1–A3 and disabled for A4 |
| Why not the env var | The field is env-aliased (musehub/config.py:168-171, F7) but the settings object is already instantiated, so setting the environment variable at test time has no effect. Attribute patching is the only correct lever |
| Compile-time flag | OVERSEER_PROVENANCE_ENRICHMENT_AUTHORIZED at musehub/services/musehub_overseer_provenance.py:16 (F7) stays untouched — never patched, never read-modified. Posture is the only variable |
| Postgres | Not required, not started, not referenced by the new file |
| Fixture ref | flow_run:fixture-overseer-001, seeded in-memory at musehub/services/musehub_overseer_provenance.py:89-100 (F7). The new file must not seed, mutate, or delete store entries |
| Definition order | The four cases must be defined in the file order A1, A3, A2, A4 (pytest runs in definition order; the repo pins -p no:randomly at pyproject.toml:60). A3's unknown-ref lookup must precede A2's seeded-ref read so that A2 returning the full envelope demonstrates the miss did not mutate or evict store state — that is the data-integrity tier, achieved with no fifth case |
Frozen WHAT — the four pinned assertions
Exactly four test cases. Each issues an anonymous GET (no Authorization header).
| ID | Request | Frozen expectation |
|---|---|---|
| A1 | Posture enabled; {base}/api/overseer-run-provenance/flow_run:fixture-overseer-001 |
200, envelope schema asserted (below) |
| A2 | Posture enabled; same ref percent-encoded as flow_run%3Afixture-overseer-001 |
200, the returned run ref is decoded, and the record equals A1's record exactly |
| A3 | Posture enabled; unknown but well-formed ref flow_run:not-seeded-0001 |
404 |
| A4 | Posture disabled; the known-good fixture ref from A1 |
404 — fail-closed |
A1 — prefixed path returns 200 with the asserted envelope
Assert the response body against the serializer at
musehub/services/musehub_overseer_provenance.py:69-84, 111-127 (F7):
All thirteen record values are pinned, so the expected record is a complete mapping that
A2 can reuse verbatim. Ten values come from the seeded fixture at
musehub/services/musehub_overseer_provenance.py:89-100 (F7); the remaining three —
schema, contractVersion, and untrusted — are serializer constants injected at
musehub/services/musehub_overseer_provenance.py:69-84 (F7) and are not fixture fields.
| Field | Frozen value |
|---|---|
top-level schema |
musehub.overseer_run_provenance_envelope/v0 |
record.schema |
scooling.overseer_run_provenance/v0 |
record.contractVersion |
scooling.overseer_provenance_enrichment/v0 |
record.runRef |
flow_run:fixture-overseer-001 |
record.reviewTrayOutcomeRef |
outcome:flow_run:fixture-overseer-001 |
record.outcome |
pass |
record.constitutionVersion |
0.1.0 |
record.agentVersionRef |
agent:fixture-overseer-v0.1.0 |
record.workerModelFamily |
family:ollama |
record.checkerModelFamily |
family:llama_cpp |
record.externalRef |
JSON null — the key stays present with a null value |
record.actorHash |
the character a repeated 64 times |
record.producedAt |
2026-07-09T12:00:00Z |
record.untrusted |
true — hardcoded at musehub/services/musehub_overseer_provenance.py:83 (F7), never derived from the fixture. MuseHub enrichment is always untrusted relative to canonical Knowtation state, so this is true for every record and must not be guessed as false |
| content-free | asserted as an exact key set, not a list of absences — see below |
The envelope is asserted over HTTP, not by calling the service — that is the whole point of F7b. Two exactness requirements make content-freeness mechanically checkable rather than a judgment call:
- The top-level key set must equal exactly
{schema, record}. - The record key set must equal exactly these thirteen keys, matching
musehub/services/musehub_overseer_provenance.py:69-84(F7):schema,contractVersion,runRef,reviewTrayOutcomeRef,outcome,constitutionVersion,agentVersionRef,workerModelFamily,checkerModelFamily,externalRef,actorHash,producedAt,untrusted.
Equality — not "these two content keys are absent" — is what makes the assertion fail if any future change adds prompt text, completion text, or any other request/response body field to the wire.
A2 — percent-encoded ref returns 200 with a decoded ref
The request must be issued with the literal encoded segment
flow_run%3Afixture-overseer-001 (this is the form the Scooling adapter sends). Assert
200 and that record.runRef equals the decoded flow_run:fixture-overseer-001. The
decode happens in the ASGI layer before routing; F7b asserts the observable outcome and
must not re-implement or bypass encoding.
A2 additionally carries the data-integrity burden, because the frozen definition order
places it after A3's unknown-ref lookup. A2 must therefore assert the whole record —
mapping equality against the complete expected record of A1, all thirteen keys and all
thirteen values — not merely the run ref. This is what proves the miss in A3 neither evicted
nor mutated store state. Value equality is required, not just a 200: the shape validator at
musehub/services/musehub_overseer_provenance.py:47-66 (F7) does not inspect produced_at
and only length-checks external_ref, so a mutation of those fields would still return 200
and would slip past a status-only check.
A3 — unknown ref returns 404
Use a ref that satisfies the ref pattern at
musehub/services/musehub_overseer_provenance.py:18 (F7) but is absent from the store, so
the assertion proves a store miss rather than a pattern rejection. Assert 404 — never
200, never 5xx.
A4 — posture off returns 404 (fail-closed)
With the posture set to disabled and the known-good fixture ref from A1, assert
404. Two further requirements:
- The status must be 404, not 403 and not 5xx — the gate at
musehub/api/routes/musehub/overseer_provenance.py:16-18(F7) hides existence rather than advertising a forbidden resource. - The response body must not contain the record or any of its fields — no leakage of
runRef,outcome, oractorHashwhen the posture is off.
Using the ref that A1 proves exists is essential: it distinguishes "the gate closed the door" from "the row was missing anyway".
Fail-closed rules (frozen)
| Condition | Required behaviour |
|---|---|
Posture not exactly the string enabled |
404 for every ref, including seeded ones |
| Ref absent from the store | 404 |
| Ref failing the ref pattern | 404 (never 5xx) |
| Any 404 | No record fields in the body |
| Anonymous request | Allowed for the public fixture row only; F7b must not add credentials, headers, or signed requests |
Test matrix — seven-tier coverage
| Tier | Coverage for this slice | Where |
| --- | --- | --- |
| unit | Service logic: envelope shape, unknown ref, malformed ref, posture on/off, seed validation | Existing six tests, tests/unit/test_overseer_provenance_f7.py (F7) — unchanged by F7b |
| integration | The gap this freeze closes — A1–A4 across the HTTP boundary through the real app route table, real router registration, and the real optional-auth dependency. Module reachability and /api-prefixed contract presence are covered alongside it by the gates carried in B2. Registration uniqueness is covered by nothing on this branch — neither the assertions nor the gates detect a duplicate root mount (§ "Verify tooling must travel with F7b"); it rests on a prohibition, and closing it is a named follow-up | New tests/unit/test_overseer_provenance_f7_http.py + tools/verify/check_consumer_contract.py:130-131 |
| e2e | Live staging spot-check of the prefixed path after merge + deploy | Out of scope — Tier 3. Tracked in docs/MUSEHUB-OVERSEER-HANDOVER.md:169-175; must not be simulated or claimed by F7b |
| stress | Not applicable and deliberately omitted: the handler is a read-only in-memory dictionary lookup with no I/O, no connection pool, and no shared mutable state. A loop over the same lookup would assert nothing about production load, whose real limits are the ASGI server and DB pool that this slice does not use |
| data-integrity | A GET must not mutate the store: with the frozen definition order A1, A3, A2, A4, the unknown-ref lookup (A3) runs before A2's seeded-ref read, and A2 asserts the full record — exact key set plus every field value — so a miss that evicted or mutated state fails A2. Record-shape rejection is already covered by the seed-validation unit test | Definition-order row in the mechanism table; the full-record requirement in § A2 |
| performance | No timing gate. The four cases run in-process with no DB and no network; the meaningful budget is that the whole new file stays in the sub-second range that tests/unit/ already exhibits (9 tests in 0.09 s, measured this session). F7b must not add a wall-clock assertion that would flake on loaded CI |
| security | A4 proves fail-closed on posture off with no body leakage; anonymous access is exercised through the real optional-auth dependency (only the DB session is overridden, never the auth dependencies); no secrets, tokens, or credentials appear in the new file; no path-traversal surface — the ref is pattern-validated server-side | A4 + the override prohibitions above |
Every freeze-review finding on this artifact MUST cite file+line (SPEC §6).
Ground-truth edge
F7b (Auto) may treat this document as ground truth for the scoped work without re-deriving it: it must not re-litigate the registration question, re-measure the discovery ordering, or redesign the assertion set. Any disagreement with a frozen decision is a stop-and-report, not a redesign.
This is a side-check freeze under the Check OK path; it does not open a new
docs.lanes baton.
Out of scope for F7b (named, not silently dropped)
| Item | Where it belongs |
|---|---|
Asserting /api-prefixed template presence inside the new test file |
Covered mechanically by tools/verify/check_consumer_contract.py:130-131, carried in B2. The new test file must not duplicate it, and must not grow a fifth case to cover it |
| Gating registration uniqueness (no second root-level mount) | Covered by nothing today — proven in round 7 (§ "Verify tooling must travel with F7b"). Closing it means extending tools/verify/check_consumer_contract.py to reject unexpected paths entries for the contract endpoints, which F7b may not edit. Named follow-up step, not silently dropped |
Flipping the consumer-contract entry from pending to required |
Only after F7 merges to dev — tools/verify/check_consumer_contract.py:97-103 |
| Authenticated / private-row behaviour | Deferred in the route's own comment; v0 serves the public fixture row only |
| Durable persistence for the enrichment store | Future phase; the in-memory store is the frozen v0 shape |
| Replying on the proposal, Muse push, Gabriel re-review, staging deploy | F7b closing steps and Tier 3 operator gates, per docs/MUSEHUB-OVERSEER-HANDOVER.md:104-139 |
Exit criteria for F7b (mechanical, no judgment)
- Exactly one new authored file,
tests/unit/test_overseer_provenance_f7_http.py, containing exactly the four cases A1–A4, defined in the frozen order A1, A3, A2, A4. - Measured as the diff between the recorded B2 commit ref and the F7b tip, the F7b diff
adds exactly that one file and touches no file under
musehub/api/routes/, nomusehub/main.py, and noconftest.py— governance docs excepted. - With Postgres not running, both files pass:
.venv/bin/python -m pytest tests/unit/test_overseer_provenance_f7_http.py tests/unit/test_overseer_provenance_f7.py -q - The six pre-existing F7 unit tests still pass unmodified.
- All three carried gates exit 0, invoked through their wrappers so interpreter resolution is
the tooling's own concern. The four
scripts/verify/files are mode100644in the Muse tree with no execute bit, so they must be invoked viabash— a barescripts/verify/…path exits 126 (permission denied), which is not a pass:bash scripts/verify/verify_app_imports.sh,bash scripts/verify/verify_routes_registered.sh,bash scripts/verify/verify_consumer_contract.sh. Do notchmod +xthem — the prohibition table forbids modifying a carried file, and criterion 6 pins mode as well as content. After B1 the consumer-contract gate must take its served-but-pending branch, printing theNOTE: … is now served but still marked pendingline attools/verify/check_consumer_contract.py:135-139— not thePENDING: … not yet merged to devline at:150, which is the pre-B1 output and does not satisfy this criterion. Exit 3 means no interpreter could import the app (scripts/verify/_python_env.sh:41-58) and is not a pass. Exit 2 means the app failed to import under the resolved interpreter (tools/verify/check_routes_registered.py:113-116,tools/verify/check_consumer_contract.py:113-116); because_python_env.sh:36-37selects.venv/bin/pythonon the executable bit alone without applying the_verify_can_importguard it uses for thepython3fallback at:38, a stale.venvalso produces exit 2. Confirm with${PY} -c 'import musehub.main'before reporting exit 2 as a code defect. - The seven files carried in B2 are byte-identical to their
feat/musehub-overseer-governanceversions — verifiable by comparing each againstmuse cat --at feat/musehub-overseer-governance <path>— and unchanged in mode, whichmuse catcannot show: confirm the fourscripts/verify/files are still100644viamuse ls-tree -r feat/musehub-overseer-governancecompared against the F7b tree. Content comparison alone would pass achmod +x. /build-verification-reviewreachespassbefore any DONE claim — green tests alone are not sufficient.
Tier authority
| Action | Tier |
|---|---|
| Feature-branch Muse commit of this freeze + governance docs | 1 — do without asking |
| F7b test implementation on a feature branch | 1 |
Merge to Muse main / dev, push to staging, deploy, posture flip in any live environment |
3 — operator authorization required |
Escalation categories that stop the review loop: security, irreversible, real_money,
gates_tier3.
Review record
| Round | Reviewer | Verdict | Resolution |
|---|---|---|---|
| 0 | ok check-ok --dry-run (mechanical checklist, C1–C8) |
pass | 0 findings |
| 1 | Freeze-review loop (independent thinking reviewer) | findings | F1 BLOCKER — no branch baseline named, making exit criteria 2 and 3 jointly unsatisfiable → added § "Branch baseline for F7b" (B1/B2) and re-scoped criterion 2 to the post-B1 baseline. F2 MAJOR — data-integrity ordering contradicted the case order → pinned definition order A1, A3, A2, A4 and rewrote the data-integrity row. F3 MAJOR — "shipped" over-claimed an unmerged branch → restated as implemented-but-not-merged with contract/handover citations. F4 MINOR — bare python not on PATH → exit criterion 5 now uses .venv/bin/python. F5 MINOR — content-free assertion unenumerable → replaced with exact thirteen-key record key set. Also tightened three citation ranges the reviewer flagged as loose (:39-44, :69-84, 111-127, :89-100) |
| 2 | Freeze-review loop (same independent reviewer, re-read from disk) | findings | R2-F1 MAJOR — the data-integrity burden moved to A2 but A2 still asserted only status and run ref → § A2 now requires the full record (exact key set and every field value), citing the validator's blind spots at musehub/services/musehub_overseer_provenance.py:47-66 (F7); assertion table and data-integrity rows updated. R2-F2 MINOR — the post-B1 baseline was not identifiable as a ref → B1 must land as its own Muse commit whose ref is recorded in the handover, and criterion 2 is now the diff between that ref and the F7b tip. Also replaced the branch-dependent "index 51 of 52" with the load-bearing sort-key fact the reviewer could confirm |
| 3 | Freeze-review loop (same independent reviewer, re-read from disk) | findings | R3-F1 MAJOR — § A2 scoped value equality to "every field value pinned in A1's table", but A1 pinned only six of thirteen values, excluding producedAt and externalRef — the two the rationale exists to protect → A1's table now pins all thirteen values, and § A2 now requires mapping equality against that complete expected record |
| 4 | Freeze-review loop (same independent reviewer, re-read from disk) | findings | R4-F1 MAJOR — a regression from round 3: re-sourcing A1's table "from the seeded fixture" silently dropped record.untrusted, which is a serializer constant at musehub/services/musehub_overseer_provenance.py:83 (F7) and not a fixture field, leaving twelve values pinned while three statements claimed thirteen → row restored with value true and an explicit note that it must not be guessed as false; the table preamble now names both value sources (fixture :89-100 for ten, serializer constants :69-84 for schema, contractVersion, untrusted) |
| 5 | Freeze-review loop (same independent reviewer, re-read from disk) | pass | 0 findings. Reviewer independently AST-parsed _serialize_record on the F7 branch (13 keys, 13 pinned rows, no missing, no extra, identical order), executed the F7 service and matched all thirteen values plus the top-level schema against live output, and confirmed the ten-fixture / three-serializer-constant split. All nine findings from rounds 1–4 resolved with no new regression; A1–A4 each judged unambiguously buildable from this document alone. F7b (Auto) is cleared to start |
| 6 | Post-pass amendment (operator-authorized scope change, not a review finding) |
amended — re-review required | A1 BLOCKER that rounds 1–5 all missed: exit criterion 5 invoked tools/verify/ scripts that exist on no branch F7b would be cut from (enumerated this session: 3 files on feat/musehub-overseer-governance, 0 on dev, main, and feat/9a-4-f7-overseer-provenance), so the criterion was unexecutable — and the out-of-scope table used those same absent scripts to justify not asserting registration uniqueness, leaving the reviewer's exact requested change ungated on the F7b branch. Operator chose to carry the tooling rather than defer it → new § "Verify tooling must travel with F7b" (seven files, verbatim, as commit B2); step order renumbered B1/B2/B3; criterion 2's baseline moved to the recorded B2 ref; criterion 5 rewritten to invoke the shell wrappers, whose _python_env.sh also supersedes finding F4's .venv workaround; new criterion 6 pins byte-identity of the carried files; new prohibition against editing or relaxing a carried gate; integration and out-of-scope rows updated. Assertion set A1–A4 unchanged |
| 7 | Freeze-review loop (independent reviewer, amendment scope) | findings | A2-F1 BLOCKER — the amendment's central justification was false. The reviewer added a duplicate root-level include_router and re-ran both carried gates: both exited 0 with byte-identical ARTIFACT_SHA256 digests, because the consumer gate is a presence test over three hardcoded templates and the routes gate is a reachability test, and FastAPI's duplicate operationId is a warning rather than an exception (tools/verify/check_consumer_contract.py:112, :130-131; tools/verify/check_routes_registered.py:151). Carrying the tooling does not gate registration uniqueness → rationale, test matrix, and out-of-scope rows rewritten to state actual coverage, with the absence of any uniqueness guard recorded explicitly and named as follow-up work. A2-F2 MAJOR — the four scripts/verify/ files are mode 100644; criterion 5's bare-path invocation exits 126 → invocations now prefixed with bash, with chmod +x explicitly forbidden. A2-F3 MAJOR — line 83 still said "two steps" above the three-row table, letting an Auto session skip B2 entirely → corrected. A2-F4 MINOR — mechanism table still claimed "exactly one new file" unqualified → "authored". A2-F5 MINOR — criterion 5 cited the ContractPath declaration instead of the served-but-pending report branch → now cites :135-139 and pins the expected NOTE: string against the pre-B1 PENDING: line at :150. A2-F6 MINOR — exit 2 attributed solely to app-import failure, but _python_env.sh:36-37 selects .venv/bin/python on the executable bit without the _verify_can_import guard it applies to the python3 fallback, so a stale venv also yields 2 → qualified. Reviewer independently confirmed the branch enumeration, the seven-file closure (stdlib + musehub.main only, with no dependency on kit config, a route manifest, or a policy file, and an empty UNMOUNTED_ALLOWLIST), that musehub/api/routes/** and main.py are byte-identical between dev and the governance branch, and that A1 survives a duplicate mount (238 → 265 paths, none removed) |
| 8 | Freeze-review loop (same independent reviewer, amendment fixes) | findings | All six round-7 findings confirmed fixed: every one of the eleven cited line numbers re-derived independently at feat/musehub-overseer-governance, and criterion 5's three bash invocations executed verbatim (all exit 0, and the current-tree output is the PENDING: variant the criterion correctly rejects as pre-B1). Four MINOR inaccuracies remained in the newly written coverage text, all now corrected: R8-F1 the coverage table claimed /api template presence was gated for all three templates, but the overseer template is pending, so its absence is reported without failing (tools/verify/check_consumer_contract.py:142-148 vs :149-150) — proven by running the gate on a tree lacking the F7 route, which exited 0 with 2/3 → row changed to Partly with the mechanism named. R8-F2 check_routes_registered.py was twice described as an OpenAPI/template gate, but it contains no reference to OpenAPI and its docstring calls identity comparison "immune to prefix drift" (:11-15) → mislabel removed from both the carried-file table and the out-of-scope row. R8-F3 "the two L1 checkpoints" where three are carried → corrected. R8-F4 criterion 6's muse cat comparison would pass a chmod +x because Muse emits content only → criterion 6 now also pins mode via muse ls-tree, and the chmod prohibition no longer rests on a check that could not detect it. No BLOCKER, no MAJOR; assertion set, mechanism table, prohibitions, fail-closed rules, and tier authority confirmed untouched and internally consistent |