Repository navigation
Detect a conformance fixture case that every runner excludes (#602) - #743
Conversation
Each runner's case census (#742) answers "did THIS runner account for every case". It cannot answer the question #602 actually asks — is any case executed by NO runner — because a case every runner excludes leaves all six censuses green: each one counted its own skip. Only a comparison across runners sees it. Every runner now writes the cases it did not execute, with reasons, to conformance/manifests/<runner>.json, and scripts/check-fixture-execution.rb fails when a case appears in all six. Manifests rather than parsed output because TypeScript prints no `SKIP:` line — a skip there is `it.skip`, reported in vitest's own format — so a gate scraping stdout would be blind to exactly one runner, in the silent direction: TypeScript would contribute an empty exclusion set and no case could reach all-six. Each manifest also records `executed`, asserted against the census total by both writer and reader: without it a case a runner silently dropped is simply absent from its exclusion set, and absent reads identically to "ran fine". THE ABSENCE RULE IS THE DESIGN. FULL mode requires all six manifests and fails if any is missing — a missing manifest must never read as "that runner executed everything", which is exactly what makes an all-six case invisible. Swift's runner is macOS-only, so a Linux run produces five and uses PARTIAL mode: an exclusion shared by every VISIBLE runner is a warning, never a failure, because five-of-six is not the all-six claim and a warning cannot false-fail. Both modes fail on zero manifests. CI resolves it properly rather than living with the partial answer: the six language jobs each upload their manifest and the existing fan-in job runs FULL mode over all six. That step is ordered AFTER the results check, because the job is `if: always()` — a language job that died before its upload would otherwise be reported as a missing manifest, burying the real cause. The local target depends on `conformance` rather than trusting whatever manifests are on disk, which would let it validate last week's exclusion sets. Maximum overlap today is 2 of 6 (#596 narrowed it), so the gate is green on arrival and a live run only proves it can say yes. scripts/test-check-fixture-execution.rb crafts the all-six state and every absence, integrity and disagreement case; it already found one defect (a non-object manifest crashed with a backtrace instead of naming the file). Verified end-to-end too: adding one shared exclusion to all six real manifests makes the gate fail and name every runner's reason. SPEC §19 now describes the gate that exists, closing the gap #740's P1 found — where the prose promised `make check-fixture-execution` after the source-text parser it referred to had been withdrawn.
Sensitive Change Detection (shadow mode)This PR modifies control-plane files:
|
…mance Both jobs are matrixed and run their conformance suite on ONE version — `matrix.ruby == '3.3'`, `matrix.python == '3.13'`. The upload steps went in unconditionally, so on every other leg the conformance step was skipped, no manifest was written, and `if-no-files-found: error` failed the job. Seven red checks, all of them this. Mirroring the condition is the fix rather than relaxing if-no-files-found: a leg that DOES run conformance and produces no manifest is a real defect, and that is exactly what the fan-in gate's absence rule depends on being loud. Go, TypeScript, Kotlin and Swift have no matrix, so their uploads stay unconditional and each artifact name remains unique.
There was a problem hiding this comment.
Pull request overview
Adds cross-runner detection for conformance cases deliberately excluded by all six SDK runners.
Changes:
- Writes execution manifests from every conformance runner.
- Adds full/partial fixture-execution validation and self-tests.
- Collects manifests in CI and documents the gate.
Tip
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/test.yml |
Uploads and collects runner manifests. |
.gitignore |
Ignores generated manifests. |
Makefile |
Adds the local execution gate target. |
SPEC.md |
Documents census and cross-runner checks. |
scripts/check-fixture-execution.rb |
Implements manifest validation and overlap detection. |
scripts/test-check-fixture-execution.rb |
Tests gate failure modes. |
conformance/runner/go/main.go |
Records Go exclusions. |
conformance/runner/go/manifest.go |
Serializes the Go manifest. |
conformance/runner/python/runner.py |
Records and writes Python exclusions. |
conformance/runner/ruby/runner.rb |
Records and writes Ruby exclusions. |
conformance/runner/typescript/case-census.ts |
Adds TypeScript manifest serialization. |
conformance/runner/typescript/runner.test.ts |
Produces the TypeScript manifest. |
conformance/runner/swift/Sources/ConformanceSupport/ExecutionManifest.swift |
Serializes the Swift manifest. |
conformance/runner/swift/Sources/ConformanceRunner/Runner.swift |
Records Swift exclusions. |
kotlin/conformance/src/main/kotlin/com/basecamp/sdk/conformance/ExecutionManifest.kt |
Serializes the Kotlin manifest. |
kotlin/conformance/src/main/kotlin/com/basecamp/sdk/conformance/Main.kt |
Records Kotlin exclusions. |
Suppressed comments (1)
.github/workflows/test.yml:695
- This upload runs in every Python matrix leg, but only the 3.13 leg runs the conformance runner and creates
python.json. The other matrix legs will fail atif-no-files-found: error, so the Python job and fan-in gate cannot pass. Restrict the upload to the producing leg.
run: |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…arget in Two review findings from Copilot, both correct. `--partial` describes the INPUT — "this run cannot produce all six" — not a licence to soften the verdict. When every expected runner reported anyway (a macOS developer passing the flag out of habit, or a CI step keeping it for safety), "excluded by all present runners" IS the all-six claim, and the warning path let the one state this gate exists to reject exit 0. Partial handling now applies only when a manifest is genuinely absent. The self-test case for it was shown to fail against the un-narrowed code first. `check-fixture-execution` was also absent from `check-targets`: the edit that was supposed to add it matched the first occurrence of its anchor, which was the .PHONY line, so the target existed and `make check` never ran it. Now in the list, verified by reading the check-targets line itself rather than grepping the file.
|
Round one at The CI failure (7 red checks, one cause): the Ruby and Python jobs are matrixed and run conformance on one version only, so my unconditional upload fired on every other leg with nothing to upload and A soundness hole in
Consequence worth stating: the |
|
@codex review |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 16 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
Makefile:1094
make checkstill does not run this target.check-targetsat Makefile:1483 depends onconformance, but not oncheck-fixture-execution, so it only produces the manifests and never runs either the cross-runner comparison or its negative self-test. Addcheck-fixture-executionto that dependency list (the directconformancedependency can then be removed because this target already includes it).
# the runners that produce them. Inside `make check` it is free, because
# conformance is phony and already a prerequisite there.
scripts/check-fixture-execution.rb:181
--partialreturns success unconditionally here, even when all six expected manifests are present. This can happen with a leftover Swift manifest on Linux, and then a genuine all-six exclusion is only warned about instead of failing. Use partial warning behavior only while at least one runner is actually missing; a complete manifest set should continue into the full failure path.
# `--partial` is a statement about the INPUT, not a licence to soften the
.github/workflows/test.yml:941
- This CI path runs only the live, green-by-construction comparison;
scripts/test-check-fixture-execution.rbis not invoked anywhere in the workflow. Because the committed manifests have no all-six overlap, the failure branch can regress to always-success while every required check stays green. Run the negative self-test here (or inspec-gates) so CI proves the new gate can reject its target state.
run: ruby scripts/check-fixture-execution.rb
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 351f62d9fd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (1)
Makefile:1099
- Running
conformanceonly overwrites manifests produced on this host; it never removes old ones. On Linux, aswift.jsonleft by an earlier macOS run therefore survives, so--partialsees all six manifests and treats stale Swift exclusions as current. When the case count is unchanged, this can mask a newly all-six-excluded case (or cause a false failure). Clear the manifest directory before the conformance prerequisites run, not in this recipe after them.
check-fixture-execution: conformance
Four review findings, all real, two of them defects in the gate's core claim. CASE IDENTITY IS [file, name], NOT name. Codex is right that fixture case names are not unique: verified, "replace-omission-clears: sparse replace sends the request verbatim with no GET" appears in THREE fixtures and the non-idempotent POST retry name in two, while names are unique within a file. Keyed on name alone, a runner excluding one of those collapsed two entries into one — its own `executed + excluded` integrity check would then fail spuriously, and worse, a name excluded by three runners in one file and three in another would read as excluded by all six. A false failure on cases that all run. Every runner now records the fixture file, and the gate keys on the pair; it also rejects the same case excluded twice by one runner, which would otherwise add up while the comparison saw a single entry. STALE MANIFESTS. Both bots found this, and it was created by the --partial narrowing in the previous commit. On Linux `conformance-swift` is a no-op, so a swift.json left by an earlier macOS run over the same checkout survives while the other five refresh; the gate then sees six manifests, stops treating the run as partial, and compares five current exclusion sets against a stale sixth. Silent-wrong, in a gate whose whole claim is about what the runners actually did. The target now wipes conformance/manifests and runs the suite itself, so "six manifests present" means "six runners reported in this run". The reset is in the recipe, not a prerequisite: prerequisite order is not guaranteed under `make -j`, and a reset racing the runners would delete the output it exists to protect. check-targets therefore lists this target INSTEAD of `conformance`, so the suite still runs exactly once per `make check`. SELF-TEST IN CI. The fan-in job ran only the live gate, whose inputs are built to pass; the self-test was reachable only through the Make target the workflow deliberately bypasses. A regression making the gate accept a missing manifest or an all-six exclusion would have left CI green — the check-targets-is-not-CI -coverage shape, in the PR that adds the gate. It now runs as its own step. New self-test cases: one name in two files is two cases and must NOT fire; the same name in one file excluded everywhere still must; an exclusion without its fixture file is rejected; one case excluded twice in a manifest is rejected.
|
Round two at Case identity was wrong. Codex found that fixture case names are not unique; I verified before acting, and it is worse than one collision — Stale manifests, found by both bots, and created by my own The self-test did not run in CI. Reachable only through the Make target the workflow bypasses, so a regression making the gate accept a missing manifest or an all-six exclusion would have left CI green — New self-test cases: one name in two files must not fire; the same name in one file excluded everywhere still must; an exclusion missing its fixture file is rejected; one case excluded twice in a manifest is rejected. Local: |
|
@codex review |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (6)
conformance/runner/go/manifest.go:76
- This consistency guard has no negative test: the normal runner always passes matching counts, and no Go test calls
writeManifest, so removing or reversing the guard would leave the suite green. Add a unit test that supplies mismatchedExecuted/Excludedaccounting and asserts that writing fails.
if m.Executed+len(m.Excluded) != m.Total {
return fmt.Errorf(
"manifest for %s is internally inconsistent: %d executed + %d excluded != %d non-live "+
"cases; the run dropped a case without recording it as either",
m.Runner, m.Executed, len(m.Excluded), m.Total)
conformance/runner/ruby/runner.rb:185
- No Ruby runner test exercises this mismatch branch, while the live run always supplies consistent counts. Add a focused test for
ExecutionManifest.writethat expectsExecutionManifest::Erroron inconsistent accounting; otherwise this writer-side assertion can regress without failing CI.
if executed + excluded.length != total
raise Error, "manifest for #{runner} is internally inconsistent: #{executed} executed + " \
"#{excluded.length} excluded != #{total} non-live cases; the run dropped a " \
"case without recording it as either"
end
conformance/runner/python/runner.py:186
- The writer’s rejection path is not covered by the Python runner tests; committed fixtures only exercise internally consistent accounting. Add a test that calls
write_execution_manifestwith a mismatched total and verifies theRuntimeError, so this fail-closed invariant cannot silently disappear.
if executed + len(excluded) != total:
raise RuntimeError(
f"manifest for {runner} is internally inconsistent: {executed} executed + "
f"{len(excluded)} excluded != {total} non-live cases; the run dropped a case "
"without recording it as either"
kotlin/conformance/src/main/kotlin/com/basecamp/sdk/conformance/ExecutionManifest.kt:55
- No Kotlin conformance unit test references
ExecutionManifest, and normal fixtures always make this equation true. Add anExecutionManifestTestmismatch case that expectsExecutionManifest.Error, so the writer-side integrity check is proven to reject bad accounting rather than only proven to accept good input.
if (executed + excluded.size != total) {
throw Error(
"manifest for $runner is internally inconsistent: $executed executed + " +
"${excluded.size} excluded != $total non-live cases; the run dropped a case " +
"without recording it as either"
conformance/runner/typescript/case-census.ts:194
writeExecutionManifestis invoked only withregistered - excluded.length, so this mismatch branch is never exercised. Add a case tocase-census.test.tsthat passes inconsistent accounting and expects an exception; deleting this guard currently leaves all tests green.
if (executed + excluded.length !== total) {
throw new Error(
`manifest for ${runner} is internally inconsistent: ${executed} executed + ` +
`${excluded.length} excluded != ${total} non-live cases; the run dropped a case ` +
"without recording it as either",
conformance/runner/swift/Sources/ConformanceSupport/ExecutionManifest.swift:67
- The new support writer has no corresponding Swift test, so its fail-closed accounting branch is only exercised on success by the real fixture tree. Add an
ExecutionManifestTestsmismatch case that assertsManifestError.inconsistent; this target exists specifically to make support failure modes testable.
guard executed + excluded.count == total else {
throw ManifestError.inconsistent(
"manifest for \(runner) is internally inconsistent: \(executed) executed + "
+ "\(excluded.count) excluded != \(total) non-live cases; the run dropped a "
+ "case without recording it as either")
The previous commit had check-fixture-execution run `$(MAKE) conformance`
itself so it could wipe the manifest directory first. That worked, and cost
something I did not price: with `conformance` no longer listed in
check-targets, `conformance-kotlin` stopped being reachable from
check-targets in the graph check-gradle-serialization walks — so that gate
could no longer see one of the two Gradle invocations it exists to keep off
each other. Its self-test caught it exactly as designed ("conformance-kotlin
edge removed: expected the gate to FAIL, it passed"), which is a mutation
case earning its keep on a change nobody wrote it for.
Restructured so both properties hold. `conformance` is back in check-targets
and check-fixture-execution depends on it, restoring reachability. The reset
is now a phony target that all six language targets take as an ORDER-ONLY
prerequisite (`|`), which is what makes it correct under `make -j`: make
builds a prerequisite to completion before any dependent starts, and builds
the phony target once per invocation, so the reset cannot race the runners it
protects. A plain prerequisite of the aggregate target would not do that —
siblings may run concurrently.
It fires for a single-language run too, deliberately: `make conformance-go`
clears the directory, so it holds only what that invocation produced and the
gate goes partial rather than silently mixing runs across machines.
Verified both directions: a planted stale swift.json is wiped and regenerated,
and `make conformance-go` alone leaves only go.json.
|
CI caught a cost I had not priced, at Making Its self-test is what failed, not the gate: Restructured so both properties hold rather than trading one for the other:
Verified both directions: a planted stale Also worth noting the fan-in job behaved correctly through this: the results check failed first and the gate steps were skipped, rather than reporting a derived "missing manifest" for jobs that never ran. That ordering was itself a review finding earlier in this PR. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (6)
.github/workflows/test.yml:492
- On a workflow re-run, the prior attempt's artifact can still have this name.
upload-artifactv7 defaultsoverwriteto false, so this step fails with a name-conflict error before the fan-in gate can run. Allow this runner to replace its previous-attempt manifest.
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: execution-manifest-typescript
path: conformance/manifests/typescript.json
if-no-files-found: error
.github/workflows/test.yml:600
- On a workflow re-run, the prior attempt's artifact can still have this name.
upload-artifactv7 defaultsoverwriteto false, so this step fails with a name-conflict error before the fan-in gate can run. Allow this runner to replace its previous-attempt manifest.
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: execution-manifest-ruby
path: conformance/manifests/ruby.json
if-no-files-found: error
.github/workflows/test.yml:712
- On a workflow re-run, the prior attempt's artifact can still have this name.
upload-artifactv7 defaultsoverwriteto false, so this step fails with a name-conflict error before the fan-in gate can run. Allow this runner to replace its previous-attempt manifest.
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: execution-manifest-python
path: conformance/manifests/python.json
if-no-files-found: error
.github/workflows/test.yml:787
- On a workflow re-run, the prior attempt's artifact can still have this name.
upload-artifactv7 defaultsoverwriteto false, so this step fails with a name-conflict error before the fan-in gate can run. Allow this runner to replace its previous-attempt manifest.
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: execution-manifest-swift
path: conformance/manifests/swift.json
if-no-files-found: error
.github/workflows/test.yml:865
- On a workflow re-run, the prior attempt's artifact can still have this name.
upload-artifactv7 defaultsoverwriteto false, so this step fails with a name-conflict error before the fan-in gate can run. Allow this runner to replace its previous-attempt manifest.
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: execution-manifest-kotlin
path: conformance/manifests/kotlin.json
if-no-files-found: error
.github/workflows/test.yml:409
- On a workflow re-run, the prior attempt's artifact can still have this name.
upload-artifactv7 defaultsoverwriteto false, so this step fails with a name-conflict error before the fan-in gate can run. Allow this runner to replace its previous-attempt manifest.
This issue also appears in the following locations of the same file:
- line 488
- line 596
- line 708
- line 783
- line 861
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: execution-manifest-go
path: conformance/manifests/go.json
if-no-files-found: error
Copilot flagged this on all six uploads. Re-running a job creates a new ATTEMPT within the same run, and upload-artifact v4+ artifacts are immutable per run — so the second attempt fails on a name conflict with the first attempt's manifest, before the fan-in gate can run. Reachable exactly when someone is re-running to get a red PR green, which is the worst time for the gate to become unreachable. `overwrite: true` is also right on the merits, not just as conflict avoidance: the collecting gate must read THIS attempt's exclusion set. An artifact left by a previous attempt is the CI-side version of the stale-manifest bug fixed two commits ago, and the same answer applies — six manifests present must mean six runners reported in this run.
|
Round three at Copilot's six suppressed comments were all one real issue, and a good one: re-running a job creates a new attempt within the same run, and It is also right on the merits rather than merely avoiding the conflict: the collecting gate must read this attempt's exclusion set. An artifact left by a previous attempt is the CI-side version of the stale-manifest bug fixed two commits ago, and it takes the same answer — six manifests present must mean six runners reported in this run. Codex's clean verdict is against Status at the previous head |
|
@codex review |
|
Codex Review: Didn't find any major issues. Breezy! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Closes #602.
The question the census could not answer
#742 gave every runner a case census: at the end of its own run it asserts that
passed + failed + skippedequals the non-live case count underconformance/tests,counted independently of its own load path. That catches a case executed by no runner for
a mechanical reason — an unrecognized
mode, a fixture that failed to parse or wasnever globbed, one nested where no runner looks, a case dropped between load and dispatch.
It cannot catch the case #602 actually names. When every runner excludes the same fixture
deliberately, all six censuses stay green — each one counted its own skip. Only a
comparison across runners sees it.
What this adds
Every runner writes the cases it did not execute, with reasons, to
conformance/manifests/<runner>.json.scripts/check-fixture-execution.rbreads all sixand fails when a case appears in every one.
On the current tree: 198 non-live cases across 6 runners; none excluded everywhere
(maximum overlap 2 of 6) — which is what #602 predicted and #596 narrowed it to.
Manifests rather than parsed output. Five runners print
SKIP: <name>; TypeScriptdoes not, because a skip there is
it.skip, reported in vitest's own format. A gatescraping stdout would be blind to exactly one runner in the silent direction —
TypeScript would contribute an empty exclusion set and no case could ever reach all-six.
executedis recorded, not just the exclusions, and asserted against the census totalby both writer and reader. Without it, a case a runner silently dropped is simply absent
from its exclusion set — and "absent" reads identically to "ran fine", so the comparison
would score the case as covered by that runner precisely when it was not.
The absence rule is the design
A gate over six inputs is only as good as its behaviour when one is missing, and missing
is the normal state here:
conformance-swiftis macOS-only.--partial, Linux)A missing manifest is never read as "that runner executed everything" — that assumption is
exactly what makes an all-six case invisible. Partial mode warns rather than fails because
five-of-six is not the all-six claim and Swift may well execute the case; a warning cannot
produce a false failure, which is the only reason it is allowed to run on partial input.
Both modes fail on zero manifests.
CI resolves it properly rather than living with the partial answer. The six language
jobs each upload their manifest; the existing
conformancefan-in job — which alreadyneeds:all six — downloads them and runs FULL mode. That step is ordered after theresults check, deliberately: the job is
if: always(), so a language job that died beforeits upload would otherwise be reported as a missing manifest, burying the real cause
behind a derived one.
The local target depends on
conformancerather than trusting whatever manifests are ondisk, which would let it validate last week's exclusion sets.
Proving it can say no
Maximum overlap is 2 of 6, so the gate is green on arrival and a live run only proves it
can say yes.
scripts/test-check-fixture-execution.rbcrafts the state no committedfixture can produce:
executed + excluded != total→ failsIt already earned its keep: it found a manifest shaped as a JSON string crashing with a
Ruby backtrace instead of naming the file.
Verified end-to-end as well — adding one shared exclusion to all six real manifests
makes the gate fail and print every runner's recorded reason.
Also
SPEC §19 now describes the gate that exists. This closes the gap #740's P1 found, where
the prose promised
make check-fixture-executionafter the source-text parser it referredto had been withdrawn to
conformance-skips-parser-archive.#736 is not closed here, but this is its input: the roster stays restated-not-derived, and
these manifests are what make it checkable for set equality against what the runners
actually reported — stronger than parsing their source, which is why it waited.
Verification
make check-fixture-execution— green in FULL mode on macOS (6 manifests), PARTIAL on Linux.scripts/test-check-fixture-execution.rb— all cases pass.make lint-actions(actionlint + zizmor) — clean on the workflow changes.make doc-constants-check,check-runner-test-reachability,check-gradle-serialization— green.make checkrunning on macOS (first exercise of FULL mode insidemake check) and on Linux.Summary by cubic
Detects and fails a build when any conformance fixture case is excluded by all runners. Previously each runner’s census stayed green on all‑six exclusions; now each runner writes an execution manifest and a cross‑runner gate compares them.
conformance/manifests/<runner>.jsonwithrunner,total_non_live,executed, andexcludedentries keyed by[file, name]; files are sorted, gitignored, and written even on failing runs.scripts/check-fixture-execution.rbvalidates manifests and fails on an all‑six exclusion. FULL mode requires all six manifests; PARTIAL (--partial) accepts fewer and warns on “all‑visible” overlap. It rejects malformed JSON, missing keys, unknown/duplicate runners, duplicate case exclusions,executed + excluded != total_non_live, count disagreements, and zero manifests. A self‑test (scripts/test-check-fixture-execution.rb) proves the rejections.conformance-manifests-resetis an order‑only prerequisite of all six language targets; it wipesconformance/manifests/before any runner writes so a run never mixes stale manifests.check-fixture-executiondepends onconformanceand is included incheck-targets.overwrite: trueto allow a re‑run to replace its own manifest and avoid name conflicts.Written for commit 3aed2b5. Summary will update on new commits.