Repository navigation
test(e2e): gate the class of defect that let a broken query pass - #1852
Conversation
E-scenario runbooks are executed by a human operator, never by CI, so a command that cannot run at all can sit in a runbook indefinitely. That is a documentation bug only until it meets a gate whose pass condition is universally quantified -- such a condition is vacuously true over the empty set, so a command that fails and returns nothing writes the passing answer into the verdict rather than an error. E4 Phase 3b did this: gh was passed a --arg flag it does not have, --jq swallowed it as its expression, gh exited 1 with empty stdout, and 2>$null hid it. The two fields marked # dispositive -- where true is the FAIL evidence -- were handed their false by an infrastructure failure. PR #1821 fixed that instance. This adds the mechanism that catches the next one. The linter reads every fenced command in .squad/e2e/ and fails the build on either property that made the silent pass possible: gh invoked with --arg, or stderr suppressed without a nearby exit-code check. It joins shell line-continuations so a flag is judged against the whole command, and reads only fenced blocks, skipping comments -- so E4's own prose postmortem of this defect does not trip the gate that exists because of it. Verified by mutation: reintroducing the original Phase 3b command fails both rules, while the real runbooks stay green. Measured at introduction, 16 of 17 suppression sites already paired with an exit-code check, so this codifies discipline the runbooks already practice. The half that cannot be mechanized -- assert a gate's input set is non-empty before evaluating it, and never let an infrastructure failure reach a dispositive field -- is recorded as convention in the E2E README. Executes no commands; needs no fixture, network, or Actions minutes. Closes #1822 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 20afe6d2-444e-414e-8a39-e67ab67ca6df
🟡 Impact Analysis — PR #1852Risk tier: 🟡 MEDIUM 📊 Summary
🎯 Risk Factors
📦 Modules Affectedsquad-state (1 file)
tests (1 file)
This report is generated automatically for every PR. See #733 for details. |
|
🛫 PR Readiness Check
PR Scope: 🔧 Infrastructure
|
| Status | Check | Details |
|---|---|---|
| ✅ | Single commit | 1 commit — clean history |
| ✅ | Not in draft | Ready for review |
| ✅ | Branch up to date | Up to date with dev |
| ❌ | Copilot review | No Copilot review yet — it may still be processing |
| ✅ | Changeset present | No source files changed — changeset not required |
| ✅ | Scope clean | |
| ✅ | No merge conflicts | No merge conflicts |
| ❌ | Copilot threads resolved | 1 unresolved Copilot thread(s) — fix and resolve before merging |
| ✅ | CI passing | All checks passing |
Files Changed (2 files, +213 −0)
| File | +/− |
|---|---|
.squad/e2e/README.md |
+16 −0 |
test/e2e-runbook-hygiene.test.ts |
+197 −0 |
Total: +213 −0
This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.
There was a problem hiding this comment.
🟡 Changes recommended
The new hygiene test has an off-by-one bug vs its own documented window semantics, which can cause incorrect lint failures.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a CI gate to lint .squad/e2e/ runbook fenced-command blocks for failure-masking patterns that can produce vacuous “pass” outcomes (notably gh --arg misuse and stderr suppression without an exit-code check), and documents the underlying runbook conventions in the E2E README.
Changes:
- Introduces
test/e2e-runbook-hygiene.test.tsto statically lint fenced runbook commands (including joining line continuations) for two specific regression shapes. - Documents the non-mechanizable “universally quantified gates must assert non-empty input” convention in
.squad/e2e/README.md.
File summaries
| File | Description |
|---|---|
| test/e2e-runbook-hygiene.test.ts | New Vitest suite that lints fenced runbook command blocks for gh --arg and unpaired stderr suppression patterns. |
| .squad/e2e/README.md | Adds a “runbook conventions” section explaining the defect class and pointing to the new hygiene gate. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const window = lines | ||
| .slice(idx, idx + EXIT_CHECK_WINDOW) | ||
| .map((l) => l.text) | ||
| .join('\n'); |
…ds (#1856) The E2E runbooks targeted `bradygaster/aspiregregator-squad-e2e`, which was deleted on 2026-08-24 along with every other test repo. Runs now bill to `octodemo`, and the fixture was recreated from the pristine read-only origin `bradygaster/Aspiregregator`. Moved the PROCEDURE, not the EVIDENCE. E1:4 and E4:27/:256 record which repo produced the readings they sit beside; rewriting those would falsify the records. They keep the retired name and are annotated as retired. Only the re-run inputs (`$FIXTURE`) were repointed. Three findings the repoint surfaced, none fixable by find-and-replace: 1. The repo NAME was reused across owners, so E1's `blocked_by_job_url` cross-check -- which matched the bare name -- could no longer identify the fixture, and would accept evidence from the deleted repo. Tightened to match owner AND name. 2. The new fixture is a bare application repo with no `.squad/` directory, so it has no roster. E4's central criterion (which leaked tokens are dispositive vs ambiguous) is DERIVED from roster contents, not fixed vocabulary. Added a re-derivation gate stating the reasoning rule, so a re-run cannot inherit the retired fixture's classification. 3. Phase 0 refreshes an activated fixture; it cannot create one. Added Phase 0-pre, which asserts activation state before choosing a path rather than letting 0b report UNREADABLE on all four files and burning a diagnostic cycle. Verification: the E2E runbook hygiene linter (#1822 / PR #1852) passes, and was mutation-tested against the new fenced block -- removing the exit-code pairing fails it at E4:390, so the new block is genuinely covered rather than vacuously passing. Closes #1851 Co-authored-by: brady gaster <bradygaster@github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 20afe6d2-444e-414e-8a39-e67ab67ca6df
Closes #1822
What this is
#1822 is not "fix the broken query" — PR #1821 already did that, and did it well (exit-code capture, empty-cutoff guard, zero-rows scored
INCONCLUSIVE, an explicit do not recordfalseon the dispositive fields). #1822 asks for the mechanism that would have caught it, and would catch the next one.This adds that mechanism.
The shape of the defect
E-scenario runbooks are executed by a human operator, never by CI, so a command that cannot run at all can sit in a runbook indefinitely. That is a documentation bug right up until it meets a gate whose pass condition is universally quantified — "every
squad:{x}on a newly created issue must be a roster Name". Such a condition is vacuously true over the empty set.So a broken command did not merely fail to inform the verdict. It wrote the passing answer into the two fields the schema marks
# dispositive, wheretrueis the FAIL evidence. An infrastructure failure minted the green result for the gate whose entire purpose was catching falsely-minted labels.Measured against the bar in
.squad/decisions.md(2026-08-20): "A test that passes while the system is broken is decoration, not a gate."The gate
test/e2e-runbook-hygiene.test.tslints every fenced command in.squad/e2e/and fails the build on either property that made the silent pass possible:ghinvoked with--arg. It is ajqflag;ghhas none.--jqconsumes it as its expression and the rest degrade into stray positionals, soghexits 1 with empty stdout rather than an obvious parse error — which is precisely why it survived review.Two details that matter:
--jq --argon a continuation line — a per-line linter would have missed it.Verification
Mutation-tested, not just run. Reintroducing the original Phase 3b command — continuation and all — fails both rules with the offending file and line reported:
The real runbooks stay green throughout, including E4's prose description of the same bug.
Measured at introduction: 16 of 17 existing suppression sites already paired with an exit-code check. The single apparent miss was the comment describing the defect. So this codifies discipline the runbooks already practice rather than imposing a new one — it fails only on regression.
test/e2e-runbook-hygiene.test.ts+test/docs-links.test.ts: 6 passed.The half that cannot be mechanized
Detecting "this gate is universally quantified" reliably from prose would produce false positives, so it is recorded as written convention in
.squad/e2e/README.mdinstead:# dispositivefield. Non-zero exit or unset cutoff meansINCONCLUSIVE, never a verdict value.Cost
Executes no commands. Needs no fixture, network, or Actions minutes.
Note for the E4 re-run
Because #1821 is merged, E4 Phase 3b is already sound — the re-run is not blocked on this PR. What does block it is #1851: E1 and E4 both hardcode
bradygaster/aspiregregator-squad-e2e, which no longer exists.Owners
FIDO (test/CI gate) with Sims (runbook convention), per the issue.
No changeset — no
packages/*/src/changes.