Skip to content

fix(meta-285): make the candidate-consumption harness admissible evidence - #12

Merged
qmarcelle merged 6 commits into
mainfrom
feature/meta-285-defect-consume-standard-candidate-harness-three-checks
Aug 13, 2026
Merged

fix(meta-285): make the candidate-consumption harness admissible evidence#12
qmarcelle merged 6 commits into
mainfrom
feature/meta-285-defect-consume-standard-candidate-harness-three-checks

Conversation

@qmarcelle

@qmarcelle qmarcelle commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Closes META-285.

The harness that gates the 0.4.5 candidate train could not be trusted in either direction. Some checks could never pass, others could never fail, and a crash mid-run still emitted a clean verdict.

Repository state vs the issue description

Two of the three "always-fail" checks the issue names had already been repaired in-tree by 296e7c0 and ce0a175. They were never demonstrated red-then-green, so they were unverified fixes, and they get the same treatment here as the rest.

Issue item State at a31242b This PR
callTool() destructured for tools already using listTools() shape now verified before use, watched red
path === "hooks/" already startsWith watched red against a real pack inventory
destructive --help installer guard landed; harness still aimed at repoRoot harness no longer aims installer runs at the source repo
client.close() happy-path only open closes in finally
comparator duplicated in its test open exported and imported
raw ENOENT/SyntaxError open legible failure, exit 2

The audit the review did not run

The issue asked whether any check can never fail. Three found:

  1. hook.denies read status !== 0. spawnSync returns status: null when the process never launched, and null !== 0 is true — a candidate shipping no hook scored a passing deny.
  2. mcp.tool-call-responds read !result.isError. isError is optional on CallToolResult, so any malformed result passed.
  3. The receipt itself. Unreached checks were not recorded at all while the verdict was computed as failures === 0 ? "CONSUMABLE" over only what executed. A throw after seven passing checks emitted 7/7 passed — CONSUMABLE.

A fourth was found by running the harness, not by unit tests: node <missing-script> launches fine and exits 1, so the replacement hook.denies still read a module-load crash as a deny. Fixed in 1dc3784. This is the argument for the end-to-end watched-red criterion rather than a formality.

Structure

Every assertion moved into scripts/migration/consumption-checks.mjs as a pure predicate the harness calls and the tests exercise — one implementation, reachable by tests. run() now requires an explicit cwd; there is no repoRoot default left to forget. Installer --help runs in a disposable sandbox that is then asserted empty, and the source tree is fingerprinted before/after as a recorded check.

The check plan is declared up front, so unreached checks appear as not_run and CONSUMABLE requires that every planned check actually ran.

Evidence

Three real harness runs, receipts inspected:

Case Verdict Summary
valid candidate CONSUMABLE 21 passed, 0 failed, 0 not run
hooks/ removed from files[] NOT_CONSUMABLE 17 passed, 4 failed, 0 not run
aborted mid-run (fixture removed) INCOMPLETE 8 passed, 0 failed, 13 not run

The third is the case the old harness reported as 8/8 passed — CONSUMABLE.

Mutation campaign: each defect reintroduced one at a time, suite must go red. 12/12 RED. A test that stays green under its own defect is not evidence.

npm run check ALL GREEN. 222/222 tests (baseline 147, +75). Working tree clean before and after every harness run.

Changesets

