Skip to content

test(e2e): make an empty mutation list and a convergence re-run assert something - #601

Merged
Vivswan merged 1 commit into
mainfrom
test/e2e-vacuous-assertions
Nov 12, 2026
Merged

Vivswan merged 1 commit into
mainfrom
test/e2e-vacuous-assertions

Conversation

@Vivswan

@Vivswan Vivswan commented Nov 12, 2026 •

Copy link
Copy Markdown
Owner

Before

$ bun must-fail.mts /repo          # origin/main: each run below must fail
an empty mutation list beside a run that sends PATCH and DELETE
  PASS  empty-mutations-beside-two-writes
a convergence re-run that skips its only section
  PASS  converges-with-every-section-skipped
an apply-idempotence check that skips its only section
  PASS  apply-idempotent-with-every-section-skipped
control: one section skipped for a denied read, one checked clean (must PASS)
  PASS  converges-with-one-legitimate-skip
the catalog's token for the typo "label" beside a run that refuses "labelz"
  expected in stdout: "label"
  PASS  token-for-label-beside-a-refused-labelz

runs that did not fail as they must: 4

After

$ bun must-fail.mts /repo          # this branch
an empty mutation list beside a run that sends PATCH and DELETE
  FAIL  empty-mutations-beside-two-writes
          mutations: [] expects no write, but the run sent 2: PATCH /repos/e2e-owner/e2e-repo, DELETE /repos/e2e-owner/e2e-repo/labels/wontfix
a convergence re-run that skips its only section
  FAIL  converges-with-every-section-skipped
          convergence: the check's result is "partial" and no section reads clean, so it verified nothing
an apply-idempotence check that skips its only section
  FAIL  apply-idempotent-with-every-section-skipped
          apply-idempotence: the check's result is "partial" and no section reads clean, so it verified nothing
control: one section skipped for a denied read, one checked clean (must PASS)
  PASS  converges-with-one-legitimate-skip
