gabriel / musehub public
2026-08-11-f7-http-coverage-freeze.md markdown
386 lines 38.4 KB
Raw
sha256:f65847c6a4ae29872f3ccbc2420f91a8948949b84a06320bdaf2ac281c89ff8a docs(F7b): governance sync — BUILT awaiting Gabriel; carry … Human patch 27 days ago

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 livetools/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.pyERROR … 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.py9 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 Yestools/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, or actorHash when 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 devtools/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)

  1. 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.
  2. 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/, no musehub/main.py, and no conftest.py — governance docs excepted.
  3. 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
  4. The six pre-existing F7 unit tests still pass unmodified.
  5. 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 mode 100644 in the Muse tree with no execute bit, so they must be invoked via bash — a bare scripts/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 not chmod +x them — 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 the NOTE: … is now served but still marked pending line at tools/verify/check_consumer_contract.py:135-139not the PENDING: … not yet merged to dev line 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-37 selects .venv/bin/python on the executable bit alone without applying the _verify_can_import guard it uses for the python3 fallback at :38, a stale .venv also produces exit 2. Confirm with ${PY} -c 'import musehub.main' before reporting exit 2 as a code defect.
  6. The seven files carried in B2 are byte-identical to their feat/musehub-overseer-governance versions — verifiable by comparing each against muse cat --at feat/musehub-overseer-governance <path>and unchanged in mode, which muse cat cannot show: confirm the four scripts/verify/ files are still 100644 via muse ls-tree -r feat/musehub-overseer-governance compared against the F7b tree. Content comparison alone would pass a chmod +x.
  7. /build-verification-review reaches pass before 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
File History 1 commit
sha256:f65847c6a4ae29872f3ccbc2420f91a8948949b84a06320bdaf2ac281c89ff8a docs(F7b): governance sync — BUILT awaiting Gabriel; carry … Human patch 27 days ago