diff --git a/docs/handoff/BRIEF-CLOSE-assessment-verification-2026-07-25.md b/docs/handoff/BRIEF-CLOSE-assessment-verification-2026-07-25.md index cd1f18a2..a37777a6 100644 --- a/docs/handoff/BRIEF-CLOSE-assessment-verification-2026-07-25.md +++ b/docs/handoff/BRIEF-CLOSE-assessment-verification-2026-07-25.md @@ -15,7 +15,9 @@ | Coercion derives its whitelist from the enum instead of restating it | `workbench/api.py::_coerce_grounding_source` | same file, parametrized over the registration | | Ledger absence policy declared, not passed (ADR-0263 rule 5) | `core/ratified_ledger.py::CAPABILITY_LEDGERS` | `tests/test_ratified_ledger_bridge.py::TestCapabilityManifest` | | Deductive suite in the pre-push gate | `scripts/hooks/pre-push` | measured 285 tests / 29s vs smoke 216 / 62s | -| Smoke↔CI parity assertion made bidirectional | `tests/test_cli_test_suites.py` | mutation-checked; carries a dated `PENDING_IN_CI` list — see §7 | +| One-command local CI runner, fail-closed on interpreter | `scripts/ci/local-ci.sh` | `gate` tier verified: 236 + 10 + 285 | +| Lane roster red on clean `main` fixed | `tests/test_lane_sha_verifier.py` | two arc lanes were never registered | +| Test writing proposals into the live in-repo sink | `tests/test_derived_close_proposals.py` | plus a vacuous `or True` assertion removed | | Correction-binding lockstep pinned on the deduction path | `tests/test_prior_surface_deduction_binding.py` (6) | mutation-checked | | Ratification ceremony + `core proposal-queue ratify` | `teaching/ratification.py`, `core/cli_proposal_queue.py` | `tests/test_ratification_ceremony.py` (14) | | Domain-keyed ADR index | `docs/adr/INDEX-by-domain.md` | `tests/test_adr_index.py` (5) | @@ -99,19 +101,24 @@ latter produces a renderer with nothing to render. | `hash_surface` on `TurnEvent`, or not | undecided | same | | Arena queue entry + ledger reseal after ratification | deferred by design (bridge rule 1) | `RatificationReceipt.pending_stages` | | Forgejo zero-length-object claim | unverified from this environment | assessment doc, preamble | -| **Three files owed to `smoke.yml`** | blocked (push credential lacks `workflow` OAuth scope) | `tests/test_cli_test_suites.py::PENDING_IN_CI` | +**A non-finding, recorded so it is not re-found.** Mid-arc this brief listed +"three files owed to `smoke.yml`" as blocked work: `test_pack_draft_serve_boundary.py` +plus the two new provenance files sit in the local gate and not in the CI yaml, +which a newly-bidirectional parity assertion flagged as drift. -**On that last row — the one thing this arc found and could not finish.** The -CI smoke gate is narrower than the local one. `test_pack_draft_serve_boundary.py` -(ADR-0253 dual-pack boundary, INV-33) has been in the local pre-push gate and -absent from `smoke.yml`, and the parity pin only ever checked the *other* -direction, so it went unseen. The two new deduction-provenance files belong -there for the same reason. The three-line edit was authored and rejected at push -(`refusing to allow an OAuth App to create or update workflow smoke.yml without -workflow scope`), so it is recorded as a named, dated exception rather than -silently dropped: the assertion still fires on any *new* divergence, and a -second guard fires once the three land so the list cannot rot. Anyone pushing -with `workflow` scope closes it by adding three lines and emptying the tuple. +It is not drift, and the assertion was wrong to exist. Per `AGENTS.md`, GitHub +Actions are billing-locked dead signals, the Forgejo host cannot run workflows +either, and CI is run **manually in-worktree** — which is faster anyway on UMA +hardware with real RAM. `smoke.yml` is secondary observability, not a gate. The +local suite is the source and is free to be broader; asserting the reverse turns +a file nobody executes into a maintenance obligation that agents cannot even +discharge (pushing workflow changes needs an OAuth scope the push credential +lacks). + +Reverted the same day, with the reasoning left at the assertion site so the +"completion" is not attempted again. The surviving direction — CI ⊆ local — +still protects something real: if `smoke.yml` ever names a path the local gate +lacks, the local gate is narrower than a published claim about it. ## 8. Gates and their state at close diff --git a/tests/test_cli_test_suites.py b/tests/test_cli_test_suites.py index 50c999ac..8017fc4f 100644 --- a/tests/test_cli_test_suites.py +++ b/tests/test_cli_test_suites.py @@ -80,36 +80,26 @@ def test_cli_smoke_suite_covers_ci_smoke_gate() -> None: f"core/cli_test.py TEST_SUITES['smoke']: {sorted(missing_locally)}" ) - # The other direction, which this pin did not check until 2026-07-25 and - # which had in fact drifted: tests/test_pack_draft_serve_boundary.py sat in - # the local gate and not in smoke.yml. The list's own comment promises the - # two are *equal* ("so the local-first pre-push gate equals the CI gate - # rather than silently narrowing it"), and a one-directional assertion can - # only ever hold half of that. A file only the local gate runs is a file - # whose regression is invisible to anyone reading a green CI badge. + # DELIBERATELY ONE-DIRECTIONAL — do not "complete" this by also asserting + # local_paths <= ci_paths. # - # PENDING_IN_CI is a named, dated exception list, not a suppression. These - # three are already in the local gate and belong in smoke.yml; the edit was - # authored and could not be pushed from the authoring session, whose - # credential lacked the `workflow` OAuth scope. Anyone with that scope - # closes this by adding the three lines and emptying this tuple. The - # assertion below still fires on any NEW divergence, which is the - # protection that was missing entirely before. - PENDING_IN_CI = ( - "tests/test_pack_draft_serve_boundary.py", - "tests/test_prior_surface_deduction_binding.py", - "tests/test_workbench_deduction_provenance.py", - ) - missing_in_ci = local_paths - ci_paths - set(PENDING_IN_CI) - assert not missing_in_ci, ( - "CI smoke gate is narrower than the CLI smoke suite; add to " - f".github/workflows/smoke.yml: {sorted(missing_in_ci)}" - ) - stale_exceptions = set(PENDING_IN_CI) & ci_paths - assert not stale_exceptions, ( - "these are in smoke.yml now — drop them from PENDING_IN_CI: " - f"{sorted(stale_exceptions)}" - ) + # A bidirectional version was added on 2026-07-25 and reverted the same day. + # It read the list's comment ("so the local-first pre-push gate equals the + # CI gate") as promising equality, and flagged + # tests/test_pack_draft_serve_boundary.py as drift because it sits in the + # local gate and not in smoke.yml. That is not drift. Per AGENTS.md, GitHub + # Actions are billing-locked and produce dead signals, the Forgejo host + # cannot run workflows either, and CI is run manually in-worktree + # (scripts/ci/local-ci.sh). smoke.yml is secondary observability, not a + # gate. + # + # So the local suite is the SOURCE, and it is free to be broader. Asserting + # the reverse turns a file nobody executes into a maintenance obligation — + # one that agents cannot even discharge, since pushing workflow changes + # needs an OAuth scope the push credential lacks. The direction checked + # above is the one that still protects something: if smoke.yml ever names a + # path the local gate lacks, the local gate is genuinely narrower than a + # published claim about it. def test_core_test_suite_expands_to_expected_pytest_paths(monkeypatch) -> None: