{
 "schema": "uta-runner-mutants/1.0",
 "target": "score-runner.mjs (the reference scorer \u2014 the tested thing)",
 "note": "Deterministic byte patches applied to the pristine score-runner.mjs source. Each mutant is a known-bad runner variant; the answer key must CATCH every one (observable divergence in the separation matrix and/or the reference-mode verdict). Patch semantics: mode 'first' replaces only the first occurrence, mode 'all' replaces every occurrence; the expected occurrence count is fail-closed-checked before patching, so a mutated target source cannot slip through silently. Each mutant file's sha256 is pinned in answer-key.json, making the mutant bytes themselves reproducible by strangers.",
 "mutants": [
  {
   "id": "stage-blind",
   "bug": "scoring ignores stage mismatches \u2014 a stage-liar scores perfect",
   "property": "stage_scoring_rule: the stage vector is compared, not just the boolean",
   "find": "if (correct && expected_stages && r.stages) {",
   "replace": "if (false && correct && expected_stages && r.stages) {",
   "mode": "first",
   "occurrences": 1
  },
  {
   "id": "memorizer-promote",
   "bug": "the memorizer cheat is graded as honest: recognition now passes everything",
   "property": "the separation matrix must keep the memorizer separated",
   "find": "return { verify: digest === validAtcSha, stages: null };",
   "replace": "return { verify: true, stages: null };",
   "mode": "first",
   "occurrences": 1
  },
  {
   "id": "score-inflate",
   "bug": "every runner's score is inflated by one vector",
   "property": "honest counting \u2014 the reference must show 13/13, not 14/13",
   "find": "return { ok, total: cards.length, failed };",
   "replace": "return { ok: ok + 1, total: cards.length, failed };",
   "mode": "first",
   "occurrences": 1
  },
  {
   "id": "anchor-narrow",
   "bug": "the pinned anchor set silently shrinks to ca-test-1 \u2014 ca-test-2 cards stop verifying",
   "property": "pinned_trust_anchors: the anchor set is {ca-test-1, ca-test-2}, not a memory of the first card's CA",
   "find": "anchors.includes(card.payload.identity.public_key)",
   "replace": "anchors[0] === card.payload.identity.public_key",
   "mode": "all",
   "occurrences": 4
  },
  {
   "id": "expiry-blind",
   "bug": "the reference runner stops checking expiry (only the reference path)",
   "property": "expiry policy \u2014 expired-atc must fail",
   "find": "card.payload.metadata.expires_at > NOW",
   "replace": "true || card.payload.metadata.expires_at > NOW",
   "mode": "first",
   "occurrences": 5
  },
  {
   "id": "status-blind",
   "bug": "the reference runner stops checking card status (only the reference path)",
   "property": "status policy \u2014 revoked-atc must fail",
   "find": "const statusOk = card.status === 'active';",
   "replace": "const statusOk = true;",
   "mode": "first",
   "occurrences": 5
  },
  {
   "id": "sig-accept-all",
   "bug": "the reference runner accepts every signature without verifying (only the reference path)",
   "property": "signature verification \u2014 invalid-signature must fail",
   "find": "const sigOk = cryptoVerify(null, buf,",
   "replace": "const sigOk = true || cryptoVerify(null, buf,",
   "mode": "first",
   "occurrences": 5
  },
  {
   "id": "translation-flip",
   "bug": "unsigned translation cards are rejected by the reference runner",
   "property": "the translation family is accepted by policy",
   "find": "if (!card.signature) return { verify: true, stages: null }; // translation family",
   "replace": "if (!card.signature) return { verify: false, stages: null }; // translation family",
   "mode": "first",
   "occurrences": 1
  },
  {
   "id": "stage-liar-cured",
   "bug": "the built-in stage-liar demonstrator silently starts telling the truth",
   "property": "the published separation matrix is the runner's observable contract",
   "find": "return { verify: truth.verify, stages: { ...truth.stages, signature_verification: 'fail' } }; // \u2190 the lie",
   "replace": "return { verify: truth.verify, stages: truth.stages }; // the lie removed",
   "mode": "first",
   "occurrences": 1
  },
  {
   "id": "over-rejector-cured",
   "bug": "the built-in over-rejector demonstrator silently stops choking on x_* fields",
   "property": "the published separation matrix is the runner's observable contract (over-rejection stays visible)",
   "find": "if (hasUnknown) return { verify: false, stages: null };",
   "replace": "if (hasUnknown) return reference(card, digest);",
   "mode": "first",
   "occurrences": 1
  }
 ]
}