No changeset. workspacejson/integrations has no .changeset/ directory, and the fixed group in §6 of the Aug 12 contract covers @workspacejson/spec and @workspacejson/rules in the Standard repo. This PR touches only scripts/migration/** and tests/migration/** — verification scripts and tests. No published @workspacejson/codex-mcp bytes, exports, schema, or metadata change. Verified against origin/main...HEAD: all five changed paths (scripts/migration/{consume-standard-candidate,consumption-checks,verify-receipt}.mjs, tests/migration/{consumption-checks,verify-receipt}.test.ts) fall outside package.json files[], which is itself unchanged (dist, hooks, scripts/install.mjs, .codex-plugin, .mcp.json, vsix, README.md, LICENSE). src/ is untouched, so dist/ is unchanged. scripts/install.mjs — the one shipped script — is deliberately not modified.

Merge order

Per §4/§7 of the Aug 12 contract, META-322 calibrates the Integrations review contract and merges first. This PR is not merge-eligible until that calibrated current-head contract exists and this head passes it. No .greptile/** or review-policy files are touched here.

Summary by Sourcery

Harden the standard-candidate consumption harness and receipt verifier so their outputs are trustworthy evidence of package consumability and CI parity.

New Features:

  • Add a dedicated consumption-checks module defining the harness check plan, reusable predicates, and receipt construction, and exercise it with comprehensive watched-red tests.
  • Expose a receipt comparator and reader from the verification script so tests and CI share a single implementation instead of duplicating comparison logic.

Bug Fixes:

  • Ensure harness checks can both pass and fail by validating process results and tool response shapes, avoiding vacuous passes when commands fail to launch or results are malformed.
  • Fix the harness to treat aborted runs and unreached checks as incomplete rather than consumable, and to close MCP clients reliably even on errors.
  • Prevent the installer --help probe from performing destructive installs into the source checkout by running it in an isolated sandbox and asserting no artifacts are written.
  • Make the receipt verifier report legible errors instead of raw ENOENT/SyntaxError stacks when receipts are missing or malformed.

Enhancements:

  • Refactor harness assertions out of the CLI script into a single shared module, enabling consistent behavior between the harness and its tests and enforcing an explicit check plan.
  • Improve argument parsing, usage output, and execution guarding of the consumption harness, including requiring explicit working directories and optional help-only runs.
  • Strengthen receipt validation by enforcing required fields, rejecting empty check sets, and distinguishing consumable, not-consumable, and incomplete verdicts based on the full plan.
  • Enhance tests for receipt verification to cover malformed inputs, exact violation text, and non-object JSON structures, increasing confidence in CI gating behavior.

Tests:

  • Add a comprehensive test suite for the consumption-checks module that demonstrates each harness check failing under controlled defects and passing under valid inputs, and verifies full coverage of the declared check plan.
  • Extend verify-receipt tests to use the exported comparator, cover additional divergence scenarios, and validate robust error handling for receipt reading and structure checking.

…ence

The harness that gates the 0.4.5 candidate train could not be trusted in
either direction: some checks could never pass, others could never fail,
and a crash mid-run still emitted a clean verdict.

Structural changes:

- Every assertion moves into scripts/migration/consumption-checks.mjs as a
  pure predicate the harness calls and the tests exercise. One
  implementation, reachable by tests. Inline assertions are how a check
  enters service unwatched.

- run() now requires an explicit cwd. The old repoRoot default is how the
  installer --help probe came to run a real install into the source
  checkout; the probe now runs in a disposable sandbox that is then
  asserted empty, and the source tree is fingerprinted before/after as a
  recorded check.

- The check plan is declared up front, so unreached checks are recorded as
  not_run rather than omitted. The verdict was previously computed over
  only the checks that happened to execute, so an abort after seven passes
  emitted "7/7 passed - CONSUMABLE". CONSUMABLE now requires that every
  planned check ran.

- The MCP client closes in a finally. A throw between connect and close
  leaked the spawned server and hung CI instead of failing it.

Vacuous-pass defects found by auditing the check set in the direction the
original review did not:

- hook.denies read `status !== 0`. spawnSync returns status null when the
  process never launched, and null !== 0 is true, so a missing hook scored
  a PASS and the receipt claimed the artifact enforced denial.

- mcp.tool-call-responds read `!result.isError`. isError is optional on
  CallToolResult, so any malformed result passed.

verify-receipt.mjs exports compareReceipts, which its test now imports
instead of reimplementing; the copy's messages had already drifted from
the source. readReceipt reports missing/malformed input with the role and
path, and exits 2 (did not run) rather than 1 (diverged). An empty check
set is refused rather than compared clean against another empty one.

Every check in CHECK_PLAN is demonstrated red against a controlled broken
input and green against a valid one, and a coverage test fails if a check
is added to the plan without both.

Refs META-285, META-140, META-165, META-238
Running the harness against a candidate built with hooks/ removed from
package.json files[] exposed a vacuous pass in the previous commit's own
replacement predicate.

`node <missing-script>` launches successfully — the node binary exists —
and exits 1 after failing to resolve the module. checkHookDenies asked
only "did it launch, and was the exit non-zero", so a candidate shipping
no hook at all scored a passing deny check. Same shape as the original
`status !== 0`, one level further in.

The deny is now proven by exit code AND the absence of a Node
module-resolution or unhandled-error signature in the output. A genuine
deny whose message happens to contain a path-like fragment still passes.

Worth recording how this was found: the unit cases did not catch it. Only
running the whole harness against a deliberately broken candidate did,
which is the argument for that acceptance criterion rather than a
formality to satisfy after the fact.

Refs META-285
Copilot AI lite review requested due to automatic review settings August 12, 2026 13:40

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@sourcery-ai

sourcery-ai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refactors the standard-candidate consumption harness so all checks are centralized in a reusable module, fully test-covered (watched red/green), and its receipts and parity verifier become trustworthy, non-destructive evidence of candidate consumability and CI parity.

Sequence diagram for the updated consumption harness run and receipt generation

sequenceDiagram
  actor Dev
  participant Harness as consume_standard_candidate_mjs
  participant Checks as consumption_checks_mjs
  participant Recorder as createRecorder
  participant Receipt as consumption_receipt_json

  Dev->>Harness: node consume-standard-candidate.mjs [--out <dir>]
  Harness->>Checks: import CHECK_PLAN, check* predicates, createRecorder
  Harness->>Recorder: createRecorder()
  Harness->>Harness: treeFingerprint() (before)

  note over Harness: Steps 1-6: pack, install, MCP, hook, installer
  Harness->>Checks: checkTarballExists(...)
  Harness->>Recorder: record("pack.tarball", outcome)
  Harness->>Checks: checkToolCallResponded(...)
  Harness->>Recorder: record("mcp.tool-call-responds", outcome)
  Harness->>Checks: checkHookDenies(...)
  Harness->>Recorder: record("hook.denies", outcome)
  Harness->>Checks: checkInstallerHelp(...)
  Harness->>Recorder: record("installer.help-usage", outcome)

  Harness->>Harness: treeFingerprint() (after)
  Harness->>Checks: checkTreeUnchanged(before, after)
  Harness->>Recorder: record("repo.tree-unchanged", outcome)

  Harness->>Recorder: buildReceipt({ aborted? })
  Recorder-->>Harness: receipt {verdict, summary, checks[]}
  Harness->>Receipt: write consumption-receipt.json
  Harness->>Dev: log verdict (CONSUMABLE / NOT_CONSUMABLE / INCOMPLETE)
  Harness->>Dev: exitCode (0 if CONSUMABLE, else 1/2)
Loading

File-Level Changes

Change Details Files
Centralize harness assertions into reusable, testable predicates and a receipt builder, then wire the harness to use that module.
  • Introduce CHECK_PLAN and EXPECTED_TOOLS as the single source of truth for all harness checks and expected MCP tools.
  • Implement pure check* predicates that validate input shape before value, avoiding vacuous passes from missing fields or failed processes.
  • Add a createRecorder abstraction that records outcomes against the declared plan and produces receipts that distinguish pass, fail, and not_run with a verdict of CONSUMABLE, NOT_CONSUMABLE, or INCOMPLETE.
  • Expose receiptIsClean to formalize the notion of a clean consumption receipt.
scripts/migration/consumption-checks.mjs
scripts/migration/consume-standard-candidate.mjs
Make the consumption harness non-destructive, robust to partial runs, and structurally safer to invoke.
  • Require an explicit cwd in run(), removing the repoRoot default and preventing accidental operations on the source checkout.
  • Add a git-status-based treeFingerprint and a repo.tree-unchanged check to prove the harness does not modify the repository, even on abort.
  • Run installer --help inside a dedicated sandbox and assert it remains empty, turning destructive help behavior into a visible failure instead of a silent source-tree rewrite.
  • Handle process launch failures and crashes explicitly via runOrDie, launched(), and crashedInsteadOfRunning(), avoiding misclassification of non-launches or module-load errors as successful checks.
  • Ensure the MCP client is closed in a finally block to avoid leaking the server and hanging CI on exceptions.
  • Distinguish aborted runs from failing runs by tracking an aborted reason and emitting INCOMPLETE verdicts with not_run checks instead of treating partial runs as clean.
  • Add a CLI help mode and argument parser that supports --out and --help, with clear usage text and error reporting for unknown or malformed args.
scripts/migration/consume-standard-candidate.mjs
scripts/migration/consumption-checks.mjs
Replace inline, duplicated receipt comparison logic with a single exported implementation and strengthen receipt shape validation and IO behavior.
  • Export compareReceipts from verify-receipt.mjs and remove the inline copy from verify-receipt.test.ts, so tests exercise the exact implementation shipped in CI.
  • Introduce readReceipt that wraps file IO and JSON parsing, turning raw ENOENT/SyntaxError stacks into legible, role-aware error messages for CI readers.
  • Add assertReceiptShape to enforce the presence and basic validity of verdict, summary, and non-empty checks with string ids, rejecting structurally unusable receipts rather than comparing them vacuously.
  • Improve compareReceipts violation messages to include actual reference/candidate values, enabling precise assertions in tests.
  • Guard verify-receipt.mjs main() so it only runs when invoked as a script, allowing clean import in tests without side effects.
  • Treat IO/shape failures as exit 2 (gate could not run) distinct from exit 1 (receipts diverged), with clear console messaging.
scripts/migration/verify-receipt.mjs
tests/migration/verify-receipt.test.ts
Add comprehensive watched-red/watched-green tests for harness predicates, receipt behavior, and receipt verification IO and shape handling.
  • Create tests/migration/consumption-checks.test.ts that exercises every CHECK_PLAN entry both in its passing and failing directions, and asserts coverage matches the plan (no unproven or orphaned checks).
  • Add targeted regression tests for previously identified META-285 defects: hooks/ directory presence via prefix matching, tools/list using real ListToolsResult shape, hook.denies avoiding vacuous passes on status null or module-not-found exits, and installer help reading both stdout and stderr.
  • Test createRecorder’s behavior for complete, failing, and aborted runs, including summary counts, verdict selection precedence, not_run semantics, and guardrails against unknown/duplicate ids or malformed outcomes.
  • Extend verify-receipt tests to cover shape validation, empty check sets, malformed receipts, and the exact violation and error messages produced by compareReceipts and readReceipt.
tests/migration/consumption-checks.test.ts
tests/migration/verify-receipt.test.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot 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.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown

Greptile Summary

The PR refactors the candidate-consumption harness into reusable predicates and strengthens its receipts so aborted or unexecuted checks cannot produce a clean verdict.

  • Runs installer help checks in a disposable sandbox and records source-tree integrity.
  • Declares the complete check plan up front and distinguishes failed, unexecuted, and successful checks.
  • Exports receipt reading and comparison logic for direct test coverage while improving malformed-input diagnostics.

Confidence Score: 4/5

The PR is not yet safe to merge because duplicate receipt IDs can still hide divergent check results and produce a successful parity comparison.

Receipt validation accepts repeated string IDs, and the comparator subsequently collapses those entries into Maps before checking statuses and violations, leaving the previously reported false-clean comparison path outstanding.

Files Needing Attention: scripts/migration/verify-receipt.mjs; tests/migration/verify-receipt.test.ts

Important Files Changed

Filename Overview
scripts/migration/consume-standard-candidate.mjs Refactors the packed-candidate workflow around explicit working directories, reusable predicates, reliable client cleanup, and complete receipt generation.
scripts/migration/consumption-checks.mjs Introduces the declared check plan, defensive predicates, and receipt recorder used by the consumption harness.
scripts/migration/verify-receipt.mjs Adds reusable receipt parsing and shape checks, but duplicate check IDs still collapse during Map-based comparison.
tests/migration/consumption-checks.test.ts Adds bidirectional predicate and check-plan coverage for the consumption harness.
tests/migration/verify-receipt.test.ts Expands direct comparator and malformed-input coverage, while leaving duplicate-ID behavior uncovered.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Build and pack candidate] --> B[Install packed artifact in temporary directory]
  B --> C[Exercise installed MCP server]
  C --> D[Exercise packed hook]
  D --> E[Run installer help in empty sandbox]
  E --> F[Check source tree unchanged]
  F --> G[Build receipt from declared check plan]
  G -->|Failures present| H[NOT_CONSUMABLE]
  G -->|Checks not run| I[INCOMPLETE]
  G -->|All checks pass| J[CONSUMABLE]
Loading

Reviews (2): Last reviewed commit: "Merge branch 'main' into feature/meta-28..." | Re-trigger Greptile

Comment thread scripts/migration/verify-receipt.mjs
qmarcelle added a commit that referenced this pull request Aug 12, 2026
Records the 2026-08-12 branch-protection change: Greptile Review added to required contexts, pinned to app id 867647, with strict, both build-and-smoke contexts, and required_conversation_resolution preserved. Sourcery deliberately not promoted.

Also records the first post-policy proof on PR #12 at 76d495d and the accepted cost to stale PR #6.

Refs META-322
…n PATH trust

Three findings from review, all in the same class the PR already treats as
its subject: a check that cannot fail, or that trusts something it should
have proven.

Duplicate check ids (Greptile P1). `compareReceipts` projects `checks` into
a Map keyed by id, which keeps only the last entry for a repeated id. A
receipt carrying the same check twice with divergent statuses lost the
earlier one and compared clean against the survivor — an internally
inconsistent receipt reported as PARITY. `assertReceiptShape` now rejects
duplicates on both sides. `createRecorder` already refused to emit such a
receipt; this refuses to read one, so neither end of the gate trusts it.
Watched red: three cases fail against the previous comparator and only
those three.

Bare `git` on PATH (Sonar S4036, the Security rating). `treeFingerprint` is
the evidence that the harness did not mutate the source checkout, and it
resolved `git` through PATH — letting a writable PATH entry decide what
"git status" reports. The one tool that must not be forgeable is the one
asked to prove nothing changed. Resolved from fixed locations; absence
yields a non-string fingerprint, which `checkTreeUnchanged` already fails
on rather than skipping.

Bare `node` for the spawned server, hook, and installer. `process.execPath`
instead, so the packed artifact runs under the same Node the harness runs
under. As written, the Node 20/22 CI matrix was only ever applied to the
harness process; whatever `node` resolved to on PATH ran the server under
test, so one matrix dimension was not testing what it claimed to.

Also: comparator sorts with an explicit comparator (S2871), and the crash
detector's `^\s*` no longer spans newlines under /m — `\s` matches `\n`, so
the indent could be consumed across line boundaries, which both backtracks
super-linearly and let a non-indented "at" line match (S8786).

Harness re-run end to end: 21/21 CONSUMABLE, tree unmodified.

@greptile-apps greptile-apps Bot 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.

qmarcelle has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@greptile-apps greptile-apps Bot 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.

qmarcelle has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

qmarcelle added a commit that referenced this pull request Aug 13, 2026
The §2 table claimed three required contexts. The API returns five —
`standard-candidate-consumption` and `SonarCloud Code Analysis` were promoted
without the change reaching this file.

This is the case §2 already legislates for ("the API is right and this file is
stale — treat that as a documentation defect, and correct it in the PR that
noticed"), exercised for the first time. The drift is its own argument: the
stale table said `SonarCloud Code Analysis` was non-blocking, so a reader
trusting it would have concluded PR #12 was mergeable while it was blocked on
exactly that context.

Corrected:
- §2 required-context row and the sentence restating it.
- §2 "Observed gap" — `standard-candidate-consumption` is now required, leaving
  `parity-receipt-reproduction` as the only CI job whose failure does not block.
  That is this PR's subject, so the section now names the dependency rather than
  describing a gap that has since narrowed.
- §2 and §4 PR #12 observations re-dated to the protection in force when they
  were taken. They are kept, not rewritten — the mechanism they demonstrate is
  unaffected, only the non-blocking claim expired.
- Provenance line re-measured: 2026-08-13 against `main` at `70cfd57`.

No protection setting was changed by this commit. It only makes the file agree
with what the API already returns.
qmarcelle added a commit that referenced this pull request Aug 13, 2026
)

Prerequisite for promoting `parity-receipt-reproduction` to a required check
under META-337.

Both substantive steps of the job were gated on `has_receipt == 'true'`, so a
branch with no committed receipt skipped them, concluded `success`, and logged
"No committed parity receipt found — skipping reproduction check." Harmless
while the job is advisory; merge-authorizing the moment it is required, because
the cheapest way to satisfy a failing parity gate would be to delete the
receipt. Absence is now a hard failure, and the failure output names the
invariant, the authority, the evidence that discharges it, and what must not be
weakened.

Also in this PR:

- The step is pinned to `shell: bash`, since its semantics depend on
  `set -euo pipefail` and runner defaults are an implementation detail
  (Sourcery review).
- `docs/review/merge-policy.md` §2 corrected against the live API. It claimed
  three required contexts; the API returns five —`standard-candidate-consumption`
  and `SonarCloud Code Analysis` had been promoted without the change reaching
  the file. §2 already legislates for exactly this ("the API is right and this
  file is stale — correct it in the PR that noticed"); this is its first
  exercise. The drift mattered: the stale table called SonarCloud non-blocking,
  so a reader trusting it would have judged PR #12 mergeable while it was
  blocked on that context.

@greptile-apps greptile-apps Bot 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.

qmarcelle has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@sonarqubecloud

Copy link
Copy Markdown

@qmarcelle
qmarcelle merged commit be1de9f into main Aug 13, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants