From 8acba80a1af0b7b4ccf29a273bfbd102e1f1e448 Mon Sep 17 00:00:00 2001 From: Timothy Date: Thu, 29 Oct 2026 22:26:09 +0100 Subject: [PATCH] fix(935): state the measured fact (both hosts 1.7 on 2026-10-28), not an unmeasured mixed fleet The fleet is uniform on jq 1.7 per the job logs; 1.6 stays in the set only because the image tag is unpinned. Real-jq 1.6 coverage is currently absent, and the shims cover only the empty-input -e quirk, not the NUL or parse-error ones. Measurement dated by the job date. Remaining "runner's jq 1.6" comments reworded. Co-Authored-By: Claude Opus 5.5 Claude-Session: f9ce6bc6-5b36-491a-8d72-89c1f70dd3e1 --- .gitea/workflows/pr-checks.yml | 13 ++++++------ docs/ci-cd.md | 17 ++++++++-------- docs/decisions/README.md | 2 +- .../records/ci/jq-accepted-version-set.md | 20 ++++++++++--------- scripts/jq-preflight.sh | 10 +++++----- scripts/pr-changed-files.sh | 4 ++-- scripts/tests/test_jq_preflight.py | 6 +++--- scripts/tests/test_pr_changed_files.py | 4 ++-- 8 files changed, 40 insertions(+), 36 deletions(-) diff --git a/.gitea/workflows/pr-checks.yml b/.gitea/workflows/pr-checks.yml index 2727cd159..a385eb772 100644 --- a/.gitea/workflows/pr-checks.yml +++ b/.gitea/workflows/pr-checks.yml @@ -546,12 +546,13 @@ jobs: - name: Install test dependencies run: python3 -m pip install --disable-pip-version-check --quiet pytest pyyaml # jq gets its OWN step because its VERSION, not merely its presence, is load-bearing - # (ersatztv#648). `--expect` makes this a TRIPWIRE over an accepted SET of versions: as of - # 2026-10-29 the `small` fleet is mixed, 1.6 or 1.7 depending on which host takes the job (both - # map to the floating runner-images:ubuntu-latest image, no digest pin), so the set is exactly - # {1.6, 1.7} (ersatztv#935). Any other version goes red. Which of the two the suite runs under is - # decided by the runner, so the 1.6 coverage is incidental. server-management#1080 proposes a fleet - # digest pin; when it settles one version, narrow this to that version. + # (ersatztv#648). `--expect` makes this a TRIPWIRE over an accepted SET of versions: the + # set is exactly {1.6, 1.7} (ersatztv#935). Both `small` runners that took PR #933/#934 reported + # 1.7 (2026-10-28), but both map to the floating runner-images:ubuntu-latest image with no digest + # pin, so a host may hold an older pull and 1.6 stays in the set. Any other version goes red. No + # known runner reports 1.6 today, so real-jq 1.6 coverage of scripts/tests is currently absent. + # server-management#1080 proposes a fleet digest pin; when it settles one version, narrow this + # to that version. # # The pin lives HERE and deliberately NOT in review-verdict.yml: that workflow writes the # branch-protection-required `review-verdict/h10` status, so pinning a version there would turn diff --git a/docs/ci-cd.md b/docs/ci-cd.md index 6dcd6b15a..d089d939d 100644 --- a/docs/ci-cd.md +++ b/docs/ci-cd.md @@ -1344,10 +1344,10 @@ the runner image. Every shell gate in this repo — `decisions-guard`, `script-tests`'s own harness, `pretooluse-merge-consent.sh`, `review-verdict.yml`, `scripts/pr-changed-files.sh` — is authored and -tested on a developer Mac shipping **jq 1.8.x**. The CI runners' jq is **not one version**: as of -2026-10-29 both `small` runners map to the floating `runner-images:ubuntu-latest` image with no digest -pin, so each host keeps whatever it last pulled (jq 1.7 reported on both hosts that took PR #933 and -#934; a host holding an older image still reports 1.6; server-management#1080). Author to the +tested on a developer Mac shipping **jq 1.8.x**. The CI runners' jq is **not pinned**: both `small` +runners map to the floating `runner-images:ubuntu-latest` image with no digest pin, so each host keeps +whatever it last pulled. Measured 2026-10-28: jq 1.7 on both hosts (PR #933 job 12347, PR #934 job +12360); no known runner reports 1.6, but an unpinned host could (server-management#1080). Author to the 1.6-compatible subset, which is the **floor**; three concrete constructs diverge between 1.6 and later versions and each one produced a real bug when it hit CI for the first time: @@ -1368,10 +1368,11 @@ alone instead of guessing at the runner image. 1.6,1.7` additionally asserts the version is a member of the comma-separated set and fails loudly (exit 1) on any other version; a malformed set (`1.6,`, `1.x`) is a usage error (exit 2). It is used **only** by `script-tests` (`.gitea/workflows/pr-checks.yml`). The set is exactly {1.6, 1.7}: a -1.8 runner stays red so a person decides. Which member the suite runs under is decided by whichever -runner takes the job, so **real-jq 1.6 coverage of `scripts/tests/` is incidental**; the 1.6 quirks -are still exercised on every run by in-repo jq shims (`test_pr_changed_files.py`, -`test_merge_consent_exemption.py`). The floor stays at 1.6. **server-management#1080** (a fleet digest +1.8 runner stays red so a person decides. As of 2026-10-28 no known runner provides +jq 1.6, so **real-jq 1.6 coverage of `scripts/tests/` is currently absent**; 1.6 stays in the set only +because the tag is unpinned. In-repo jq shims (`test_pr_changed_files.py`, +`test_merge_consent_exemption.py`) reproduce the empty-input `-e` quirk only; the NUL-truncation and +parse-error-exit-code quirks have no coverage on a 1.7 runner. The floor stays at 1.6. **server-management#1080** (a fleet digest pin) is what narrows the set: when it settles one version, change `--expect` to that single version. The tripwire is advisory in name only: `pretooluse-merge-consent.sh` reads the combined status, so a red `script-tests` blocks the hook-mediated merge path until the `--expect` value is fixed. diff --git a/docs/decisions/README.md b/docs/decisions/README.md index c8294d09f..b4ebbf05e 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -57,7 +57,7 @@ the link for rationale. Superseded/retired history lives in `archive/`. Regenera | `ci.grep-q-pipefail-inversion` | In any script running under `set -o pipefail`, a security or classification predicate of the form `producer \| grep -q…` is FORBIDDEN: `grep -q` exits at its first match, the producer then takes SIGPIPE and exits 141 once the data exceeds the pipe buffer (~64K), so `pipefail` reports the pipeline as FAILED even though grep MATCHED — inverting the predicate exactly when the input is large. A here-string (`grep -q… <<< "$data"`) is ALSO forbidden: bash materialises a large here-string via temporary storage, so it fails when temp space is full or unwritable, and inside an `if`/`!` that failure flips the predicate the same way. COUNT instead — `n=$(printf '%s\n' "$data" \| grep -cE "$re")` — because `grep -c` drains stdin (no early exit, no SIGPIPE) over an ordinary pipe (no temp file). Read grep's status honestly: exit 1 means a zero count and is a legitimate answer, anything >1 is a real error. Evaluate the counts ONCE at TOP LEVEL, never inline inside an `if`/`elif` condition: inside `$( )` an `exit` leaves only the subshell and `set -e` does not fire, so an error silently reads as "no match". Validate that each result is numeric and fail closed if not. This applies to both the enforced gate `.gitea/workflows/review-verdict.yml` and the advisory hook `.claude/hooks/pretooluse-merge-consent.sh`. | 2026-07-29 | [link](records/ci/grep-q-pipefail-inversion.md) | | `ci.image-build-delegates-the-spa-suite` | `docker/Dockerfile` runs no vitest suite. Its web-build stage lints, typechecks and BUILDS the SPA; the suite runs once, unfiltered, in `docker-build.yml`'s `test` job on a real checkout, and `build` carries `needs: [test, migrations, scan]` so no image is published past a red suite. THAT EDGE IS NOW THE ONLY LAYER, so the guard checks it is real and not merely present — and it does so by PINNING TEXT rather than parsing it. The commands each SPA-carrying Dockerfile stage runs, and the gating step's `run:` body and `if:`, are compared as strings against a declared pin; the step and its job must carry no `continue-on-error` in any spelling, the job no job-level `if:`, and the publish step must keep its own `docs_only` gate. A guard that asks what a command MEANS was wrong nine times in three review rounds. A pin is immune to a different SPELLING of the command, which is that whole class; it is not immune to the same text meaning something else, so the routes to that are pinned or refused too: `working-directory` and the step's own `shell:`, the workflow `defaults.run.shell` and the job-level `defaults` overriding it, a stage `SHELL`, `web/package.json`'s script map (pinned WHOLE — selecting on the literal `vitest` missed `npm run test`, `npm t` and the `prebuild`/`preinstall` lifecycle hooks), `web/vite.config.ts` PINNED WHOLE — it decides what the suite collects and what `vite build` loads, and pinning a BLOCK of it was defeated seven measured ways across three rounds, so the partial match was withdrawn rather than respelled an eighth time; and the ABSENCE of any config file that outranks it — `vitest.config.*` for vitest, and `vite.config.js`/`.mjs` for `vite build`, whose `DEFAULT_CONFIG_FILES` lists them BEFORE `.ts`. Pinning one file is worthless while a second can outrank it, and that family went short twice. The gating job's `container:` image is left to `ci.image-pin-population` rather than guarded twice. What is unmodelled is a LIST, not an "only": an `ENV` changing what a pinned `RUN` resolves, the plugin BODIES, and a publish through another action. The count of such routes lives in the `docs/guard-inventory.md` row and nowhere else — it briefly existed in three places with two different values, which is how a number stays wrong. It is a running total, wrong at every count so far. They share one shape, which is the transferable part: A PIN ASSUMES IT IS PINNING THE ARTIFACT THAT STILL DECIDES, and every route found so far is authority moving to another file, another occurrence, another workflow, or a hook the pinned command invokes. Do NOT restore a filtered in-image run by naming the specs that cannot run there: that list is a population nothing derives, and the red it produces when it goes stale is unreachable on a PR — `Build & push image (amd64)` is `if: github.event_name != 'pull_request'` — so it lands on `main` and on the `v*` tag path, where it fails the release cut. Held in both directions by `scripts/tests/test_image_build_delegates_the_spa_suite.py`. | 2026-08-30 | [link](records/ci/image-build-delegates-the-spa-suite.md) | | `ci.infra-shaped-red-under-load` | When a job dies inside a setup/cache step before your code compiles, check the runner host's load before diagnosing the diff, and never file a CI bug off one sample under pressure. | 2026-07-21 | [link](records/ci/infra-shaped-red-under-load.md) | -| `ci.jq-accepted-version-set` | Every shell gate that shells out to `jq` is authored to the jq 1.6-compatible subset (floor 1.6, unchanged). As of 2026-10-29 the `small` runner fleet is MIXED: both small runners map to the floating `runner-images:ubuntu-latest` image with no digest pin, so each host keeps whatever it last pulled (jq 1.7 reported on both hosts that took PR #933 and #934; a host holding an older image still reports 1.6). `scripts/jq-preflight.sh --expect 1.6,1.7` in the `script-tests` job accepts exactly that comma-separated SET and fails loudly on any other version; which member the suite runs under is decided by the runner, so the real-jq 1.6 coverage is INCIDENTAL, not guaranteed. server-management#1080 (fleet digest pin) is the event that narrows the set to one version. `review-verdict.yml` stays floor-only (no `--expect`): it writes the branch-protection-required `review-verdict/h10` status. | 2026-10-29 | [link](records/ci/jq-accepted-version-set.md) | +| `ci.jq-accepted-version-set` | Every shell gate that shells out to `jq` is authored to the jq 1.6-compatible subset (floor 1.6, unchanged). Both `small` runners map to the floating `runner-images:ubuntu-latest` image with no digest pin, so the fleet jq version is not guaranteed; measured 2026-10-28, both hosts report jq 1.7 and no known runner reports 1.6. `scripts/jq-preflight.sh --expect 1.6,1.7` in the `script-tests` job accepts exactly that comma-separated SET and fails loudly on any other version; 1.6 stays in the set only because the tag is unpinned, and real-jq 1.6 coverage of `scripts/tests/` is currently absent. server-management#1080 (fleet digest pin) is the event that narrows the set to one version. `review-verdict.yml` stays floor-only (no `--expect`): it writes the branch-protection-required `review-verdict/h10` status. | 2026-10-29 | [link](records/ci/jq-accepted-version-set.md) | | `ci.killed-job-triage` | Never trust a job's `conclusion` field alone — read the log tail and require an `❌ Failure - Main …` marker before treating a red as a real failure. | 2026-07-21 | [link](records/ci/killed-job-triage.md) | | `ci.monitor-armed-at-pr-open` | Arm a CI monitor on the PR head sha the moment the PR opens, polling the commit-status endpoint — not at the end of the work. | 2026-07-21 | [link](records/ci/monitor-armed-at-pr-open.md) | | `ci.no-host-health-gating` | Push when your work is validated — never SSH to bumblebee to sample load/RAM first, and never hand-schedule around other sessions' runs. | 2026-07-21 | [link](records/ci/no-host-health-gating.md) | diff --git a/docs/decisions/records/ci/jq-accepted-version-set.md b/docs/decisions/records/ci/jq-accepted-version-set.md index 28929fb00..5e34e2ee6 100644 --- a/docs/decisions/records/ci/jq-accepted-version-set.md +++ b/docs/decisions/records/ci/jq-accepted-version-set.md @@ -5,15 +5,15 @@ status: active since: '2026-10-29' supersedes: ci.jq-version-contract@2026-07-26 superseded-by: none -rule: 'Every shell gate that shells out to `jq` is authored to the jq 1.6-compatible subset (floor 1.6, unchanged). As of 2026-10-29 the `small` runner fleet is MIXED: both small runners map to the floating `runner-images:ubuntu-latest` image with no digest pin, so each host keeps whatever it last pulled (jq 1.7 reported on both hosts that took PR #933 and #934; a host holding an older image still reports 1.6). `scripts/jq-preflight.sh --expect 1.6,1.7` in the `script-tests` job accepts exactly that comma-separated SET and fails loudly on any other version; which member the suite runs under is decided by the runner, so the real-jq 1.6 coverage is INCIDENTAL, not guaranteed. server-management#1080 (fleet digest pin) is the event that narrows the set to one version. `review-verdict.yml` stays floor-only (no `--expect`): it writes the branch-protection-required `review-verdict/h10` status.' -signals: 'jq version divergence, jq 1.6 vs 1.7, script-tests jq tripwire red, jq-1.7 (parsed 1.7; floor 1.6), --expect set, mixed runner fleet, floating runner-images ubuntu-latest, server-management#1080, jq-preflight, merge blocked by combined status · paths: `scripts/jq-preflight.sh`, `scripts/tests/test_jq_preflight.py`, `.gitea/workflows/pr-checks.yml`, `.gitea/workflows/review-verdict.yml`, `docs/ci-cd.md` · issues: #935, #648, #643, #647' +rule: 'Every shell gate that shells out to `jq` is authored to the jq 1.6-compatible subset (floor 1.6, unchanged). Both `small` runners map to the floating `runner-images:ubuntu-latest` image with no digest pin, so the fleet jq version is not guaranteed; measured 2026-10-28, both hosts report jq 1.7 and no known runner reports 1.6. `scripts/jq-preflight.sh --expect 1.6,1.7` in the `script-tests` job accepts exactly that comma-separated SET and fails loudly on any other version; 1.6 stays in the set only because the tag is unpinned, and real-jq 1.6 coverage of `scripts/tests/` is currently absent. server-management#1080 (fleet digest pin) is the event that narrows the set to one version. `review-verdict.yml` stays floor-only (no `--expect`): it writes the branch-protection-required `review-verdict/h10` status.' +signals: 'jq version divergence, jq 1.6 vs 1.7, script-tests jq tripwire red, jq-1.7 (parsed 1.7; floor 1.6), --expect set, unpinned runner image, floating runner-images ubuntu-latest, server-management#1080, jq-preflight, merge blocked by combined status · paths: `scripts/jq-preflight.sh`, `scripts/tests/test_jq_preflight.py`, `.gitea/workflows/pr-checks.yml`, `.gitea/workflows/review-verdict.yml`, `docs/ci-cd.md` · issues: #935, #648, #643, #647' mechanics: '`scripts/jq-preflight.sh` (no args) -> floor+observability in every gate job; `scripts/jq-preflight.sh --expect 1.6,1.7` -> set tripwire, `script-tests` job only (`test_jq_preflight.py` pins the exact value); `docs/ci-cd.md` -> "The jq contract"' --- Supersedes `ci.jq-version-contract` (2026-07-26, archived), whose premise was that the CI runner ships jq 1.6. That is no longer a fact the repo can state. -**Measured 2026-10-29.** `script-tests`' tripwire (`--expect 1.6`) went red on jazz-small-runner +**Measured 2026-10-28.** `script-tests`' tripwire (`--expect 1.6`) went red on jazz-small-runner (PR #933, job 12347) and on bumblebee's small-runner (PR #934, job 12360), each reporting `jq-1.7 (parsed 1.7; floor 1.6)`. Cause (server-management#1080): both `small` runners map to the floating `runner-images:ubuntu-latest` image with no digest pin or forced pull, so each host keeps @@ -40,11 +40,13 @@ authored to the 1.6 subset is harmless on 1.7, and the three divergences that mo (`jq -e` over empty input, `contains("\u0000")`, the parse-error exit code) remain real on whichever host still runs 1.6. -**What coverage is lost.** The real-jq 1.6 run of `scripts/tests/` is now incidental: it happens only -when a 1.6 host takes the job, and nothing guarantees that on any given PR. The 1.6 quirks are still -exercised on every run by in-repo jq shims that reproduce them (`test_pr_changed_files.py`, -`test_merge_consent_exemption.py`), so the shim-based proofs hold on a 1.7 runner; what is not -covered on a 1.7 run is any NEW 1.6-only divergence in a construct no shim models. +**What coverage is lost.** As of 2026-10-28 no known runner provides jq 1.6, so the real-jq 1.6 run +of `scripts/tests/` is currently absent; it would happen only if a host held an older image. In-repo +jq shims reproduce ONE of the three 1.6 divergences: `jq -e` over empty input exiting 0 +(`test_pr_changed_files.py`, `test_merge_consent_exemption.py`). The `contains("\u0000")` truncation +and the parse-error exit code (4 vs 5) have no shim, so on a 1.7 run they are covered only by the +constructs having been replaced with version-stable ones, and any NEW 1.6-only divergence is +uncovered. **What narrows the set.** server-management#1080. When it pins the fleet to one jq version, change the `--expect` value in `pr-checks.yml` to that single version (the script accepts a one-member set) @@ -62,7 +64,7 @@ floor rises, with the fleet evidence in hand. #598), and `script-tests` is a Gitea Actions job folded into it. Only the `merge_when_checks_succeed` path, which defers to Gitea's required checks, is spared. This is the cost of the tripwire, accepted because the remedy is a one-line edit to `--expect`, and the - 2026-10-29 firing is that cost being paid. + 2026-10-28 firing is that cost being paid. - *Baking jq into `docker/ci/Dockerfile` cannot reach the gate.* `review-verdict.yml` is `runs-on: small` with no toolchain-image pin and `ci.small-lane-git-only` keeps that lane git-only, so it gets the host's jq whatever the toolchain image holds. The fleet pin in diff --git a/scripts/jq-preflight.sh b/scripts/jq-preflight.sh index 7c383ff6f..ad3999839 100755 --- a/scripts/jq-preflight.sh +++ b/scripts/jq-preflight.sh @@ -2,8 +2,8 @@ # Make the jq version a job's shell gates run under OBSERVABLE, and any drift LOUD. # # ersatztv#648. Every shell gate in this repo is authored and tested on a developer Mac shipping -# jq 1.8.x. The CI runners shipped jq 1.6 when this was written (2026-07-26); as of 2026-10-29 the -# `small` fleet is MIXED, 1.6 or 1.7 depending on which host takes the job (server-management#1080). +# jq 1.8.x. The CI runners shipped jq 1.6 when this was written (2026-07-26); on 2026-10-28 +# both `small` runners reported jq 1.7 (server-management#1080). # Nothing pinned or checked that, and until ersatztv#631 the one # thing that could have noticed (scripts/tests/) never ran on the runner. Three independent divergences # surfaced in a single day: @@ -27,9 +27,9 @@ # # So the contract is the other way round: 1.6 is the FLOOR every gate must work on. Both `small` # runners map to the floating runner-images:ubuntu-latest image with no digest pin, so each host keeps -# whatever it last pulled (2026-10-29: 1.6 on some, 1.7 on others). Which jq `scripts/tests/` runs -# under is therefore decided by which runner takes the job: the 1.6 coverage is INCIDENTAL, not -# guaranteed. server-management#1080 proposes a fleet digest pin; when it settles one version, narrow +# whatever it last pulled (2026-10-28: both measured hosts reported 1.7; a host may hold an older +# pull). No known runner reports 1.6, so real-jq 1.6 coverage of `scripts/tests/` is currently +# absent; 1.6 stays accepted only because the tag is unpinned. server-management#1080 proposes a fleet digest pin; when it settles one version, narrow # the `--expect` set in pr-checks.yml to that version. # # TWO MODES, deliberately asymmetric: diff --git a/scripts/pr-changed-files.sh b/scripts/pr-changed-files.sh index fcaa70d99..8f1e65e57 100755 --- a/scripts/pr-changed-files.sh +++ b/scripts/pr-changed-files.sh @@ -79,7 +79,7 @@ # ETV_GITEA_URL | GITEA_BASE_URL -> API base; defaults to the homelab Gitea. A value ending in # /api/v1 is used as-is, otherwise /api/v1 is appended. # -# jq COMPATIBILITY (ersatztv#648). This runs on CI runners whose jq is 1.6 or 1.7 (mixed fleet, 2026-10-29; +# jq COMPATIBILITY (ersatztv#648). This runs on CI runners whose jq is unpinned (1.7 measured 2026-10-28; # floor 1.6), while it is # authored on Macs shipping 1.8.x. It is therefore written to the 1.6-compatible subset: # * never rely on `jq -e`'s exit status over EMPTY input — 1.6 exits 0 where >=1.7 exits 4, which is @@ -181,7 +181,7 @@ while [ "$page" -le "$MAX_PAGES" ]; do raw=$(gq "repos/$owner/$repo/pulls/$pr/files?limit=${PAGE_SIZE}&page=${page}") # An EMPTY body is rejected in SHELL, before jq sees it. `jq -e` over empty input exits 4 on - # jq >= 1.7 but 0 on jq 1.6, and a runner may still report 1.6 (mixed fleet, 2026-10-29) — leaving this to jq's exit status is the + # jq >= 1.7 but 0 on jq 1.6, and a runner may still report 1.6 (unpinned image) — leaving this to jq's exit status is the # exact fail-open ersatztv#647 found in the enforced copy. A transport failure must never # masquerade as a legitimate short final page. if [ -z "${raw//[[:space:]]/}" ]; then diff --git a/scripts/tests/test_jq_preflight.py b/scripts/tests/test_jq_preflight.py index 7b344b9e8..f30b892b5 100644 --- a/scripts/tests/test_jq_preflight.py +++ b/scripts/tests/test_jq_preflight.py @@ -1,7 +1,7 @@ """Tests for `scripts/jq-preflight.sh` — the jq version contract (ersatztv#648). The axis this guards. Every shell gate in this repo is authored on a Mac shipping jq 1.8.x; the CI -runners shipped jq 1.6 (2026-07-26) and are now a mix of 1.6 and 1.7 (2026-10-29). Nothing pinned +runners shipped jq 1.6 (2026-07-26) and both measured runners report 1.7 (2026-10-28). Nothing pinned or checked that, and three independent divergences surfaced in a single day — `jq -e` over empty input (exit 4 vs 0), `contains("")` (false vs true for every string), and the parse-error exit code (5 vs 4, colliding with "no output"). Each was patched with a @@ -153,7 +153,7 @@ def test_expect_match_passes(preflight): @pytest.mark.parametrize("version_line", ["jq-1.6", "jq-1.7", "jq-1.7.1", "jq-1.6-dirty"]) def test_the_accepted_set_passes_on_either_member(preflight, version_line): - """The mixed fleet (server-management#1080): whichever runner takes the job must be green.""" + """The unpinned fleet (server-management#1080): whichever version the runner holds must be green.""" preflight.with_jq(version_line) r = preflight.run("--expect", ACCEPTED) assert r.returncode == 0, r.stderr @@ -378,7 +378,7 @@ def test_script_tests_pins_the_jq_version(): """ pr_checks = (WORKFLOWS / "pr-checks.yml").read_text() assert "jq-preflight.sh --expect" in pr_checks, "script-tests must pin the jq version — that pin is the tripwire" - # Exactly the mixed-fleet set until server-management#1080 settles one version (#935): not 1.6 alone + # Exactly the accepted set until server-management#1080 settles one version (#935): not 1.6 alone # (red on a 1.7 runner), not a wider set (a 1.8 runner must stay red). assert re.search(rf"jq-preflight\.sh --expect {re.escape(ACCEPTED)}\s*$", pr_checks, re.M), ( f"script-tests must run --expect {ACCEPTED}" diff --git a/scripts/tests/test_pr_changed_files.py b/scripts/tests/test_pr_changed_files.py index c161dc12d..d0ca34f1c 100644 --- a/scripts/tests/test_pr_changed_files.py +++ b/scripts/tests/test_pr_changed_files.py @@ -493,7 +493,7 @@ def test_a_SHORT_page_does_not_end_the_enumeration(enumerate_files): assert "ErsatzTV/Program.cs" in enumerate_files.paths() -# --- The same guard, under the runner's jq 1.6 ------------------------------------------------ +# --- The same guard, under jq 1.6 ------------------------------------------------ # # `test_transport_failure_mid_pagination_fails_closed` above does NOT isolate the explicit # `if [ -z "${raw//[[:space:]]/}" ]` clause — mutation confirms it: deleting that clause leaves @@ -1840,7 +1840,7 @@ def _run_classify( curl.write_text(WORKFLOW_STUB_CURL) curl.chmod(0o755) if jq16: - # Reproduce the runner's jq 1.6 (`-e` over EMPTY input exits 0, not 4). The shim is the one + # Reproduce jq 1.6 (`-e` over EMPTY input exits 0, not 4). The shim is the one # already used for pr-changed-files.sh in this file, so its fidelity is covered by that # suite's own verify-the-verifier test. jq = bindir / "jq"