the catalog's token for the typo "label" beside a run that refuses "labelz"
  expected in stdout: "unknown top-level section: label (known:"
  FAIL  token-for-label-beside-a-refused-labelz
          stdout missing: unknown top-level section: label (known:

runs that did not fail as they must: 0

How

  • mutations: [] means the run sent no write. Only an absent key asserts nothing.
  • A fixpoint check passes when it reports clean, or every section it skipped has a read the mock's permission gate denied in that re-run's log and at least one section row reads clean.
  • Six rejection tokens carry the wording around their own key, so refusing another key cannot satisfy them.
  • tiers is deleted. A scenario file that writes it is refused at load. No file does.
  • writeDeniedSections and mayWrite are deleted. Nothing asserted them. For the owner: the fuzz still does not check that a denied write leaves state alone.
  • Library: tableRows in test/docs/markdown.ts, covers reading the summary's rows.

Proof

  • Scenarios: the three that write mutations: [] send no write. None changed.
  • Corpus: 373/373 passed.
  • Fuzz: seed 20261111, 25/25 iterations ok.
  • Unit: 445 pass, 0 fail.
Technical details
  • The blocks: must-fail.mts is a scratch script that feeds five scenario documents to the runner's parseScenario and runScenario, with TMPDIR pointed at a scratch directory.
  • Before ran it on the tree of origin/main at faf72dab, After on this branch.
  • The fifth run's token is what the unknown-top-level-key case of INVALID_SETTINGS_CASES builds for the typo label, read from the tree under test.
  • The five documents, each a scenario file's content:
name: empty-mutations-beside-two-writes
settings:
  repository: { has_issues: false }
  labels: [{ name: bug, color: d73a4a }]
live_state:
  labels: [{ name: bug, color: d73a4a }, { name: wontfix, color: ffffff }]
expect: { exit_code: 0, result: applied, mutations: [] }
---
name: converges-with-every-section-skipped
settings:
  labels: [{ name: bug, color: d73a4a }]
denial_style: 403
token_permissions: { issues: none }
inputs: { mode: apply, on_missing_permission: warn }
expect: { exit_code: 0, fixpoint: converges }
---
name: apply-idempotent-with-every-section-skipped
# the document above, with fixpoint: apply_idempotent
---
name: converges-with-one-legitimate-skip
# the converges document above, plus one section the token can read
settings:
  repository: { has_issues: false }
  labels: [{ name: bug, color: d73a4a }]
denial_style: 403
token_permissions: { issues: none }
inputs: { mode: apply, on_missing_permission: warn }
expect: { exit_code: 0, fixpoint: converges }
---
name: token-for-label-beside-a-refused-labelz
settings:
  labels: [{ name: bug, color: d73a4a }]
  labelz: []
inputs: { mode: check }
expect: { exit_code: 1, stdout_contains: ["<the token>"], zero_requests: true }
  • Unit, without a child process: on origin/main, requestLogFailures({ mutations: [] }, <a log with one PATCH>) returns [].
  • The decoy: on origin/main a rejection of { zzz_decoy: [], _zzz: "x" } contains the token of six catalog cases.
  • The six cases: unknown-top-level-key (label, milestone, branch), unknown-underscore-key (_layerin, _undeclared), array-section-wrong-type, record-section-wrong-type, pages-wrong-type, interaction-limits-wrong-type (each a bare section key).
  • Their tokens now: unknown top-level section: <typo> (known:, unknown underscore key: <key>., _undeclared must be one of, and <section>: , the start of the section's own issue.
  • The class pin: genInvalidSettings > no token is satisfied by a rejection that names only other keys, with a control that the bare label and _layerin are found in the decoy.
  • Census, mutations: []: 372 scenarios, 137 set expect.mutations, 3 set it to [] (empty-settings, policy-matrix-apply-fail, secret-scanning-patterns-bootstrap-fail), read from loadScenarios(scenarioRoots()).
  • Each of the three passes bun test/e2e/run.ts --scenario <name> under the new rule: 1/1 passed.
  • Census, tiers, on origin/main: grep -rnE '\.tiers\b' test src .github --include='*.ts' finds 0 readers, grep -rnE 'tiers: \["mock"\]' finds 12 literals, grep -rlE '^tiers:' test --include='*.yml' finds 0 files.
  • Control for that census: the same reader grep for .fixpoint finds 12.
  • The refusal is one row of the scenario-schema table in test/e2e/foundation.test.ts: Unrecognized key: "tiers".
  • Census, the two predictions, on origin/main: writeDeniedSections and mayWrite each appear 0 times outside test/e2e/generators/{oracle.ts,oracle.test.ts}.
  • Control for that census: allowedExitCodes, a prediction the fuzz does read, appears 6 times outside them.
  • Why deleted, not wired: the prediction reads the section's permission, while endpoints carry their own overrides (endpointPermission), so "no write lands in a section without a write grant" is not sound as written.
  • The verdict on a multi-repo run: the skipped list is the run's union, so a denied read of the section on any target explains a skip.
  • A check that exits non-zero is left to the exit-code failure, and the verdict returns nothing for it.
  • Corpus: bun test/e2e/run.ts --sections all, 372 scenarios and the corpus witness line.
  • Fuzz: FUZZ_SEED=20261111 bun test/e2e/fuzz.ts --iterations 25, after bun run build:schema.
  • Unit: bun test test/e2e/runner test/e2e/generators test/e2e/foundation.test.ts test/e2e/fuzz.test.ts test/e2e/run.test.ts, 8 files.
  • Files: test/e2e/runner/{runner.ts,apply-idempotence-proof.ts,runner.test.ts}, test/e2e/{scenario.ts,foundation.test.ts,fuzz.ts}, test/e2e/generators/{invalid-settings.ts,generators.test.ts,oracle.ts,oracle.test.ts,single-scenario.ts,multi-scenario.ts,merge-scenario.ts,discovery-scenario.ts}.

…t something

Three assertions of the e2e harness passed on anything.

`mutations: []` was read as an empty subsequence, which every write log contains, so the scenarios that say "this run writes nothing" passed on a run that wrote. An empty list now means the run sent no write; only an absent key asserts nothing.

A fixpoint proof's check re-run was judged on exit code, writes, and mock violations alone, so under on-missing-permission: warn a re-run that skipped every section passed. The check now has to have verified something: it reports clean, or every section it skipped has a read the mock's permission gate denied in that re-run and at least one section reads clean. The apply-idempotence proof ends in the same verdict.

Six cases of the invalid-settings catalog expected a token that the rejection's own boilerplate also contains (the known-sections list, the directive advice), so the check passed whichever key the run refused. Each token now carries the wording around its own key, and a decoy rejection that names only other keys pins the class.

Two dead fields go. `tiers` was parsed and read by nothing, so `tiers: [live]` ran on the mock silently; the strict schema now refuses the key. `writeDeniedSections` and `mayWrite` were predicted by the fuzz oracle and read only by its own test: the prediction reads the section's permission while endpoints carry their own overrides, so it could not be asserted as written.
Copilot AI balanced review requested due to automatic review settings November 12, 2026 03:36
@github-actions

Copy link
Copy Markdown
Contributor

File size check

0 over a hard cap (fails), 6 warning(s).

File Size Tier Cap
src/engine/diff.ts:56 11 comment lines warn 10
src/github/api-error.ts:9 11 comment lines warn 10
src/problem.ts:73 18 comment lines warn 10
src/sections/contract/errors.ts:13 11 comment lines warn 10
src/sections/contract/module.ts:55 12 comment lines warn 10
src/sections/shared/list/decl.ts:265 14 comment lines warn 10

Split the file, wrap the line, shorten or exempt the comment, or list the path in .file-size-allow.local with a # reason.

5 managed file(s) skipped; repo-platform owns them.

@github-actions

Copy link
Copy Markdown
Contributor

Member shape check

1 finding(s).

File Line Rule
test/sections/contract/validate-hook.test.ts 80 all-or-none

Add the blank lines, make the comment one line directly above the item, comment every field or none, or list the path in .member-shape-allow.local with a # reason.

4 generated file(s) skipped.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues are reported for the changes strengthening end-to-end test assertions.

No open issues 🎉

What changed in this PR

Strengthens the end-to-end test harness for GitHub Settings as Code so empty mutation expectations and fixpoint checks cannot pass without meaningful assertions.

Changes:

  • Makes mutations: [] require zero writes and adds evidence checks to convergence and apply-idempotence reruns.
  • Makes rejection tokens key-specific and adds regression tests against unrelated error messages.
  • Removes unused scenario tiers and write-permission predictions.
File Description
test/​e2e/​scenario.ts Documents stronger assertions and removes tiers from the schema.
test/​e2e/​runner/​runner.ts Enforces empty mutation expectations and checks fixpoint evidence.
test/​e2e/​runner/​runner.test.ts Tests no-write expectations and verified versus unverified checks.
test/​e2e/​runner/​apply-idempotence-proof.ts Applies the shared evidence check after the second apply.
test/​e2e/​generators/​single-scenario.ts Removes tiers from single-repository scenarios.
test/​e2e/​generators/​oracle.ts Removes unused mayWrite and writeDeniedSections predictions.
test/​e2e/​generators/​oracle.test.ts Updates oracle tests for the reduced prediction model.
test/​e2e/​generators/​multi-scenario.ts Removes tiers from multi-repository scenarios.
test/​e2e/​generators/​merge-scenario.ts Removes tiers from layered-settings scenarios.
test/​e2e/​generators/​invalid-settings.ts Tightens rejection tokens to identify their offending keys.
test/​e2e/​generators/​generators.test.ts Checks that unrelated rejection messages cannot satisfy tokens.
test/​e2e/​generators/​discovery-scenario.ts Removes tiers from discovery scenarios.
test/​e2e/​fuzz.ts Removes tiers from fuzz-generated scenarios.
test/​e2e/​foundation.test.ts Adds a schema test rejecting the removed tiers key.

Review effort: Balanced

@Vivswan
Vivswan marked this pull request as ready for review November 12, 2026 03:54
@Vivswan Vivswan added the merge-when-green Owner approved: merge once every gate is green label Nov 12, 2026
@Vivswan
Vivswan merged commit ee8584f into main Nov 12, 2026
32 checks passed
@Vivswan
Vivswan deleted the test/e2e-vacuous-assertions branch November 12, 2026 05:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-when-green Owner approved: merge once every gate is green

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants