revert(tests): drop the local-to-CI smoke parity assertion — CI is not a gate

Reverts the bidirectional half of the smoke parity pin added earlier today,
and records why at the assertion site so it is not "completed" again.

I read the suite list's comment ("so the local-first pre-push gate equals the
CI gate") as promising equality, made the assertion symmetric, and reported
tests/test_pack_draft_serve_boundary.py as drift because it sits in the local
gate and not in .github/workflows/smoke.yml.

That was wrong, and AGENTS.md says so in a line I had already read: GitHub
Actions are billing-locked and produce dead signals, never chase them. The
Forgejo host (e2-micro) cannot run workflows either. CI here is run MANUALLY
in-worktree — usually faster anyway on UMA hardware with real RAM — which is
what scripts/ci/local-ci.sh now front-ends. smoke.yml is secondary
observability, not a gate.

So the local suite is the SOURCE and is free to be broader. Asserting the
reverse converts a file nobody executes into a maintenance obligation, and one
agents cannot discharge at that — pushing workflow changes needs an OAuth scope
the push credential lacks. The three "owed" lines were never owed.

The surviving direction (CI subset-of 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.

Arc-close brief updated to match: the "three files owed to smoke.yml" open item
is replaced by a recorded non-finding, so the next reader does not re-derive it.
Its shipped-work table now lists what actually landed in its place — the local
CI runner, the lane-roster fix, and the proposal-sink test-isolation fix.

[Verification]: 21 passed across test_cli_test_suites, test_lane_sha_verifier,
test_adr_index; ruff clean on the changed test.
NON-CANONICAL: Python 3.12.11, not the pinned 3.12.13 (unavailable for
linux-x86_64). Hash-pinned tests reproduce committed values regardless.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FduW6Krm3PPQv3P5iwBYtx
This commit is contained in:
Claude 2026-07-25 13:44:30 +00:00
parent b318501c55
commit 50fa287d17
No known key found for this signature in database
2 changed files with 39 additions and 42 deletions

View file

@ -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

View file

@ -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: