diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19a259a..0bd448c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,7 +104,17 @@ jobs: name: test runs-on: ubuntu-24.04 steps: + # Full history, not the default depth-1 clone. HAC-343 froze three + # contracts before any arm produced a result, and + # experiments/hac-343/bin/verify-packet.mjs proves that by resolving each + # freeze commit and comparing the bytes on disk to the blob that commit + # recorded. A shallow checkout has neither the commits nor the blobs, so + # the verifier fails with `path exists on disk, but not in ` and + # test/hac-343-check-wiring.test.mjs fails with it. Measured: exit 1 at + # depth 1, exit 0 with full history. - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Pinned for the same reason as codecov-action below: a third-party action # on a mutable tag. Sonar did not flag this one only because it is not new @@ -515,6 +525,87 @@ jobs: echo "genuinely changed, that is a claim change and belongs on HAC-333." } >> "$GITHUB_STEP_SUMMARY" + evaluation-gate: + name: Evaluation gate + runs-on: ubuntu-24.04 + steps: + # Full history, not the default depth-1 clone. HAC-343 froze three + # contracts before any arm produced a result, and the verifier proves + # that by resolving each freeze commit and comparing the bytes on disk + # to the blob that commit recorded. A shallow checkout has neither the + # commits nor the blobs: the verifier fails with `path exists on disk, + # but not in `. Measured: exit 1 at depth 1, exit 0 with history. + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 + - uses: actions/setup-node@v4 + with: + node-version: '22.19.0' + cache: pnpm + - name: Install dependencies + run: pnpm install --frozen-lockfile --ignore-scripts + + # `check:packet:eval` builds first: alone among the packet verifiers, + # experiments/hac-343/lib/arms.mjs loads the compiled decision core from + # dist/, because the experiment measures the real arbitrate() rather than + # a reimplementation of it. + - name: Verify the HAC-343 evaluation packet + run: pnpm run check:packet:eval + + # The seam, in both directions: an invalid packet must fail this gate, + # and a HAC-343 field moving underneath the committed view model must + # fail the cockpit gate. + - name: The cockpit's HAC-343 bindings fail when the packet moves + run: pnpm vitest run test/hac-343-check-wiring.test.mjs + + # `judge-export.json` is a *derived* presentation artifact: the verifier + # above recomputes the report from the raw records, but it never rebuilt + # the export. Ten judge-facing values reach the comparison panel through + # that file alone — all four strategy labels, the per-target-lock + # credibility figure, and the canonical result commit — so a hand edit + # there reached a judge with every gate green. + # + # Rebuild and assertion are two steps on purpose. An enforcement step's + # `run` must be exactly one expected command, because a multi-line body is + # a shell script and no amount of reading it tells you which lines + # actually execute: `if false; then`, a heredoc and an open quote all put + # a command at the start of a line without running it. One command per + # step is a shape that can be checked instead of inferred. + - name: Rebuild the derived judge export + run: node experiments/hac-343/bin/build-judge-export.mjs + + - name: The judge export is byte-identical to its rebuild + run: git diff --exit-code -- experiments/hac-343/evidence/judge-export.json + + - name: Explain the failure + if: failure() + run: | + { + echo "## Evaluation gate failed" + echo + echo "**Invariant.** Every metric in \`experiments/hac-343/evidence/results.json\`" + echo "is recomputed from the raw records rather than read back from the summary," + echo "and the three contracts frozen before the run — metric definitions, corpus" + echo "and execution semantics — are still byte-identical to the blobs their freeze" + echo "commits recorded." + echo + echo "**Why it matters.** The judge cockpit binds twenty-four comparison cells" + echo "into this packet. A packet that no longer verifies is a packet whose numbers" + echo "a judge is nonetheless reading, and \"frozen before results\" stops being" + echo "checkable by anyone who was not there." + echo + echo "**Authority.** Repository CI, reported as \`Evaluation gate\`." + echo + echo "**Evidence required.** \`pnpm run check:packet:eval\` passing on this commit," + echo "and \`test/hac-343-check-wiring.test.mjs\` green." + echo + echo "**Do not weaken.** Do not re-freeze a contract to match an edit, do not read" + echo "a metric out of the summary to make the recomputation agree, and do not drop" + echo "\`fetch-depth: 0\` to make the freeze-commit checks stop resolving. If the" + echo "experiment genuinely changed, it is a new run, not an edited one." + } >> "$GITHUB_STEP_SUMMARY" + cockpit-contract-gate: name: Cockpit contract gate runs-on: ubuntu-24.04 diff --git a/experiments/hac-343/bin/build-corpus.mjs b/experiments/hac-343/bin/build-corpus.mjs new file mode 100644 index 0000000..3bcf6fc --- /dev/null +++ b/experiments/hac-343/bin/build-corpus.mjs @@ -0,0 +1,230 @@ +#!/usr/bin/env node +/** + * HAC-343 — build and freeze the corpus manifest. + * + * WORKSPACEJSON_CLI= node experiments/hac-343/bin/build-corpus.mjs + * + * Materializes the family-2 fixture histories, mines both, records what the + * miner actually observed, validates every corpus requirement the frozen metric + * manifest imposes, and writes `evidence/corpus.json`. + * + * This runs *before* any arm exists. That ordering is the point: the corpus and + * its labels are fixed, and demonstrated to be fixed, before anything can be + * measured against them. A corpus adjusted after seeing an arm's output is not a + * corpus, and `metric-definitions.json` forbids it in writing. + * + * Family 1's fixtures are HAC-330's, reused rather than rebuilt: this script + * reads their recorded revisions from that packet and does not regenerate them, + * so the budget family in this corpus is the same history the S-1 gate proved. + */ +import { createHash } from 'node:crypto'; +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { loadMiner, mineEvidence, resolveCliCheckout, verifyPin } from '../../hac-330/lib/evidence.mjs'; +import { buildFixture, FIXTURES, SUBJECT_PATHS } from '../lib/families/registry.mjs'; +import { SCENARIOS, FAMILIES, corpusCounts, validateCorpus } from '../lib/corpus.mjs'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const EXPERIMENT_DIR = resolve(HERE, '..'); +const REPO_ROOT = resolve(EXPERIMENT_DIR, '..', '..'); +const EVIDENCE_DIR = join(EXPERIMENT_DIR, 'evidence'); +const WORK_DIR = join(EXPERIMENT_DIR, '.work', 'fixtures'); + +process.chdir(REPO_ROOT); + +const writeJson = (path, value) => writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`); +const sha256 = (buffer) => createHash('sha256').update(buffer).digest('hex'); + +const failures = []; +function check(id, passed, detail) { + if (!passed) failures.push(`${id}: ${detail}`); + console.log(` ${passed ? 'PASS' : 'FAIL'} ${id.padEnd(14)} ${detail}`); + return passed; +} + +const section = (title) => console.log(`\n── ${title} ${'─'.repeat(Math.max(0, 60 - title.length))}`); + +// --------------------------------------------------------------------------- + +section('Pinned checkouts'); + +const cliCheckout = resolveCliCheckout(); +const pins = { + 'workspacejson-cli': verifyPin('workspacejson-cli', cliCheckout), + 'workspacejson-standard': verifyPin('workspacejson-standard', join(cliCheckout, '..', 'standard')), +}; +for (const [id, pin] of Object.entries(pins)) { + check(id === 'workspacejson-cli' ? 'PIN-CLI' : 'PIN-STD', pin.matches && pin.clean, `${pin.observedSha} ${pin.clean ? 'clean' : 'DIRTY'}`); +} + +// --------------------------------------------------------------------------- + +section('Family 1 — budget (reused from HAC-330)'); + +const budgetFixtures = JSON.parse( + readFileSync(join(REPO_ROOT, 'experiments', 'hac-330', 'evidence', 'fixtures.json'), 'utf8'), +); +check( + 'F1-REUSED', + typeof budgetFixtures.baseline?.head === 'string' && typeof budgetFixtures.perturbed?.head === 'string', + `baseline ${budgetFixtures.baseline?.head?.slice(0, 12)} perturbed ${budgetFixtures.perturbed?.head?.slice(0, 12)}`, +); +check( + 'F1-TREE', + budgetFixtures.baseline.tree === budgetFixtures.perturbed.tree, + `shared final tree ${budgetFixtures.baseline.tree.slice(0, 12)}`, +); + +// --------------------------------------------------------------------------- + +section('Family 2 — registry (built here)'); + +mkdirSync(WORK_DIR, { recursive: true }); +const registryFixtures = {}; +for (const [name, steps] of Object.entries(FIXTURES)) { + registryFixtures[name] = buildFixture(join(WORK_DIR, name), steps); + const f = registryFixtures[name]; + console.log(` built ${name.padEnd(10)} head=${f.head.slice(0, 12)} tree=${f.tree.slice(0, 12)} commits=${f.commitCount}`); +} + +check( + 'F2-TREE', + registryFixtures.baseline.tree === registryFixtures.perturbed.tree, + `shared final tree ${registryFixtures.baseline.tree.slice(0, 12)} — the perturbation is history-only`, +); +check( + 'F2-SHAPE', + registryFixtures.baseline.commitCount === registryFixtures.perturbed.commitCount, + `${registryFixtures.baseline.commitCount} commits in each`, +); + +// --------------------------------------------------------------------------- + +section('Family 2 — mined evidence'); + +const miner = await loadMiner(); +const registryEvidence = {}; +const qualifying = {}; + +for (const name of Object.keys(FIXTURES)) { + const evidence = await mineEvidence({ + fixture: name, + repo: join(WORK_DIR, name), + miner, + }); + registryEvidence[name] = evidence; + + const selection = evidence.envelope?.selection ?? evidence.selection; + const pairs = (selection?.pairs ?? []).filter((p) => p.support >= 3); + qualifying[name] = pairs.map((p) => ({ files: p.files, support: p.support, occurrences: p.occurrences ?? 0 })); + + writeJson(join(EVIDENCE_DIR, `registry.${name}.evidence.json`), evidence.envelope ?? evidence); +} + +const spansSubjects = (pairs) => + pairs.some( + (p) => + (p.files[0] === SUBJECT_PATHS.left && p.files[1] === SUBJECT_PATHS.right) || + (p.files[0] === SUBJECT_PATHS.right && p.files[1] === SUBJECT_PATHS.left), + ); + +check( + 'F2-COUPLED', + spansSubjects(qualifying.baseline), + `baseline carries ${SUBJECT_PATHS.left} <-> ${SUBJECT_PATHS.right}`, +); +check( + 'F2-PERTURBED', + !spansSubjects(qualifying.perturbed), + `perturbed does NOT carry that pair — only the subject coupling was removed`, +); + +const touchesIndependent = (pairs) => + pairs.some((p) => p.files.includes(SUBJECT_PATHS.independent) && + (p.files.includes(SUBJECT_PATHS.left) || p.files.includes(SUBJECT_PATHS.right))); +check( + 'F2-INDEP', + !touchesIndependent(qualifying.baseline), + `${SUBJECT_PATHS.independent} couples to neither subject path, so it is usable as the INDEPENDENT counterpart`, +); + +// --------------------------------------------------------------------------- + +section('Corpus requirements'); + +const corpusFailures = validateCorpus(); +check('CORPUS-VALID', corpusFailures.length === 0, corpusFailures.length ? corpusFailures.join('; ') : 'every requirement in metric-definitions.json holds'); + +const counts = corpusCounts(); +check('CORPUS-BREADTH', FAMILIES.length >= 2, `${FAMILIES.length} families, ${counts.total} scenarios`); + +// --------------------------------------------------------------------------- + +section('Write manifest'); + +const manifest = { + experiment: 'HAC-343', + kind: 'corpus manifest', + status: 'FROZEN_BEFORE_RESULTS', + revision: 'r01', + supersedes: [], + frozenRule: + 'Committed in its own commit, after metric-definitions.json and before any arm implementation or results.json. Scenario counts, labels and intents are fixed here. A corpus change after any result exists invalidates that result and requires a rerun, per metric-definitions.json corpusRequirements.noOptimisation.', + metricDefinitions: { + file: 'experiments/hac-343/evidence/metric-definitions.json', + revision: 'r01', + sha256: sha256(readFileSync(join(EVIDENCE_DIR, 'metric-definitions.json'))), + }, + breadthRationale: + 'Two structurally different hazard classes, so a result is not one topology repeated. budget is arithmetic (composed increases overshoot a ceiling); registry is referential (one intent removes a referent the other points at). Both carry all five ground-truth classes, so a per-family divergence is about hazard shape rather than uneven class coverage.', + families: { + budget: { + hazardClass: 'arithmetic', + invariant: 'sum(services[].reserved) <= budget.totalReservable', + source: 'experiments/hac-330 — reused verbatim, not rebuilt', + subjectPaths: { + left: 'services/alpha/reservation.json', + right: 'services/beta/reservation.json', + independent: 'services/gamma/reservation.json', + }, + fixtures: budgetFixtures, + }, + registry: { + hazardClass: 'referential', + invariant: 'every route.service and alias target resolves in registry/services.json', + source: 'experiments/hac-343/lib/families/registry.mjs — built by this script', + subjectPaths: SUBJECT_PATHS, + fixtures: registryFixtures, + qualifyingPairs: qualifying, + controls: { + sharedFinalTree: registryFixtures.baseline.tree, + commitCount: registryFixtures.baseline.commitCount, + note: 'Same four controls as HAC-330: identical final tree, identical commit count, commit i touching the same number of files in both, and the invariant holding at every commit (asserted in planCommits).', + }, + }, + }, + counts, + scenarios: SCENARIOS, + validation: { + corpusRequirements: corpusFailures.length === 0 ? 'PASS' : corpusFailures, + checksRun: ['PIN-CLI', 'PIN-STD', 'F1-REUSED', 'F1-TREE', 'F2-TREE', 'F2-SHAPE', 'F2-COUPLED', 'F2-PERTURBED', 'F2-INDEP', 'CORPUS-VALID', 'CORPUS-BREADTH'], + }, + reproduction: { + buildCommand: 'WORKSPACEJSON_CLI= node experiments/hac-343/bin/build-corpus.mjs', + pins, + }, +}; + +writeJson(join(EVIDENCE_DIR, 'corpus.json'), manifest); +console.log(` wrote experiments/hac-343/evidence/corpus.json (${counts.total} scenarios, ${FAMILIES.length} families)`); + +// --------------------------------------------------------------------------- + +if (failures.length > 0) { + console.error(`\nFAILED — ${failures.length} check(s):`); + for (const failure of failures) console.error(` ${failure}`); + process.exit(1); +} +console.log('\nCorpus frozen. All checks passed.'); diff --git a/experiments/hac-343/bin/build-judge-export.mjs b/experiments/hac-343/bin/build-judge-export.mjs new file mode 100644 index 0000000..d0c1ab7 --- /dev/null +++ b/experiments/hac-343/bin/build-judge-export.mjs @@ -0,0 +1,364 @@ +#!/usr/bin/env node +/** + * HAC-343 — derive the judge-facing export from the frozen result. + * + * node experiments/hac-343/bin/build-judge-export.mjs + * + * Presentation only. It reads the frozen packet and writes `judge-export.json`; + * it modifies nothing that was frozen — not the metric definitions, the corpus, + * the arm semantics, the raw records, the aggregator, or the canonical result. + * + * ## Why this exists rather than a hand-written summary + * + * The canonical result is correct and unpromotable as-is. Its unsafe-joint-state + * denominator is COUPLED scenarios only, so A4 reads 0/2 (0.0%) while having + * produced an invalid joint state in two of sixteen scenarios — the two where the + * co-change evidence was deliberately removed. Rendering that bare number to a + * judge would be the exact class of claim this packet exists to prevent. + * + * The repair is not a re-measurement. It is to stop collapsing a heterogeneous + * corpus into one denominator, and to present two questions separately: + * + * Panel 1 — under the evidence that was available, how do the four + * coordination strategies compare? + * Panel 2 — does removing that evidence reverse Interlock's decision? + * + * Both panels are the same frozen records read two ways. Neither is a new run. + * + * ## No hand-entered outcome values + * + * Every figure is built by `figure()`, which requires a `derivedFrom` pointer + * naming where in the frozen packet it came from, and the script refuses to emit + * if any figure lacks one. A number typed in by hand cannot acquire a pointer, + * so it cannot reach the export. + */ +import { execFileSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { SCENARIOS, FAMILIES } from '../lib/corpus.mjs'; +import { ARMS } from '../lib/arms.mjs'; +import { FROZEN_COMMITS, ORDERS } from '../lib/aggregate.mjs'; +import { GIT } from '../../hac-330/lib/exec.mjs'; + +/** + * Default `.sort()` order, stated explicitly. + * + * `Array#sort` with no comparator stringifies and compares UTF-16 code units. + * These arrays are already strings, so `<` and `>` reproduce that order exactly + * — which is the point: this is an evidence-chain artifact, and `localeCompare` + * would make the committed result depend on the runner's locale. + */ +const byCodeUnit = (a, b) => { + if (a < b) return -1; + return a > b ? 1 : 0; +}; + +const EXPERIMENT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const REPO_ROOT = resolve(EXPERIMENT_DIR, '..', '..'); +const EVIDENCE_DIR = join(EXPERIMENT_DIR, 'evidence'); + +const read = (name) => readFileSync(join(EVIDENCE_DIR, name)); +const json = (name) => JSON.parse(read(name).toString('utf8')); +const sha256 = (buffer) => createHash('sha256').update(buffer).digest('hex'); + +/** The canonical result commit. Recorded so the export names its own source. */ +const CANONICAL_RESULT_COMMIT = execFileSync( + GIT, + ['-C', REPO_ROOT, 'log', '-1', '--format=%H', '--', 'experiments/hac-343/evidence/results.json'], + { encoding: 'utf8' }, +).trim(); + +const raw = json('raw-results.json'); +const results = json('results.json'); +const report = results.report; + +// --------------------------------------------------------------------------- + +const problems = []; + +/** + * Every number in the export passes through here. + * + * `derivedFrom` is mandatory and names the location in the frozen packet the + * figure came from. A hand-typed value has no such location, which is what makes + * "no hand-entered outcome values" mechanically enforced rather than promised. + */ +function figure(numerator, denominator, derivedFrom) { + if (typeof derivedFrom !== 'string' || derivedFrom.length === 0) { + problems.push(`figure ${numerator}/${denominator} has no derivedFrom pointer`); + } + if (denominator === 0) { + return { numerator, denominator, display: 'n/a (0 cases)', derivedFrom }; + } + return { + numerator, + denominator, + display: `${numerator}/${denominator}`, + percent: Number(((numerator / denominator) * 100).toFixed(1)), + derivedFrom, + }; +} + +/** Records for one scenario x arm, both orders. */ +const recordsFor = (scenarioId, arm) => + ORDERS.map((order) => raw.records.find((r) => r.scenarioId === scenarioId && r.arm === arm && r.order === order)); + +const scenariosLabelled = (label) => SCENARIOS.filter((s) => s.label === label); + +/** A scenario counts invalid when the fixture verifier rejected either order. */ +const invalidInEitherOrder = (scenarioId, arm) => + recordsFor(scenarioId, arm).some((r) => r.error != null || r.oracle?.holds === false); + +// --------------------------------------------------------------------------- +// Panel 1 — operational utility under available evidence +// --------------------------------------------------------------------------- + +const LABELS = { + A1_uncoordinated: 'Uncoordinated', + A2_global_lock: 'Global lock', + A3_per_target_lock: 'Per-target lock', + A4_interlock: 'Interlock', +}; + +const panel1 = { + question: 'Under the co-change evidence that was available, how do the four coordination strategies compare?', + scope: 'COUPLED and INDEPENDENT scenarios only. Evidence-ablation scenarios are Panel 2; inadmissible-evidence scenarios are reported under limitations.', + rows: ARMS.map((arm) => ({ + arm, + label: LABELS[arm], + coupledUnsafe: figure( + report.aggregate[arm].unsafeJointState.numerator, + report.aggregate[arm].unsafeJointState.denominator, + `results.json report.aggregate.${arm}.unsafeJointState`, + ), + safeParallelism: figure( + report.aggregate[arm].spr.safeParallelismRetained.numerator, + report.aggregate[arm].spr.safeParallelismRetained.denominator, + `results.json report.aggregate.${arm}.spr.safeParallelismRetained`, + ), + })), + reading: + 'Global locking preserved safety by eliminating concurrency. Per-target locking preserved concurrency but missed every cross-target composition hazard. Interlock is the only arm in both left-hand columns at once on this corpus.', +}; + +// A3's credibility gate, without which its unsafe column is dismissible. +const crossTarget = SCENARIOS.filter((s) => s.label === 'COUPLED' || s.label === 'INDEPENDENT'); +const a3Parallelised = crossTarget.filter((s) => + recordsFor(s.id, 'A3_per_target_lock').every((r) => r.concurrent === true), +).length; + +panel1.perTargetLockCredibility = { + claim: 'A3 is a real lock, so its misses are blindness rather than absence of a lock.', + serializedSameTargetContention: figure( + report.lockValidity.A3_per_target_lock.numerator, + report.lockValidity.A3_per_target_lock.denominator, + 'results.json report.lockValidity.A3_per_target_lock', + ), + parallelisedCrossTarget: figure(a3Parallelised, crossTarget.length, 'raw-results.json records[A3, cross-target].concurrent'), + missedCrossTargetHazards: figure( + report.aggregate.A3_per_target_lock.unsafeJointState.numerator, + report.aggregate.A3_per_target_lock.unsafeJointState.denominator, + 'results.json report.aggregate.A3_per_target_lock.unsafeJointState', + ), + note: 'It locked exactly what a lock can see. A composition hazard spanning two lock keys is not visible to any per-key discipline.', +}; + +// --------------------------------------------------------------------------- +// Panel 2 — evidence ablation / causal control +// --------------------------------------------------------------------------- + +const coupledScenarios = scenariosLabelled('COUPLED'); +const perturbedScenarios = scenariosLabelled('EVIDENCE_PERTURBED'); + +const panel2 = { + question: 'Is Interlock’s safety derived from the evidence, or from something else?', + design: + 'The perturbed fixtures hold the intents and the final tree identical to their coupled counterparts and change only the commit history, so the coupling is absent from the mined evidence while the composition remains genuinely hazardous.', + rows: [ + { + condition: 'Interlock + coupling evidence present', + invalidOutcomes: figure( + coupledScenarios.filter((s) => invalidInEitherOrder(s.id, 'A4_interlock')).length, + coupledScenarios.length, + 'raw-results.json records[A4, COUPLED].oracle.holds', + ), + decision: [...new Set(coupledScenarios.flatMap((s) => recordsFor(s.id, 'A4_interlock').flatMap((r) => (r.verdicts ?? []).map((v) => v.decision))))].sort(byCodeUnit), + }, + { + condition: 'Interlock + coupling evidence removed', + invalidOutcomes: figure( + perturbedScenarios.filter((s) => invalidInEitherOrder(s.id, 'A4_interlock')).length, + perturbedScenarios.length, + 'raw-results.json records[A4, EVIDENCE_PERTURBED].oracle.holds', + ), + decision: [...new Set(perturbedScenarios.flatMap((s) => recordsFor(s.id, 'A4_interlock').flatMap((r) => (r.verdicts ?? []).map((v) => v.decision))))].sort(byCodeUnit), + }, + ], + reading: + 'Interlock’s safety is evidence-derived. With revision-bound composition evidence present it withheld both hazardous compositions while retaining both safe parallel opportunities. When that evidence was deliberately removed, the decision reversed and both invariants failed.', + forbiddenRendering: + 'A4 must not be described as globally 0% unsafe, and the sixteen-scenario corpus must not be collapsed into a single unsafe-rate denominator. The 0/2 in Panel 1 is bounded to COUPLED scenarios and means nothing without Panel 2 beside it.', +}; + +// --------------------------------------------------------------------------- +// Limitations +// --------------------------------------------------------------------------- + +const inadmissible = scenariosLabelled('EVIDENCE_INADMISSIBLE'); +const failedClosed = inadmissible.filter((s) => + recordsFor(s.id, 'A4_interlock').every((r) => (r.outcomes ?? []).every((o) => o.applied === false)), +).length; + +const reasonMismatches = inadmissible + .map((s) => { + const observed = [...new Set(recordsFor(s.id, 'A4_interlock').map((r) => r.refusalReason))]; + return { scenario: s.id, expected: s.expectedRefusalReason, observed: observed.join(','), matches: observed.length === 1 && observed[0] === s.expectedRefusalReason }; + }) + .filter((row) => !row.matches); + +const limitations = { + inadmissibleEvidence: { + failedClosed: figure(failedClosed, inadmissible.length, 'raw-results.json records[A4, EVIDENCE_INADMISSIBLE].outcomes[].applied'), + exactReasonAgreement: figure( + report.aggregate.A4_interlock.refusalCorrectness.numerator, + report.aggregate.A4_interlock.refusalCorrectness.denominator, + 'results.json report.aggregate.A4_interlock.refusalCorrectness', + ), + mismatches: reasonMismatches, + statement: + 'Every inadmissible-evidence scenario failed closed and applied no mutation. Exact refusal-reason agreement with the frozen corpus was lower: two scenarios refused with HISTORY_NOT_MINED where the corpus predicted HISTORY_EVIDENCE_UNAVAILABLE. The envelope’s completeness state is NOT_MINED, so the decision core took the correct branch and the corpus expectation was mistaken. Neither mismatch permitted a mutation. The corpus is frozen and stays wrong on the record.', + }, + outsideScope: [ + 'same-target atomicity and target-side compare-and-set', + 'exactly-once execution', + 'restart and recovery behaviour', + 'joint human authorization', + 'Agent Runtime and Agent Gateway participation', + ], + outsideScopeNote: + 'HAC-343 measures a deterministic coordination decision. It does not model a target-side CAS, so A4 permitting two writes to one path is outside what this experiment tested rather than evidence that it is safe. Those belong to HAC-317 and HAC-327.', + corpusBound: + 'Sixteen scenarios across two hazard families. Every rate is a property of this corpus and is not a population estimate. No confidence intervals: the decision core is deterministic and the corpus is enumerated exhaustively, so there is no sampling process.', +}; + +// --------------------------------------------------------------------------- +// Provenance +// --------------------------------------------------------------------------- + +const provenance = { + canonicalResultCommit: CANONICAL_RESULT_COMMIT, + frozenCommits: FROZEN_COMMITS, + digests: { + 'metric-definitions.json': sha256(read('metric-definitions.json')), + 'corpus.json': sha256(read('corpus.json')), + 'execution-semantics.json': sha256(read('execution-semantics.json')), + 'raw-results.json': sha256(read('raw-results.json')), + 'results.json': sha256(read('results.json')), + }, + matrix: figure(raw.records.length, SCENARIOS.length * ARMS.length * ORDERS.length, 'raw-results.json records.length'), + families: FAMILIES, + perFamilyIdentical: FAMILIES.every((family) => + ARMS.every( + (arm) => + JSON.stringify(report.perFamily[family][arm].unsafeJointState.rate) === + JSON.stringify(report.perFamily[FAMILIES[0]][arm].unsafeJointState.rate), + ), + ), + evidenceProducer: { + note: 'The evidence consumed by the run is frozen in git and records its own producer. @workspacejson/cli@0.6.2 was present at execution but provably not loaded: nothing in the runner’s import graph reaches the miner, and the run plans identically with WORKSPACEJSON_CLI unset or pointing at a nonexistent path. It is reproduction tooling, not an input.', + pinnedCliSha: 'defac1e5dce6fb692a48e775fb44854b371cbca4', + miningCoreBundleSha256: '7aa5ae231d6713449d6c1790f0b19a509e82ec0c84d67a8a6a52ff492ec27bb8', + }, + // Captured directly from the registry, not transcribed. + npmIntegrity: { + '@workspacejson/cli@0.6.2': 'sha512-DyXe4oY4s6paN9lgLkFnhj9x46Excg3GSSQfcF3VBTzGj2LUeosaM3iZ5NgM1was8hQWhxibyS1a7YOq5OxI5Q==', + '@workspacejson/spec@0.5.0': 'sha512-KpsUxvLXFHHHKY6F58tWBnqsx5REJjK99Kum1+ATU4b8oUGlStfVkWyphNQ+nFZU3hy/ckNLZTxs4mpOeWGQLA==', + '@workspacejson/rules@0.5.0': 'sha512-UlJUnDdc1In4oAMCNMFbFnCAVUGSb1HR0MeP3Pa6Db3uHrH/OvXPyQCqGXLDe9ii3D+6Ua/OHAJMeocKv2bL1Q==', + }, + // No toolchain block. It used to record process.version/platform, which is the + // *builder's* machine, not the frozen run's — so it sat in provenance asserting + // a fact about an environment that never produced this evidence, and it made the + // export reproducible only on the OS that first built it. The frozen artifacts do + // not record the run's environment, so there is nothing here to bind to. +}; + +// Every integrity string must be a complete sha512 base64 digest. Line-wrapped +// or truncated values are a real failure mode when a figure is copied through a +// terminal, so length is asserted rather than eyeballed. +for (const [pkg, integrity] of Object.entries(provenance.npmIntegrity)) { + if (!/^sha512-[A-Za-z0-9+/]{86}==$/.test(integrity)) { + problems.push(`npm integrity for ${pkg} is not a complete sha512 digest (length ${integrity.length})`); + } +} + +// --------------------------------------------------------------------------- + +const boundedClaim = + 'On a frozen sixteen-scenario corpus spanning two structurally different hazard classes — an arithmetic budget ceiling and a referential service registry — global locking preserved safety by eliminating concurrency, per-target locking preserved concurrency but missed every composition hazard spanning distinct targets, and Interlock withheld both hazardous compositions while retaining both safe parallel opportunities. Interlock’s safety is evidence-derived: with the co-change evidence deliberately removed and the intents unchanged, its decision reversed and both invariants failed. Bounded to this corpus; no claim is made about exactly-once execution, restart behaviour, target-side atomicity, or production readiness.'; + +const overclaims = [ + 'Interlock is 0% unsafe — it produced invalid joint states in the two evidence-ablation scenarios by design.', + 'Interlock prevents composition hazards — it withheld the hazardous compositions present in this corpus, given evidence that described them.', + 'Interlock is safer than locking — it is safe against a hazard class per-key locking cannot see; per-target locking is correct for the hazard it addresses.', + 'A 100% / 0% headline over all sixteen scenarios — the corpus is heterogeneous and must not share one denominator.', + 'Statistically significant, or any interval — the corpus is an exhaustive deterministic enumeration, not a sample.', + 'Production-ready, exactly-once, or restart-safe — none were tested here.', +]; + +const exportDocument = { + experiment: 'HAC-343', + kind: 'judge export (derived, presentation only)', + derivedFrom: { canonicalResultCommit: CANONICAL_RESULT_COMMIT, modifiesNothingFrozen: true }, + generator: 'experiments/hac-343/bin/build-judge-export.mjs', + boundedClaim, + panel1, + panel2, + limitations, + orderEffects: { + count: report.orderEffects.length, + allSameArm: [...new Set(report.orderEffects.map((e) => e.arm))], + statement: + 'Every order disagreement is A2 under a single global lock, where whichever intent enters the critical section first wins and the other is rejected. Real and expected. Safety held under both orders for every arm, so the aggregation absorbs it without hiding it.', + derivedFrom: 'results.json report.orderEffects', + }, + mustNotClaim: overclaims, + provenance, +}; + +if (problems.length > 0) { + console.error('REFUSING TO EMIT — the export contains values it cannot trace:'); + for (const problem of problems) console.error(` ${problem}`); + process.exit(1); +} + +writeFileSync(join(EVIDENCE_DIR, 'judge-export.json'), `${JSON.stringify(exportDocument, null, 2)}\n`); + +// --------------------------------------------------------------------------- + +const pad = (s, n) => String(s).padEnd(n); +console.log('PANEL 1 — operational utility under available evidence'); +console.log(` ${pad('', 20)}${'coupled unsafe'.padStart(16)}${'safe parallelism'.padStart(19)}`); +for (const row of panel1.rows) { + console.log(` ${pad(row.label, 20)}${row.coupledUnsafe.display.padStart(16)}${row.safeParallelism.display.padStart(19)}`); +} +const cred = panel1.perTargetLockCredibility; +console.log(`\n Per-target lock credibility: serialized same-target ${cred.serializedSameTargetContention.display}, ` + + `parallelised cross-target ${cred.parallelisedCrossTarget.display}, missed ${cred.missedCrossTargetHazards.display}`); + +console.log('\nPANEL 2 — evidence ablation (causal control)'); +for (const row of panel2.rows) { + console.log(` ${pad(row.condition, 42)}${row.invalidOutcomes.display.padStart(8)} invalid [${row.decision.join(' + ')}]`); +} + +console.log('\nLIMITATIONS'); +console.log(` failed closed on inadmissible evidence ${limitations.inadmissibleEvidence.failedClosed.display}`); +console.log(` exact refusal-reason agreement ${limitations.inadmissibleEvidence.exactReasonAgreement.display}`); +for (const m of limitations.inadmissibleEvidence.mismatches) { + console.log(` ${m.scenario}: expected ${m.expected}, observed ${m.observed}`); +} + +console.log(`\nMatrix ${provenance.matrix.display} · per-family identical: ${provenance.perFamilyIdentical}`); +console.log(`Derived from ${CANONICAL_RESULT_COMMIT.slice(0, 12)} · wrote evidence/judge-export.json`); diff --git a/experiments/hac-343/bin/run-experiment.mjs b/experiments/hac-343/bin/run-experiment.mjs new file mode 100644 index 0000000..cc86646 --- /dev/null +++ b/experiments/hac-343/bin/run-experiment.mjs @@ -0,0 +1,256 @@ +#!/usr/bin/env node +/** + * HAC-343 — execute the frozen corpus through the four arms. + * + * WORKSPACEJSON_CLI= node experiments/hac-343/bin/run-experiment.mjs + * node experiments/hac-343/bin/run-experiment.mjs --plan # wiring only, no execution + * + * Consumes only what is already frozen: the corpus (dbdcaa9), the metric + * definitions (0a6babb) and the arm semantics (276750b). It decides nothing. + * Every scenario runs against every arm in both intent orders — 16 x 4 x 2 = + * 128 raw records — and a record is written for a failure exactly as for a + * success, because an arm must not be able to improve a rate by declining to + * produce a record. + * + * `--plan` resolves fixtures, evidence and the full record matrix and prints + * what *would* execute, without running an arm or writing a result. It exists so + * the wiring can be proven before any number exists. + */ +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { buildFixture as buildBudget, FIXTURES as BUDGET_FIXTURES } from '../../hac-330/bin/build-fixtures.mjs'; +import { git } from '../../hac-330/lib/exec.mjs'; +import { buildFixture as buildRegistry, FIXTURES as REGISTRY_FIXTURES } from '../lib/families/registry.mjs'; +import { SCENARIOS, FAMILIES, INADMISSIBLE_EVIDENCE } from '../lib/corpus.mjs'; +import { ARMS, runArm } from '../lib/arms.mjs'; +import { aggregate, ORDERS } from '../lib/aggregate.mjs'; +import { oracle, resetWorktree, sha256 } from '../lib/executor.mjs'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const EXPERIMENT_DIR = resolve(HERE, '..'); +const REPO_ROOT = resolve(EXPERIMENT_DIR, '..', '..'); +const EVIDENCE_DIR = join(EXPERIMENT_DIR, 'evidence'); +const WORK_DIR = join(EXPERIMENT_DIR, '.work', 'run'); + +process.chdir(REPO_ROOT); + +const PLAN_ONLY = process.argv.includes('--plan'); +const writeJson = (path, value) => writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`); +const readJson = (path) => JSON.parse(readFileSync(path, 'utf8')); + +// --------------------------------------------------------------------------- +// Evidence resolution — declared by the corpus, never invented here +// --------------------------------------------------------------------------- + +const HAC330_EVIDENCE = join(REPO_ROOT, 'experiments', 'hac-330', 'evidence'); +const HAC343_EVIDENCE = EVIDENCE_DIR; + +/** + * The evidence envelope a scenario is evaluated against. + * + * A scenario's `evidenceOverride` names one of the frozen inadmissible sources + * from the corpus. The runner looks that name up; it never decides on its own + * that a scenario should get degraded evidence, because a runner that could + * choose when to hand an arm unreadable evidence could choose to hand it to one + * arm and not another. + */ +function evidenceFor(scenario) { + if (scenario.evidenceOverride) { + const source = INADMISSIBLE_EVIDENCE[scenario.evidenceOverride]; + if (!source) throw new Error(`${scenario.id}: unknown evidenceOverride ${scenario.evidenceOverride}`); + if (source.file === null) { + return { envelope: null, source: 'absent', sha256: null, expectedReason: source.expectedReason }; + } + const path = join(HAC330_EVIDENCE, source.file); + const bytes = readFileSync(path); + return { + envelope: JSON.parse(bytes.toString('utf8')), + source: `experiments/hac-330/evidence/${source.file}`, + sha256: sha256(bytes), + expectedReason: source.expectedReason, + }; + } + + const path = + scenario.family === 'budget' + ? join(HAC330_EVIDENCE, `${scenario.fixture}.evidence.json`) + : join(HAC343_EVIDENCE, `registry.${scenario.fixture}.evidence.json`); + const bytes = readFileSync(path); + return { + envelope: JSON.parse(bytes.toString('utf8')), + source: path.replace(`${REPO_ROOT}/`, ''), + sha256: sha256(bytes), + expectedReason: null, + }; +} + +/** Intents tagged with stable ids from the scenario's canonical order. */ +const identify = (scenario) => scenario.intents.map((intent, index) => ({ ...intent, id: `i${index}` })); + +/** The two execution orders. `AB` is canonical order; `BA` is reversed. */ +function ordered(intents, order) { + return order === 'AB' ? [...intents] : [...intents].reverse(); +} + +// --------------------------------------------------------------------------- +// Fixtures +// --------------------------------------------------------------------------- + +function buildFixtures() { + mkdirSync(WORK_DIR, { recursive: true }); + const repos = { budget: {}, registry: {} }; + for (const fixture of ['baseline', 'perturbed']) { + repos.budget[fixture] = join(WORK_DIR, 'budget', fixture); + buildBudget(repos.budget[fixture], BUDGET_FIXTURES[fixture]); + repos.registry[fixture] = join(WORK_DIR, 'registry', fixture); + buildRegistry(repos.registry[fixture], REGISTRY_FIXTURES[fixture]); + } + return repos; +} + +const headOf = (repo) => git(repo, ['rev-parse', 'HEAD']).trim(); + +// --------------------------------------------------------------------------- +// The matrix +// --------------------------------------------------------------------------- + +/** Every (scenario, arm, order) triple that must produce a record. */ +function matrix() { + const rows = []; + for (const scenario of SCENARIOS) { + for (const arm of ARMS) { + for (const order of ORDERS) rows.push({ scenario, arm, order }); + } + } + return rows; +} + +if (PLAN_ONLY) { + const rows = matrix(); + const evidence = new Map(); + for (const scenario of SCENARIOS) evidence.set(scenario.id, evidenceFor(scenario)); + + console.log(`plan: ${SCENARIOS.length} scenarios x ${ARMS.length} arms x ${ORDERS.length} orders = ${rows.length} records\n`); + for (const scenario of SCENARIOS) { + const e = evidence.get(scenario.id); + console.log( + ` ${scenario.id.padEnd(38)} ${scenario.label.padEnd(23)} evidence=${e.source}${e.expectedReason ? ` expect=${e.expectedReason}` : ''}`, + ); + } + console.log(`\nno arm executed, no result written`); + process.exit(0); +} + +// --------------------------------------------------------------------------- +// Execution +// --------------------------------------------------------------------------- + +console.log('Building fixtures…'); +const repos = buildFixtures(); +const sourceRevisions = { + budget: { baseline: headOf(repos.budget.baseline), perturbed: headOf(repos.budget.perturbed) }, + registry: { baseline: headOf(repos.registry.baseline), perturbed: headOf(repos.registry.perturbed) }, +}; + +const records = []; +for (const { scenario, arm, order } of matrix()) { + const repo = repos[scenario.family][scenario.fixture]; + const evidence = evidenceFor(scenario); + const intents = ordered(identify(scenario), order); + const sourceRevision = sourceRevisions[scenario.family][scenario.fixture]; + + const record = { + scenarioId: scenario.id, + family: scenario.family, + label: scenario.label, + fixture: scenario.fixture, + fixtureRevision: sourceRevision, + arm, + order, + intents, + evidence: { source: evidence.source, sha256: evidence.sha256, expectedReason: evidence.expectedReason }, + error: null, + }; + + try { + resetWorktree(repo); + const run = runArm({ + arm, + repo, + family: scenario.family, + scenario, + intents, + evidence: evidence.envelope, + sourceRevision, + }); + + record.verdicts = run.verdicts ?? null; + record.outcomes = run.outcomes; + record.lockGroups = run.lockGroups ?? null; + record.concurrent = run.concurrent; + record.refusalReason = run.refusalReason ?? null; + record.oracle = oracle(repo, scenario.family); + } catch (error) { + // A thrown execution is still a record. Dropping it would let an arm + // improve a rate by failing, and the aggregator treats an errored record + // as unsafe rather than as absent. + record.error = String(error?.message ?? error); + record.outcomes = record.outcomes ?? []; + record.concurrent = record.concurrent ?? false; + record.oracle = record.oracle ?? null; + } finally { + resetWorktree(repo); + } + + records.push(record); +} + +console.log(`Executed ${records.length} records (${records.filter((r) => r.error).length} errored).`); + +writeJson(join(EVIDENCE_DIR, 'raw-results.json'), { + experiment: 'HAC-343', + kind: 'raw results', + frozenInputs: { + metricDefinitions: '0a6babbc5d1a3f69b057f98093108ee508072e48', + corpus: 'dbdcaa940933f90091a838f5f183031c7556afad', + executionSemantics: '276750ba7a4a51461fb2447b361d69be5e2a020b', + }, + sourceRevisions, + records, +}); + +// Aggregation is a pure function over exactly those records. +const report = aggregate({ records, scenarios: SCENARIOS, arms: ARMS, families: FAMILIES }); + +writeJson(join(EVIDENCE_DIR, 'results.json'), { + experiment: 'HAC-343', + kind: 'results', + metricDefinitionsSha256: sha256(readFileSync(join(EVIDENCE_DIR, 'metric-definitions.json'))), + corpusSha256: sha256(readFileSync(join(EVIDENCE_DIR, 'corpus.json'))), + executionSemanticsSha256: sha256(readFileSync(join(EVIDENCE_DIR, 'execution-semantics.json'))), + rawResultsSha256: sha256(readFileSync(join(EVIDENCE_DIR, 'raw-results.json'))), + report, +}); + +// Lock validity is reported before anything else: a baseline that did not lock +// makes every downstream comparison meaningless. +console.log('\nLock validity (gate, evaluated before headline metrics):'); +for (const [arm, validity] of Object.entries(report.lockValidity)) { + console.log(` ${arm.padEnd(22)} ${validity.display}`); +} + +if (report.defects.length > 0) { + console.error('\nDEFECT GATES TRIPPED — the harness is wrong, these are not results:'); + for (const defect of report.defects) console.error(` ${defect.gate}/${defect.arm}: ${defect.detail}`); + process.exit(1); +} + +console.log('\nPer-arm (aggregate across both families):'); +for (const arm of ARMS) { + console.log(` ${arm.padEnd(22)} ${report.aggregate[arm].spr.rendering}`); +} + +console.log(`\nWrote raw-results.json and results.json. Verify with:`); +console.log(' node experiments/hac-343/bin/verify-packet.mjs'); diff --git a/experiments/hac-343/bin/verify-packet.mjs b/experiments/hac-343/bin/verify-packet.mjs new file mode 100644 index 0000000..c76fa10 --- /dev/null +++ b/experiments/hac-343/bin/verify-packet.mjs @@ -0,0 +1,201 @@ +#!/usr/bin/env node +/** + * Verify the committed HAC-343 packet against itself. + * + * node experiments/hac-343/bin/verify-packet.mjs + * + * Needs no sibling checkout, no network and no fixtures, so CI can enforce it. + * It does not re-run the experiment; what it proves is that nobody edited a + * number, a definition or a raw record afterwards. + * + * The load-bearing property is that **every metric is recomputed from the raw + * records** rather than read from the summary. A verifier that compared a + * summary to itself would pass on any summary. `aggregate()` is a pure function, + * so the same raw records must reproduce the committed report byte for byte. + * + * It also pins the three freeze commits. Each frozen contract must still be the + * file its freeze commit introduced: if `metric-definitions.json` is edited + * later, its last-touching commit stops being 0a6babb and this fails. That is + * what makes "frozen before results" checkable by someone who was not there. + */ +import { execFileSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { existsSync, readFileSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { SCENARIOS, FAMILIES } from '../lib/corpus.mjs'; +import { ARMS } from '../lib/arms.mjs'; +import { aggregate, FROZEN_COMMITS, ORDERS } from '../lib/aggregate.mjs'; +import { GIT } from '../../hac-330/lib/exec.mjs'; + +const EXPERIMENT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const REPO_ROOT = resolve(EXPERIMENT_DIR, '..', '..'); +const EVIDENCE_DIR = join(EXPERIMENT_DIR, 'evidence'); + +const read = (name) => readFileSync(join(EVIDENCE_DIR, name)); +const json = (name) => JSON.parse(read(name).toString('utf8')); +const sha256 = (buffer) => createHash('sha256').update(buffer).digest('hex'); + +const failures = []; +function verify(claim, passed, detail = '') { + if (!passed) failures.push(claim); + console.log(` ${passed ? 'ok ' : 'FAIL'} ${claim}${detail ? ` — ${detail}` : ''}`); + return passed; +} + +const section = (title) => console.log(`\n── ${title} ${'─'.repeat(Math.max(0, 58 - title.length))}`); + +// --------------------------------------------------------------------------- + +section('Freeze commits'); + +for (const [file, expectedSha] of Object.entries(FROZEN_COMMITS)) { + let lastTouching = null; + try { + lastTouching = execFileSync(GIT, ['-C', REPO_ROOT, 'log', '-1', '--format=%H', '--', file], { + encoding: 'utf8', + }).trim(); + } catch (error) { + lastTouching = ``; + } + verify( + `${file} is still the file its freeze commit introduced`, + lastTouching === expectedSha, + `last touched by ${lastTouching.slice(0, 12)}, expected ${expectedSha.slice(0, 12)}`, + ); + + // `git log` answers about committed history only, so an uncommitted edit to a + // frozen contract would pass the check above. Compare the bytes on disk to the + // blob the freeze commit recorded, which closes that door. + let frozenBytes = null; + try { + frozenBytes = execFileSync(GIT, ['-C', REPO_ROOT, 'show', `${expectedSha}:${file}`], { + encoding: 'buffer', + maxBuffer: 32 * 1024 * 1024, + }); + } catch (error) { + frozenBytes = null; + } + const onDisk = existsSync(join(REPO_ROOT, file)) ? readFileSync(join(REPO_ROOT, file)) : null; + verify( + `${file} on disk is byte-identical to its frozen blob`, + frozenBytes !== null && onDisk !== null && sha256(frozenBytes) === sha256(onDisk), + frozenBytes && onDisk ? `sha256 ${sha256(onDisk).slice(0, 12)}` : 'could not read one side', + ); +} + +// --------------------------------------------------------------------------- + +section('Packet presence'); + +const hasRaw = existsSync(join(EVIDENCE_DIR, 'raw-results.json')); +const hasResults = existsSync(join(EVIDENCE_DIR, 'results.json')); + +if (!hasRaw || !hasResults) { + console.log(` .. no results yet (raw-results.json ${hasRaw ? 'present' : 'absent'}, results.json ${hasResults ? 'present' : 'absent'})`); + console.log('\nMachinery verified; the experiment has not been executed.'); + process.exit(failures.length > 0 ? 1 : 0); +} + +const raw = json('raw-results.json'); +const results = json('results.json'); + +// --------------------------------------------------------------------------- + +section('Digests'); + +verify( + 'results.json cites the metric definitions it was computed against', + results.metricDefinitionsSha256 === sha256(read('metric-definitions.json')), +); +verify('results.json cites the frozen corpus', results.corpusSha256 === sha256(read('corpus.json'))); +verify( + 'results.json cites the frozen execution semantics', + results.executionSemanticsSha256 === sha256(read('execution-semantics.json')), +); +verify( + 'results.json cites the exact raw records it summarises', + results.rawResultsSha256 === sha256(read('raw-results.json')), +); +for (const [name, sha] of Object.entries(raw.frozenInputs ?? {})) { + verify(`raw-results.json pins ${name}`, Object.values(FROZEN_COMMITS).includes(sha), sha.slice(0, 12)); +} + +// --------------------------------------------------------------------------- + +section('Completeness'); + +const expected = SCENARIOS.length * ARMS.length * ORDERS.length; +verify( + `every scenario x arm x order produced a record (${expected})`, + raw.records.length === expected, + `${raw.records.length} present`, +); + +const seen = new Set(raw.records.map((r) => `${r.scenarioId}|${r.arm}|${r.order}`)); +const missing = []; +for (const scenario of SCENARIOS) { + for (const arm of ARMS) { + for (const order of ORDERS) { + if (!seen.has(`${scenario.id}|${arm}|${order}`)) missing.push(`${scenario.id}|${arm}|${order}`); + } + } +} +verify('no record is missing', missing.length === 0, missing.slice(0, 3).join(', ')); + +verify( + 'every record carries its oracle evidence or an explicit error', + raw.records.every((r) => r.error != null || (r.oracle?.exitCode !== undefined && /^[0-9a-f]{64}$/.test(r.oracle.verifierSha256))), +); + +// --------------------------------------------------------------------------- + +section('Recomputation'); + +let recomputed = null; +let recomputeError = null; +try { + recomputed = aggregate({ records: raw.records, scenarios: SCENARIOS, arms: ARMS, families: FAMILIES }); +} catch (error) { + recomputeError = String(error?.message ?? error); +} + +verify('aggregation reruns over the raw records', recomputeError === null, recomputeError ?? ''); + +if (recomputed) { + verify( + 'the committed report is exactly what the raw records produce', + JSON.stringify(recomputed) === JSON.stringify(results.report), + 'recomputed from raw records, not read from the summary', + ); + + verify( + 'no SPR figure is present without its unsafe-joint-state rate', + ARMS.every((arm) => { + const spr = recomputed.aggregate[arm]?.spr; + return spr?.safeParallelismRetained && spr.unsafeJointState && typeof spr.rendering === 'string'; + }), + ); + + verify( + 'lock validity is reported for every arm before the headline metrics', + ARMS.every((arm) => recomputed.lockValidity[arm] !== undefined), + ); + + verify( + 'per-family metrics exist for every family', + FAMILIES.every((family) => ARMS.every((arm) => recomputed.perFamily[family]?.[arm]?.spr)), + ); + + verify('no defect gate is tripped', recomputed.defects.length === 0, recomputed.defects.map((d) => `${d.gate}/${d.arm}`).join(', ')); +} + +// --------------------------------------------------------------------------- + +if (failures.length > 0) { + console.error(`\nFAILED — ${failures.length} check(s):`); + for (const failure of failures) console.error(` ${failure}`); + process.exit(1); +} +console.log('\nHAC-343 packet verified.'); diff --git a/experiments/hac-343/evidence/corpus.json b/experiments/hac-343/evidence/corpus.json new file mode 100644 index 0000000..13e9ca4 --- /dev/null +++ b/experiments/hac-343/evidence/corpus.json @@ -0,0 +1,554 @@ +{ + "experiment": "HAC-343", + "kind": "corpus manifest", + "status": "FROZEN_BEFORE_RESULTS", + "revision": "r01", + "supersedes": [], + "frozenRule": "Committed in its own commit, after metric-definitions.json and before any arm implementation or results.json. Scenario counts, labels and intents are fixed here. A corpus change after any result exists invalidates that result and requires a rerun, per metric-definitions.json corpusRequirements.noOptimisation.", + "metricDefinitions": { + "file": "experiments/hac-343/evidence/metric-definitions.json", + "revision": "r01", + "sha256": "2cfff5b19812d9805d5c5a129bb8e8ac7009ae8b5d21a10a85bb31863bc79700" + }, + "breadthRationale": "Two structurally different hazard classes, so a result is not one topology repeated. budget is arithmetic (composed increases overshoot a ceiling); registry is referential (one intent removes a referent the other points at). Both carry all five ground-truth classes, so a per-family divergence is about hazard shape rather than uneven class coverage.", + "families": { + "budget": { + "hazardClass": "arithmetic", + "invariant": "sum(services[].reserved) <= budget.totalReservable", + "source": "experiments/hac-330 — reused verbatim, not rebuilt", + "subjectPaths": { + "left": "services/alpha/reservation.json", + "right": "services/beta/reservation.json", + "independent": "services/gamma/reservation.json" + }, + "fixtures": { + "totalReservable": 130, + "baseline": { + "repo": "experiments/hac-330/.work/fixtures/baseline", + "head": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "tree": "fc015c39d48019fd8bb1b3e25ae97f70ebf5262e", + "commitCount": 17 + }, + "perturbed": { + "repo": "experiments/hac-330/.work/fixtures/perturbed", + "head": "db8a63ec9405191bdd40d0ed0fc69684fca5d17b", + "tree": "fc015c39d48019fd8bb1b3e25ae97f70ebf5262e", + "commitCount": 17 + } + } + }, + "registry": { + "hazardClass": "referential", + "invariant": "every route.service and alias target resolves in registry/services.json", + "source": "experiments/hac-343/lib/families/registry.mjs — built by this script", + "subjectPaths": { + "left": "registry/services.json", + "right": "routing/routes.json", + "independent": "observability/dashboards.json" + }, + "fixtures": { + "baseline": { + "repo": "/Users/user1/dev/interlock/experiments/hac-343/.work/fixtures/baseline", + "head": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "tree": "b57f883c20fa06246799e610eed88b90d160dfc5", + "commitCount": 18 + }, + "perturbed": { + "repo": "/Users/user1/dev/interlock/experiments/hac-343/.work/fixtures/perturbed", + "head": "f50ecbe40530af357750952235bb262948f9e84e", + "tree": "b57f883c20fa06246799e610eed88b90d160dfc5", + "commitCount": 18 + } + }, + "qualifyingPairs": { + "baseline": [ + { + "files": [ + "registry/services.json", + "routing/routes.json" + ], + "support": 9, + "occurrences": 13 + }, + { + "files": [ + "docs/runbook.md", + "observability/dashboards.json" + ], + "support": 5, + "occurrences": 5 + } + ], + "perturbed": [ + { + "files": [ + "docs/runbook.md", + "observability/dashboards.json" + ], + "support": 5, + "occurrences": 5 + }, + { + "files": [ + "registry/aliases.json", + "routing/routes.json" + ], + "support": 5, + "occurrences": 11 + }, + { + "files": [ + "registry/aliases.json", + "registry/services.json" + ], + "support": 5, + "occurrences": 13 + } + ] + }, + "controls": { + "sharedFinalTree": "b57f883c20fa06246799e610eed88b90d160dfc5", + "commitCount": 18, + "note": "Same four controls as HAC-330: identical final tree, identical commit count, commit i touching the same number of files in both, and the invariant holding at every commit (asserted in planCommits)." + } + } + }, + "counts": { + "total": 16, + "byLabel": { + "COUPLED": 2, + "INDEPENDENT": 2, + "SAME_TARGET_CONTENTION": 2, + "EVIDENCE_PERTURBED": 2, + "EVIDENCE_INADMISSIBLE": 8 + }, + "byFamily": { + "budget": 8, + "registry": 8 + }, + "byFamilyAndLabel": { + "budget": { + "COUPLED": 1, + "INDEPENDENT": 1, + "SAME_TARGET_CONTENTION": 1, + "EVIDENCE_PERTURBED": 1, + "EVIDENCE_INADMISSIBLE": 4 + }, + "registry": { + "COUPLED": 1, + "INDEPENDENT": 1, + "SAME_TARGET_CONTENTION": 1, + "EVIDENCE_PERTURBED": 1, + "EVIDENCE_INADMISSIBLE": 4 + } + } + }, + "scenarios": [ + { + "id": "budget/coupled/alpha-beta", + "family": "budget", + "label": "COUPLED", + "fixture": "baseline", + "rationale": "alpha and beta are historical counterparties at support 8. Each raise is valid alone (120 <= 130); composed they reach 140 > 130.", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60 + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60 + } + ], + "composeViolatesInvariant": true + }, + { + "id": "budget/independent/alpha-gamma", + "family": "budget", + "label": "INDEPENDENT", + "fixture": "baseline", + "rationale": "alpha and gamma never appear in one commit in the baseline history. Composed they reach 128 <= 130, so permitting both is correct.", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60 + }, + { + "op": "set-reservation", + "path": "services/gamma/reservation.json", + "service": "gamma", + "reserved": 28 + } + ], + "composeViolatesInvariant": false + }, + { + "id": "budget/same-target/alpha-alpha", + "family": "budget", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "rationale": "Both intents write services/alpha/reservation.json. Any real lock must serialize this; it exists to prove the lock arms lock.", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60 + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 55 + } + ], + "composeViolatesInvariant": false + }, + { + "id": "budget/perturbed/alpha-beta", + "family": "budget", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "rationale": "Identical intents and identical final tree to budget/coupled/alpha-beta, against a history where alpha and beta never co-occur. The composition is still arithmetically unsafe; only the evidence changed.", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60 + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60 + } + ], + "composeViolatesInvariant": true, + "perturbationOf": "budget/coupled/alpha-beta" + }, + { + "id": "budget/inadmissible/absent", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "rationale": "The coupled intents against absent evidence. Correct behavior is explicit refusal with reason EVIDENCE_ABSENT, never a permit.", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60 + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60 + } + ], + "composeViolatesInvariant": true, + "evidenceOverride": "absent", + "expectedRefusalReason": "EVIDENCE_ABSENT" + }, + { + "id": "budget/inadmissible/shallow", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "rationale": "The coupled intents against shallow evidence. Correct behavior is explicit refusal with reason HISTORY_NOT_MINED, never a permit.", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60 + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60 + } + ], + "composeViolatesInvariant": true, + "evidenceOverride": "shallow", + "expectedRefusalReason": "HISTORY_NOT_MINED" + }, + { + "id": "budget/inadmissible/noRepository", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "rationale": "The coupled intents against noRepository evidence. Correct behavior is explicit refusal with reason HISTORY_EVIDENCE_UNAVAILABLE, never a permit.", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60 + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60 + } + ], + "composeViolatesInvariant": true, + "evidenceOverride": "noRepository", + "expectedRefusalReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + { + "id": "budget/inadmissible/misattributed", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "rationale": "The coupled intents against misattributed evidence. Correct behavior is explicit refusal with reason EVIDENCE_REPOSITORY_MISMATCH, never a permit.", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60 + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60 + } + ], + "composeViolatesInvariant": true, + "evidenceOverride": "misattributed", + "expectedRefusalReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + { + "id": "registry/coupled/retire-vs-route", + "family": "registry", + "label": "COUPLED", + "fixture": "baseline", + "rationale": "services and routes are historical counterparties at support 9. Retiring the unrouted legacy-pricing service is valid alone; routing to it is valid alone; composed the route dangles.", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing" + } + ], + "composeViolatesInvariant": true + }, + { + "id": "registry/independent/route-vs-dashboards", + "family": "registry", + "label": "INDEPENDENT", + "fixture": "baseline", + "rationale": "dashboards co-changes only with the runbook, never with services or routes. Adding a route to an existing service and bumping a dashboard revision compose safely.", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/health", + "service": "checkout" + }, + { + "op": "bump-dashboards", + "path": "observability/dashboards.json", + "revision": 99 + } + ], + "composeViolatesInvariant": false + }, + { + "id": "registry/same-target/route-vs-route", + "family": "registry", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "rationale": "Both intents write routing/routes.json. Any real lock must serialize this; it exists to prove the lock arms lock.", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/a", + "service": "checkout" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/b", + "service": "inventory" + } + ], + "composeViolatesInvariant": false + }, + { + "id": "registry/perturbed/retire-vs-route", + "family": "registry", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "rationale": "Identical intents and identical final tree to registry/coupled/retire-vs-route, against a history where services and routes never co-occur. The composition still dangles; only the evidence changed.", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing" + } + ], + "composeViolatesInvariant": true, + "perturbationOf": "registry/coupled/retire-vs-route" + }, + { + "id": "registry/inadmissible/absent", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "rationale": "The coupled intents against absent evidence. Correct behavior is explicit refusal with reason EVIDENCE_ABSENT, never a permit.", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing" + } + ], + "composeViolatesInvariant": true, + "evidenceOverride": "absent", + "expectedRefusalReason": "EVIDENCE_ABSENT" + }, + { + "id": "registry/inadmissible/shallow", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "rationale": "The coupled intents against shallow evidence. Correct behavior is explicit refusal with reason HISTORY_NOT_MINED, never a permit.", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing" + } + ], + "composeViolatesInvariant": true, + "evidenceOverride": "shallow", + "expectedRefusalReason": "HISTORY_NOT_MINED" + }, + { + "id": "registry/inadmissible/noRepository", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "rationale": "The coupled intents against noRepository evidence. Correct behavior is explicit refusal with reason HISTORY_EVIDENCE_UNAVAILABLE, never a permit.", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing" + } + ], + "composeViolatesInvariant": true, + "evidenceOverride": "noRepository", + "expectedRefusalReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + { + "id": "registry/inadmissible/misattributed", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "rationale": "The coupled intents against misattributed evidence. Correct behavior is explicit refusal with reason EVIDENCE_REPOSITORY_MISMATCH, never a permit.", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing" + } + ], + "composeViolatesInvariant": true, + "evidenceOverride": "misattributed", + "expectedRefusalReason": "EVIDENCE_REPOSITORY_MISMATCH" + } + ], + "validation": { + "corpusRequirements": "PASS", + "checksRun": [ + "PIN-CLI", + "PIN-STD", + "F1-REUSED", + "F1-TREE", + "F2-TREE", + "F2-SHAPE", + "F2-COUPLED", + "F2-PERTURBED", + "F2-INDEP", + "CORPUS-VALID", + "CORPUS-BREADTH" + ] + }, + "reproduction": { + "buildCommand": "WORKSPACEJSON_CLI= node experiments/hac-343/bin/build-corpus.mjs", + "pins": { + "workspacejson-cli": { + "id": "workspacejson-cli", + "repository": "workspacejson/cli", + "remote": "https://github.com/workspacejson/cli.git", + "disposition": "EXECUTE_READ_ONLY", + "pinnedSha": "defac1e5dce6fb692a48e775fb44854b371cbca4", + "observedSha": "defac1e5dce6fb692a48e775fb44854b371cbca4", + "clean": true, + "matches": true, + "problems": [] + }, + "workspacejson-standard": { + "id": "workspacejson-standard", + "repository": "workspacejson/standard", + "remote": "https://github.com/workspacejson/standard.git", + "disposition": "READ_ONLY_PINNED", + "pinnedSha": "a3caece60bde12c41105a9987f50afa9e33dcb7b", + "observedSha": "a3caece60bde12c41105a9987f50afa9e33dcb7b", + "clean": true, + "matches": true, + "problems": [] + } + } + } +} diff --git a/experiments/hac-343/evidence/execution-semantics.json b/experiments/hac-343/evidence/execution-semantics.json new file mode 100644 index 0000000..0871580 --- /dev/null +++ b/experiments/hac-343/evidence/execution-semantics.json @@ -0,0 +1,92 @@ +{ + "experiment": "HAC-343", + "kind": "execution semantics", + "status": "FROZEN_BEFORE_RESULTS", + "revision": "r01", + "supersedes": [], + "frozenRule": "Third freeze, after metric-definitions.json (0a6babb) and corpus.json (dbdcaa9), and before any arm produces a result. Fixes how an arm executes and how the postcondition is judged, so neither can be tuned once an outcome is visible.", + + "layerSeparation": { + "ration": "Four questions are answered by four separate mechanisms. Collapsing any two of them would let the harness agree with itself, and the result would be unfalsifiable.", + "layers": [ + { + "question": "Is my action valid from what I can currently see?", + "answeredBy": "the action's local precondition, evaluated inside the executor against the state visible at that moment", + "note": "This is the check every locally-correct agent already performs. Each intent in the corpus passes it in isolation by construction; that is what makes the hazard invisible one request at a time." + }, + { + "question": "When does that check happen?", + "answeredBy": "the arm's coordination policy — no lock, one global lock, a target-derived lock, or the Interlock decision", + "note": "This is the only thing that varies between arms." + }, + { + "question": "Are these two actions compositionally coupled?", + "answeredBy": "arbitrate() over frozen co-change evidence — A4 only", + "note": "No other arm has access to this question, which is the point of the comparison." + }, + { + "question": "Did the resulting joint state actually remain valid?", + "answeredBy": "the fixture's own verify.mjs, executed as a subprocess after the arm has finished", + "note": "The oracle. See oracleProtocol." + } + ] + }, + + "oracleProtocol": { + "rule": "The postcondition is decided ONLY by shelling out to the fixture's own verify.mjs. No arm, and no part of the evaluation harness, contains its own implementation of a shared invariant.", + "rationale": "If the harness knew what 'valid' meant, a harness defect could make an arm look safe by agreeing with itself. The verifier ships inside the fixture, was written by the fixture generator, and is read by nothing that decides whether to permit.", + "recordedPerExecution": [ + "verifierPath", + "verifierSha256", + "command", + "exitCode", + "stdout", + "stderr", + "stateSha256", + "holds" + ], + "exitCodeIsTheVerdict": "holds === (exitCode === 0). Verifier stdout is recorded but never parsed for the verdict; a verifier that printed a reassuring report while exiting non-zero must read as a violation.", + "verifierFailureIsNotSafety": "A verifier that cannot run at all (spawn error, non-zero exit for a reason other than a dangling/overshooting state) fails the scenario rather than being recorded as holding. An unanswerable question is not an answer of 'valid'.", + "discriminationGate": { + "rule": "Before any result is generated, each family's verifier is mutation-tested against three constructed states: a known-valid state must pass, a known-invalid COUPLED composition must fail, and a known-invalid SAME_TARGET composition must fail where the family admits one. If a verifier does not discriminate correctly on all three, the experiment stops.", + "rationale": "Family 2's verifier is generated by the same generator that built the fixture. That makes it independent of the arm harness but not epistemically independent of fixture construction, so it is proven capable of failing before it is trusted to report success." + } + }, + + "concurrencyModel": { + "definition": "Two intents execute CONCURRENTLY when both evaluate their local precondition against the same base snapshot, and both writes then land. They execute SERIALLY when the second evaluates its precondition against the state the first already wrote.", + "rationale": "This is the entire hazard, stated precisely: every precondition was true when it was checked and false by the time the last write landed. It is the model HAC-330's broker already uses, generalised across families and arms rather than reimplemented.", + "criticalSection": { + "rule": "Every lock-bearing arm uses the identical critical section: acquire(key) -> read current state -> re-evaluate the action's ordinary local precondition against that state -> mutate or reject -> release(key).", + "whyRereadMatters": "Merely executing two already-approved mutations in sequence would still overshoot, which would weaken the lock baselines unfairly. A real locking implementation lets the second action see the first action's update before it decides, and both A2 and A3 do." + } + }, + + "arms": { + "A1_uncoordinated": { "lockKey": null, "note": "no critical section; both intents read the base snapshot" }, + "A2_global_lock": { "lockKey": "constant 'GLOBAL'", "note": "every intent contends; the critical section is entered one at a time regardless of what is written" }, + "A3_per_target_lock": { "lockKey": "the intent's mutation target path", "note": "same critical section as A2, keyed by target. Same-target intents contend and serialize; different-target intents take different locks and proceed concurrently." }, + "A4_interlock": { "lockKey": "n/a", "note": "arbitrate() decides parallel, serialized or refused; execution then follows the concurrency model above" }, + "parityRule": "All four arms share one action executor. Only the coordination policy varies. A4 receives no stronger validation, no retry the baselines lack, and no different mutation semantics. Any capability added to A4's execution path must be added to all arms or the comparison is void." + }, + + "orderPolicy": { + "rule": "Every scenario is executed in BOTH intent orders, A->B and B->A. Neither order is chosen as canonical, and order is never selected after an outcome is visible.", + "rationale": "Serialization creates order effects. Picking one ordering would let a favorable arrangement stand in for a property the arm does not have.", + "aggregation": { + "unit": "the scenario, unchanged — metric-definitions.json r01 froze every denominator as a scenario count, and this policy does not reopen it", + "unsafeJointState": "the scenario counts as unsafe if EITHER order produces a verifier violation; safety must hold under all orders", + "permittedConcurrently": "the scenario counts toward the SPR numerator only if BOTH orders permitted both intents concurrently; parallelism is claimed only when it is order-independent", + "falseBlock": "the scenario counts as blocked if EITHER order failed to permit both intents concurrently", + "refusal": "the scenario counts as correctly refused only if BOTH orders refused with the expected reason code", + "direction": "Every rule above is conservative toward A4: it can lose on one order and lose overall, and it cannot win on a lucky ordering. Both per-order raw results are retained regardless, so a reader can see any order effect the aggregation hides." + }, + "orderEffectReporting": "Any scenario whose two orders disagree on any recorded outcome is listed explicitly in results.json under orderEffects, with both raw executions. A disagreement is a finding, not noise to be averaged away." + }, + + "determinism": { + "clock": "Intent recordedAt values are fixed constants derived from the scenario id and intent position, never from the wall clock. arbitrate() breaks precedence ties on recordedAt then correlationId, so a wall-clock timestamp would make the leader nondeterministic across runs.", + "correlationIds": "Derived deterministically from scenario id and intent position.", + "worktree": "Every execution resets the fixture worktree before it begins, so no scenario inherits state from another." + } +} diff --git a/experiments/hac-343/evidence/judge-export.json b/experiments/hac-343/evidence/judge-export.json new file mode 100644 index 0000000..b1b2d07 --- /dev/null +++ b/experiments/hac-343/evidence/judge-export.json @@ -0,0 +1,244 @@ +{ + "experiment": "HAC-343", + "kind": "judge export (derived, presentation only)", + "derivedFrom": { + "canonicalResultCommit": "7ede0f97e55685c16e5bb762b5e7fbe471a6e8b0", + "modifiesNothingFrozen": true + }, + "generator": "experiments/hac-343/bin/build-judge-export.mjs", + "boundedClaim": "On a frozen sixteen-scenario corpus spanning two structurally different hazard classes — an arithmetic budget ceiling and a referential service registry — global locking preserved safety by eliminating concurrency, per-target locking preserved concurrency but missed every composition hazard spanning distinct targets, and Interlock withheld both hazardous compositions while retaining both safe parallel opportunities. Interlock’s safety is evidence-derived: with the co-change evidence deliberately removed and the intents unchanged, its decision reversed and both invariants failed. Bounded to this corpus; no claim is made about exactly-once execution, restart behaviour, target-side atomicity, or production readiness.", + "panel1": { + "question": "Under the co-change evidence that was available, how do the four coordination strategies compare?", + "scope": "COUPLED and INDEPENDENT scenarios only. Evidence-ablation scenarios are Panel 2; inadmissible-evidence scenarios are reported under limitations.", + "rows": [ + { + "arm": "A1_uncoordinated", + "label": "Uncoordinated", + "coupledUnsafe": { + "numerator": 2, + "denominator": 2, + "display": "2/2", + "percent": 100, + "derivedFrom": "results.json report.aggregate.A1_uncoordinated.unsafeJointState" + }, + "safeParallelism": { + "numerator": 2, + "denominator": 2, + "display": "2/2", + "percent": 100, + "derivedFrom": "results.json report.aggregate.A1_uncoordinated.spr.safeParallelismRetained" + } + }, + { + "arm": "A2_global_lock", + "label": "Global lock", + "coupledUnsafe": { + "numerator": 0, + "denominator": 2, + "display": "0/2", + "percent": 0, + "derivedFrom": "results.json report.aggregate.A2_global_lock.unsafeJointState" + }, + "safeParallelism": { + "numerator": 0, + "denominator": 2, + "display": "0/2", + "percent": 0, + "derivedFrom": "results.json report.aggregate.A2_global_lock.spr.safeParallelismRetained" + } + }, + { + "arm": "A3_per_target_lock", + "label": "Per-target lock", + "coupledUnsafe": { + "numerator": 2, + "denominator": 2, + "display": "2/2", + "percent": 100, + "derivedFrom": "results.json report.aggregate.A3_per_target_lock.unsafeJointState" + }, + "safeParallelism": { + "numerator": 2, + "denominator": 2, + "display": "2/2", + "percent": 100, + "derivedFrom": "results.json report.aggregate.A3_per_target_lock.spr.safeParallelismRetained" + } + }, + { + "arm": "A4_interlock", + "label": "Interlock", + "coupledUnsafe": { + "numerator": 0, + "denominator": 2, + "display": "0/2", + "percent": 0, + "derivedFrom": "results.json report.aggregate.A4_interlock.unsafeJointState" + }, + "safeParallelism": { + "numerator": 2, + "denominator": 2, + "display": "2/2", + "percent": 100, + "derivedFrom": "results.json report.aggregate.A4_interlock.spr.safeParallelismRetained" + } + } + ], + "reading": "Global locking preserved safety by eliminating concurrency. Per-target locking preserved concurrency but missed every cross-target composition hazard. Interlock is the only arm in both left-hand columns at once on this corpus.", + "perTargetLockCredibility": { + "claim": "A3 is a real lock, so its misses are blindness rather than absence of a lock.", + "serializedSameTargetContention": { + "numerator": 2, + "denominator": 2, + "display": "2/2", + "percent": 100, + "derivedFrom": "results.json report.lockValidity.A3_per_target_lock" + }, + "parallelisedCrossTarget": { + "numerator": 4, + "denominator": 4, + "display": "4/4", + "percent": 100, + "derivedFrom": "raw-results.json records[A3, cross-target].concurrent" + }, + "missedCrossTargetHazards": { + "numerator": 2, + "denominator": 2, + "display": "2/2", + "percent": 100, + "derivedFrom": "results.json report.aggregate.A3_per_target_lock.unsafeJointState" + }, + "note": "It locked exactly what a lock can see. A composition hazard spanning two lock keys is not visible to any per-key discipline." + } + }, + "panel2": { + "question": "Is Interlock’s safety derived from the evidence, or from something else?", + "design": "The perturbed fixtures hold the intents and the final tree identical to their coupled counterparts and change only the commit history, so the coupling is absent from the mined evidence while the composition remains genuinely hazardous.", + "rows": [ + { + "condition": "Interlock + coupling evidence present", + "invalidOutcomes": { + "numerator": 0, + "denominator": 2, + "display": "0/2", + "percent": 0, + "derivedFrom": "raw-results.json records[A4, COUPLED].oracle.holds" + }, + "decision": [ + "ALLOW_SERIALIZED", + "WITHHOLD_SERIALIZE" + ] + }, + { + "condition": "Interlock + coupling evidence removed", + "invalidOutcomes": { + "numerator": 2, + "denominator": 2, + "display": "2/2", + "percent": 100, + "derivedFrom": "raw-results.json records[A4, EVIDENCE_PERTURBED].oracle.holds" + }, + "decision": [ + "ALLOW_PARALLEL" + ] + } + ], + "reading": "Interlock’s safety is evidence-derived. With revision-bound composition evidence present it withheld both hazardous compositions while retaining both safe parallel opportunities. When that evidence was deliberately removed, the decision reversed and both invariants failed.", + "forbiddenRendering": "A4 must not be described as globally 0% unsafe, and the sixteen-scenario corpus must not be collapsed into a single unsafe-rate denominator. The 0/2 in Panel 1 is bounded to COUPLED scenarios and means nothing without Panel 2 beside it." + }, + "limitations": { + "inadmissibleEvidence": { + "failedClosed": { + "numerator": 8, + "denominator": 8, + "display": "8/8", + "percent": 100, + "derivedFrom": "raw-results.json records[A4, EVIDENCE_INADMISSIBLE].outcomes[].applied" + }, + "exactReasonAgreement": { + "numerator": 6, + "denominator": 8, + "display": "6/8", + "percent": 75, + "derivedFrom": "results.json report.aggregate.A4_interlock.refusalCorrectness" + }, + "mismatches": [ + { + "scenario": "budget/inadmissible/noRepository", + "expected": "HISTORY_EVIDENCE_UNAVAILABLE", + "observed": "HISTORY_NOT_MINED", + "matches": false + }, + { + "scenario": "registry/inadmissible/noRepository", + "expected": "HISTORY_EVIDENCE_UNAVAILABLE", + "observed": "HISTORY_NOT_MINED", + "matches": false + } + ], + "statement": "Every inadmissible-evidence scenario failed closed and applied no mutation. Exact refusal-reason agreement with the frozen corpus was lower: two scenarios refused with HISTORY_NOT_MINED where the corpus predicted HISTORY_EVIDENCE_UNAVAILABLE. The envelope’s completeness state is NOT_MINED, so the decision core took the correct branch and the corpus expectation was mistaken. Neither mismatch permitted a mutation. The corpus is frozen and stays wrong on the record." + }, + "outsideScope": [ + "same-target atomicity and target-side compare-and-set", + "exactly-once execution", + "restart and recovery behaviour", + "joint human authorization", + "Agent Runtime and Agent Gateway participation" + ], + "outsideScopeNote": "HAC-343 measures a deterministic coordination decision. It does not model a target-side CAS, so A4 permitting two writes to one path is outside what this experiment tested rather than evidence that it is safe. Those belong to HAC-317 and HAC-327.", + "corpusBound": "Sixteen scenarios across two hazard families. Every rate is a property of this corpus and is not a population estimate. No confidence intervals: the decision core is deterministic and the corpus is enumerated exhaustively, so there is no sampling process." + }, + "orderEffects": { + "count": 12, + "allSameArm": [ + "A2_global_lock" + ], + "statement": "Every order disagreement is A2 under a single global lock, where whichever intent enters the critical section first wins and the other is rejected. Real and expected. Safety held under both orders for every arm, so the aggregation absorbs it without hiding it.", + "derivedFrom": "results.json report.orderEffects" + }, + "mustNotClaim": [ + "Interlock is 0% unsafe — it produced invalid joint states in the two evidence-ablation scenarios by design.", + "Interlock prevents composition hazards — it withheld the hazardous compositions present in this corpus, given evidence that described them.", + "Interlock is safer than locking — it is safe against a hazard class per-key locking cannot see; per-target locking is correct for the hazard it addresses.", + "A 100% / 0% headline over all sixteen scenarios — the corpus is heterogeneous and must not share one denominator.", + "Statistically significant, or any interval — the corpus is an exhaustive deterministic enumeration, not a sample.", + "Production-ready, exactly-once, or restart-safe — none were tested here." + ], + "provenance": { + "canonicalResultCommit": "7ede0f97e55685c16e5bb762b5e7fbe471a6e8b0", + "frozenCommits": { + "experiments/hac-343/evidence/metric-definitions.json": "0a6babbc5d1a3f69b057f98093108ee508072e48", + "experiments/hac-343/evidence/corpus.json": "dbdcaa940933f90091a838f5f183031c7556afad", + "experiments/hac-343/evidence/execution-semantics.json": "276750ba7a4a51461fb2447b361d69be5e2a020b" + }, + "digests": { + "metric-definitions.json": "2cfff5b19812d9805d5c5a129bb8e8ac7009ae8b5d21a10a85bb31863bc79700", + "corpus.json": "68c60b38087de00886338de78b9a8b673c1467bf056adaf057c4fd211929575c", + "execution-semantics.json": "eca7fac8c0e74eda0af7199cca16ac3d06d3808a14f2e603b6b0c1a380ff97cc", + "raw-results.json": "6f737d44c3b33c298b59d1bf84d028d6c56524ba14ce34c7db82e3f5dddec9eb", + "results.json": "49a3fd7eb5f960c023ff64f74a264fdda66f74bc1a966dea63c225ecdb44d1fb" + }, + "matrix": { + "numerator": 128, + "denominator": 128, + "display": "128/128", + "percent": 100, + "derivedFrom": "raw-results.json records.length" + }, + "families": [ + "budget", + "registry" + ], + "perFamilyIdentical": true, + "evidenceProducer": { + "note": "The evidence consumed by the run is frozen in git and records its own producer. @workspacejson/cli@0.6.2 was present at execution but provably not loaded: nothing in the runner’s import graph reaches the miner, and the run plans identically with WORKSPACEJSON_CLI unset or pointing at a nonexistent path. It is reproduction tooling, not an input.", + "pinnedCliSha": "defac1e5dce6fb692a48e775fb44854b371cbca4", + "miningCoreBundleSha256": "7aa5ae231d6713449d6c1790f0b19a509e82ec0c84d67a8a6a52ff492ec27bb8" + }, + "npmIntegrity": { + "@workspacejson/cli@0.6.2": "sha512-DyXe4oY4s6paN9lgLkFnhj9x46Excg3GSSQfcF3VBTzGj2LUeosaM3iZ5NgM1was8hQWhxibyS1a7YOq5OxI5Q==", + "@workspacejson/spec@0.5.0": "sha512-KpsUxvLXFHHHKY6F58tWBnqsx5REJjK99Kum1+ATU4b8oUGlStfVkWyphNQ+nFZU3hy/ckNLZTxs4mpOeWGQLA==", + "@workspacejson/rules@0.5.0": "sha512-UlJUnDdc1In4oAMCNMFbFnCAVUGSb1HR0MeP3Pa6Db3uHrH/OvXPyQCqGXLDe9ii3D+6Ua/OHAJMeocKv2bL1Q==" + } + } +} diff --git a/experiments/hac-343/evidence/metric-definitions.json b/experiments/hac-343/evidence/metric-definitions.json new file mode 100644 index 0000000..0775e16 --- /dev/null +++ b/experiments/hac-343/evidence/metric-definitions.json @@ -0,0 +1,179 @@ +{ + "experiment": "HAC-343", + "kind": "metric definitions", + "status": "FROZEN_BEFORE_RESULTS", + "frozenRule": "This file defines every arm, label and metric before any result exists. It is committed in its own commit, ahead of the commit that adds results.json. A metric that is defined after its value is known is not a measurement, and the packet's central claim is reproducibility. If a definition here proves unworkable during the run, the correct repair is a new revision of this file with the reason recorded in supersedes[], never an edit in place once a result has been generated against it.", + "revision": "r01", + "supersedes": [], + + "boundedClaim": { + "statement": "Global locking can preserve safety by sacrificing concurrency; per-target locking preserves concurrency but can miss hazards spanning distinct targets; Interlock tests whether composition evidence can preserve both on this frozen corpus.", + "rule": "This is the only claim shape the experiment may support. No arm is described as simply 'safe' or 'unsafe' without naming the hazard class it was tested against, and no result is generalised beyond this corpus.", + "notAClaim": "That per-target locking is a poor engineering practice. It is correct for the hazard it addresses — same-target contention — and this experiment tests a different hazard class." + }, + + "scopeBoundary": { + "usesOnly": "Semantics already proven by HAC-330 and HAC-326 and present in the deterministic decision core at src/broker/pairing/arbitrate.ts.", + "notClaimed": [ + "HAC-317 joint human authorization", + "exactly-once execution", + "restart safety or recovery", + "production readiness", + "Agent Runtime or Agent Gateway participation", + "any lifecycle state not present in the frozen evidence" + ], + "boundedTo": "The frozen corpus enumerated in corpus.json. Every rate below is a property of that corpus and is not a population estimate." + }, + + "decisionCore": { + "module": "dist/broker/pairing/arbitrate.js", + "source": "src/broker/pairing/arbitrate.ts", + "rationale": "arbitrate.ts is the only core that emits ALLOW_SERIALIZED. experiments/hac-330/lib/decide.mjs answers a different question (a fixed set submitted together) and collapses every coupled outcome to WITHHOLD_SERIALIZE, which would pin Safe Parallelism Retained at zero for every coupled scenario and make the Interlock arm indistinguishable from arm A2 by construction. Binding the evaluation to decide.mjs would therefore produce a null result that looked like a finding.", + "buildCommand": "pnpm run build" + }, + + "arms": { + "A1_uncoordinated": { + "label": "Uncoordinated baseline", + "mechanism": "No composition-aware decision. Every intent proceeds as soon as it arrives.", + "expectedRole": "Establishes that the hazard is real and that each intent is individually valid.", + "canFail": "If A1 produces no unsafe joint state on a coupled scenario, the scenario does not encode a real composition hazard and is reported as a corpus defect, not as an Interlock success." + }, + "A2_global_lock": { + "label": "Naive safe baseline — global serialization", + "mechanism": "A single coarse lock over the whole target. Exactly one intent mutates at a time; every other intent waits regardless of what it writes or what evidence exists.", + "specifiedBy": "HAC-343 'Required arms' item 2.", + "expectedRole": "The simplest mechanism that prevents concurrent mutation outright.", + "knownWeakness": "Safe Parallelism Retained is zero for this arm by construction, not by measurement. Reporting A4 as beating A2 on parallelism alone is therefore a tautology and is not permitted as a headline claim. A2 exists to establish the safety ceiling and the concurrency floor.", + "validityGate": { + "rule": "A2 must serialize every scenario without exception, including those labelled INDEPENDENT and SAME_TARGET_CONTENTION. A single concurrent permit means the global lock is not global and the run FAILS.", + "rationale": "Same reason as A3's gate: a baseline that did not actually lock would make every downstream comparison meaningless." + } + }, + "A3_per_target_lock": { + "label": "Credible per-target locking baseline", + "mechanism": "A lock per written path. Two intents proceed concurrently when their target path sets are disjoint, and serialize only when they share a path.", + "specifiedBy": "HAC-319 'Do not weaken the naive baseline to make Interlock look good. It should be the strongest simple alternative a skeptical judge would reasonably propose.' HAC-343 r02 adopts the four-arm design so the child agrees with the parent.", + "namingRule": "A3 is NEVER described as a 'safe baseline'. It is safe against same-target contention and may remain blind to composition hazards spanning distinct targets. Any rendering that calls A3 simply safe or simply unsafe, without naming the hazard class, is a claim defect.", + "expectedRole": "The coordination discipline a skeptical judge proposes instead of Interlock. It has no access to co-change evidence.", + "validityGate": { + "rule": "A3 must serialize every scenario labelled SAME_TARGET_CONTENTION. If it does not, it is a defective lock rather than a blind one, and the run FAILS rather than reporting A3 as unsafe.", + "rationale": "Without this gate an allow-all implementation would produce the same unsafe result as a real per-target lock, and the finding would be worthless — a skeptical judge would correctly dismiss it as a strawman that never locked anything. The gate is what makes an unsafe A3 result mean 'per-target locking cannot see this hazard' rather than 'their baseline was broken'." + }, + "canFail": "If A3 withholds the cross-target coupled composition, Interlock's distinguishing claim is false on this corpus and the finding is reported as such. This arm is included precisely because it is capable of defeating or narrowing the product thesis." + }, + "A4_interlock": { + "label": "Interlock treatment", + "mechanism": "arbitrate() over the frozen co-change evidence envelope, pending-intent set, and source revision.", + "outcomeMapping": { + "ALLOW_PARALLEL": "proceeds concurrently; counts as retained parallelism", + "ALLOW_SERIALIZED": "proceeds alone while coupled peers withhold; safe progress but NOT parallelism", + "WITHHOLD_SERIALIZE": "withheld pending the leading intent; not parallelism", + "INSUFFICIENT_EVIDENCE": "refused fail-closed; excluded from parallelism metrics and counted under refusal" + } + } + }, + + "groundTruthLabels": { + "rule": "Every scenario carries exactly one label, assigned by construction of the fixture history and independent of any arm's output.", + "COUPLED": "CROSS-TARGET by construction: the two intents write DISTINCT paths, and the fixture history contains a qualifying co-change pair spanning those paths at support >= couplingMinSupport. Composing both intents violates the target invariant. Distinct paths are required — a coupling within one path would be visible to per-target locking and would not test the distinction this experiment exists to measure.", + "INDEPENDENT": "The two intents write distinct paths and the fixture history contains no qualifying pair spanning them. Composing both intents preserves the target invariant.", + "SAME_TARGET_CONTENTION": "Both intents write the SAME path. Every lock arm must serialize these; A4 is not required to, because the target's own revision check already refuses the second write. Present solely to prove A2 and A3 are real locks rather than allow-all implementations, and therefore that an unsafe A3 result on COUPLED scenarios is blindness rather than absence of a lock.", + "EVIDENCE_PERTURBED": "Identical intents and identical final tree, but a history in which the coupling is absent. Used to test that the decision follows the evidence rather than the intents.", + "EVIDENCE_INADMISSIBLE": "The evidence envelope is absent, malformed, unmined, shallow, misattributed, or pinned to a different revision. The correct behavior is explicit refusal, never a permit." + }, + + "metrics": { + "unsafeJointStateRate": { + "question": "How often does an arm allow a composition that violates the target invariant?", + "numerator": "scenarios in which the arm permitted both intents and the resulting target state failed the invariant sum(services[].reserved) <= budget.totalReservable", + "denominator": "all scenarios labelled COUPLED", + "excluded": "scenarios labelled EVIDENCE_INADMISSIBLE, which are scored under refusalCorrectness instead; the exclusion count is reported alongside the rate", + "direction": "lower is better; 0 is the safety bar" + }, + "permitRate": { + "question": "How much work does the arm let through at all?", + "numerator": "scenarios in which at least one intent proceeded", + "denominator": "all scenarios", + "note": "Reported per arm without judgement. A high permit rate is only a virtue when unsafeJointStateRate is 0." + }, + "falseBlockRate": { + "question": "How often does the arm withhold work that was safe to run concurrently?", + "numerator": "scenarios labelled INDEPENDENT in which the arm did not permit both intents to proceed concurrently", + "denominator": "all scenarios labelled INDEPENDENT with admissible evidence", + "excluded": "EVIDENCE_INADMISSIBLE scenarios; a fail-closed refusal on unreadable evidence is correct behavior and must not be scored as a false block", + "direction": "lower is better" + }, + "evidenceSensitivityRate": { + "question": "Does the decision change when the evidence changes, holding the intents fixed?", + "numerator": "EVIDENCE_PERTURBED scenarios in which the arm's decision differs from its decision on the corresponding COUPLED scenario", + "denominator": "all EVIDENCE_PERTURBED scenarios", + "note": "A1, A2 and A3 consume no evidence and are expected to score 0. That is the point: it demonstrates their decisions are not evidence-driven. A non-zero score for A1/A2/A3 indicates a harness defect and fails the run." + }, + "lockValidity": { + "question": "Did the lock arms actually lock?", + "numerator": "SAME_TARGET_CONTENTION scenarios the arm serialized", + "denominator": "all SAME_TARGET_CONTENTION scenarios", + "appliesTo": ["A2_global_lock", "A3_per_target_lock"], + "gate": "Anything below 100% for A2 or A3 fails the run outright. This is a precondition on the harness, not a score to compare arms on, and it is reported before any other metric so a reader can see the baselines were real before reading what they missed.", + "note": "A1 is expected to score 0 — it has no lock — and A4 is not scored here; its refusal of a second same-path write comes from the target revision check, not from a lock." + }, + "refusalCorrectness": { + "question": "Does inadmissible evidence produce an explicit refusal rather than a permit?", + "numerator": "EVIDENCE_INADMISSIBLE scenarios refused with a machine-readable reason code", + "denominator": "all EVIDENCE_INADMISSIBLE scenarios", + "note": "Only A4 can score above 0; the lock arms have no evidence input and will permit. Reported to make that asymmetry visible rather than to flatter A4." + } + }, + + "headlineKpi": { + "name": "Safe Parallelism Retained", + "abbreviation": "SPR", + "informalStatement": "Of the concurrent actions that were genuinely independent, what fraction did this arm allow to run in parallel — while withholding every unsafe composition?", + "numerator": "scenarios labelled INDEPENDENT in which the arm permitted both intents to proceed concurrently", + "denominator": "all scenarios labelled INDEPENDENT with admissible evidence", + "excludedFromDenominator": [ + "EVIDENCE_INADMISSIBLE scenarios — refusal there is correct and scoring it as lost parallelism would penalise fail-closed behavior", + "COUPLED scenarios — withholding there is the safety property, not lost parallelism" + ], + "safetyPrecondition": { + "rule": "SPR is reported ONLY as an ordered pair with unsafeJointStateRate, never as a bare number.", + "rationale": "An arm that permits everything scores SPR = 100% and is unsafe. Publishing SPR alone would make the worst arm look best. The canonical rendering is 'SPR X% at unsafe-joint-state rate Y%'.", + "qualifiedClaim": "An arm may be described as retaining safe parallelism only when its unsafeJointStateRate is 0. Otherwise the SPR figure is reported with the qualifier 'unsafe' attached." + }, + "ambiguousCases": { + "ALLOW_SERIALIZED": "counts as permitted but NOT as parallel; it appears in permitRate, not in the SPR numerator", + "INSUFFICIENT_EVIDENCE": "excluded from both numerator and denominator; counted under refusalCorrectness", + "rule": "No scenario may be silently dropped. Every exclusion is counted and printed next to the metric it was excluded from." + }, + "replacementClause": "If SPR proves misleading during the run, it is replaced in a new revision of this file with the reason recorded, before results are generated. A decorative metric is not preserved." + }, + + "corpusRequirements": { + "rule": "The corpus is invalid, and the run fails, unless every requirement below holds. Checked mechanically by run-experiment.mjs before any arm executes.", + "mustContain": [ + "at least one COUPLED scenario whose two intents write DISTINCT paths — without cross-target coupling the experiment cannot test the distinction between per-target locking and composition evidence, which is its entire subject", + "at least one INDEPENDENT scenario, or SPR and falseBlockRate have empty denominators and report n/a", + "at least one SAME_TARGET_CONTENTION scenario, or the A2 and A3 validity gates cannot be evaluated and the baselines are unproven", + "at least one EVIDENCE_PERTURBED scenario, or evidenceSensitivityRate has an empty denominator", + "at least one EVIDENCE_INADMISSIBLE scenario per distinct refusal reason the core can emit and the corpus can produce" + ], + "balanceRule": "Scenario counts per label are recorded in corpus.json and printed with the results. The corpus is not tuned after seeing any arm's output; a corpus change invalidates existing results and requires a rerun.", + "noOptimisation": "No scenario is added or removed to move a metric. Scenarios are derived from the fixture histories, which are generated before any arm runs." + }, + + "reportingConvention": { + "intervals": "No confidence intervals are reported. The decision function is deterministic and the corpus is a frozen finite enumeration evaluated exhaustively, so there is no sampling process and no sampling variability. Attaching an interval would imply a population and a draw that do not exist. Exact counts are reported instead.", + "denominators": "Every rate is printed as numerator/denominator, never as a bare percentage.", + "zeroDenominator": "A metric whose denominator is 0 is reported as 'n/a (0 cases)' and never as 0%, 100%, or a passing green state.", + "negativeFindings": "Results that contradict the product thesis are retained in results.json and in the judge-safe summary. If A4 does not materially outperform A3 on SPR at equal safety, that is the reported finding and the product claim is narrowed.", + "promotion": "No value from this experiment appears on any judge-facing surface (HAC-335, HAC-336, cockpit) until results.json is committed and verify-packet.mjs passes." + }, + + "reproduction": { + "buildCommand": "pnpm run build", + "runCommand": "WORKSPACEJSON_CLI= node experiments/hac-343/bin/run-experiment.mjs", + "verifyCommand": "node experiments/hac-343/bin/verify-packet.mjs", + "checkoutNote": "run-experiment.mjs refuses to start unless the pinned workspacejson/cli and workspacejson/standard checkouts match experiments/hac-330/evidence/pins.json and are clean. Resolution walks up for a sibling 'cli' directory; set WORKSPACEJSON_CLI when this checkout is not inside the documented interlock-workspace layout." + } +} diff --git a/experiments/hac-343/evidence/raw-results.json b/experiments/hac-343/evidence/raw-results.json new file mode 100644 index 0000000..040442b --- /dev/null +++ b/experiments/hac-343/evidence/raw-results.json @@ -0,0 +1,11076 @@ +{ + "experiment": "HAC-343", + "kind": "raw results", + "frozenInputs": { + "metricDefinitions": "0a6babbc5d1a3f69b057f98093108ee508072e48", + "corpus": "dbdcaa940933f90091a838f5f183031c7556afad", + "executionSemantics": "276750ba7a4a51461fb2447b361d69be5e2a020b" + }, + "sourceRevisions": { + "budget": { + "baseline": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "perturbed": "db8a63ec9405191bdd40d0ed0fc69684fca5d17b" + }, + "registry": { + "baseline": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "perturbed": "f50ecbe40530af357750952235bb262948f9e84e" + } + }, + "records": [ + { + "scenarioId": "budget/coupled/alpha-beta", + "family": "budget", + "label": "COUPLED", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A1_uncoordinated", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "NONE#i0", + "NONE#i1" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/coupled/alpha-beta", + "family": "budget", + "label": "COUPLED", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A1_uncoordinated", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "NONE#i1", + "NONE#i0" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/coupled/alpha-beta", + "family": "budget", + "label": "COUPLED", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A2_global_lock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "projected total 140 against ceiling 130" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "88b218be66522efb9b99b1cdbb31adc1082239c037cf46b6306a9a67e95579b8", + "holds": true + } + }, + { + "scenarioId": "budget/coupled/alpha-beta", + "family": "budget", + "label": "COUPLED", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A2_global_lock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "projected total 140 against ceiling 130" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 40\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 40, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "7628d5c46706d47fc51ada9c40a2ddd8ebd7935c32834a2bab50b476ca79de18", + "holds": true + } + }, + { + "scenarioId": "budget/coupled/alpha-beta", + "family": "budget", + "label": "COUPLED", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A3_per_target_lock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "services/beta/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "services/alpha/reservation.json", + "services/beta/reservation.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/coupled/alpha-beta", + "family": "budget", + "label": "COUPLED", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A3_per_target_lock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "services/beta/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "services/beta/reservation.json", + "services/alpha/reservation.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/coupled/alpha-beta", + "family": "budget", + "label": "COUPLED", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A4_interlock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": [ + { + "decision": "ALLOW_SERIALIZED", + "reasonCode": "SERIALIZED_PRECEDENCE", + "detail": "1 qualifying co-change coupling(s) at basis eb67a6f56b3bf7e71846e7324d21af44565c0b70; this intent holds precedence within the coupled set and proceeds alone while the others are withheld", + "couplings": [ + { + "correlationIds": [ + "ilk-54cdbe73c44fa9ca3702b594", + "ilk-b822d34b80e66d99a2bddc7a" + ], + "files": [ + "services/alpha/reservation.json", + "services/beta/reservation.json" + ], + "support": 8, + "occurrences": 10 + } + ], + "evidenceRefs": [ + "basis:eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "artifact:sha256:2c021d0c593aac252c4f7f61d8d6bd03b3bfcccf7a2f647691a1a2b894eb21d6" + ] + }, + { + "decision": "WITHHOLD_SERIALIZE", + "reasonCode": "COUPLING_OBSERVED", + "detail": "1 qualifying co-change coupling(s) between this intent and 1 intent(s) already in flight at basis eb67a6f56b3bf7e71846e7324d21af44565c0b70; this intent does not hold precedence, so the composition is withheld and it must be resubmitted once the leading intent settles", + "couplings": [ + { + "correlationIds": [ + "ilk-b822d34b80e66d99a2bddc7a", + "ilk-54cdbe73c44fa9ca3702b594" + ], + "files": [ + "services/alpha/reservation.json", + "services/beta/reservation.json" + ], + "support": 8, + "occurrences": 10 + } + ], + "evidenceRefs": [ + "basis:eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "artifact:sha256:2c021d0c593aac252c4f7f61d8d6bd03b3bfcccf7a2f647691a1a2b894eb21d6" + ] + } + ], + "outcomes": [ + { + "intentId": "i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "WITHHELD_SERIALIZE", + "detail": "1 qualifying co-change coupling(s) between this intent and 1 intent(s) already in flight at basis eb67a6f56b3bf7e71846e7324d21af44565c0b70; this intent does not hold precedence, so the composition is withheld and it must be resubmitted once the leading intent settles" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "88b218be66522efb9b99b1cdbb31adc1082239c037cf46b6306a9a67e95579b8", + "holds": true + } + }, + { + "scenarioId": "budget/coupled/alpha-beta", + "family": "budget", + "label": "COUPLED", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A4_interlock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": [ + { + "decision": "ALLOW_SERIALIZED", + "reasonCode": "SERIALIZED_PRECEDENCE", + "detail": "1 qualifying co-change coupling(s) at basis eb67a6f56b3bf7e71846e7324d21af44565c0b70; this intent holds precedence within the coupled set and proceeds alone while the others are withheld", + "couplings": [ + { + "correlationIds": [ + "ilk-b822d34b80e66d99a2bddc7a", + "ilk-54cdbe73c44fa9ca3702b594" + ], + "files": [ + "services/alpha/reservation.json", + "services/beta/reservation.json" + ], + "support": 8, + "occurrences": 10 + } + ], + "evidenceRefs": [ + "basis:eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "artifact:sha256:2c021d0c593aac252c4f7f61d8d6bd03b3bfcccf7a2f647691a1a2b894eb21d6" + ] + }, + { + "decision": "WITHHOLD_SERIALIZE", + "reasonCode": "COUPLING_OBSERVED", + "detail": "1 qualifying co-change coupling(s) between this intent and 1 intent(s) already in flight at basis eb67a6f56b3bf7e71846e7324d21af44565c0b70; this intent does not hold precedence, so the composition is withheld and it must be resubmitted once the leading intent settles", + "couplings": [ + { + "correlationIds": [ + "ilk-54cdbe73c44fa9ca3702b594", + "ilk-b822d34b80e66d99a2bddc7a" + ], + "files": [ + "services/alpha/reservation.json", + "services/beta/reservation.json" + ], + "support": 8, + "occurrences": 10 + } + ], + "evidenceRefs": [ + "basis:eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "artifact:sha256:2c021d0c593aac252c4f7f61d8d6bd03b3bfcccf7a2f647691a1a2b894eb21d6" + ] + } + ], + "outcomes": [ + { + "intentId": "i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "WITHHELD_SERIALIZE", + "detail": "1 qualifying co-change coupling(s) between this intent and 1 intent(s) already in flight at basis eb67a6f56b3bf7e71846e7324d21af44565c0b70; this intent does not hold precedence, so the composition is withheld and it must be resubmitted once the leading intent settles" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 40\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 40, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "7628d5c46706d47fc51ada9c40a2ddd8ebd7935c32834a2bab50b476ca79de18", + "holds": true + } + }, + { + "scenarioId": "budget/independent/alpha-gamma", + "family": "budget", + "label": "INDEPENDENT", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A1_uncoordinated", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/gamma/reservation.json", + "service": "gamma", + "reserved": 28, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 108 against ceiling 130" + } + ], + "lockGroups": [ + "NONE#i0", + "NONE#i1" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 28\n }\n ],\n \"total\": 128,\n \"headroom\": 2,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 28 + } + }, + "stateSha256": "3765e5a10f5c9295041f2c6bae2abac9847a687b8fa0fd555922d9f8dab8a54e", + "holds": true + } + }, + { + "scenarioId": "budget/independent/alpha-gamma", + "family": "budget", + "label": "INDEPENDENT", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A1_uncoordinated", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/gamma/reservation.json", + "service": "gamma", + "reserved": 28, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 108 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "NONE#i1", + "NONE#i0" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 28\n }\n ],\n \"total\": 128,\n \"headroom\": 2,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 28 + } + }, + "stateSha256": "3765e5a10f5c9295041f2c6bae2abac9847a687b8fa0fd555922d9f8dab8a54e", + "holds": true + } + }, + { + "scenarioId": "budget/independent/alpha-gamma", + "family": "budget", + "label": "INDEPENDENT", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A2_global_lock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/gamma/reservation.json", + "service": "gamma", + "reserved": 28, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 128 against ceiling 130" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 28\n }\n ],\n \"total\": 128,\n \"headroom\": 2,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 28 + } + }, + "stateSha256": "3765e5a10f5c9295041f2c6bae2abac9847a687b8fa0fd555922d9f8dab8a54e", + "holds": true + } + }, + { + "scenarioId": "budget/independent/alpha-gamma", + "family": "budget", + "label": "INDEPENDENT", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A2_global_lock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/gamma/reservation.json", + "service": "gamma", + "reserved": 28, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 108 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 128 against ceiling 130" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 28\n }\n ],\n \"total\": 128,\n \"headroom\": 2,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 28 + } + }, + "stateSha256": "3765e5a10f5c9295041f2c6bae2abac9847a687b8fa0fd555922d9f8dab8a54e", + "holds": true + } + }, + { + "scenarioId": "budget/independent/alpha-gamma", + "family": "budget", + "label": "INDEPENDENT", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A3_per_target_lock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/gamma/reservation.json", + "service": "gamma", + "reserved": 28, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "services/gamma/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 108 against ceiling 130" + } + ], + "lockGroups": [ + "services/alpha/reservation.json", + "services/gamma/reservation.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 28\n }\n ],\n \"total\": 128,\n \"headroom\": 2,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 28 + } + }, + "stateSha256": "3765e5a10f5c9295041f2c6bae2abac9847a687b8fa0fd555922d9f8dab8a54e", + "holds": true + } + }, + { + "scenarioId": "budget/independent/alpha-gamma", + "family": "budget", + "label": "INDEPENDENT", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A3_per_target_lock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/gamma/reservation.json", + "service": "gamma", + "reserved": 28, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "services/gamma/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 108 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "services/gamma/reservation.json", + "services/alpha/reservation.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 28\n }\n ],\n \"total\": 128,\n \"headroom\": 2,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 28 + } + }, + "stateSha256": "3765e5a10f5c9295041f2c6bae2abac9847a687b8fa0fd555922d9f8dab8a54e", + "holds": true + } + }, + { + "scenarioId": "budget/independent/alpha-gamma", + "family": "budget", + "label": "INDEPENDENT", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A4_interlock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/gamma/reservation.json", + "service": "gamma", + "reserved": 28, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": [ + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at eb67a6f56b3bf7e71846e7324d21af44565c0b70 was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "artifact:sha256:2c021d0c593aac252c4f7f61d8d6bd03b3bfcccf7a2f647691a1a2b894eb21d6" + ] + }, + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at eb67a6f56b3bf7e71846e7324d21af44565c0b70 was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "artifact:sha256:2c021d0c593aac252c4f7f61d8d6bd03b3bfcccf7a2f647691a1a2b894eb21d6" + ] + } + ], + "outcomes": [ + { + "intentId": "i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 108 against ceiling 130" + } + ], + "lockGroups": null, + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 28\n }\n ],\n \"total\": 128,\n \"headroom\": 2,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 28 + } + }, + "stateSha256": "3765e5a10f5c9295041f2c6bae2abac9847a687b8fa0fd555922d9f8dab8a54e", + "holds": true + } + }, + { + "scenarioId": "budget/independent/alpha-gamma", + "family": "budget", + "label": "INDEPENDENT", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A4_interlock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/gamma/reservation.json", + "service": "gamma", + "reserved": 28, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": [ + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at eb67a6f56b3bf7e71846e7324d21af44565c0b70 was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "artifact:sha256:2c021d0c593aac252c4f7f61d8d6bd03b3bfcccf7a2f647691a1a2b894eb21d6" + ] + }, + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at eb67a6f56b3bf7e71846e7324d21af44565c0b70 was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "artifact:sha256:2c021d0c593aac252c4f7f61d8d6bd03b3bfcccf7a2f647691a1a2b894eb21d6" + ] + } + ], + "outcomes": [ + { + "intentId": "i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 108 against ceiling 130" + }, + { + "intentId": "i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": null, + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 28\n }\n ],\n \"total\": 128,\n \"headroom\": 2,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 28 + } + }, + "stateSha256": "3765e5a10f5c9295041f2c6bae2abac9847a687b8fa0fd555922d9f8dab8a54e", + "holds": true + } + }, + { + "scenarioId": "budget/same-target/alpha-alpha", + "family": "budget", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A1_uncoordinated", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 55, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 115 against ceiling 130" + } + ], + "lockGroups": [ + "NONE#i0", + "NONE#i1" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 55\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 115,\n \"headroom\": 15,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 55, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "27d210c9e587149b6ed22bd594a9aeb908293ee2acf1fe4d0dd919f6bcb24112", + "holds": true + } + }, + { + "scenarioId": "budget/same-target/alpha-alpha", + "family": "budget", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A1_uncoordinated", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 55, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 115 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "NONE#i1", + "NONE#i0" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "88b218be66522efb9b99b1cdbb31adc1082239c037cf46b6306a9a67e95579b8", + "holds": true + } + }, + { + "scenarioId": "budget/same-target/alpha-alpha", + "family": "budget", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A2_global_lock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 55, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 115 against ceiling 130" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 55\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 115,\n \"headroom\": 15,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 55, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "27d210c9e587149b6ed22bd594a9aeb908293ee2acf1fe4d0dd919f6bcb24112", + "holds": true + } + }, + { + "scenarioId": "budget/same-target/alpha-alpha", + "family": "budget", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A2_global_lock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 55, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 115 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "88b218be66522efb9b99b1cdbb31adc1082239c037cf46b6306a9a67e95579b8", + "holds": true + } + }, + { + "scenarioId": "budget/same-target/alpha-alpha", + "family": "budget", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A3_per_target_lock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 55, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 115 against ceiling 130" + } + ], + "lockGroups": [ + "services/alpha/reservation.json" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 55\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 115,\n \"headroom\": 15,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 55, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "27d210c9e587149b6ed22bd594a9aeb908293ee2acf1fe4d0dd919f6bcb24112", + "holds": true + } + }, + { + "scenarioId": "budget/same-target/alpha-alpha", + "family": "budget", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A3_per_target_lock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 55, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 115 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "services/alpha/reservation.json" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "88b218be66522efb9b99b1cdbb31adc1082239c037cf46b6306a9a67e95579b8", + "holds": true + } + }, + { + "scenarioId": "budget/same-target/alpha-alpha", + "family": "budget", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A4_interlock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 55, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": [ + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at eb67a6f56b3bf7e71846e7324d21af44565c0b70 was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "artifact:sha256:2c021d0c593aac252c4f7f61d8d6bd03b3bfcccf7a2f647691a1a2b894eb21d6" + ] + }, + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at eb67a6f56b3bf7e71846e7324d21af44565c0b70 was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "artifact:sha256:2c021d0c593aac252c4f7f61d8d6bd03b3bfcccf7a2f647691a1a2b894eb21d6" + ] + } + ], + "outcomes": [ + { + "intentId": "i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 115 against ceiling 130" + } + ], + "lockGroups": null, + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 55\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 115,\n \"headroom\": 15,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 55, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "27d210c9e587149b6ed22bd594a9aeb908293ee2acf1fe4d0dd919f6bcb24112", + "holds": true + } + }, + { + "scenarioId": "budget/same-target/alpha-alpha", + "family": "budget", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A4_interlock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 55, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/baseline.evidence.json", + "sha256": "f716297558dfa325e8eef222623af0a461d0879f739cd7d0f7853d7a1ebd6f22", + "expectedReason": null + }, + "error": null, + "verdicts": [ + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at eb67a6f56b3bf7e71846e7324d21af44565c0b70 was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "artifact:sha256:2c021d0c593aac252c4f7f61d8d6bd03b3bfcccf7a2f647691a1a2b894eb21d6" + ] + }, + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at eb67a6f56b3bf7e71846e7324d21af44565c0b70 was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "artifact:sha256:2c021d0c593aac252c4f7f61d8d6bd03b3bfcccf7a2f647691a1a2b894eb21d6" + ] + } + ], + "outcomes": [ + { + "intentId": "i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 115 against ceiling 130" + }, + { + "intentId": "i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": null, + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "88b218be66522efb9b99b1cdbb31adc1082239c037cf46b6306a9a67e95579b8", + "holds": true + } + }, + { + "scenarioId": "budget/perturbed/alpha-beta", + "family": "budget", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "fixtureRevision": "db8a63ec9405191bdd40d0ed0fc69684fca5d17b", + "arm": "A1_uncoordinated", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/perturbed.evidence.json", + "sha256": "b6dca507294c46997828f5f36d1018cfb3a72c5dd65b7b6e217ba2aedb3cf02b", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "NONE#i0", + "NONE#i1" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/perturbed/alpha-beta", + "family": "budget", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "fixtureRevision": "db8a63ec9405191bdd40d0ed0fc69684fca5d17b", + "arm": "A1_uncoordinated", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/perturbed.evidence.json", + "sha256": "b6dca507294c46997828f5f36d1018cfb3a72c5dd65b7b6e217ba2aedb3cf02b", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "NONE#i1", + "NONE#i0" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/perturbed/alpha-beta", + "family": "budget", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "fixtureRevision": "db8a63ec9405191bdd40d0ed0fc69684fca5d17b", + "arm": "A2_global_lock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/perturbed.evidence.json", + "sha256": "b6dca507294c46997828f5f36d1018cfb3a72c5dd65b7b6e217ba2aedb3cf02b", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "projected total 140 against ceiling 130" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "88b218be66522efb9b99b1cdbb31adc1082239c037cf46b6306a9a67e95579b8", + "holds": true + } + }, + { + "scenarioId": "budget/perturbed/alpha-beta", + "family": "budget", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "fixtureRevision": "db8a63ec9405191bdd40d0ed0fc69684fca5d17b", + "arm": "A2_global_lock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/perturbed.evidence.json", + "sha256": "b6dca507294c46997828f5f36d1018cfb3a72c5dd65b7b6e217ba2aedb3cf02b", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "projected total 140 against ceiling 130" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 40\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 40, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "7628d5c46706d47fc51ada9c40a2ddd8ebd7935c32834a2bab50b476ca79de18", + "holds": true + } + }, + { + "scenarioId": "budget/perturbed/alpha-beta", + "family": "budget", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "fixtureRevision": "db8a63ec9405191bdd40d0ed0fc69684fca5d17b", + "arm": "A3_per_target_lock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/perturbed.evidence.json", + "sha256": "b6dca507294c46997828f5f36d1018cfb3a72c5dd65b7b6e217ba2aedb3cf02b", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "services/beta/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "services/alpha/reservation.json", + "services/beta/reservation.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/perturbed/alpha-beta", + "family": "budget", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "fixtureRevision": "db8a63ec9405191bdd40d0ed0fc69684fca5d17b", + "arm": "A3_per_target_lock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/perturbed.evidence.json", + "sha256": "b6dca507294c46997828f5f36d1018cfb3a72c5dd65b7b6e217ba2aedb3cf02b", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "services/beta/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "services/beta/reservation.json", + "services/alpha/reservation.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/perturbed/alpha-beta", + "family": "budget", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "fixtureRevision": "db8a63ec9405191bdd40d0ed0fc69684fca5d17b", + "arm": "A4_interlock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/perturbed.evidence.json", + "sha256": "b6dca507294c46997828f5f36d1018cfb3a72c5dd65b7b6e217ba2aedb3cf02b", + "expectedReason": null + }, + "error": null, + "verdicts": [ + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at db8a63ec9405191bdd40d0ed0fc69684fca5d17b was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:db8a63ec9405191bdd40d0ed0fc69684fca5d17b", + "artifact:sha256:ec9bd6736e951f1a03b89bd02da918f67c3fde6ff4f6dfca25b4dc48120d08d5" + ] + }, + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at db8a63ec9405191bdd40d0ed0fc69684fca5d17b was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:db8a63ec9405191bdd40d0ed0fc69684fca5d17b", + "artifact:sha256:ec9bd6736e951f1a03b89bd02da918f67c3fde6ff4f6dfca25b4dc48120d08d5" + ] + } + ], + "outcomes": [ + { + "intentId": "i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": null, + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/perturbed/alpha-beta", + "family": "budget", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "fixtureRevision": "db8a63ec9405191bdd40d0ed0fc69684fca5d17b", + "arm": "A4_interlock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/perturbed.evidence.json", + "sha256": "b6dca507294c46997828f5f36d1018cfb3a72c5dd65b7b6e217ba2aedb3cf02b", + "expectedReason": null + }, + "error": null, + "verdicts": [ + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at db8a63ec9405191bdd40d0ed0fc69684fca5d17b was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:db8a63ec9405191bdd40d0ed0fc69684fca5d17b", + "artifact:sha256:ec9bd6736e951f1a03b89bd02da918f67c3fde6ff4f6dfca25b4dc48120d08d5" + ] + }, + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at db8a63ec9405191bdd40d0ed0fc69684fca5d17b was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:db8a63ec9405191bdd40d0ed0fc69684fca5d17b", + "artifact:sha256:ec9bd6736e951f1a03b89bd02da918f67c3fde6ff4f6dfca25b4dc48120d08d5" + ] + } + ], + "outcomes": [ + { + "intentId": "i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": null, + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/absent", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A1_uncoordinated", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "absent", + "sha256": null, + "expectedReason": "EVIDENCE_ABSENT" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "NONE#i0", + "NONE#i1" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/absent", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A1_uncoordinated", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "absent", + "sha256": null, + "expectedReason": "EVIDENCE_ABSENT" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "NONE#i1", + "NONE#i0" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/absent", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A2_global_lock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "absent", + "sha256": null, + "expectedReason": "EVIDENCE_ABSENT" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "projected total 140 against ceiling 130" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "88b218be66522efb9b99b1cdbb31adc1082239c037cf46b6306a9a67e95579b8", + "holds": true + } + }, + { + "scenarioId": "budget/inadmissible/absent", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A2_global_lock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "absent", + "sha256": null, + "expectedReason": "EVIDENCE_ABSENT" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "projected total 140 against ceiling 130" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 40\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 40, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "7628d5c46706d47fc51ada9c40a2ddd8ebd7935c32834a2bab50b476ca79de18", + "holds": true + } + }, + { + "scenarioId": "budget/inadmissible/absent", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A3_per_target_lock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "absent", + "sha256": null, + "expectedReason": "EVIDENCE_ABSENT" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "services/beta/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "services/alpha/reservation.json", + "services/beta/reservation.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/absent", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A3_per_target_lock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "absent", + "sha256": null, + "expectedReason": "EVIDENCE_ABSENT" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "services/beta/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "services/beta/reservation.json", + "services/alpha/reservation.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/absent", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A4_interlock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "absent", + "sha256": null, + "expectedReason": "EVIDENCE_ABSENT" + }, + "error": null, + "verdicts": [ + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "EVIDENCE_ABSENT", + "detail": "no co-change evidence was supplied; absence of evidence is not evidence of independence", + "couplings": [], + "evidenceRefs": [] + }, + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "EVIDENCE_ABSENT", + "detail": "no co-change evidence was supplied; absence of evidence is not evidence of independence", + "couplings": [], + "evidenceRefs": [] + } + ], + "outcomes": [ + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "EVIDENCE_ABSENT" + }, + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "EVIDENCE_ABSENT" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": "EVIDENCE_ABSENT", + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 40\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 100,\n \"headroom\": 30,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 40, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "7e5fb4253787d6688ed00f21562dd7bf7bd313f97dfba74a923bd439df9ebba1", + "holds": true + } + }, + { + "scenarioId": "budget/inadmissible/absent", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A4_interlock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "absent", + "sha256": null, + "expectedReason": "EVIDENCE_ABSENT" + }, + "error": null, + "verdicts": [ + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "EVIDENCE_ABSENT", + "detail": "no co-change evidence was supplied; absence of evidence is not evidence of independence", + "couplings": [], + "evidenceRefs": [] + }, + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "EVIDENCE_ABSENT", + "detail": "no co-change evidence was supplied; absence of evidence is not evidence of independence", + "couplings": [], + "evidenceRefs": [] + } + ], + "outcomes": [ + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "EVIDENCE_ABSENT" + }, + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "EVIDENCE_ABSENT" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": "EVIDENCE_ABSENT", + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 40\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 100,\n \"headroom\": 30,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 40, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "7e5fb4253787d6688ed00f21562dd7bf7bd313f97dfba74a923bd439df9ebba1", + "holds": true + } + }, + { + "scenarioId": "budget/inadmissible/shallow", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A1_uncoordinated", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/shallow.evidence.json", + "sha256": "6e7b4ddf6446fb76d7509879e6ae3fba8ab9bbbe177b3b6b5ac2262c7fb25678", + "expectedReason": "HISTORY_NOT_MINED" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "NONE#i0", + "NONE#i1" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/shallow", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A1_uncoordinated", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/shallow.evidence.json", + "sha256": "6e7b4ddf6446fb76d7509879e6ae3fba8ab9bbbe177b3b6b5ac2262c7fb25678", + "expectedReason": "HISTORY_NOT_MINED" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "NONE#i1", + "NONE#i0" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/shallow", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A2_global_lock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/shallow.evidence.json", + "sha256": "6e7b4ddf6446fb76d7509879e6ae3fba8ab9bbbe177b3b6b5ac2262c7fb25678", + "expectedReason": "HISTORY_NOT_MINED" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "projected total 140 against ceiling 130" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "88b218be66522efb9b99b1cdbb31adc1082239c037cf46b6306a9a67e95579b8", + "holds": true + } + }, + { + "scenarioId": "budget/inadmissible/shallow", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A2_global_lock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/shallow.evidence.json", + "sha256": "6e7b4ddf6446fb76d7509879e6ae3fba8ab9bbbe177b3b6b5ac2262c7fb25678", + "expectedReason": "HISTORY_NOT_MINED" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "projected total 140 against ceiling 130" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 40\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 40, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "7628d5c46706d47fc51ada9c40a2ddd8ebd7935c32834a2bab50b476ca79de18", + "holds": true + } + }, + { + "scenarioId": "budget/inadmissible/shallow", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A3_per_target_lock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/shallow.evidence.json", + "sha256": "6e7b4ddf6446fb76d7509879e6ae3fba8ab9bbbe177b3b6b5ac2262c7fb25678", + "expectedReason": "HISTORY_NOT_MINED" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "services/beta/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "services/alpha/reservation.json", + "services/beta/reservation.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/shallow", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A3_per_target_lock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/shallow.evidence.json", + "sha256": "6e7b4ddf6446fb76d7509879e6ae3fba8ab9bbbe177b3b6b5ac2262c7fb25678", + "expectedReason": "HISTORY_NOT_MINED" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "services/beta/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "services/beta/reservation.json", + "services/alpha/reservation.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/shallow", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A4_interlock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/shallow.evidence.json", + "sha256": "6e7b4ddf6446fb76d7509879e6ae3fba8ab9bbbe177b3b6b5ac2262c7fb25678", + "expectedReason": "HISTORY_NOT_MINED" + }, + "error": null, + "verdicts": [ + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "HISTORY_NOT_MINED", + "detail": "completeness is NOT_MINED; the history behind this evidence was not successfully mined", + "couplings": [], + "evidenceRefs": [] + }, + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "HISTORY_NOT_MINED", + "detail": "completeness is NOT_MINED; the history behind this evidence was not successfully mined", + "couplings": [], + "evidenceRefs": [] + } + ], + "outcomes": [ + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "HISTORY_NOT_MINED" + }, + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "HISTORY_NOT_MINED" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": "HISTORY_NOT_MINED", + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 40\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 100,\n \"headroom\": 30,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 40, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "7e5fb4253787d6688ed00f21562dd7bf7bd313f97dfba74a923bd439df9ebba1", + "holds": true + } + }, + { + "scenarioId": "budget/inadmissible/shallow", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A4_interlock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/shallow.evidence.json", + "sha256": "6e7b4ddf6446fb76d7509879e6ae3fba8ab9bbbe177b3b6b5ac2262c7fb25678", + "expectedReason": "HISTORY_NOT_MINED" + }, + "error": null, + "verdicts": [ + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "HISTORY_NOT_MINED", + "detail": "completeness is NOT_MINED; the history behind this evidence was not successfully mined", + "couplings": [], + "evidenceRefs": [] + }, + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "HISTORY_NOT_MINED", + "detail": "completeness is NOT_MINED; the history behind this evidence was not successfully mined", + "couplings": [], + "evidenceRefs": [] + } + ], + "outcomes": [ + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "HISTORY_NOT_MINED" + }, + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "HISTORY_NOT_MINED" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": "HISTORY_NOT_MINED", + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 40\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 100,\n \"headroom\": 30,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 40, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "7e5fb4253787d6688ed00f21562dd7bf7bd313f97dfba74a923bd439df9ebba1", + "holds": true + } + }, + { + "scenarioId": "budget/inadmissible/noRepository", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A1_uncoordinated", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/no-repository.evidence.json", + "sha256": "f4fed9c840e8df30ab8405e26079a48dcc509b282110323f3080ddfcd5d0b1dd", + "expectedReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "NONE#i0", + "NONE#i1" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/noRepository", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A1_uncoordinated", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/no-repository.evidence.json", + "sha256": "f4fed9c840e8df30ab8405e26079a48dcc509b282110323f3080ddfcd5d0b1dd", + "expectedReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "NONE#i1", + "NONE#i0" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/noRepository", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A2_global_lock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/no-repository.evidence.json", + "sha256": "f4fed9c840e8df30ab8405e26079a48dcc509b282110323f3080ddfcd5d0b1dd", + "expectedReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "projected total 140 against ceiling 130" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "88b218be66522efb9b99b1cdbb31adc1082239c037cf46b6306a9a67e95579b8", + "holds": true + } + }, + { + "scenarioId": "budget/inadmissible/noRepository", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A2_global_lock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/no-repository.evidence.json", + "sha256": "f4fed9c840e8df30ab8405e26079a48dcc509b282110323f3080ddfcd5d0b1dd", + "expectedReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "projected total 140 against ceiling 130" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 40\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 40, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "7628d5c46706d47fc51ada9c40a2ddd8ebd7935c32834a2bab50b476ca79de18", + "holds": true + } + }, + { + "scenarioId": "budget/inadmissible/noRepository", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A3_per_target_lock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/no-repository.evidence.json", + "sha256": "f4fed9c840e8df30ab8405e26079a48dcc509b282110323f3080ddfcd5d0b1dd", + "expectedReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "services/beta/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "services/alpha/reservation.json", + "services/beta/reservation.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/noRepository", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A3_per_target_lock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/no-repository.evidence.json", + "sha256": "f4fed9c840e8df30ab8405e26079a48dcc509b282110323f3080ddfcd5d0b1dd", + "expectedReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "services/beta/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "services/beta/reservation.json", + "services/alpha/reservation.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/noRepository", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A4_interlock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/no-repository.evidence.json", + "sha256": "f4fed9c840e8df30ab8405e26079a48dcc509b282110323f3080ddfcd5d0b1dd", + "expectedReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + "error": null, + "verdicts": [ + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "HISTORY_NOT_MINED", + "detail": "completeness is NOT_MINED; the history behind this evidence was not successfully mined", + "couplings": [], + "evidenceRefs": [] + }, + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "HISTORY_NOT_MINED", + "detail": "completeness is NOT_MINED; the history behind this evidence was not successfully mined", + "couplings": [], + "evidenceRefs": [] + } + ], + "outcomes": [ + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "HISTORY_NOT_MINED" + }, + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "HISTORY_NOT_MINED" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": "HISTORY_NOT_MINED", + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 40\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 100,\n \"headroom\": 30,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 40, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "7e5fb4253787d6688ed00f21562dd7bf7bd313f97dfba74a923bd439df9ebba1", + "holds": true + } + }, + { + "scenarioId": "budget/inadmissible/noRepository", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A4_interlock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/no-repository.evidence.json", + "sha256": "f4fed9c840e8df30ab8405e26079a48dcc509b282110323f3080ddfcd5d0b1dd", + "expectedReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + "error": null, + "verdicts": [ + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "HISTORY_NOT_MINED", + "detail": "completeness is NOT_MINED; the history behind this evidence was not successfully mined", + "couplings": [], + "evidenceRefs": [] + }, + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "HISTORY_NOT_MINED", + "detail": "completeness is NOT_MINED; the history behind this evidence was not successfully mined", + "couplings": [], + "evidenceRefs": [] + } + ], + "outcomes": [ + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "HISTORY_NOT_MINED" + }, + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "HISTORY_NOT_MINED" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": "HISTORY_NOT_MINED", + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 40\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 100,\n \"headroom\": 30,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 40, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "7e5fb4253787d6688ed00f21562dd7bf7bd313f97dfba74a923bd439df9ebba1", + "holds": true + } + }, + { + "scenarioId": "budget/inadmissible/misattributed", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A1_uncoordinated", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/misattributed.evidence.json", + "sha256": "47b1e8693c97d46cf3ae314013fa15ede0fe098ea056766e5252835ff8234953", + "expectedReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "NONE#i0", + "NONE#i1" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/misattributed", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A1_uncoordinated", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/misattributed.evidence.json", + "sha256": "47b1e8693c97d46cf3ae314013fa15ede0fe098ea056766e5252835ff8234953", + "expectedReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "NONE#i1", + "NONE#i0" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/misattributed", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A2_global_lock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/misattributed.evidence.json", + "sha256": "47b1e8693c97d46cf3ae314013fa15ede0fe098ea056766e5252835ff8234953", + "expectedReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "projected total 140 against ceiling 130" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "88b218be66522efb9b99b1cdbb31adc1082239c037cf46b6306a9a67e95579b8", + "holds": true + } + }, + { + "scenarioId": "budget/inadmissible/misattributed", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A2_global_lock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/misattributed.evidence.json", + "sha256": "47b1e8693c97d46cf3ae314013fa15ede0fe098ea056766e5252835ff8234953", + "expectedReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "projected total 140 against ceiling 130" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 40\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 120,\n \"headroom\": 10,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 40, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "7628d5c46706d47fc51ada9c40a2ddd8ebd7935c32834a2bab50b476ca79de18", + "holds": true + } + }, + { + "scenarioId": "budget/inadmissible/misattributed", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A3_per_target_lock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/misattributed.evidence.json", + "sha256": "47b1e8693c97d46cf3ae314013fa15ede0fe098ea056766e5252835ff8234953", + "expectedReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i1", + "lockKey": "services/beta/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "services/alpha/reservation.json", + "services/beta/reservation.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/misattributed", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A3_per_target_lock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/misattributed.evidence.json", + "sha256": "47b1e8693c97d46cf3ae314013fa15ede0fe098ea056766e5252835ff8234953", + "expectedReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "services/beta/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + }, + { + "intentId": "i0", + "lockKey": "services/alpha/reservation.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "projected total 120 against ceiling 130" + } + ], + "lockGroups": [ + "services/beta/reservation.json", + "services/alpha/reservation.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 60\n },\n {\n \"service\": \"beta\",\n \"reserved\": 60\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 140,\n \"headroom\": -10,\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 60, + "beta": 60, + "gamma": 20 + } + }, + "stateSha256": "f67e01a1e02a0e142de5017c652d44eae2c4668c241f4d2725e04761bc6ce35a", + "holds": false + } + }, + { + "scenarioId": "budget/inadmissible/misattributed", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A4_interlock", + "order": "AB", + "intents": [ + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + }, + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/misattributed.evidence.json", + "sha256": "47b1e8693c97d46cf3ae314013fa15ede0fe098ea056766e5252835ff8234953", + "expectedReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + "error": null, + "verdicts": [ + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "EVIDENCE_REPOSITORY_MISMATCH", + "detail": "the evidence is not attributed to the repository it names: requested experiments/hac-330/.work/fixtures/baseline/probe-not-a-repository, mined experiments/hac-330/.work/fixtures/baseline", + "couplings": [], + "evidenceRefs": [] + }, + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "EVIDENCE_REPOSITORY_MISMATCH", + "detail": "the evidence is not attributed to the repository it names: requested experiments/hac-330/.work/fixtures/baseline/probe-not-a-repository, mined experiments/hac-330/.work/fixtures/baseline", + "couplings": [], + "evidenceRefs": [] + } + ], + "outcomes": [ + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "EVIDENCE_REPOSITORY_MISMATCH" + }, + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "EVIDENCE_REPOSITORY_MISMATCH" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": "EVIDENCE_REPOSITORY_MISMATCH", + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 40\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 100,\n \"headroom\": 30,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 40, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "7e5fb4253787d6688ed00f21562dd7bf7bd313f97dfba74a923bd439df9ebba1", + "holds": true + } + }, + { + "scenarioId": "budget/inadmissible/misattributed", + "family": "budget", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "eb67a6f56b3bf7e71846e7324d21af44565c0b70", + "arm": "A4_interlock", + "order": "BA", + "intents": [ + { + "op": "set-reservation", + "path": "services/beta/reservation.json", + "service": "beta", + "reserved": 60, + "id": "i1" + }, + { + "op": "set-reservation", + "path": "services/alpha/reservation.json", + "service": "alpha", + "reserved": 60, + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/misattributed.evidence.json", + "sha256": "47b1e8693c97d46cf3ae314013fa15ede0fe098ea056766e5252835ff8234953", + "expectedReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + "error": null, + "verdicts": [ + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "EVIDENCE_REPOSITORY_MISMATCH", + "detail": "the evidence is not attributed to the repository it names: requested experiments/hac-330/.work/fixtures/baseline/probe-not-a-repository, mined experiments/hac-330/.work/fixtures/baseline", + "couplings": [], + "evidenceRefs": [] + }, + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "EVIDENCE_REPOSITORY_MISMATCH", + "detail": "the evidence is not attributed to the repository it names: requested experiments/hac-330/.work/fixtures/baseline/probe-not-a-repository, mined experiments/hac-330/.work/fixtures/baseline", + "couplings": [], + "evidenceRefs": [] + } + ], + "outcomes": [ + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "EVIDENCE_REPOSITORY_MISMATCH" + }, + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "EVIDENCE_REPOSITORY_MISMATCH" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": "EVIDENCE_REPOSITORY_MISMATCH", + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "b6ba3066cdc47ef513bd31b4b6adfd9edc95e50e8b5f33c5597e95b5785fc644", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"sum(services[].reserved) <= budget.totalReservable\",\n \"totalReservable\": 130,\n \"reserved\": [\n {\n \"service\": \"alpha\",\n \"reserved\": 40\n },\n {\n \"service\": \"beta\",\n \"reserved\": 40\n },\n {\n \"service\": \"gamma\",\n \"reserved\": 20\n }\n ],\n \"total\": 100,\n \"headroom\": 30,\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "totalReservable": 130, + "services": { + "alpha": 40, + "beta": 40, + "gamma": 20 + } + }, + "stateSha256": "7e5fb4253787d6688ed00f21562dd7bf7bd313f97dfba74a923bd439df9ebba1", + "holds": true + } + }, + { + "scenarioId": "registry/coupled/retire-vs-route", + "family": "registry", + "label": "COUPLED", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A1_uncoordinated", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + } + ], + "lockGroups": [ + "NONE#i0", + "NONE#i1" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/coupled/retire-vs-route", + "family": "registry", + "label": "COUPLED", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A1_uncoordinated", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + } + ], + "lockGroups": [ + "NONE#i1", + "NONE#i0" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/coupled/retire-vs-route", + "family": "registry", + "label": "COUPLED", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A2_global_lock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "legacy-pricing is not declared" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 3,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "7f36b72da07d6c9ba564c6acbe8aa1393bc68e35800ad2a4c9149df8b94f83c8", + "holds": true + } + }, + { + "scenarioId": "registry/coupled/retire-vs-route", + "family": "registry", + "label": "COUPLED", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A2_global_lock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "legacy-pricing is still referenced" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 4,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "90c8a9050d6f8a201e40b83d1741ae08dfefdde50f9e9b1192e36967a8945738", + "holds": true + } + }, + { + "scenarioId": "registry/coupled/retire-vs-route", + "family": "registry", + "label": "COUPLED", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A3_per_target_lock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "registry/services.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + } + ], + "lockGroups": [ + "registry/services.json", + "routing/routes.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/coupled/retire-vs-route", + "family": "registry", + "label": "COUPLED", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A3_per_target_lock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "registry/services.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + } + ], + "lockGroups": [ + "routing/routes.json", + "registry/services.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/coupled/retire-vs-route", + "family": "registry", + "label": "COUPLED", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A4_interlock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": [ + { + "decision": "ALLOW_SERIALIZED", + "reasonCode": "SERIALIZED_PRECEDENCE", + "detail": "1 qualifying co-change coupling(s) at basis 50c48393ef0df2d1a31abf71a45b5ac3127fb8bf; this intent holds precedence within the coupled set and proceeds alone while the others are withheld", + "couplings": [ + { + "correlationIds": [ + "ilk-1abbf14e8f89f201486db619", + "ilk-5938deaefdc631fa0619df0c" + ], + "files": [ + "registry/services.json", + "routing/routes.json" + ], + "support": 9, + "occurrences": 13 + } + ], + "evidenceRefs": [ + "basis:50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "artifact:sha256:9973221981f938af9dc6f8e046e1ad27148bbffc577b6ed947117ba3dc60759f" + ] + }, + { + "decision": "WITHHOLD_SERIALIZE", + "reasonCode": "COUPLING_OBSERVED", + "detail": "1 qualifying co-change coupling(s) between this intent and 1 intent(s) already in flight at basis 50c48393ef0df2d1a31abf71a45b5ac3127fb8bf; this intent does not hold precedence, so the composition is withheld and it must be resubmitted once the leading intent settles", + "couplings": [ + { + "correlationIds": [ + "ilk-5938deaefdc631fa0619df0c", + "ilk-1abbf14e8f89f201486db619" + ], + "files": [ + "registry/services.json", + "routing/routes.json" + ], + "support": 9, + "occurrences": 13 + } + ], + "evidenceRefs": [ + "basis:50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "artifact:sha256:9973221981f938af9dc6f8e046e1ad27148bbffc577b6ed947117ba3dc60759f" + ] + } + ], + "outcomes": [ + { + "intentId": "i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "WITHHELD_SERIALIZE", + "detail": "1 qualifying co-change coupling(s) between this intent and 1 intent(s) already in flight at basis 50c48393ef0df2d1a31abf71a45b5ac3127fb8bf; this intent does not hold precedence, so the composition is withheld and it must be resubmitted once the leading intent settles" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 3,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "7f36b72da07d6c9ba564c6acbe8aa1393bc68e35800ad2a4c9149df8b94f83c8", + "holds": true + } + }, + { + "scenarioId": "registry/coupled/retire-vs-route", + "family": "registry", + "label": "COUPLED", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A4_interlock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": [ + { + "decision": "ALLOW_SERIALIZED", + "reasonCode": "SERIALIZED_PRECEDENCE", + "detail": "1 qualifying co-change coupling(s) at basis 50c48393ef0df2d1a31abf71a45b5ac3127fb8bf; this intent holds precedence within the coupled set and proceeds alone while the others are withheld", + "couplings": [ + { + "correlationIds": [ + "ilk-5938deaefdc631fa0619df0c", + "ilk-1abbf14e8f89f201486db619" + ], + "files": [ + "registry/services.json", + "routing/routes.json" + ], + "support": 9, + "occurrences": 13 + } + ], + "evidenceRefs": [ + "basis:50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "artifact:sha256:9973221981f938af9dc6f8e046e1ad27148bbffc577b6ed947117ba3dc60759f" + ] + }, + { + "decision": "WITHHOLD_SERIALIZE", + "reasonCode": "COUPLING_OBSERVED", + "detail": "1 qualifying co-change coupling(s) between this intent and 1 intent(s) already in flight at basis 50c48393ef0df2d1a31abf71a45b5ac3127fb8bf; this intent does not hold precedence, so the composition is withheld and it must be resubmitted once the leading intent settles", + "couplings": [ + { + "correlationIds": [ + "ilk-1abbf14e8f89f201486db619", + "ilk-5938deaefdc631fa0619df0c" + ], + "files": [ + "registry/services.json", + "routing/routes.json" + ], + "support": 9, + "occurrences": 13 + } + ], + "evidenceRefs": [ + "basis:50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "artifact:sha256:9973221981f938af9dc6f8e046e1ad27148bbffc577b6ed947117ba3dc60759f" + ] + } + ], + "outcomes": [ + { + "intentId": "i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "WITHHELD_SERIALIZE", + "detail": "1 qualifying co-change coupling(s) between this intent and 1 intent(s) already in flight at basis 50c48393ef0df2d1a31abf71a45b5ac3127fb8bf; this intent does not hold precedence, so the composition is withheld and it must be resubmitted once the leading intent settles" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 4,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "90c8a9050d6f8a201e40b83d1741ae08dfefdde50f9e9b1192e36967a8945738", + "holds": true + } + }, + { + "scenarioId": "registry/independent/route-vs-dashboards", + "family": "registry", + "label": "INDEPENDENT", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A1_uncoordinated", + "order": "AB", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/health", + "service": "checkout", + "id": "i0" + }, + { + "op": "bump-dashboards", + "path": "observability/dashboards.json", + "revision": 99, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "checkout is declared" + }, + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "dashboards carry no referential obligation" + } + ], + "lockGroups": [ + "NONE#i0", + "NONE#i1" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 4,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/health", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 99 + }, + "stateSha256": "8ee53e9cf505e1fdd0bc96ff20e386d21bba95949e44d0153b981f02c578ac6e", + "holds": true + } + }, + { + "scenarioId": "registry/independent/route-vs-dashboards", + "family": "registry", + "label": "INDEPENDENT", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A1_uncoordinated", + "order": "BA", + "intents": [ + { + "op": "bump-dashboards", + "path": "observability/dashboards.json", + "revision": 99, + "id": "i1" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/health", + "service": "checkout", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "dashboards carry no referential obligation" + }, + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "checkout is declared" + } + ], + "lockGroups": [ + "NONE#i1", + "NONE#i0" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 4,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/health", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 99 + }, + "stateSha256": "8ee53e9cf505e1fdd0bc96ff20e386d21bba95949e44d0153b981f02c578ac6e", + "holds": true + } + }, + { + "scenarioId": "registry/independent/route-vs-dashboards", + "family": "registry", + "label": "INDEPENDENT", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A2_global_lock", + "order": "AB", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/health", + "service": "checkout", + "id": "i0" + }, + { + "op": "bump-dashboards", + "path": "observability/dashboards.json", + "revision": 99, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "checkout is declared" + }, + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "dashboards carry no referential obligation" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 4,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/health", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 99 + }, + "stateSha256": "8ee53e9cf505e1fdd0bc96ff20e386d21bba95949e44d0153b981f02c578ac6e", + "holds": true + } + }, + { + "scenarioId": "registry/independent/route-vs-dashboards", + "family": "registry", + "label": "INDEPENDENT", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A2_global_lock", + "order": "BA", + "intents": [ + { + "op": "bump-dashboards", + "path": "observability/dashboards.json", + "revision": 99, + "id": "i1" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/health", + "service": "checkout", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "dashboards carry no referential obligation" + }, + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "checkout is declared" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 4,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/health", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 99 + }, + "stateSha256": "8ee53e9cf505e1fdd0bc96ff20e386d21bba95949e44d0153b981f02c578ac6e", + "holds": true + } + }, + { + "scenarioId": "registry/independent/route-vs-dashboards", + "family": "registry", + "label": "INDEPENDENT", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A3_per_target_lock", + "order": "AB", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/health", + "service": "checkout", + "id": "i0" + }, + { + "op": "bump-dashboards", + "path": "observability/dashboards.json", + "revision": 99, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "checkout is declared" + }, + { + "intentId": "i1", + "lockKey": "observability/dashboards.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "dashboards carry no referential obligation" + } + ], + "lockGroups": [ + "routing/routes.json", + "observability/dashboards.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 4,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/health", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 99 + }, + "stateSha256": "8ee53e9cf505e1fdd0bc96ff20e386d21bba95949e44d0153b981f02c578ac6e", + "holds": true + } + }, + { + "scenarioId": "registry/independent/route-vs-dashboards", + "family": "registry", + "label": "INDEPENDENT", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A3_per_target_lock", + "order": "BA", + "intents": [ + { + "op": "bump-dashboards", + "path": "observability/dashboards.json", + "revision": 99, + "id": "i1" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/health", + "service": "checkout", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "observability/dashboards.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "dashboards carry no referential obligation" + }, + { + "intentId": "i0", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "checkout is declared" + } + ], + "lockGroups": [ + "observability/dashboards.json", + "routing/routes.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 4,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/health", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 99 + }, + "stateSha256": "8ee53e9cf505e1fdd0bc96ff20e386d21bba95949e44d0153b981f02c578ac6e", + "holds": true + } + }, + { + "scenarioId": "registry/independent/route-vs-dashboards", + "family": "registry", + "label": "INDEPENDENT", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A4_interlock", + "order": "AB", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/health", + "service": "checkout", + "id": "i0" + }, + { + "op": "bump-dashboards", + "path": "observability/dashboards.json", + "revision": 99, + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": [ + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at 50c48393ef0df2d1a31abf71a45b5ac3127fb8bf was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "artifact:sha256:9973221981f938af9dc6f8e046e1ad27148bbffc577b6ed947117ba3dc60759f" + ] + }, + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at 50c48393ef0df2d1a31abf71a45b5ac3127fb8bf was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "artifact:sha256:9973221981f938af9dc6f8e046e1ad27148bbffc577b6ed947117ba3dc60759f" + ] + } + ], + "outcomes": [ + { + "intentId": "i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "checkout is declared" + }, + { + "intentId": "i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "dashboards carry no referential obligation" + } + ], + "lockGroups": null, + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 4,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/health", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 99 + }, + "stateSha256": "8ee53e9cf505e1fdd0bc96ff20e386d21bba95949e44d0153b981f02c578ac6e", + "holds": true + } + }, + { + "scenarioId": "registry/independent/route-vs-dashboards", + "family": "registry", + "label": "INDEPENDENT", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A4_interlock", + "order": "BA", + "intents": [ + { + "op": "bump-dashboards", + "path": "observability/dashboards.json", + "revision": 99, + "id": "i1" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/health", + "service": "checkout", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": [ + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at 50c48393ef0df2d1a31abf71a45b5ac3127fb8bf was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "artifact:sha256:9973221981f938af9dc6f8e046e1ad27148bbffc577b6ed947117ba3dc60759f" + ] + }, + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at 50c48393ef0df2d1a31abf71a45b5ac3127fb8bf was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "artifact:sha256:9973221981f938af9dc6f8e046e1ad27148bbffc577b6ed947117ba3dc60759f" + ] + } + ], + "outcomes": [ + { + "intentId": "i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "dashboards carry no referential obligation" + }, + { + "intentId": "i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "checkout is declared" + } + ], + "lockGroups": null, + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 4,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/health", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 99 + }, + "stateSha256": "8ee53e9cf505e1fdd0bc96ff20e386d21bba95949e44d0153b981f02c578ac6e", + "holds": true + } + }, + { + "scenarioId": "registry/same-target/route-vs-route", + "family": "registry", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A1_uncoordinated", + "order": "AB", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/a", + "service": "checkout", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/b", + "service": "inventory", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "checkout is declared" + }, + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "inventory is declared" + } + ], + "lockGroups": [ + "NONE#i0", + "NONE#i1" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 5,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/a", + "service": "checkout" + }, + { + "path": "/b", + "service": "inventory" + }, + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "7d2efa978596ed86cbdf709118f3b64366cacc6c74bf464a1ca25fc7a9deebc8", + "holds": true + } + }, + { + "scenarioId": "registry/same-target/route-vs-route", + "family": "registry", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A1_uncoordinated", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/b", + "service": "inventory", + "id": "i1" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/a", + "service": "checkout", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "inventory is declared" + }, + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "checkout is declared" + } + ], + "lockGroups": [ + "NONE#i1", + "NONE#i0" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 5,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/a", + "service": "checkout" + }, + { + "path": "/b", + "service": "inventory" + }, + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "7d2efa978596ed86cbdf709118f3b64366cacc6c74bf464a1ca25fc7a9deebc8", + "holds": true + } + }, + { + "scenarioId": "registry/same-target/route-vs-route", + "family": "registry", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A2_global_lock", + "order": "AB", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/a", + "service": "checkout", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/b", + "service": "inventory", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "checkout is declared" + }, + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "inventory is declared" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 5,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/a", + "service": "checkout" + }, + { + "path": "/b", + "service": "inventory" + }, + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "7d2efa978596ed86cbdf709118f3b64366cacc6c74bf464a1ca25fc7a9deebc8", + "holds": true + } + }, + { + "scenarioId": "registry/same-target/route-vs-route", + "family": "registry", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A2_global_lock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/b", + "service": "inventory", + "id": "i1" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/a", + "service": "checkout", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "inventory is declared" + }, + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "checkout is declared" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 5,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/a", + "service": "checkout" + }, + { + "path": "/b", + "service": "inventory" + }, + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "7d2efa978596ed86cbdf709118f3b64366cacc6c74bf464a1ca25fc7a9deebc8", + "holds": true + } + }, + { + "scenarioId": "registry/same-target/route-vs-route", + "family": "registry", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A3_per_target_lock", + "order": "AB", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/a", + "service": "checkout", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/b", + "service": "inventory", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "checkout is declared" + }, + { + "intentId": "i1", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "inventory is declared" + } + ], + "lockGroups": [ + "routing/routes.json" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 5,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/a", + "service": "checkout" + }, + { + "path": "/b", + "service": "inventory" + }, + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "7d2efa978596ed86cbdf709118f3b64366cacc6c74bf464a1ca25fc7a9deebc8", + "holds": true + } + }, + { + "scenarioId": "registry/same-target/route-vs-route", + "family": "registry", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A3_per_target_lock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/b", + "service": "inventory", + "id": "i1" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/a", + "service": "checkout", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "inventory is declared" + }, + { + "intentId": "i0", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "checkout is declared" + } + ], + "lockGroups": [ + "routing/routes.json" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 5,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/a", + "service": "checkout" + }, + { + "path": "/b", + "service": "inventory" + }, + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "7d2efa978596ed86cbdf709118f3b64366cacc6c74bf464a1ca25fc7a9deebc8", + "holds": true + } + }, + { + "scenarioId": "registry/same-target/route-vs-route", + "family": "registry", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A4_interlock", + "order": "AB", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/a", + "service": "checkout", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/b", + "service": "inventory", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": [ + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at 50c48393ef0df2d1a31abf71a45b5ac3127fb8bf was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "artifact:sha256:9973221981f938af9dc6f8e046e1ad27148bbffc577b6ed947117ba3dc60759f" + ] + }, + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at 50c48393ef0df2d1a31abf71a45b5ac3127fb8bf was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "artifact:sha256:9973221981f938af9dc6f8e046e1ad27148bbffc577b6ed947117ba3dc60759f" + ] + } + ], + "outcomes": [ + { + "intentId": "i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "checkout is declared" + }, + { + "intentId": "i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "inventory is declared" + } + ], + "lockGroups": null, + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 5,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/a", + "service": "checkout" + }, + { + "path": "/b", + "service": "inventory" + }, + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "7d2efa978596ed86cbdf709118f3b64366cacc6c74bf464a1ca25fc7a9deebc8", + "holds": true + } + }, + { + "scenarioId": "registry/same-target/route-vs-route", + "family": "registry", + "label": "SAME_TARGET_CONTENTION", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A4_interlock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/b", + "service": "inventory", + "id": "i1" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/a", + "service": "checkout", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.baseline.evidence.json", + "sha256": "0bd84e0498987375a729799672b94028c7ea3bbf5b399d20470e5023b111d862", + "expectedReason": null + }, + "error": null, + "verdicts": [ + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at 50c48393ef0df2d1a31abf71a45b5ac3127fb8bf was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "artifact:sha256:9973221981f938af9dc6f8e046e1ad27148bbffc577b6ed947117ba3dc60759f" + ] + }, + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at 50c48393ef0df2d1a31abf71a45b5ac3127fb8bf was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "artifact:sha256:9973221981f938af9dc6f8e046e1ad27148bbffc577b6ed947117ba3dc60759f" + ] + } + ], + "outcomes": [ + { + "intentId": "i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "inventory is declared" + }, + { + "intentId": "i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "checkout is declared" + } + ], + "lockGroups": null, + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 5,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/a", + "service": "checkout" + }, + { + "path": "/b", + "service": "inventory" + }, + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "7d2efa978596ed86cbdf709118f3b64366cacc6c74bf464a1ca25fc7a9deebc8", + "holds": true + } + }, + { + "scenarioId": "registry/perturbed/retire-vs-route", + "family": "registry", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "fixtureRevision": "f50ecbe40530af357750952235bb262948f9e84e", + "arm": "A1_uncoordinated", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.perturbed.evidence.json", + "sha256": "717eff9ac2d144608e34b05a37bfd432cd50ca65daf5a68893b4dbac08f432ac", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + } + ], + "lockGroups": [ + "NONE#i0", + "NONE#i1" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/perturbed/retire-vs-route", + "family": "registry", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "fixtureRevision": "f50ecbe40530af357750952235bb262948f9e84e", + "arm": "A1_uncoordinated", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.perturbed.evidence.json", + "sha256": "717eff9ac2d144608e34b05a37bfd432cd50ca65daf5a68893b4dbac08f432ac", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + } + ], + "lockGroups": [ + "NONE#i1", + "NONE#i0" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/perturbed/retire-vs-route", + "family": "registry", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "fixtureRevision": "f50ecbe40530af357750952235bb262948f9e84e", + "arm": "A2_global_lock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.perturbed.evidence.json", + "sha256": "717eff9ac2d144608e34b05a37bfd432cd50ca65daf5a68893b4dbac08f432ac", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "legacy-pricing is not declared" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 3,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "7f36b72da07d6c9ba564c6acbe8aa1393bc68e35800ad2a4c9149df8b94f83c8", + "holds": true + } + }, + { + "scenarioId": "registry/perturbed/retire-vs-route", + "family": "registry", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "fixtureRevision": "f50ecbe40530af357750952235bb262948f9e84e", + "arm": "A2_global_lock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.perturbed.evidence.json", + "sha256": "717eff9ac2d144608e34b05a37bfd432cd50ca65daf5a68893b4dbac08f432ac", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "legacy-pricing is still referenced" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 4,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "90c8a9050d6f8a201e40b83d1741ae08dfefdde50f9e9b1192e36967a8945738", + "holds": true + } + }, + { + "scenarioId": "registry/perturbed/retire-vs-route", + "family": "registry", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "fixtureRevision": "f50ecbe40530af357750952235bb262948f9e84e", + "arm": "A3_per_target_lock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.perturbed.evidence.json", + "sha256": "717eff9ac2d144608e34b05a37bfd432cd50ca65daf5a68893b4dbac08f432ac", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "registry/services.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + } + ], + "lockGroups": [ + "registry/services.json", + "routing/routes.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/perturbed/retire-vs-route", + "family": "registry", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "fixtureRevision": "f50ecbe40530af357750952235bb262948f9e84e", + "arm": "A3_per_target_lock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.perturbed.evidence.json", + "sha256": "717eff9ac2d144608e34b05a37bfd432cd50ca65daf5a68893b4dbac08f432ac", + "expectedReason": null + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "registry/services.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + } + ], + "lockGroups": [ + "routing/routes.json", + "registry/services.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/perturbed/retire-vs-route", + "family": "registry", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "fixtureRevision": "f50ecbe40530af357750952235bb262948f9e84e", + "arm": "A4_interlock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.perturbed.evidence.json", + "sha256": "717eff9ac2d144608e34b05a37bfd432cd50ca65daf5a68893b4dbac08f432ac", + "expectedReason": null + }, + "error": null, + "verdicts": [ + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at f50ecbe40530af357750952235bb262948f9e84e was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:f50ecbe40530af357750952235bb262948f9e84e", + "artifact:sha256:b6f94506db06d2b72a581bccd73fca02efb22953e2c9eb13f18a09b2961df00a" + ] + }, + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at f50ecbe40530af357750952235bb262948f9e84e was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:f50ecbe40530af357750952235bb262948f9e84e", + "artifact:sha256:b6f94506db06d2b72a581bccd73fca02efb22953e2c9eb13f18a09b2961df00a" + ] + } + ], + "outcomes": [ + { + "intentId": "i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + } + ], + "lockGroups": null, + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/perturbed/retire-vs-route", + "family": "registry", + "label": "EVIDENCE_PERTURBED", + "fixture": "perturbed", + "fixtureRevision": "f50ecbe40530af357750952235bb262948f9e84e", + "arm": "A4_interlock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-343/evidence/registry.perturbed.evidence.json", + "sha256": "717eff9ac2d144608e34b05a37bfd432cd50ca65daf5a68893b4dbac08f432ac", + "expectedReason": null + }, + "error": null, + "verdicts": [ + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at f50ecbe40530af357750952235bb262948f9e84e was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:f50ecbe40530af357750952235bb262948f9e84e", + "artifact:sha256:b6f94506db06d2b72a581bccd73fca02efb22953e2c9eb13f18a09b2961df00a" + ] + }, + { + "decision": "ALLOW_PARALLEL", + "reasonCode": "NO_QUALIFYING_COUPLING", + "detail": "history at f50ecbe40530af357750952235bb262948f9e84e was mined (QUALIFYING_RELATIONSHIP_OBSERVED) and shows no pair between this intent and the 1 intent(s) in flight at support >= 3", + "couplings": [], + "evidenceRefs": [ + "basis:f50ecbe40530af357750952235bb262948f9e84e", + "artifact:sha256:b6f94506db06d2b72a581bccd73fca02efb22953e2c9eb13f18a09b2961df00a" + ] + } + ], + "outcomes": [ + { + "intentId": "i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + } + ], + "lockGroups": null, + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/absent", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A1_uncoordinated", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "absent", + "sha256": null, + "expectedReason": "EVIDENCE_ABSENT" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + } + ], + "lockGroups": [ + "NONE#i0", + "NONE#i1" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/absent", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A1_uncoordinated", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "absent", + "sha256": null, + "expectedReason": "EVIDENCE_ABSENT" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + } + ], + "lockGroups": [ + "NONE#i1", + "NONE#i0" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/absent", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A2_global_lock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "absent", + "sha256": null, + "expectedReason": "EVIDENCE_ABSENT" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "legacy-pricing is not declared" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 3,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "7f36b72da07d6c9ba564c6acbe8aa1393bc68e35800ad2a4c9149df8b94f83c8", + "holds": true + } + }, + { + "scenarioId": "registry/inadmissible/absent", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A2_global_lock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "absent", + "sha256": null, + "expectedReason": "EVIDENCE_ABSENT" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "legacy-pricing is still referenced" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 4,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "90c8a9050d6f8a201e40b83d1741ae08dfefdde50f9e9b1192e36967a8945738", + "holds": true + } + }, + { + "scenarioId": "registry/inadmissible/absent", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A3_per_target_lock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "absent", + "sha256": null, + "expectedReason": "EVIDENCE_ABSENT" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "registry/services.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + } + ], + "lockGroups": [ + "registry/services.json", + "routing/routes.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/absent", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A3_per_target_lock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "absent", + "sha256": null, + "expectedReason": "EVIDENCE_ABSENT" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "registry/services.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + } + ], + "lockGroups": [ + "routing/routes.json", + "registry/services.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/absent", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A4_interlock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "absent", + "sha256": null, + "expectedReason": "EVIDENCE_ABSENT" + }, + "error": null, + "verdicts": [ + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "EVIDENCE_ABSENT", + "detail": "no co-change evidence was supplied; absence of evidence is not evidence of independence", + "couplings": [], + "evidenceRefs": [] + }, + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "EVIDENCE_ABSENT", + "detail": "no co-change evidence was supplied; absence of evidence is not evidence of independence", + "couplings": [], + "evidenceRefs": [] + } + ], + "outcomes": [ + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "EVIDENCE_ABSENT" + }, + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "EVIDENCE_ABSENT" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": "EVIDENCE_ABSENT", + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 3,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "82d02d7c6de64aaa3ac31e56849e1ec15603abf806faa79cc3247f5f039fe8d8", + "holds": true + } + }, + { + "scenarioId": "registry/inadmissible/absent", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A4_interlock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "absent", + "sha256": null, + "expectedReason": "EVIDENCE_ABSENT" + }, + "error": null, + "verdicts": [ + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "EVIDENCE_ABSENT", + "detail": "no co-change evidence was supplied; absence of evidence is not evidence of independence", + "couplings": [], + "evidenceRefs": [] + }, + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "EVIDENCE_ABSENT", + "detail": "no co-change evidence was supplied; absence of evidence is not evidence of independence", + "couplings": [], + "evidenceRefs": [] + } + ], + "outcomes": [ + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "EVIDENCE_ABSENT" + }, + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "EVIDENCE_ABSENT" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": "EVIDENCE_ABSENT", + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 3,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "82d02d7c6de64aaa3ac31e56849e1ec15603abf806faa79cc3247f5f039fe8d8", + "holds": true + } + }, + { + "scenarioId": "registry/inadmissible/shallow", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A1_uncoordinated", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/shallow.evidence.json", + "sha256": "6e7b4ddf6446fb76d7509879e6ae3fba8ab9bbbe177b3b6b5ac2262c7fb25678", + "expectedReason": "HISTORY_NOT_MINED" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + } + ], + "lockGroups": [ + "NONE#i0", + "NONE#i1" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/shallow", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A1_uncoordinated", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/shallow.evidence.json", + "sha256": "6e7b4ddf6446fb76d7509879e6ae3fba8ab9bbbe177b3b6b5ac2262c7fb25678", + "expectedReason": "HISTORY_NOT_MINED" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + } + ], + "lockGroups": [ + "NONE#i1", + "NONE#i0" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/shallow", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A2_global_lock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/shallow.evidence.json", + "sha256": "6e7b4ddf6446fb76d7509879e6ae3fba8ab9bbbe177b3b6b5ac2262c7fb25678", + "expectedReason": "HISTORY_NOT_MINED" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "legacy-pricing is not declared" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 3,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "7f36b72da07d6c9ba564c6acbe8aa1393bc68e35800ad2a4c9149df8b94f83c8", + "holds": true + } + }, + { + "scenarioId": "registry/inadmissible/shallow", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A2_global_lock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/shallow.evidence.json", + "sha256": "6e7b4ddf6446fb76d7509879e6ae3fba8ab9bbbe177b3b6b5ac2262c7fb25678", + "expectedReason": "HISTORY_NOT_MINED" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "legacy-pricing is still referenced" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 4,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "90c8a9050d6f8a201e40b83d1741ae08dfefdde50f9e9b1192e36967a8945738", + "holds": true + } + }, + { + "scenarioId": "registry/inadmissible/shallow", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A3_per_target_lock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/shallow.evidence.json", + "sha256": "6e7b4ddf6446fb76d7509879e6ae3fba8ab9bbbe177b3b6b5ac2262c7fb25678", + "expectedReason": "HISTORY_NOT_MINED" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "registry/services.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + } + ], + "lockGroups": [ + "registry/services.json", + "routing/routes.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/shallow", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A3_per_target_lock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/shallow.evidence.json", + "sha256": "6e7b4ddf6446fb76d7509879e6ae3fba8ab9bbbe177b3b6b5ac2262c7fb25678", + "expectedReason": "HISTORY_NOT_MINED" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "registry/services.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + } + ], + "lockGroups": [ + "routing/routes.json", + "registry/services.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/shallow", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A4_interlock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/shallow.evidence.json", + "sha256": "6e7b4ddf6446fb76d7509879e6ae3fba8ab9bbbe177b3b6b5ac2262c7fb25678", + "expectedReason": "HISTORY_NOT_MINED" + }, + "error": null, + "verdicts": [ + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "HISTORY_NOT_MINED", + "detail": "completeness is NOT_MINED; the history behind this evidence was not successfully mined", + "couplings": [], + "evidenceRefs": [] + }, + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "HISTORY_NOT_MINED", + "detail": "completeness is NOT_MINED; the history behind this evidence was not successfully mined", + "couplings": [], + "evidenceRefs": [] + } + ], + "outcomes": [ + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "HISTORY_NOT_MINED" + }, + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "HISTORY_NOT_MINED" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": "HISTORY_NOT_MINED", + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 3,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "82d02d7c6de64aaa3ac31e56849e1ec15603abf806faa79cc3247f5f039fe8d8", + "holds": true + } + }, + { + "scenarioId": "registry/inadmissible/shallow", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A4_interlock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/shallow.evidence.json", + "sha256": "6e7b4ddf6446fb76d7509879e6ae3fba8ab9bbbe177b3b6b5ac2262c7fb25678", + "expectedReason": "HISTORY_NOT_MINED" + }, + "error": null, + "verdicts": [ + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "HISTORY_NOT_MINED", + "detail": "completeness is NOT_MINED; the history behind this evidence was not successfully mined", + "couplings": [], + "evidenceRefs": [] + }, + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "HISTORY_NOT_MINED", + "detail": "completeness is NOT_MINED; the history behind this evidence was not successfully mined", + "couplings": [], + "evidenceRefs": [] + } + ], + "outcomes": [ + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "HISTORY_NOT_MINED" + }, + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "HISTORY_NOT_MINED" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": "HISTORY_NOT_MINED", + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 3,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "82d02d7c6de64aaa3ac31e56849e1ec15603abf806faa79cc3247f5f039fe8d8", + "holds": true + } + }, + { + "scenarioId": "registry/inadmissible/noRepository", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A1_uncoordinated", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/no-repository.evidence.json", + "sha256": "f4fed9c840e8df30ab8405e26079a48dcc509b282110323f3080ddfcd5d0b1dd", + "expectedReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + } + ], + "lockGroups": [ + "NONE#i0", + "NONE#i1" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/noRepository", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A1_uncoordinated", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/no-repository.evidence.json", + "sha256": "f4fed9c840e8df30ab8405e26079a48dcc509b282110323f3080ddfcd5d0b1dd", + "expectedReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + } + ], + "lockGroups": [ + "NONE#i1", + "NONE#i0" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/noRepository", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A2_global_lock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/no-repository.evidence.json", + "sha256": "f4fed9c840e8df30ab8405e26079a48dcc509b282110323f3080ddfcd5d0b1dd", + "expectedReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "legacy-pricing is not declared" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 3,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "7f36b72da07d6c9ba564c6acbe8aa1393bc68e35800ad2a4c9149df8b94f83c8", + "holds": true + } + }, + { + "scenarioId": "registry/inadmissible/noRepository", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A2_global_lock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/no-repository.evidence.json", + "sha256": "f4fed9c840e8df30ab8405e26079a48dcc509b282110323f3080ddfcd5d0b1dd", + "expectedReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "legacy-pricing is still referenced" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 4,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "90c8a9050d6f8a201e40b83d1741ae08dfefdde50f9e9b1192e36967a8945738", + "holds": true + } + }, + { + "scenarioId": "registry/inadmissible/noRepository", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A3_per_target_lock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/no-repository.evidence.json", + "sha256": "f4fed9c840e8df30ab8405e26079a48dcc509b282110323f3080ddfcd5d0b1dd", + "expectedReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "registry/services.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + } + ], + "lockGroups": [ + "registry/services.json", + "routing/routes.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/noRepository", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A3_per_target_lock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/no-repository.evidence.json", + "sha256": "f4fed9c840e8df30ab8405e26079a48dcc509b282110323f3080ddfcd5d0b1dd", + "expectedReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "registry/services.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + } + ], + "lockGroups": [ + "routing/routes.json", + "registry/services.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/noRepository", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A4_interlock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/no-repository.evidence.json", + "sha256": "f4fed9c840e8df30ab8405e26079a48dcc509b282110323f3080ddfcd5d0b1dd", + "expectedReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + "error": null, + "verdicts": [ + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "HISTORY_NOT_MINED", + "detail": "completeness is NOT_MINED; the history behind this evidence was not successfully mined", + "couplings": [], + "evidenceRefs": [] + }, + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "HISTORY_NOT_MINED", + "detail": "completeness is NOT_MINED; the history behind this evidence was not successfully mined", + "couplings": [], + "evidenceRefs": [] + } + ], + "outcomes": [ + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "HISTORY_NOT_MINED" + }, + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "HISTORY_NOT_MINED" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": "HISTORY_NOT_MINED", + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 3,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "82d02d7c6de64aaa3ac31e56849e1ec15603abf806faa79cc3247f5f039fe8d8", + "holds": true + } + }, + { + "scenarioId": "registry/inadmissible/noRepository", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A4_interlock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/no-repository.evidence.json", + "sha256": "f4fed9c840e8df30ab8405e26079a48dcc509b282110323f3080ddfcd5d0b1dd", + "expectedReason": "HISTORY_EVIDENCE_UNAVAILABLE" + }, + "error": null, + "verdicts": [ + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "HISTORY_NOT_MINED", + "detail": "completeness is NOT_MINED; the history behind this evidence was not successfully mined", + "couplings": [], + "evidenceRefs": [] + }, + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "HISTORY_NOT_MINED", + "detail": "completeness is NOT_MINED; the history behind this evidence was not successfully mined", + "couplings": [], + "evidenceRefs": [] + } + ], + "outcomes": [ + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "HISTORY_NOT_MINED" + }, + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "HISTORY_NOT_MINED" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": "HISTORY_NOT_MINED", + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 3,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "82d02d7c6de64aaa3ac31e56849e1ec15603abf806faa79cc3247f5f039fe8d8", + "holds": true + } + }, + { + "scenarioId": "registry/inadmissible/misattributed", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A1_uncoordinated", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/misattributed.evidence.json", + "sha256": "47b1e8693c97d46cf3ae314013fa15ede0fe098ea056766e5252835ff8234953", + "expectedReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + } + ], + "lockGroups": [ + "NONE#i0", + "NONE#i1" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/misattributed", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A1_uncoordinated", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/misattributed.evidence.json", + "sha256": "47b1e8693c97d46cf3ae314013fa15ede0fe098ea056766e5252835ff8234953", + "expectedReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "NONE#i1", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "NONE#i0", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + } + ], + "lockGroups": [ + "NONE#i1", + "NONE#i0" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/misattributed", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A2_global_lock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/misattributed.evidence.json", + "sha256": "47b1e8693c97d46cf3ae314013fa15ede0fe098ea056766e5252835ff8234953", + "expectedReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "legacy-pricing is not declared" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 3,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "7f36b72da07d6c9ba564c6acbe8aa1393bc68e35800ad2a4c9149df8b94f83c8", + "holds": true + } + }, + { + "scenarioId": "registry/inadmissible/misattributed", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A2_global_lock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/misattributed.evidence.json", + "sha256": "47b1e8693c97d46cf3ae314013fa15ede0fe098ea056766e5252835ff8234953", + "expectedReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "GLOBAL", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "GLOBAL", + "applied": false, + "rejected": true, + "reason": "LOCAL_PRECONDITION_FAILED", + "detail": "legacy-pricing is still referenced" + } + ], + "lockGroups": [ + "GLOBAL" + ], + "concurrent": false, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 4,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "90c8a9050d6f8a201e40b83d1741ae08dfefdde50f9e9b1192e36967a8945738", + "holds": true + } + }, + { + "scenarioId": "registry/inadmissible/misattributed", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A3_per_target_lock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/misattributed.evidence.json", + "sha256": "47b1e8693c97d46cf3ae314013fa15ede0fe098ea056766e5252835ff8234953", + "expectedReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i0", + "lockKey": "registry/services.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + }, + { + "intentId": "i1", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + } + ], + "lockGroups": [ + "registry/services.json", + "routing/routes.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/misattributed", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A3_per_target_lock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/misattributed.evidence.json", + "sha256": "47b1e8693c97d46cf3ae314013fa15ede0fe098ea056766e5252835ff8234953", + "expectedReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + "error": null, + "verdicts": null, + "outcomes": [ + { + "intentId": "i1", + "lockKey": "routing/routes.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared" + }, + { + "intentId": "i0", + "lockKey": "registry/services.json", + "applied": true, + "rejected": false, + "reason": "APPLIED", + "detail": "legacy-pricing is declared and unreferenced" + } + ], + "lockGroups": [ + "routing/routes.json", + "registry/services.json" + ], + "concurrent": true, + "refusalReason": null, + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 1, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\"\n ],\n \"references\": 4,\n \"dangling\": [\n {\n \"kind\": \"route\",\n \"from\": \"/pricing\",\n \"to\": \"legacy-pricing\"\n }\n ],\n \"holds\": false\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + }, + { + "path": "/pricing", + "service": "legacy-pricing" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "ca207536e9ab4563ab4a4a7ba1f9e77d0d01e20b28c60815106364501e180d72", + "holds": false + } + }, + { + "scenarioId": "registry/inadmissible/misattributed", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A4_interlock", + "order": "AB", + "intents": [ + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + }, + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/misattributed.evidence.json", + "sha256": "47b1e8693c97d46cf3ae314013fa15ede0fe098ea056766e5252835ff8234953", + "expectedReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + "error": null, + "verdicts": [ + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "EVIDENCE_REPOSITORY_MISMATCH", + "detail": "the evidence is not attributed to the repository it names: requested experiments/hac-330/.work/fixtures/baseline/probe-not-a-repository, mined experiments/hac-330/.work/fixtures/baseline", + "couplings": [], + "evidenceRefs": [] + }, + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "EVIDENCE_REPOSITORY_MISMATCH", + "detail": "the evidence is not attributed to the repository it names: requested experiments/hac-330/.work/fixtures/baseline/probe-not-a-repository, mined experiments/hac-330/.work/fixtures/baseline", + "couplings": [], + "evidenceRefs": [] + } + ], + "outcomes": [ + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "EVIDENCE_REPOSITORY_MISMATCH" + }, + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "EVIDENCE_REPOSITORY_MISMATCH" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": "EVIDENCE_REPOSITORY_MISMATCH", + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 3,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "82d02d7c6de64aaa3ac31e56849e1ec15603abf806faa79cc3247f5f039fe8d8", + "holds": true + } + }, + { + "scenarioId": "registry/inadmissible/misattributed", + "family": "registry", + "label": "EVIDENCE_INADMISSIBLE", + "fixture": "baseline", + "fixtureRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "arm": "A4_interlock", + "order": "BA", + "intents": [ + { + "op": "add-route", + "path": "routing/routes.json", + "route": "/pricing", + "service": "legacy-pricing", + "id": "i1" + }, + { + "op": "remove-service", + "path": "registry/services.json", + "service": "legacy-pricing", + "id": "i0" + } + ], + "evidence": { + "source": "experiments/hac-330/evidence/misattributed.evidence.json", + "sha256": "47b1e8693c97d46cf3ae314013fa15ede0fe098ea056766e5252835ff8234953", + "expectedReason": "EVIDENCE_REPOSITORY_MISMATCH" + }, + "error": null, + "verdicts": [ + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "EVIDENCE_REPOSITORY_MISMATCH", + "detail": "the evidence is not attributed to the repository it names: requested experiments/hac-330/.work/fixtures/baseline/probe-not-a-repository, mined experiments/hac-330/.work/fixtures/baseline", + "couplings": [], + "evidenceRefs": [] + }, + { + "decision": "INSUFFICIENT_EVIDENCE", + "reasonCode": "EVIDENCE_REPOSITORY_MISMATCH", + "detail": "the evidence is not attributed to the repository it names: requested experiments/hac-330/.work/fixtures/baseline/probe-not-a-repository, mined experiments/hac-330/.work/fixtures/baseline", + "couplings": [], + "evidenceRefs": [] + } + ], + "outcomes": [ + { + "intentId": "i1", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "EVIDENCE_REPOSITORY_MISMATCH" + }, + { + "intentId": "i0", + "applied": false, + "rejected": true, + "reason": "REFUSED_INSUFFICIENT_EVIDENCE", + "detail": "EVIDENCE_REPOSITORY_MISMATCH" + } + ], + "lockGroups": null, + "concurrent": false, + "refusalReason": "EVIDENCE_REPOSITORY_MISMATCH", + "oracle": { + "verifierPath": "verify.mjs", + "verifierSha256": "3abb89a8e9e2219a979007010363d9bae9c7ce3ac9eb714ebf13515e1c8434ba", + "command": "/Users/user1/.nvm/versions/node/v22.19.0/bin/node verify.mjs", + "exitCode": 0, + "stdout": "{\n \"invariant\": \"every route.service and alias target resolves in registry/services.json\",\n \"declared\": [\n \"checkout\",\n \"inventory\",\n \"legacy-pricing\"\n ],\n \"references\": 3,\n \"dangling\": [],\n \"holds\": true\n}\n", + "stderr": "", + "spawnFailed": false, + "state": { + "services": [ + "checkout", + "inventory", + "legacy-pricing" + ], + "routes": [ + { + "path": "/checkout", + "service": "checkout" + }, + { + "path": "/inventory", + "service": "inventory" + } + ], + "aliases": { + "cart": "checkout" + }, + "dashboardsRevision": 4 + }, + "stateSha256": "82d02d7c6de64aaa3ac31e56849e1ec15603abf806faa79cc3247f5f039fe8d8", + "holds": true + } + } + ] +} diff --git a/experiments/hac-343/evidence/registry.baseline.evidence.json b/experiments/hac-343/evidence/registry.baseline.evidence.json new file mode 100644 index 0000000..0152301 --- /dev/null +++ b/experiments/hac-343/evidence/registry.baseline.evidence.json @@ -0,0 +1,110 @@ +{ + "experiment": "HAC-330", + "fixture": "baseline", + "producer": { + "repository": "workspacejson/cli", + "remote": "https://github.com/workspacejson/cli.git", + "pinnedSha": "defac1e5dce6fb692a48e775fb44854b371cbca4", + "observedSha": "defac1e5dce6fb692a48e775fb44854b371cbca4", + "checkoutClean": true, + "package": "@workspacejson/mining-core", + "version": "0.0.0", + "published": false, + "entrypoint": "packages/mining-core/dist/index.js", + "bundleSha256": "7aa5ae231d6713449d6c1790f0b19a509e82ec0c84d67a8a6a52ff492ec27bb8", + "pipeline": "mine -> score -> select", + "l1ProjectionUsed": false, + "l1ProjectionNote": "project() is exported by the package but is deliberately not called: L1 emission onto generated.coChange is step 3 of the A-009 staged transition and the package does not authorize it." + }, + "source": { + "repository": "experiments/hac-343/.work/fixtures/baseline", + "revision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "tree": "b57f883c20fa06246799e610eed88b90d160dfc5", + "commitCount": 18, + "toplevel": "experiments/hac-343/.work/fixtures/baseline", + "isRequestedRepository": true + }, + "historyBasis": { + "basisRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "weightingVersion": "META-289 v2.2.1", + "availableTransitions": 18, + "extractedTransitions": 18, + "windowTruncated": false + }, + "completeness": { + "state": "QUALIFYING_RELATIONSHIP_OBSERVED", + "reason": "MINED", + "detail": "2 pair(s) at support >= 3; 2 emitted under a cap of 50" + }, + "receipt": { + "minSupport": 3, + "pairsBeforeCap": 2, + "pairsEmitted": 2, + "cap": 50, + "rankingRule": "support DESC, then occurrences ASC, then files[0] ASC by UTF-8 bytes, then files[1] ASC by UTF-8 bytes", + "capBound": false + }, + "artifact": { + "serialization": "serializeSelection", + "bytes": 1201, + "sha256": "9973221981f938af9dc6f8e046e1ad27148bbffc577b6ed947117ba3dc60759f" + }, + "selection": { + "l0SelectionVersion": 1, + "completeness": { + "state": "QUALIFYING_RELATIONSHIP_OBSERVED", + "reason": "MINED", + "detail": "2 pair(s) at support >= 3; 2 emitted under a cap of 50" + }, + "basisWindow": { + "basisRevision": "HEAD", + "basisCommit": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "windowTransitions": 500, + "availableTransitions": 18, + "extractedTransitions": 18, + "windowTruncated": false + }, + "scoringBasis": { + "weightingVersion": "META-289 v2.2.1", + "sizeWeightNumerator": 10, + "positionDecayHalfLife": 250, + "maxScoredFileCount": 50, + "basisRevision": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "windowOldestCommit": "31d5febab9b764879e34543bd327a98d134abc64", + "windowNewestCommit": "50c48393ef0df2d1a31abf71a45b5ac3127fb8bf", + "decayOriginPosition": 17 + }, + "exclusions": { + "maxFileCount": 50, + "scoredEventCount": 18, + "excludedEventCount": 0, + "excludedCommits": [] + }, + "receipt": { + "minSupport": 3, + "pairsBeforeCap": 2, + "pairsEmitted": 2, + "cap": 50, + "rankingRule": "support DESC, then occurrences ASC, then files[0] ASC by UTF-8 bytes, then files[1] ASC by UTF-8 bytes", + "capBound": false + }, + "pairs": [ + { + "files": [ + "registry/services.json", + "routing/routes.json" + ], + "support": 9, + "occurrences": 13 + }, + { + "files": [ + "docs/runbook.md", + "observability/dashboards.json" + ], + "support": 5, + "occurrences": 5 + } + ] + } +} diff --git a/experiments/hac-343/evidence/registry.perturbed.evidence.json b/experiments/hac-343/evidence/registry.perturbed.evidence.json new file mode 100644 index 0000000..d6e801a --- /dev/null +++ b/experiments/hac-343/evidence/registry.perturbed.evidence.json @@ -0,0 +1,118 @@ +{ + "experiment": "HAC-330", + "fixture": "perturbed", + "producer": { + "repository": "workspacejson/cli", + "remote": "https://github.com/workspacejson/cli.git", + "pinnedSha": "defac1e5dce6fb692a48e775fb44854b371cbca4", + "observedSha": "defac1e5dce6fb692a48e775fb44854b371cbca4", + "checkoutClean": true, + "package": "@workspacejson/mining-core", + "version": "0.0.0", + "published": false, + "entrypoint": "packages/mining-core/dist/index.js", + "bundleSha256": "7aa5ae231d6713449d6c1790f0b19a509e82ec0c84d67a8a6a52ff492ec27bb8", + "pipeline": "mine -> score -> select", + "l1ProjectionUsed": false, + "l1ProjectionNote": "project() is exported by the package but is deliberately not called: L1 emission onto generated.coChange is step 3 of the A-009 staged transition and the package does not authorize it." + }, + "source": { + "repository": "experiments/hac-343/.work/fixtures/perturbed", + "revision": "f50ecbe40530af357750952235bb262948f9e84e", + "tree": "b57f883c20fa06246799e610eed88b90d160dfc5", + "commitCount": 18, + "toplevel": "experiments/hac-343/.work/fixtures/perturbed", + "isRequestedRepository": true + }, + "historyBasis": { + "basisRevision": "f50ecbe40530af357750952235bb262948f9e84e", + "weightingVersion": "META-289 v2.2.1", + "availableTransitions": 18, + "extractedTransitions": 18, + "windowTruncated": false + }, + "completeness": { + "state": "QUALIFYING_RELATIONSHIP_OBSERVED", + "reason": "MINED", + "detail": "3 pair(s) at support >= 3; 3 emitted under a cap of 50" + }, + "receipt": { + "minSupport": 3, + "pairsBeforeCap": 3, + "pairsEmitted": 3, + "cap": 50, + "rankingRule": "support DESC, then occurrences ASC, then files[0] ASC by UTF-8 bytes, then files[1] ASC by UTF-8 bytes", + "capBound": false + }, + "artifact": { + "serialization": "serializeSelection", + "bytes": 1290, + "sha256": "b6f94506db06d2b72a581bccd73fca02efb22953e2c9eb13f18a09b2961df00a" + }, + "selection": { + "l0SelectionVersion": 1, + "completeness": { + "state": "QUALIFYING_RELATIONSHIP_OBSERVED", + "reason": "MINED", + "detail": "3 pair(s) at support >= 3; 3 emitted under a cap of 50" + }, + "basisWindow": { + "basisRevision": "HEAD", + "basisCommit": "f50ecbe40530af357750952235bb262948f9e84e", + "windowTransitions": 500, + "availableTransitions": 18, + "extractedTransitions": 18, + "windowTruncated": false + }, + "scoringBasis": { + "weightingVersion": "META-289 v2.2.1", + "sizeWeightNumerator": 10, + "positionDecayHalfLife": 250, + "maxScoredFileCount": 50, + "basisRevision": "f50ecbe40530af357750952235bb262948f9e84e", + "windowOldestCommit": "31d5febab9b764879e34543bd327a98d134abc64", + "windowNewestCommit": "f50ecbe40530af357750952235bb262948f9e84e", + "decayOriginPosition": 17 + }, + "exclusions": { + "maxFileCount": 50, + "scoredEventCount": 18, + "excludedEventCount": 0, + "excludedCommits": [] + }, + "receipt": { + "minSupport": 3, + "pairsBeforeCap": 3, + "pairsEmitted": 3, + "cap": 50, + "rankingRule": "support DESC, then occurrences ASC, then files[0] ASC by UTF-8 bytes, then files[1] ASC by UTF-8 bytes", + "capBound": false + }, + "pairs": [ + { + "files": [ + "docs/runbook.md", + "observability/dashboards.json" + ], + "support": 5, + "occurrences": 5 + }, + { + "files": [ + "registry/aliases.json", + "routing/routes.json" + ], + "support": 5, + "occurrences": 11 + }, + { + "files": [ + "registry/aliases.json", + "registry/services.json" + ], + "support": 5, + "occurrences": 13 + } + ] + } +} diff --git a/experiments/hac-343/evidence/results.json b/experiments/hac-343/evidence/results.json new file mode 100644 index 0000000..6db7c5a --- /dev/null +++ b/experiments/hac-343/evidence/results.json @@ -0,0 +1,796 @@ +{ + "experiment": "HAC-343", + "kind": "results", + "metricDefinitionsSha256": "2cfff5b19812d9805d5c5a129bb8e8ac7009ae8b5d21a10a85bb31863bc79700", + "corpusSha256": "68c60b38087de00886338de78b9a8b673c1467bf056adaf057c4fd211929575c", + "executionSemanticsSha256": "eca7fac8c0e74eda0af7199cca16ac3d06d3808a14f2e603b6b0c1a380ff97cc", + "rawResultsSha256": "6f737d44c3b33c298b59d1bf84d028d6c56524ba14ce34c7db82e3f5dddec9eb", + "report": { + "completeness": { + "expected": 128, + "observed": 128, + "missing": [] + }, + "lockValidity": { + "A1_uncoordinated": { + "numerator": 0, + "denominator": 2, + "rate": 0, + "display": "0/2 (0.0%)" + }, + "A2_global_lock": { + "numerator": 2, + "denominator": 2, + "rate": 1, + "display": "2/2 (100.0%)" + }, + "A3_per_target_lock": { + "numerator": 2, + "denominator": 2, + "rate": 1, + "display": "2/2 (100.0%)" + }, + "A4_interlock": { + "numerator": 0, + "denominator": 2, + "rate": 0, + "display": "0/2 (0.0%)" + } + }, + "perFamily": { + "budget": { + "A1_uncoordinated": { + "lockValidity": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "unsafeJointState": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "permit": { + "numerator": 8, + "denominator": 8, + "rate": 1, + "display": "8/8 (100.0%)" + }, + "falseBlock": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "evidenceSensitivity": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "refusalCorrectness": { + "numerator": 0, + "denominator": 4, + "rate": 0, + "display": "0/4 (0.0%)" + }, + "spr": { + "safeParallelismRetained": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "unsafeJointState": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "qualified": false, + "rendering": "SPR 1/1 (100.0%) at unsafe-joint-state rate 1/1 (100.0%) — UNSAFE, not safe parallelism" + } + }, + "A2_global_lock": { + "lockValidity": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "unsafeJointState": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "permit": { + "numerator": 8, + "denominator": 8, + "rate": 1, + "display": "8/8 (100.0%)" + }, + "falseBlock": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "evidenceSensitivity": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "refusalCorrectness": { + "numerator": 0, + "denominator": 4, + "rate": 0, + "display": "0/4 (0.0%)" + }, + "spr": { + "safeParallelismRetained": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "unsafeJointState": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "qualified": true, + "rendering": "SPR 0/1 (0.0%) at unsafe-joint-state rate 0/1 (0.0%)" + } + }, + "A3_per_target_lock": { + "lockValidity": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "unsafeJointState": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "permit": { + "numerator": 8, + "denominator": 8, + "rate": 1, + "display": "8/8 (100.0%)" + }, + "falseBlock": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "evidenceSensitivity": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "refusalCorrectness": { + "numerator": 0, + "denominator": 4, + "rate": 0, + "display": "0/4 (0.0%)" + }, + "spr": { + "safeParallelismRetained": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "unsafeJointState": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "qualified": false, + "rendering": "SPR 1/1 (100.0%) at unsafe-joint-state rate 1/1 (100.0%) — UNSAFE, not safe parallelism" + } + }, + "A4_interlock": { + "lockValidity": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "unsafeJointState": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "permit": { + "numerator": 4, + "denominator": 8, + "rate": 0.5, + "display": "4/8 (50.0%)" + }, + "falseBlock": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "evidenceSensitivity": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "refusalCorrectness": { + "numerator": 3, + "denominator": 4, + "rate": 0.75, + "display": "3/4 (75.0%)" + }, + "spr": { + "safeParallelismRetained": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "unsafeJointState": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "qualified": true, + "rendering": "SPR 1/1 (100.0%) at unsafe-joint-state rate 0/1 (0.0%)" + } + } + }, + "registry": { + "A1_uncoordinated": { + "lockValidity": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "unsafeJointState": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "permit": { + "numerator": 8, + "denominator": 8, + "rate": 1, + "display": "8/8 (100.0%)" + }, + "falseBlock": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "evidenceSensitivity": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "refusalCorrectness": { + "numerator": 0, + "denominator": 4, + "rate": 0, + "display": "0/4 (0.0%)" + }, + "spr": { + "safeParallelismRetained": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "unsafeJointState": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "qualified": false, + "rendering": "SPR 1/1 (100.0%) at unsafe-joint-state rate 1/1 (100.0%) — UNSAFE, not safe parallelism" + } + }, + "A2_global_lock": { + "lockValidity": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "unsafeJointState": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "permit": { + "numerator": 8, + "denominator": 8, + "rate": 1, + "display": "8/8 (100.0%)" + }, + "falseBlock": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "evidenceSensitivity": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "refusalCorrectness": { + "numerator": 0, + "denominator": 4, + "rate": 0, + "display": "0/4 (0.0%)" + }, + "spr": { + "safeParallelismRetained": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "unsafeJointState": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "qualified": true, + "rendering": "SPR 0/1 (0.0%) at unsafe-joint-state rate 0/1 (0.0%)" + } + }, + "A3_per_target_lock": { + "lockValidity": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "unsafeJointState": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "permit": { + "numerator": 8, + "denominator": 8, + "rate": 1, + "display": "8/8 (100.0%)" + }, + "falseBlock": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "evidenceSensitivity": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "refusalCorrectness": { + "numerator": 0, + "denominator": 4, + "rate": 0, + "display": "0/4 (0.0%)" + }, + "spr": { + "safeParallelismRetained": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "unsafeJointState": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "qualified": false, + "rendering": "SPR 1/1 (100.0%) at unsafe-joint-state rate 1/1 (100.0%) — UNSAFE, not safe parallelism" + } + }, + "A4_interlock": { + "lockValidity": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "unsafeJointState": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "permit": { + "numerator": 4, + "denominator": 8, + "rate": 0.5, + "display": "4/8 (50.0%)" + }, + "falseBlock": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "evidenceSensitivity": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "refusalCorrectness": { + "numerator": 3, + "denominator": 4, + "rate": 0.75, + "display": "3/4 (75.0%)" + }, + "spr": { + "safeParallelismRetained": { + "numerator": 1, + "denominator": 1, + "rate": 1, + "display": "1/1 (100.0%)" + }, + "unsafeJointState": { + "numerator": 0, + "denominator": 1, + "rate": 0, + "display": "0/1 (0.0%)" + }, + "qualified": true, + "rendering": "SPR 1/1 (100.0%) at unsafe-joint-state rate 0/1 (0.0%)" + } + } + } + }, + "aggregate": { + "A1_uncoordinated": { + "lockValidity": { + "numerator": 0, + "denominator": 2, + "rate": 0, + "display": "0/2 (0.0%)" + }, + "unsafeJointState": { + "numerator": 2, + "denominator": 2, + "rate": 1, + "display": "2/2 (100.0%)" + }, + "permit": { + "numerator": 16, + "denominator": 16, + "rate": 1, + "display": "16/16 (100.0%)" + }, + "falseBlock": { + "numerator": 0, + "denominator": 2, + "rate": 0, + "display": "0/2 (0.0%)" + }, + "evidenceSensitivity": { + "numerator": 0, + "denominator": 2, + "rate": 0, + "display": "0/2 (0.0%)" + }, + "refusalCorrectness": { + "numerator": 0, + "denominator": 8, + "rate": 0, + "display": "0/8 (0.0%)" + }, + "spr": { + "safeParallelismRetained": { + "numerator": 2, + "denominator": 2, + "rate": 1, + "display": "2/2 (100.0%)" + }, + "unsafeJointState": { + "numerator": 2, + "denominator": 2, + "rate": 1, + "display": "2/2 (100.0%)" + }, + "qualified": false, + "rendering": "SPR 2/2 (100.0%) at unsafe-joint-state rate 2/2 (100.0%) — UNSAFE, not safe parallelism" + } + }, + "A2_global_lock": { + "lockValidity": { + "numerator": 2, + "denominator": 2, + "rate": 1, + "display": "2/2 (100.0%)" + }, + "unsafeJointState": { + "numerator": 0, + "denominator": 2, + "rate": 0, + "display": "0/2 (0.0%)" + }, + "permit": { + "numerator": 16, + "denominator": 16, + "rate": 1, + "display": "16/16 (100.0%)" + }, + "falseBlock": { + "numerator": 2, + "denominator": 2, + "rate": 1, + "display": "2/2 (100.0%)" + }, + "evidenceSensitivity": { + "numerator": 0, + "denominator": 2, + "rate": 0, + "display": "0/2 (0.0%)" + }, + "refusalCorrectness": { + "numerator": 0, + "denominator": 8, + "rate": 0, + "display": "0/8 (0.0%)" + }, + "spr": { + "safeParallelismRetained": { + "numerator": 0, + "denominator": 2, + "rate": 0, + "display": "0/2 (0.0%)" + }, + "unsafeJointState": { + "numerator": 0, + "denominator": 2, + "rate": 0, + "display": "0/2 (0.0%)" + }, + "qualified": true, + "rendering": "SPR 0/2 (0.0%) at unsafe-joint-state rate 0/2 (0.0%)" + } + }, + "A3_per_target_lock": { + "lockValidity": { + "numerator": 2, + "denominator": 2, + "rate": 1, + "display": "2/2 (100.0%)" + }, + "unsafeJointState": { + "numerator": 2, + "denominator": 2, + "rate": 1, + "display": "2/2 (100.0%)" + }, + "permit": { + "numerator": 16, + "denominator": 16, + "rate": 1, + "display": "16/16 (100.0%)" + }, + "falseBlock": { + "numerator": 0, + "denominator": 2, + "rate": 0, + "display": "0/2 (0.0%)" + }, + "evidenceSensitivity": { + "numerator": 0, + "denominator": 2, + "rate": 0, + "display": "0/2 (0.0%)" + }, + "refusalCorrectness": { + "numerator": 0, + "denominator": 8, + "rate": 0, + "display": "0/8 (0.0%)" + }, + "spr": { + "safeParallelismRetained": { + "numerator": 2, + "denominator": 2, + "rate": 1, + "display": "2/2 (100.0%)" + }, + "unsafeJointState": { + "numerator": 2, + "denominator": 2, + "rate": 1, + "display": "2/2 (100.0%)" + }, + "qualified": false, + "rendering": "SPR 2/2 (100.0%) at unsafe-joint-state rate 2/2 (100.0%) — UNSAFE, not safe parallelism" + } + }, + "A4_interlock": { + "lockValidity": { + "numerator": 0, + "denominator": 2, + "rate": 0, + "display": "0/2 (0.0%)" + }, + "unsafeJointState": { + "numerator": 0, + "denominator": 2, + "rate": 0, + "display": "0/2 (0.0%)" + }, + "permit": { + "numerator": 8, + "denominator": 16, + "rate": 0.5, + "display": "8/16 (50.0%)" + }, + "falseBlock": { + "numerator": 0, + "denominator": 2, + "rate": 0, + "display": "0/2 (0.0%)" + }, + "evidenceSensitivity": { + "numerator": 2, + "denominator": 2, + "rate": 1, + "display": "2/2 (100.0%)" + }, + "refusalCorrectness": { + "numerator": 6, + "denominator": 8, + "rate": 0.75, + "display": "6/8 (75.0%)" + }, + "spr": { + "safeParallelismRetained": { + "numerator": 2, + "denominator": 2, + "rate": 1, + "display": "2/2 (100.0%)" + }, + "unsafeJointState": { + "numerator": 0, + "denominator": 2, + "rate": 0, + "display": "0/2 (0.0%)" + }, + "qualified": true, + "rendering": "SPR 2/2 (100.0%) at unsafe-joint-state rate 0/2 (0.0%)" + } + } + }, + "orderEffects": [ + { + "scenarioId": "budget/coupled/alpha-beta", + "arm": "A2_global_lock", + "signatures": [ + "i0:applied,i1:rejected", + "i0:rejected,i1:applied" + ] + }, + { + "scenarioId": "budget/perturbed/alpha-beta", + "arm": "A2_global_lock", + "signatures": [ + "i0:applied,i1:rejected", + "i0:rejected,i1:applied" + ] + }, + { + "scenarioId": "budget/inadmissible/absent", + "arm": "A2_global_lock", + "signatures": [ + "i0:applied,i1:rejected", + "i0:rejected,i1:applied" + ] + }, + { + "scenarioId": "budget/inadmissible/shallow", + "arm": "A2_global_lock", + "signatures": [ + "i0:applied,i1:rejected", + "i0:rejected,i1:applied" + ] + }, + { + "scenarioId": "budget/inadmissible/noRepository", + "arm": "A2_global_lock", + "signatures": [ + "i0:applied,i1:rejected", + "i0:rejected,i1:applied" + ] + }, + { + "scenarioId": "budget/inadmissible/misattributed", + "arm": "A2_global_lock", + "signatures": [ + "i0:applied,i1:rejected", + "i0:rejected,i1:applied" + ] + }, + { + "scenarioId": "registry/coupled/retire-vs-route", + "arm": "A2_global_lock", + "signatures": [ + "i0:applied,i1:rejected", + "i0:rejected,i1:applied" + ] + }, + { + "scenarioId": "registry/perturbed/retire-vs-route", + "arm": "A2_global_lock", + "signatures": [ + "i0:applied,i1:rejected", + "i0:rejected,i1:applied" + ] + }, + { + "scenarioId": "registry/inadmissible/absent", + "arm": "A2_global_lock", + "signatures": [ + "i0:applied,i1:rejected", + "i0:rejected,i1:applied" + ] + }, + { + "scenarioId": "registry/inadmissible/shallow", + "arm": "A2_global_lock", + "signatures": [ + "i0:applied,i1:rejected", + "i0:rejected,i1:applied" + ] + }, + { + "scenarioId": "registry/inadmissible/noRepository", + "arm": "A2_global_lock", + "signatures": [ + "i0:applied,i1:rejected", + "i0:rejected,i1:applied" + ] + }, + { + "scenarioId": "registry/inadmissible/misattributed", + "arm": "A2_global_lock", + "signatures": [ + "i0:applied,i1:rejected", + "i0:rejected,i1:applied" + ] + } + ], + "defects": [] + } +} diff --git a/experiments/hac-343/lib/aggregate.mjs b/experiments/hac-343/lib/aggregate.mjs new file mode 100644 index 0000000..fe10104 --- /dev/null +++ b/experiments/hac-343/lib/aggregate.mjs @@ -0,0 +1,271 @@ +/** + * HAC-343 — aggregation. A pure function over raw records. + * + * No filesystem, no git, no clock, no network. It receives the raw records and + * the frozen corpus and returns a report; the same inputs always produce the + * same output, which is what lets `verify-packet.mjs` recompute every number + * from the committed raw records rather than trusting a summary. + * + * Three properties matter more than the arithmetic: + * + * 1. **It cannot skip.** A missing scenario x arm x order combination throws. + * A metric quietly computed over 14 of 16 scenarios would report a rate that + * looks like a measurement and is not one — the "missing-run green state" + * HAC-319 forbids by name. + * 2. **It cannot suppress.** Refused, rejected and errored records are counted, + * not filtered. An arm cannot improve a rate by failing to produce a record. + * 3. **SPR cannot escape alone.** The only way to obtain an SPR figure from this + * module is as an object that also carries the unsafe-joint-state rate and + * both sets of counts. `assembleSpr` throws if asked to build one without. + * + * @see evidence/metric-definitions.json — every definition below is frozen there + * @see evidence/execution-semantics.json — the two-order aggregation rule + */ + +/** Full identities of the commits that froze this experiment's contracts. */ +export const FROZEN_COMMITS = Object.freeze({ + 'experiments/hac-343/evidence/metric-definitions.json': '0a6babbc5d1a3f69b057f98093108ee508072e48', + 'experiments/hac-343/evidence/corpus.json': 'dbdcaa940933f90091a838f5f183031c7556afad', + 'experiments/hac-343/evidence/execution-semantics.json': '276750ba7a4a51461fb2447b361d69be5e2a020b', +}); + +export const ORDERS = Object.freeze(['AB', 'BA']); + +// --------------------------------------------------------------------------- + +/** + * A rate that always shows its working. + * + * An empty denominator is `n/a (0 cases)` and never 0% or 100%: a metric with + * nothing to measure must not render as a passing green state. + */ +export function rate(numerator, denominator) { + if (denominator === 0) { + return { numerator, denominator, rate: null, display: 'n/a (0 cases)' }; + } + const value = numerator / denominator; + return { + numerator, + denominator, + rate: value, + display: `${numerator}/${denominator} (${(value * 100).toFixed(1)}%)`, + }; +} + +/** + * Build an SPR figure. The only constructor, and it refuses to make a bare one. + * + * An arm that permits everything scores SPR 100% and is unsafe; publishing that + * number alone would make the worst arm look best. The frozen definition states + * SPR is reported only as an ordered pair, so the pairing is enforced in the + * type rather than left to whoever renders it. + */ +export function assembleSpr(sprRate, unsafeRate) { + if (unsafeRate === undefined || unsafeRate === null) { + throw new Error('SPR may not be assembled without an unsafe-joint-state rate (metric-definitions.json headlineKpi.safetyPrecondition)'); + } + const qualified = unsafeRate.denominator > 0 && unsafeRate.numerator === 0; + return { + safeParallelismRetained: sprRate, + unsafeJointState: unsafeRate, + // An arm may be described as retaining *safe* parallelism only when its + // unsafe rate is zero. Otherwise the qualifier travels with the number. + qualified, + rendering: `SPR ${sprRate.display} at unsafe-joint-state rate ${unsafeRate.display}${qualified ? '' : ' — UNSAFE, not safe parallelism'}`, + }; +} + +// --------------------------------------------------------------------------- + +const key = (scenarioId, arm, order) => `${scenarioId}|${arm}|${order}`; + +/** + * Collapse a scenario's two execution orders into one verdict per the frozen + * rule. Every clause is conservative toward A4. + */ +function collapseOrders(scenarioId, arm, byOrder) { + const records = ORDERS.map((order) => byOrder.get(key(scenarioId, arm, order))); + + const errored = records.filter((r) => r.error != null); + // Safety must hold under all orders. + const unsafe = records.some((r) => r.oracle?.holds === false) || errored.length > 0; + // Parallelism is claimed only when it is order-independent. + const permittedConcurrently = records.every( + (r) => r.concurrent === true && Array.isArray(r.outcomes) && r.outcomes.length > 0 && r.outcomes.every((o) => o.applied === true), + ); + const anyApplied = records.some((r) => Array.isArray(r.outcomes) && r.outcomes.some((o) => o.applied === true)); + const serialized = records.every((r) => r.concurrent === false); + + const signatures = records.map((r) => decisionSignature(r)); + const orderDisagreement = signatures[0] !== signatures[1]; + + return { + scenarioId, + arm, + records, + unsafe, + permittedConcurrently, + anyApplied, + serialized, + errored: errored.length > 0, + signature: signatures[0], + signatures, + orderDisagreement, + refusalReasons: records.map((r) => r.refusalReason ?? null), + }; +} + +/** + * A canonical, order-independent summary of what an arm decided. + * + * Sorted by intent id so permuting the execution order cannot change the + * signature on its own — otherwise every scenario would look order-sensitive and + * the evidence-sensitivity metric would measure the permutation. + */ +export function decisionSignature(record) { + if (record.error != null) return `ERROR:${record.error}`; + if (record.refusalReason) return `REFUSED:${record.refusalReason}`; + if (Array.isArray(record.verdicts) && record.verdicts.length > 0) { + return record.verdicts.map((v) => v.decision).sort().join(','); + } + return (record.outcomes ?? []) + .map((o) => `${o.intentId}:${o.applied ? 'applied' : 'rejected'}`) + .sort() + .join(','); +} + +// --------------------------------------------------------------------------- + +function metricsFor(collapsed, scenarios, arm) { + const forArm = collapsed.filter((c) => c.arm === arm); + const scenarioOf = (c) => scenarios.find((s) => s.id === c.scenarioId); + const withLabel = (label) => forArm.filter((c) => scenarioOf(c).label === label); + + const coupled = withLabel('COUPLED'); + const independent = withLabel('INDEPENDENT'); + const sameTarget = withLabel('SAME_TARGET_CONTENTION'); + const perturbed = withLabel('EVIDENCE_PERTURBED'); + const inadmissible = withLabel('EVIDENCE_INADMISSIBLE'); + + // Evaluated first, and reported first: a baseline that did not lock makes + // every downstream comparison meaningless. + const lockValidity = rate(sameTarget.filter((c) => c.serialized).length, sameTarget.length); + + const unsafeJointState = rate(coupled.filter((c) => c.unsafe).length, coupled.length); + const permit = rate(forArm.filter((c) => c.anyApplied).length, forArm.length); + const falseBlock = rate(independent.filter((c) => !c.permittedConcurrently).length, independent.length); + + const evidenceSensitivity = rate( + perturbed.filter((c) => { + const origin = forArm.find((o) => o.scenarioId === scenarioOf(c).perturbationOf); + return origin != null && origin.signature !== c.signature; + }).length, + perturbed.length, + ); + + const refusalCorrectness = rate( + inadmissible.filter((c) => + c.refusalReasons.every((reason) => reason != null && reason === scenarioOf(c).expectedRefusalReason), + ).length, + inadmissible.length, + ); + + const spr = assembleSpr( + rate(independent.filter((c) => c.permittedConcurrently).length, independent.length), + unsafeJointState, + ); + + return { lockValidity, unsafeJointState, permit, falseBlock, evidenceSensitivity, refusalCorrectness, spr }; +} + +// --------------------------------------------------------------------------- + +/** + * Aggregate raw records into the report. + * + * @throws when any scenario x arm x order record is missing or duplicated. + */ +export function aggregate({ records, scenarios, arms, families }) { + const byOrder = new Map(); + for (const record of records) { + const k = key(record.scenarioId, record.arm, record.order); + if (byOrder.has(k)) throw new Error(`duplicate raw record for ${k}`); + byOrder.set(k, record); + } + + const expected = []; + const missing = []; + for (const scenario of scenarios) { + for (const arm of arms) { + for (const order of ORDERS) { + const k = key(scenario.id, arm, order); + expected.push(k); + if (!byOrder.has(k)) missing.push(k); + } + } + } + if (missing.length > 0) { + throw new Error( + `incomplete raw results: ${missing.length} of ${expected.length} records missing — ` + + `a metric computed over a partial matrix is not a measurement (${missing.slice(0, 5).join(', ')}${missing.length > 5 ? ', …' : ''})`, + ); + } + + const collapsed = []; + for (const scenario of scenarios) { + for (const arm of arms) collapsed.push(collapseOrders(scenario.id, arm, byOrder)); + } + + const perArm = Object.fromEntries(arms.map((arm) => [arm, metricsFor(collapsed, scenarios, arm)])); + + // Per family first, then aggregate, so a family-level failure cannot be + // averaged into an acceptable-looking whole. + const perFamily = Object.fromEntries( + families.map((family) => { + const familyScenarios = scenarios.filter((s) => s.family === family); + const ids = new Set(familyScenarios.map((s) => s.id)); + const familyCollapsed = collapsed.filter((c) => ids.has(c.scenarioId)); + return [ + family, + Object.fromEntries(arms.map((arm) => [arm, metricsFor(familyCollapsed, familyScenarios, arm)])), + ]; + }), + ); + + const orderEffects = collapsed + .filter((c) => c.orderDisagreement) + .map((c) => ({ scenarioId: c.scenarioId, arm: c.arm, signatures: c.signatures })); + + // Defect gates. These are not results; a trip means the harness is wrong. + const defects = []; + for (const arm of arms) { + if (arm === 'A4_interlock') continue; + const sensitivity = perArm[arm].evidenceSensitivity; + if (sensitivity.denominator > 0 && sensitivity.numerator > 0) { + defects.push({ + gate: 'evidenceSensitivity', + arm, + detail: `${arm} consumes no evidence, so a decision that moves when evidence moves means the harness is leaking state between arms (${sensitivity.display})`, + }); + } + } + for (const arm of ['A2_global_lock', 'A3_per_target_lock']) { + const validity = perArm[arm]?.lockValidity; + if (validity && validity.denominator > 0 && validity.numerator !== validity.denominator) { + defects.push({ + gate: 'lockValidity', + arm, + detail: `${arm} failed to serialize a SAME_TARGET_CONTENTION scenario (${validity.display}); it is a defective lock rather than a blind one, and its unsafe results prove nothing`, + }); + } + } + + return { + completeness: { expected: expected.length, observed: byOrder.size, missing }, + lockValidity: Object.fromEntries(arms.map((arm) => [arm, perArm[arm].lockValidity])), + perFamily, + aggregate: perArm, + orderEffects, + defects, + }; +} diff --git a/experiments/hac-343/lib/arms.mjs b/experiments/hac-343/lib/arms.mjs new file mode 100644 index 0000000..ce64c36 --- /dev/null +++ b/experiments/hac-343/lib/arms.mjs @@ -0,0 +1,230 @@ +/** + * HAC-343 — the four coordination policies. + * + * Every arm calls the same executor, applies the same actions, and runs the same + * local preconditions. The only thing that varies is **when** a precondition is + * evaluated, which is the whole of the hazard. + * + * Concurrency, precisely: two intents run concurrently when both evaluate their + * precondition against the same base snapshot and both writes then land. They + * run serially when the second evaluates against what the first already wrote. + * Every lock-bearing arm expresses that through one shared critical section — + * acquire, re-read, re-check, mutate or reject, release — so a lock baseline + * genuinely gets to see the other action's write before deciding. Without that + * re-read the lock arms would be strawmen that merely reordered two + * already-approved mutations, and would overshoot anyway. + * + * A1 and A3-on-distinct-targets deliberately reduce to the same code path. That + * is not a shortcut: holding two different locks provides exactly as much mutual + * exclusion as holding none, and stating it in code rather than in prose is the + * clearest form of the finding. + * + * @see evidence/execution-semantics.json — frozen before any result. + */ +import { Decision, arbitrate } from '../../../dist/broker/pairing/arbitrate.js'; + +import { + applyIntent, + criticalSection, + evaluateAgainstBase, + resetWorktree, + sha256, +} from './executor.mjs'; + +export const ARMS = Object.freeze(['A1_uncoordinated', 'A2_global_lock', 'A3_per_target_lock', 'A4_interlock']); + +/** + * Every intent must arrive carrying a stable id. + * + * The frozen corpus declares intents without one, because an id is an artifact + * of execution rather than of the scenario. The caller assigns `i0`, `i1` from + * each scenario's canonical intent order, so an id stays attached to its intent + * when the execution order is permuted — which is what lets a decision signature + * be compared across the two orders without the permutation itself looking like + * a difference. + * + * Asserted rather than defaulted: an undefined id would silently collapse both + * intents onto one record and the phase-2 replay would apply the wrong write. + */ +function assertIdentified(intents) { + for (const intent of intents) { + if (typeof intent.id !== 'string' || intent.id === '') { + throw new Error(`intent is missing a stable id: ${JSON.stringify(intent)}`); + } + } + const ids = new Set(intents.map((i) => i.id)); + if (ids.size !== intents.length) throw new Error('intent ids are not unique within the scenario'); +} + +/** + * Lock key policy. `A1` gives every intent its own key, which is the same thing + * as holding no lock: distinct keys never contend. + */ +function lockKeyFor(arm, intent) { + if (arm === 'A1_uncoordinated') return `NONE#${intent.id}`; + if (arm === 'A2_global_lock') return 'GLOBAL'; + if (arm === 'A3_per_target_lock') return intent.path; + throw new Error(`lockKeyFor: ${arm} does not use lock keys`); +} + +/** + * Execute intents under a lock-key policy. + * + * Phase 1 — each lock group evaluates from the base snapshot in isolation, and + * intents sharing a key serialize within their group so the second sees the + * first's write. Phase 2 — every approved write lands together. + * + * With one group this reduces to plain serialization; with N groups it is the + * lost-update composition, which is the point. + */ +function runLocked(repo, family, intents, arm) { + assertIdentified(intents); + resetWorktree(repo); + + const groups = new Map(); + for (const intent of intents) { + const key = lockKeyFor(arm, intent); + if (!groups.has(key)) groups.set(key, []); + groups.get(key).push(intent); + } + + const outcomes = []; + for (const [key, groupIntents] of groups) { + resetWorktree(repo); + for (const intent of groupIntents) { + const result = criticalSection(repo, family, intent); + outcomes.push({ intentId: intent.id, lockKey: key, ...result }); + } + } + + resetWorktree(repo); + for (const outcome of outcomes) { + if (outcome.applied) applyIntent(repo, family, intents.find((i) => i.id === outcome.intentId)); + } + + return { + outcomes, + lockGroups: [...groups.keys()], + concurrent: groups.size > 1, + }; +} + +// --------------------------------------------------------------------------- +// A4 — Interlock +// --------------------------------------------------------------------------- + +/** + * Deterministic pending-intent records. + * + * `recordedAt` is derived from the scenario id and the intent's position, never + * from a clock: arbitrate() breaks precedence ties on recordedAt then + * correlationId, so a wall-clock value would make the leader — and therefore the + * result — differ between runs. + */ +function pendingIntents(scenario, intents) { + const base = Date.UTC(2026, 0, 1, 0, 0, 0); + return intents.map((intent, index) => ({ + correlationId: `ilk-${sha256(`${scenario.id}#${intent.id}`).slice(0, 24)}`, + agent: `agent-${intent.id}`, + operation: intent.op, + targets: [intent.path], + intentDigest: `sha256:${sha256(JSON.stringify(intent))}`, + recordedAt: new Date(base + index * 1000).toISOString(), + expiresAt: new Date(base + 3_600_000).toISOString(), + })); +} + +function runInterlock(repo, family, scenario, intents, { evidence, sourceRevision }) { + assertIdentified(intents); + resetWorktree(repo); + + const pending = pendingIntents(scenario, intents); + + // One verdict per arriving intent, each against everything else in flight. + const verdicts = pending.map((candidate) => + arbitrate({ + candidate, + others: { ok: true, value: pending.filter((p) => p.correlationId !== candidate.correlationId) }, + evidence, + sourceRevision, + }), + ); + + const refused = verdicts.find((v) => v.decision === Decision.INSUFFICIENT_EVIDENCE); + if (refused) { + return { + verdicts, + outcomes: intents.map((intent) => ({ + intentId: intent.id, + applied: false, + rejected: true, + reason: 'REFUSED_INSUFFICIENT_EVIDENCE', + detail: refused.reasonCode, + })), + refusalReason: refused.reasonCode, + concurrent: false, + }; + } + + const allParallel = verdicts.every((v) => v.decision === Decision.ALLOW_PARALLEL); + + if (allParallel) { + // Concurrent: every intent evaluates against the base snapshot, then the + // approved writes land together — identical semantics to the lock arms' + // multi-group case, so no arm gets a different notion of "concurrent". + const evaluated = intents.map((intent) => ({ + intent, + precondition: evaluateAgainstBase(repo, family, intent), + })); + for (const { intent, precondition } of evaluated) { + if (precondition.ok) applyIntent(repo, family, intent); + } + return { + verdicts, + outcomes: evaluated.map(({ intent, precondition }) => ({ + intentId: intent.id, + applied: precondition.ok, + rejected: !precondition.ok, + reason: precondition.ok ? 'APPLIED' : 'LOCAL_PRECONDITION_FAILED', + detail: precondition.detail, + })), + concurrent: true, + }; + } + + // Serialized: whoever holds precedence proceeds through the same critical + // section every lock arm uses; the rest are withheld and would resubmit. + const outcomes = []; + for (const [index, intent] of intents.entries()) { + const verdict = verdicts[index]; + if (verdict.decision === Decision.ALLOW_SERIALIZED) { + outcomes.push({ intentId: intent.id, ...criticalSection(repo, family, intent) }); + } else { + outcomes.push({ + intentId: intent.id, + applied: false, + rejected: true, + reason: 'WITHHELD_SERIALIZE', + detail: verdict.detail, + }); + } + } + + return { verdicts, outcomes, concurrent: false }; +} + +// --------------------------------------------------------------------------- + +/** + * Run one arm over one scenario in one intent order. + * + * Returns the arm's decisions and outcomes only. Whether the resulting state is + * actually valid is not decided here — the caller asks the fixture's own + * verifier, which nothing in this file can influence. + */ +export function runArm({ arm, repo, family, scenario, intents, evidence, sourceRevision }) { + if (arm === 'A4_interlock') { + return runInterlock(repo, family, scenario, intents, { evidence, sourceRevision }); + } + return runLocked(repo, family, intents, arm); +} diff --git a/experiments/hac-343/lib/corpus.mjs b/experiments/hac-343/lib/corpus.mjs new file mode 100644 index 0000000..64e8cff --- /dev/null +++ b/experiments/hac-343/lib/corpus.mjs @@ -0,0 +1,316 @@ +/** + * HAC-343 — the frozen scenario corpus. + * + * Declarative on purpose. A scenario says which family it belongs to, which + * fixture history it is evaluated against, what the two intents write, and which + * ground-truth class it carries. It says nothing about what any arm should + * decide: the labels are properties of the fixture and the intents, assigned by + * construction, and every arm is scored against them without the arm being + * consulted. + * + * Two families, so a result is not one hazard shape repeated: + * + * - `budget` — arithmetic. Composing two valid increases overshoots a ceiling. + * Fixtures reused verbatim from HAC-330; nothing is rebuilt here. + * - `registry` — referential. One intent removes a referent the other starts + * pointing at. No arithmetic, and the hazard is asymmetric. + * + * Both families carry all five ground-truth classes, so any per-family + * divergence in the results is about hazard shape rather than about one family + * having been given an easier set of cases. + * + * @see evidence/metric-definitions.json — frozen first, in its own commit. + */ + +/** Ground-truth classes. Must match groundTruthLabels in the metric manifest. */ +export const Label = Object.freeze({ + COUPLED: 'COUPLED', + INDEPENDENT: 'INDEPENDENT', + SAME_TARGET_CONTENTION: 'SAME_TARGET_CONTENTION', + EVIDENCE_PERTURBED: 'EVIDENCE_PERTURBED', + EVIDENCE_INADMISSIBLE: 'EVIDENCE_INADMISSIBLE', +}); + +/** + * Inadmissible-evidence sources, reused from the HAC-330 packet rather than + * re-derived. Each is a real artifact the miner or its absence produced, not a + * hand-written malformed blob: that is what makes a refusal on them meaningful. + */ +export const INADMISSIBLE_EVIDENCE = Object.freeze({ + absent: { file: null, expectedReason: 'EVIDENCE_ABSENT' }, + shallow: { file: 'shallow.evidence.json', expectedReason: 'HISTORY_NOT_MINED' }, + noRepository: { file: 'no-repository.evidence.json', expectedReason: 'HISTORY_EVIDENCE_UNAVAILABLE' }, + misattributed: { file: 'misattributed.evidence.json', expectedReason: 'EVIDENCE_REPOSITORY_MISMATCH' }, +}); + +// --------------------------------------------------------------------------- +// Family 1 — budget (arithmetic hazard), fixtures from HAC-330 +// --------------------------------------------------------------------------- + +const reservation = (service, reserved) => ({ + op: 'set-reservation', + path: `services/${service}/reservation.json`, + service, + reserved, +}); + +const BUDGET = [ + { + id: 'budget/coupled/alpha-beta', + family: 'budget', + label: Label.COUPLED, + fixture: 'baseline', + rationale: + 'alpha and beta are historical counterparties at support 8. Each raise is valid alone (120 <= 130); composed they reach 140 > 130.', + intents: [reservation('alpha', 60), reservation('beta', 60)], + composeViolatesInvariant: true, + }, + { + id: 'budget/independent/alpha-gamma', + family: 'budget', + label: Label.INDEPENDENT, + fixture: 'baseline', + rationale: + 'alpha and gamma never appear in one commit in the baseline history. Composed they reach 128 <= 130, so permitting both is correct.', + intents: [reservation('alpha', 60), reservation('gamma', 28)], + composeViolatesInvariant: false, + }, + { + id: 'budget/same-target/alpha-alpha', + family: 'budget', + label: Label.SAME_TARGET_CONTENTION, + fixture: 'baseline', + rationale: + 'Both intents write services/alpha/reservation.json. Any real lock must serialize this; it exists to prove the lock arms lock.', + intents: [reservation('alpha', 60), reservation('alpha', 55)], + composeViolatesInvariant: false, + }, + { + id: 'budget/perturbed/alpha-beta', + family: 'budget', + label: Label.EVIDENCE_PERTURBED, + fixture: 'perturbed', + rationale: + 'Identical intents and identical final tree to budget/coupled/alpha-beta, against a history where alpha and beta never co-occur. The composition is still arithmetically unsafe; only the evidence changed.', + intents: [reservation('alpha', 60), reservation('beta', 60)], + composeViolatesInvariant: true, + perturbationOf: 'budget/coupled/alpha-beta', + }, + ...Object.entries(INADMISSIBLE_EVIDENCE).map(([key, source]) => ({ + id: `budget/inadmissible/${key}`, + family: 'budget', + label: Label.EVIDENCE_INADMISSIBLE, + fixture: 'baseline', + rationale: `The coupled intents against ${key} evidence. Correct behavior is explicit refusal with reason ${source.expectedReason}, never a permit.`, + intents: [reservation('alpha', 60), reservation('beta', 60)], + composeViolatesInvariant: true, + evidenceOverride: key, + expectedRefusalReason: source.expectedReason, + })), +]; + +// --------------------------------------------------------------------------- +// Family 2 — registry (referential hazard) +// --------------------------------------------------------------------------- + +const removeService = (service) => ({ + op: 'remove-service', + path: 'registry/services.json', + service, +}); + +const addRoute = (path, service) => ({ + op: 'add-route', + path: 'routing/routes.json', + route: path, + service, +}); + +const bumpDashboards = (revision) => ({ + op: 'bump-dashboards', + path: 'observability/dashboards.json', + revision, +}); + +const REGISTRY = [ + { + id: 'registry/coupled/retire-vs-route', + family: 'registry', + label: Label.COUPLED, + fixture: 'baseline', + rationale: + 'services and routes are historical counterparties at support 9. Retiring the unrouted legacy-pricing service is valid alone; routing to it is valid alone; composed the route dangles.', + intents: [removeService('legacy-pricing'), addRoute('/pricing', 'legacy-pricing')], + composeViolatesInvariant: true, + }, + { + id: 'registry/independent/route-vs-dashboards', + family: 'registry', + label: Label.INDEPENDENT, + fixture: 'baseline', + rationale: + 'dashboards co-changes only with the runbook, never with services or routes. Adding a route to an existing service and bumping a dashboard revision compose safely.', + intents: [addRoute('/health', 'checkout'), bumpDashboards(99)], + composeViolatesInvariant: false, + }, + { + id: 'registry/same-target/route-vs-route', + family: 'registry', + label: Label.SAME_TARGET_CONTENTION, + fixture: 'baseline', + rationale: + 'Both intents write routing/routes.json. Any real lock must serialize this; it exists to prove the lock arms lock.', + intents: [addRoute('/a', 'checkout'), addRoute('/b', 'inventory')], + composeViolatesInvariant: false, + }, + { + id: 'registry/perturbed/retire-vs-route', + family: 'registry', + label: Label.EVIDENCE_PERTURBED, + fixture: 'perturbed', + rationale: + 'Identical intents and identical final tree to registry/coupled/retire-vs-route, against a history where services and routes never co-occur. The composition still dangles; only the evidence changed.', + intents: [removeService('legacy-pricing'), addRoute('/pricing', 'legacy-pricing')], + composeViolatesInvariant: true, + perturbationOf: 'registry/coupled/retire-vs-route', + }, + ...Object.entries(INADMISSIBLE_EVIDENCE).map(([key, source]) => ({ + id: `registry/inadmissible/${key}`, + family: 'registry', + label: Label.EVIDENCE_INADMISSIBLE, + fixture: 'baseline', + rationale: `The coupled intents against ${key} evidence. Correct behavior is explicit refusal with reason ${source.expectedReason}, never a permit.`, + intents: [removeService('legacy-pricing'), addRoute('/pricing', 'legacy-pricing')], + composeViolatesInvariant: true, + evidenceOverride: key, + expectedRefusalReason: source.expectedReason, + })), +]; + +export const SCENARIOS = Object.freeze([...BUDGET, ...REGISTRY]); + +export const FAMILIES = Object.freeze(['budget', 'registry']); + +// --------------------------------------------------------------------------- +// Mechanical validation of the corpus requirements +// --------------------------------------------------------------------------- + +/** + * Check every requirement the frozen metric manifest places on the corpus. + * + * Returns a list of failures. An empty list is the only acceptable result, and + * the caller exits non-zero otherwise: a corpus that cannot produce a class + * silently reports that class's metric as a green zero, which is exactly the + * "missing-run green state" HAC-319 forbids. + */ +export function validateCorpus(scenarios = SCENARIOS) { + const failures = []; + const require = (condition, message) => { + if (!condition) failures.push(message); + }; + + const byLabel = (label) => scenarios.filter((s) => s.label === label); + const inFamily = (family) => scenarios.filter((s) => s.family === family); + + // Every class must be populated, or its metric has an empty denominator. + for (const label of Object.values(Label)) { + require(byLabel(label).length > 0, `no scenario carries label ${label}`); + } + + // Two-family breadth: the corpus must not be one hazard shape repeated. + require(FAMILIES.length >= 2, 'fewer than two families declared'); + for (const family of FAMILIES) { + require(inFamily(family).length > 0, `family ${family} contributes no scenarios`); + } + + // Each family must carry every class, or a per-family result is not + // comparable and a divergence could be class coverage rather than hazard shape. + for (const family of FAMILIES) { + for (const label of Object.values(Label)) { + require( + inFamily(family).some((s) => s.label === label), + `family ${family} has no ${label} scenario, so its per-family result is not comparable`, + ); + } + } + + // COUPLED must be cross-target, or per-target locking would see it and the + // experiment would not be testing the distinction it exists to test. + for (const scenario of byLabel(Label.COUPLED)) { + const paths = new Set(scenario.intents.map((i) => i.path)); + require( + paths.size === scenario.intents.length, + `${scenario.id} is labelled COUPLED but its intents share a path; COUPLED must be cross-target`, + ); + require( + scenario.composeViolatesInvariant === true, + `${scenario.id} is labelled COUPLED but does not violate the invariant when composed`, + ); + } + + // SAME_TARGET_CONTENTION must genuinely share a path, or the lock validity + // gate proves nothing. + for (const scenario of byLabel(Label.SAME_TARGET_CONTENTION)) { + const paths = new Set(scenario.intents.map((i) => i.path)); + require( + paths.size === 1, + `${scenario.id} is labelled SAME_TARGET_CONTENTION but its intents write different paths`, + ); + } + + // INDEPENDENT must be safe to compose, or permitting it would not be correct. + for (const scenario of byLabel(Label.INDEPENDENT)) { + require( + scenario.composeViolatesInvariant === false, + `${scenario.id} is labelled INDEPENDENT but violates the invariant when composed`, + ); + } + + // A perturbation must hold its intents fixed against its counterpart, or the + // evidence-sensitivity metric measures the intents rather than the evidence. + for (const scenario of byLabel(Label.EVIDENCE_PERTURBED)) { + const origin = scenarios.find((s) => s.id === scenario.perturbationOf); + require(Boolean(origin), `${scenario.id} names no perturbationOf counterpart`); + if (origin) { + require( + JSON.stringify(origin.intents) === JSON.stringify(scenario.intents), + `${scenario.id} does not hold its intents identical to ${origin.id}; the perturbation is not controlled`, + ); + require( + origin.fixture !== scenario.fixture, + `${scenario.id} uses the same fixture as ${origin.id}; nothing was perturbed`, + ); + } + } + + // Every distinct refusal reason the corpus claims must be represented once. + const reasons = new Set( + byLabel(Label.EVIDENCE_INADMISSIBLE).map((s) => s.expectedRefusalReason), + ); + require( + reasons.size === Object.keys(INADMISSIBLE_EVIDENCE).length, + `expected ${Object.keys(INADMISSIBLE_EVIDENCE).length} distinct refusal reasons, found ${reasons.size}`, + ); + + // Ids must be unique, or results cannot be joined back to scenarios. + const ids = scenarios.map((s) => s.id); + require(new Set(ids).size === ids.length, 'scenario ids are not unique'); + + return failures; +} + +/** Scenario counts per label and per family, for the manifest and the report. */ +export function corpusCounts(scenarios = SCENARIOS) { + const counts = { total: scenarios.length, byLabel: {}, byFamily: {}, byFamilyAndLabel: {} }; + for (const label of Object.values(Label)) { + counts.byLabel[label] = scenarios.filter((s) => s.label === label).length; + } + for (const family of FAMILIES) { + const rows = scenarios.filter((s) => s.family === family); + counts.byFamily[family] = rows.length; + counts.byFamilyAndLabel[family] = Object.fromEntries( + Object.values(Label).map((label) => [label, rows.filter((s) => s.label === label).length]), + ); + } + return counts; +} diff --git a/experiments/hac-343/lib/executor.mjs b/experiments/hac-343/lib/executor.mjs new file mode 100644 index 0000000..0248716 --- /dev/null +++ b/experiments/hac-343/lib/executor.mjs @@ -0,0 +1,259 @@ +/** + * HAC-343 — the shared action executor. + * + * One executor, four arms. Everything about *what an action does* and *what it + * checks before doing it* lives here; the only thing an arm contributes is + * **when** that check happens. If a capability appeared on one arm's execution + * path and not another's, the comparison would be measuring the harness rather + * than the coordination policy, so there is deliberately no per-arm hook in this + * file. + * + * ## The four layers, kept apart + * + * 1. `localPrecondition` — "is my action valid from what I can currently see?" + * The check a locally-correct agent already performs. Every intent in the + * corpus passes it in isolation, which is what makes the hazard invisible one + * request at a time. + * 2. the arm's lock policy — *when* layer 1 runs (see arms.mjs). + * 3. `arbitrate()` — "are these two actions compositionally coupled?" A4 only. + * 4. `oracle()` — "did the resulting joint state actually remain valid?" + * + * Layer 4 is the one that must not be reimplemented here. It shells out to the + * fixture's own `verify.mjs`. Nothing in this file knows what the invariant is: + * the budget adapter can add up reservations for the *record*, but it never + * decides whether the total was acceptable, and the registry adapter can list + * references without deciding whether they resolve. The verdict is an exit code + * from a subprocess the deciding code never reads. + * + * @see evidence/execution-semantics.json — frozen before any result. + */ +import { execFileSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; + +import { git } from '../../hac-330/lib/exec.mjs'; + +const sha256 = (value) => createHash('sha256').update(value).digest('hex'); +const readJson = (repo, path) => JSON.parse(readFileSync(join(repo, path), 'utf8')); +const writeJson = (repo, path, value) => + writeFileSync(join(repo, path), `${JSON.stringify(value, null, 2)}\n`); + +/** Discard every uncommitted change, so no execution inherits another's state. */ +export function resetWorktree(repo) { + git(repo, ['checkout', '--quiet', '--', '.']); + git(repo, ['clean', '--quiet', '-fd']); +} + +// --------------------------------------------------------------------------- +// Family adapters +// +// Each knows how to apply its own intents and how to state its own local +// precondition. Neither knows how to judge the joint outcome. +// --------------------------------------------------------------------------- + +const budget = { + verifier: 'verify.mjs', + + readState(repo) { + const pool = readJson(repo, 'budget/pool.json'); + const services = {}; + for (const service of ['alpha', 'beta', 'gamma']) { + services[service] = readJson(repo, `services/${service}/reservation.json`).reserved; + } + return { totalReservable: pool.totalReservable, services }; + }, + + applyIntent(repo, intent) { + if (intent.op !== 'set-reservation') throw new Error(`budget: unknown op ${intent.op}`); + writeJson(repo, intent.path, { service: intent.service, reserved: intent.reserved }); + }, + + /** + * The reservation broker's own admission check, as an ordinary service would + * write it: would this reservation fit, given what the broker can see now? + * + * This is emphatically not the oracle. It is the check that returns true for + * each intent alone and is the reason the composition is dangerous. + */ + localPrecondition(repo, intent) { + const state = budget.readState(repo); + const projected = { ...state.services, [intent.service]: intent.reserved }; + const total = Object.values(projected).reduce((sum, n) => sum + n, 0); + return { + ok: total <= state.totalReservable, + detail: `projected total ${total} against ceiling ${state.totalReservable}`, + }; + }, +}; + +const registry = { + verifier: 'verify.mjs', + + readState(repo) { + return { + services: readJson(repo, 'registry/services.json').services, + routes: readJson(repo, 'routing/routes.json').routes, + aliases: readJson(repo, 'registry/aliases.json'), + dashboardsRevision: readJson(repo, 'observability/dashboards.json').revision, + }; + }, + + applyIntent(repo, intent) { + const state = registry.readState(repo); + if (intent.op === 'remove-service') { + writeJson(repo, 'registry/services.json', { + services: state.services.filter((s) => s !== intent.service).sort(), + }); + return; + } + if (intent.op === 'add-route') { + const routes = [...state.routes, { path: intent.route, service: intent.service }]; + routes.sort((a, b) => (a.path < b.path ? -1 : 1)); + writeJson(repo, 'routing/routes.json', { routes }); + return; + } + if (intent.op === 'bump-dashboards') { + const current = readJson(repo, 'observability/dashboards.json'); + writeJson(repo, 'observability/dashboards.json', { ...current, revision: intent.revision }); + return; + } + throw new Error(`registry: unknown op ${intent.op}`); + }, + + /** + * The registry's own admission checks, as an ordinary control plane would + * write them: do not retire a service anything still points at, and do not + * route to a service that is not declared. + * + * Both are correct. Both pass in isolation. Neither can see the other action. + */ + localPrecondition(repo, intent) { + const state = registry.readState(repo); + if (intent.op === 'remove-service') { + const referencedByRoute = state.routes.some((r) => r.service === intent.service); + const referencedByAlias = Object.values(state.aliases).includes(intent.service); + return { + ok: !referencedByRoute && !referencedByAlias, + detail: referencedByRoute || referencedByAlias + ? `${intent.service} is still referenced` + : `${intent.service} is declared and unreferenced`, + }; + } + if (intent.op === 'add-route') { + const declared = state.services.includes(intent.service); + return { + ok: declared, + detail: declared + ? `${intent.service} is declared` + : `${intent.service} is not declared`, + }; + } + if (intent.op === 'bump-dashboards') { + return { ok: true, detail: 'dashboards carry no referential obligation' }; + } + throw new Error(`registry: unknown op ${intent.op}`); + }, +}; + +export const ADAPTERS = Object.freeze({ budget, registry }); + +// --------------------------------------------------------------------------- +// The oracle +// --------------------------------------------------------------------------- + +/** + * Ask the fixture's own verifier whether the resulting joint state is valid. + * + * The verdict is the process exit code. `stdout` is recorded for the packet but + * never parsed: a verifier that printed a reassuring report while exiting + * non-zero must read as a violation, not as success. + * + * A verifier that cannot run at all fails the scenario. An unanswerable question + * is not an answer of "valid" — the same rule the decision core applies to an + * unreadable pending-intent store. + */ +export function oracle(repo, family) { + const adapter = ADAPTERS[family]; + const verifierPath = join(repo, adapter.verifier); + const verifierSha256 = sha256(readFileSync(verifierPath)); + + let exitCode; + let stdout = ''; + let stderr = ''; + let spawnFailed = false; + + try { + stdout = execFileSync(process.execPath, [adapter.verifier], { + cwd: repo, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + }); + exitCode = 0; + } catch (error) { + if (typeof error.status === 'number') { + exitCode = error.status; + stdout = error.stdout ?? ''; + stderr = error.stderr ?? ''; + } else { + spawnFailed = true; + exitCode = null; + stderr = String(error.message ?? error); + } + } + + const state = adapter.readState(repo); + + return { + verifierPath: adapter.verifier, + verifierSha256, + command: `${process.execPath} ${adapter.verifier}`, + exitCode, + stdout, + stderr, + spawnFailed, + state, + stateSha256: sha256(JSON.stringify(state)), + // holds is the exit code and nothing else. A spawn failure is not safety. + holds: !spawnFailed && exitCode === 0, + }; +} + +// --------------------------------------------------------------------------- +// The critical section +// --------------------------------------------------------------------------- + +/** + * Enter, re-read, re-check, mutate or reject, leave. + * + * Every lock-bearing arm calls exactly this. The re-read is what makes A2 and A3 + * credible rather than strawmen: executing two already-approved mutations in + * sequence would still overshoot, whereas a real locking implementation lets the + * second action observe the first action's write before it decides. + */ +export function criticalSection(repo, family, intent) { + const adapter = ADAPTERS[family]; + const precondition = adapter.localPrecondition(repo, intent); + if (!precondition.ok) { + return { applied: false, rejected: true, reason: 'LOCAL_PRECONDITION_FAILED', detail: precondition.detail }; + } + adapter.applyIntent(repo, intent); + return { applied: true, rejected: false, reason: 'APPLIED', detail: precondition.detail }; +} + +/** + * Evaluate a precondition without applying, for the concurrent case. + * + * Concurrency in this model means both intents checked against the same base + * snapshot and both writes then landed. That is expressed as: check both, then + * apply both — never check-apply, check-apply. + */ +export function evaluateAgainstBase(repo, family, intent) { + return ADAPTERS[family].localPrecondition(repo, intent); +} + +export function applyIntent(repo, family, intent) { + ADAPTERS[family].applyIntent(repo, intent); +} + +export { sha256 }; diff --git a/experiments/hac-343/lib/families/registry.mjs b/experiments/hac-343/lib/families/registry.mjs new file mode 100644 index 0000000..ede4d20 --- /dev/null +++ b/experiments/hac-343/lib/families/registry.mjs @@ -0,0 +1,404 @@ +/** + * HAC-343 corpus — family 2, `registry`. + * + * Family 1 (`budget`, reused verbatim from HAC-330) encodes an **arithmetic** + * hazard: three reservations against a fixed pool, where composing two locally + * valid increases overshoots a ceiling. Every scenario in that family shares one + * topology, so a result there measures one hazard shape at several evidence + * states. This family exists so the corpus is not a single shape repeated. + * + * The hazard here is **referential**, not arithmetic: + * + * every route.service resolves in the registry, and every alias target does + * + * Nothing is summed and no ceiling exists. The composition fails because one + * intent removes a referent the other intent starts pointing at — an asymmetric + * delete-versus-add hazard rather than a symmetric overshoot. An arm that + * happened to succeed on family 1 by reasoning about magnitudes has nothing to + * reason about here. + * + * ## Why the co-change relationship is real and not hand-authored + * + * As in family 1, no coupling is written into an evidence file. The histories + * are ordinary commits and the coupling is a *consequence* of how a service + * registry is maintained: you cannot canary a service without both declaring it + * and routing to it, and you cannot retire one without withdrawing the route + * first. `registry/services.json` and `routing/routes.json` therefore move + * together every time — the co-change signal is the observable shadow of the + * referential invariant, exactly as the budget family's is the shadow of its + * arithmetic one. + * + * - `baseline` — services and routes are counterparties. Every canary cycle + * edits both files in one commit. + * - `perturbed` — `registry/aliases.json` is the counterparty for both. Services + * move with aliases, routes move with aliases, at different + * times, and **services and routes never appear in one commit**. + * + * ## What is held constant across the two histories + * + * The same four controls family 1 asserts: identical final tree, identical + * commit count, commit *i* touching the same number of files in both, and the + * invariant holding at every commit in both. Commit identity is pinned to the + * same fixed clock and author so the fixture SHAs — and therefore the mined + * basis revision and evidence digest — are reproducible on any machine. + */ +import { chmodSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; + +import { git as runGitIn } from '../../../hac-330/lib/exec.mjs'; + +/** The registry every history starts from and returns to. */ +export const SERVICES_FINAL = Object.freeze(['checkout', 'inventory', 'legacy-pricing']); + +/** + * Routes at rest. `legacy-pricing` is deliberately declared but unrouted — it is + * the deprecated service, which is what makes "retire it" a locally valid intent + * and "route to it" a separately locally valid one. + */ +export const ROUTES_FINAL = Object.freeze([ + Object.freeze({ path: '/checkout', service: 'checkout' }), + Object.freeze({ path: '/inventory', service: 'inventory' }), +]); + +/** Aliases at rest. Points at a stable service, never at the deprecated one. */ +export const ALIASES_FINAL = Object.freeze({ cart: 'checkout' }); + +const EPOCH = Date.UTC(2026, 0, 1, 0, 0, 0); +const STEP_SECONDS = 600; + +const IDENTITY = { + GIT_AUTHOR_NAME: 'HAC-343 Fixture', + GIT_AUTHOR_EMAIL: 'fixture@interlock.invalid', + GIT_COMMITTER_NAME: 'HAC-343 Fixture', + GIT_COMMITTER_EMAIL: 'fixture@interlock.invalid', +}; + +// --------------------------------------------------------------------------- +// File contents — deterministic serialization, sorted keys, trailing newline +// --------------------------------------------------------------------------- + +const json = (value) => `${JSON.stringify(value, null, 2)}\n`; + +/** + * Default `.sort()` order, stated explicitly. + * + * `Array#sort` with no comparator stringifies and compares UTF-16 code units. + * These arrays are already strings, so `<` and `>` reproduce that order exactly + * — which is the point: this is an evidence-chain artifact, and `localeCompare` + * would make the committed result depend on the runner's locale. + */ +const byCodeUnit = (a, b) => { + if (a < b) return -1; + return a > b ? 1 : 0; +}; + +const services = (list) => json({ services: [...list].sort(byCodeUnit) }); +const routes = (list) => json({ routes: [...list].sort((a, b) => (a.path < b.path ? -1 : 1)) }); +const aliases = (map) => + json(Object.fromEntries(Object.entries(map).sort(([a], [b]) => (a < b ? -1 : 1)))); +const dashboards = (revision) => + json({ revision, panels: ['request-rate', 'error-rate', 'route-latency'] }); + +const README_MD = `# Service registry fixture + +A registry of declared services and a route table that references them. + +The invariant is referential, not arithmetic: + + every route.service resolves in registry/services.json + every alias target resolves in registry/services.json + +\`verify.mjs\` exits non-zero when a reference dangles. +`; + +const VERIFY_MJS = `#!/usr/bin/env node +/** + * Check the referential invariant. Exit 0 when every reference resolves. + * + * This is the fixture's own checker, not the experiment's. An arm decides + * whether to permit a composition; this decides whether the resulting state is + * actually valid, and the two must stay independent for the result to mean + * anything. + */ +import { readFileSync } from 'node:fs'; + +const read = (path) => JSON.parse(readFileSync(new URL(path, import.meta.url), 'utf8')); + +const declared = new Set(read('./registry/services.json').services); +const routeTable = read('./routing/routes.json').routes; +const aliasMap = read('./registry/aliases.json'); + +const dangling = []; +for (const route of routeTable) { + if (!declared.has(route.service)) dangling.push({ kind: 'route', from: route.path, to: route.service }); +} +for (const [name, target] of Object.entries(aliasMap)) { + if (!declared.has(target)) dangling.push({ kind: 'alias', from: name, to: target }); +} + +const report = { + invariant: 'every route.service and alias target resolves in registry/services.json', + declared: [...declared].sort(), + references: routeTable.length + Object.keys(aliasMap).length, + dangling, + holds: dangling.length === 0, +}; + +console.log(JSON.stringify(report, null, 2)); +process.exit(report.holds ? 0 : 1); +`; + +const RUNBOOK_MD = (revision) => `# Registry runbook — revision ${revision} + +Canary a service by declaring it and routing to it. Retire one by withdrawing +the route first, then removing the declaration. Never leave a reference to a +service that is no longer declared. +`; + +// --------------------------------------------------------------------------- +// Commit plans +// --------------------------------------------------------------------------- + +/** Canary names used by the maintenance cycles. Order is fixed. */ +const CANARIES = ['checkout-next', 'inventory-next', 'checkout-canary', 'inventory-canary']; + +/** + * Doc commits land after these maintenance-commit ordinals, in both histories + * alike, so commit *i* touches the same number of files in each and first-parent + * position decay lines up. + */ +const DOC_AFTER = new Set([2, 4, 5, 7]); + +/** Baseline: services and routes move together on every canary cycle. */ +function baselineSteps() { + const steps = []; + for (const canary of CANARIES) { + steps.push({ + note: `canary ${canary} behind its own route`, + apply: (state) => { + state.services.add(canary); + state.routes.set(`/${canary}`, canary); + }, + writes: ['services', 'routes'], + }); + steps.push({ + note: `retire ${canary} and withdraw its route`, + apply: (state) => { + state.routes.delete(`/${canary}`); + state.services.delete(canary); + }, + writes: ['services', 'routes'], + }); + } + return steps; +} + +/** + * Perturbed: aliases is the counterparty for both subject files. + * + * Same four controls, same eight maintenance commits, same two files per commit + * — but services and routes never co-occur, so no qualifying pair spans them. + */ +function perturbedSteps() { + const steps = []; + for (const canary of CANARIES.slice(0, 2)) { + steps.push({ + note: `declare ${canary} and alias it`, + apply: (state) => { + state.services.add(canary); + state.aliases.set(`next-${canary}`, canary); + }, + writes: ['services', 'aliases'], + }); + steps.push({ + note: `route to ${canary} and record the routed alias`, + apply: (state) => { + state.routes.set(`/${canary}`, canary); + state.aliases.set(`routed-${canary}`, canary); + }, + writes: ['routes', 'aliases'], + }); + steps.push({ + note: `withdraw the ${canary} route and its routed alias`, + apply: (state) => { + state.routes.delete(`/${canary}`); + state.aliases.delete(`routed-${canary}`); + }, + writes: ['routes', 'aliases'], + }); + steps.push({ + note: `retire ${canary} and drop its alias`, + apply: (state) => { + state.services.delete(canary); + state.aliases.delete(`next-${canary}`); + }, + writes: ['services', 'aliases'], + }); + } + return steps; +} + +const PATH_OF = { + services: 'registry/services.json', + routes: 'routing/routes.json', + aliases: 'registry/aliases.json', +}; + +function render(state, which) { + if (which === 'services') return services([...state.services]); + if (which === 'routes') + return routes([...state.routes].map(([path, service]) => ({ path, service }))); + return aliases(Object.fromEntries(state.aliases)); +} + +/** Assert the referential invariant over the in-memory plan state. */ +function invariantHolds(state) { + for (const service of state.routes.values()) if (!state.services.has(service)) return false; + for (const target of state.aliases.values()) if (!state.services.has(target)) return false; + return true; +} + +function planCommits(steps) { + const state = { + services: new Set(), + routes: new Map(), + aliases: new Map(), + }; + + const commits = []; + + commits.push({ + message: 'chore: scaffold the service registry and its invariant', + files: { + 'README.md': README_MD, + 'verify.mjs': VERIFY_MJS, + 'registry/services.json': services([]), + 'routing/routes.json': routes([]), + 'registry/aliases.json': aliases({}), + 'observability/dashboards.json': dashboards(0), + 'docs/runbook.md': RUNBOOK_MD(0), + }, + }); + + // Services are declared one per commit against a single file, so the + // declarations create no co-change *pair* between any two paths. + for (const service of SERVICES_FINAL) { + state.services.add(service); + commits.push({ + message: `feat(registry): declare the ${service} service`, + files: { 'registry/services.json': render(state, 'services') }, + }); + } + + // Routes and aliases at rest, one file per commit, for the same reason. + for (const route of ROUTES_FINAL) state.routes.set(route.path, route.service); + commits.push({ + message: 'feat(routing): route the declared services', + files: { 'routing/routes.json': render(state, 'routes') }, + }); + for (const [name, target] of Object.entries(ALIASES_FINAL)) state.aliases.set(name, target); + commits.push({ + message: 'feat(registry): record the stable aliases', + files: { 'registry/aliases.json': render(state, 'aliases') }, + }); + + let docRevision = 0; + steps.forEach((step, index) => { + step.apply(state); + + if (!invariantHolds(state)) { + throw new Error(`fixture plan is invalid: step ${index + 1} (${step.note}) dangles a reference`); + } + + const files = {}; + for (const which of step.writes) files[PATH_OF[which]] = render(state, which); + commits.push({ message: `chore(registry): ${step.note}`, files }); + + if (DOC_AFTER.has(index + 1)) { + docRevision += 1; + commits.push({ + message: `docs(runbook): record registry procedure revision ${docRevision}`, + files: { + 'docs/runbook.md': RUNBOOK_MD(docRevision), + 'observability/dashboards.json': dashboards(docRevision), + }, + }); + } + }); + + // The plan must settle exactly where it started, or the two histories cannot + // share a final tree and a downstream difference would be the state rather + // than the evidence. + const settled = + [...state.services].sort(byCodeUnit).join() === [...SERVICES_FINAL].sort(byCodeUnit).join() && + [...state.routes.keys()].sort(byCodeUnit).join() === ROUTES_FINAL.map((r) => r.path).sort(byCodeUnit).join() && + [...state.aliases.keys()].sort(byCodeUnit).join() === Object.keys(ALIASES_FINAL).sort(byCodeUnit).join(); + if (!settled) { + throw new Error('fixture plan does not settle at the shared final state'); + } + + return commits; +} + +// --------------------------------------------------------------------------- +// Materialization +// --------------------------------------------------------------------------- + +function git(repo, args, extraEnv = {}) { + return runGitIn(repo, args, { env: { ...process.env, ...IDENTITY, ...extraEnv } }); +} + +function writeFixtureFile(repo, relativePath, content) { + const full = join(repo, relativePath); + mkdirSync(dirname(full), { recursive: true }); + writeFileSync(full, content); + // Pin the mode: an inherited umask must not be allowed to change the tree. + chmodSync(full, 0o644); +} + +export function buildFixture(repo, steps) { + rmSync(repo, { recursive: true, force: true }); + mkdirSync(repo, { recursive: true }); + + git(repo, ['init', '-b', 'main', '--quiet']); + git(repo, ['config', 'user.name', IDENTITY.GIT_AUTHOR_NAME]); + git(repo, ['config', 'user.email', IDENTITY.GIT_AUTHOR_EMAIL]); + git(repo, ['config', 'commit.gpgsign', 'false']); + git(repo, ['config', 'core.autocrlf', 'false']); + git(repo, ['config', 'gc.auto', '0']); + + const commits = planCommits(steps); + + commits.forEach((commit, index) => { + for (const [path, content] of Object.entries(commit.files)) { + writeFixtureFile(repo, path, content); + } + git(repo, ['add', '--all']); + + const when = new Date(EPOCH + index * STEP_SECONDS * 1000).toISOString(); + git(repo, ['commit', '--quiet', '--no-verify', '-m', commit.message], { + GIT_AUTHOR_DATE: when, + GIT_COMMITTER_DATE: when, + }); + }); + + return { + repo, + head: git(repo, ['rev-parse', 'HEAD']).trim(), + tree: git(repo, ['rev-parse', 'HEAD^{tree}']).trim(), + commitCount: Number(git(repo, ['rev-list', '--count', 'HEAD']).trim()), + }; +} + +export const FIXTURES = { + baseline: baselineSteps(), + perturbed: perturbedSteps(), +}; + +/** The two paths whose coupling this family is about. */ +export const SUBJECT_PATHS = Object.freeze({ + left: 'registry/services.json', + right: 'routing/routes.json', + /** Never co-changes with either subject path — the independent counterpart. */ + independent: 'observability/dashboards.json', +}); diff --git a/experiments/hac-343/test/aggregate.test.mjs b/experiments/hac-343/test/aggregate.test.mjs new file mode 100644 index 0000000..4eb071b --- /dev/null +++ b/experiments/hac-343/test/aggregate.test.mjs @@ -0,0 +1,258 @@ +/** + * HAC-343 — adversarial tests on the aggregator, before any result exists. + * + * The aggregator is the piece a skeptical reader has least reason to trust: it + * turns raw records into the numbers that go in front of judges. So it is fed + * synthetic records built to make it lie, and required not to. + * + * Every case here is a way the packet could report something flattering and + * false — a broken baseline scored as a blind one, an unsafe arm topping the + * headline metric, a scheduler-sensitive result averaged smooth, a deleted + * record shrinking a denominator, a failing family hidden inside an aggregate, + * or a lock arm appearing to respond to evidence it never reads. + * + * Written before execution on purpose. After results exist, a test that shapes + * reporting logic cannot be distinguished from one that shapes it *around the + * outcome*. + */ +import { describe, expect, it } from 'vitest'; + +import { aggregate, decisionSignature, assembleSpr, rate, ORDERS } from '../lib/aggregate.mjs'; +import { SCENARIOS, FAMILIES } from '../lib/corpus.mjs'; +import { ARMS } from '../lib/arms.mjs'; + +/** A record that looks like a clean, safe, plausible execution. */ +function baseRecord(scenario, arm, order) { + const sameTarget = scenario.label === 'SAME_TARGET_CONTENTION'; + const inadmissible = scenario.label === 'EVIDENCE_INADMISSIBLE'; + const coupled = scenario.label === 'COUPLED'; + + // A2 serializes everything; A3 serializes only same-target; A4 serializes + // coupled and refuses inadmissible; A1 never serializes. + let concurrent = true; + if (arm === 'A2_global_lock') concurrent = false; + if (arm === 'A3_per_target_lock' && sameTarget) concurrent = false; + if (arm === 'A4_interlock' && (coupled || inadmissible || sameTarget)) concurrent = false; + + const refused = arm === 'A4_interlock' && inadmissible; + + return { + scenarioId: scenario.id, + family: scenario.family, + label: scenario.label, + arm, + order, + concurrent, + refusalReason: refused ? scenario.expectedRefusalReason : null, + verdicts: null, + outcomes: scenario.intents.map((_, index) => ({ intentId: `i${index}`, applied: !refused })), + oracle: { holds: true, exitCode: 0, verifierSha256: 'a'.repeat(64) }, + error: null, + }; +} + +/** A complete 128-record matrix, optionally mutated. */ +function buildRecords(mutate = () => {}) { + const records = []; + for (const scenario of SCENARIOS) { + for (const arm of ARMS) { + for (const order of ORDERS) { + const record = baseRecord(scenario, arm, order); + mutate(record, scenario); + records.push(record); + } + } + } + return records; +} + +const run = (records) => aggregate({ records, scenarios: SCENARIOS, arms: ARMS, families: FAMILIES }); + +// --------------------------------------------------------------------------- + +describe('a clean matrix aggregates', () => { + it('accepts a complete matrix and reports no defects', () => { + const report = run(buildRecords()); + + expect(report.completeness.missing).toEqual([]); + expect(report.completeness.observed).toBe(SCENARIOS.length * ARMS.length * ORDERS.length); + expect(report.defects).toEqual([]); + }); +}); + +describe('a broken baseline cannot be scored as a blind one', () => { + it('fires the lockValidity defect gate when A3 fails one same-target case', () => { + const records = buildRecords((record, scenario) => { + if (record.arm === 'A3_per_target_lock' && scenario.label === 'SAME_TARGET_CONTENTION' && scenario.family === 'budget') { + record.concurrent = true; // did not serialize — a defective lock + } + }); + + const report = run(records); + const defect = report.defects.find((d) => d.gate === 'lockValidity' && d.arm === 'A3_per_target_lock'); + + expect(defect).toBeDefined(); + expect(report.lockValidity.A3_per_target_lock.numerator).toBe(1); + expect(report.lockValidity.A3_per_target_lock.denominator).toBe(2); + }); + + it('fires the gate for A2 as well', () => { + const records = buildRecords((record, scenario) => { + if (record.arm === 'A2_global_lock' && scenario.label === 'SAME_TARGET_CONTENTION') record.concurrent = true; + }); + + expect(run(records).defects.some((d) => d.gate === 'lockValidity' && d.arm === 'A2_global_lock')).toBe(true); + }); +}); + +describe('an unsafe arm cannot top the headline metric', () => { + it('renders A1 at SPR 100% as explicitly unsafe, never as the winner', () => { + const records = buildRecords((record, scenario) => { + // A1 permits everything, including the coupled composition, which the + // oracle then rejects. + if (record.arm === 'A1_uncoordinated' && scenario.label === 'COUPLED') { + record.oracle = { holds: false, exitCode: 1, verifierSha256: 'a'.repeat(64) }; + } + }); + + const report = run(records); + const a1 = report.aggregate.A1_uncoordinated.spr; + + expect(a1.safeParallelismRetained.rate).toBe(1); // 100% parallelism… + expect(a1.unsafeJointState.numerator).toBe(2); // …at a nonzero unsafe rate + expect(a1.qualified).toBe(false); + expect(a1.rendering).toContain('UNSAFE, not safe parallelism'); + // The unsafe rate travels with the number, always. + expect(a1.rendering).toContain('at unsafe-joint-state rate'); + }); + + it('refuses to construct an SPR figure without an unsafe-joint-state rate', () => { + expect(() => assembleSpr(rate(2, 2), undefined)).toThrow(/may not be assembled without/i); + expect(() => assembleSpr(rate(2, 2), null)).toThrow(/may not be assembled without/i); + }); +}); + +describe('scheduler sensitivity is not averaged away', () => { + it('classifies a scenario unsafe when only one order violates', () => { + const records = buildRecords((record, scenario) => { + if (record.arm === 'A4_interlock' && scenario.id === 'budget/coupled/alpha-beta' && record.order === 'BA') { + record.oracle = { holds: false, exitCode: 1, verifierSha256: 'a'.repeat(64) }; + } + }); + + const report = run(records); + + // Unsafe if EITHER order violates — the safe order does not rescue it. + expect(report.aggregate.A4_interlock.unsafeJointState.numerator).toBe(1); + }); + + it('reports an order disagreement explicitly rather than smoothing it', () => { + const records = buildRecords((record, scenario) => { + if (record.arm === 'A4_interlock' && scenario.id === 'budget/independent/alpha-gamma' && record.order === 'BA') { + record.outcomes = record.outcomes.map((o) => ({ ...o, applied: false })); + } + }); + + const report = run(records); + const effect = report.orderEffects.find((e) => e.scenarioId === 'budget/independent/alpha-gamma'); + + expect(effect).toBeDefined(); + expect(effect.arm).toBe('A4_interlock'); + // Parallel only if BOTH orders permit: one failing order loses the credit. + expect(report.aggregate.A4_interlock.spr.safeParallelismRetained.numerator).toBe(1); + }); +}); + +describe('a missing record cannot shrink a denominator', () => { + it('throws rather than aggregating a partial matrix', () => { + const records = buildRecords().filter( + (r) => !(r.scenarioId === 'registry/coupled/retire-vs-route' && r.arm === 'A4_interlock' && r.order === 'BA'), + ); + + expect(() => run(records)).toThrow(/incomplete raw results/i); + }); + + it('throws on a duplicated record rather than double-counting', () => { + const records = buildRecords(); + records.push({ ...records[0] }); + + expect(() => run(records)).toThrow(/duplicate raw record/i); + }); + + it('treats an errored record as unsafe rather than absent', () => { + const records = buildRecords((record, scenario) => { + if (record.arm === 'A4_interlock' && scenario.id === 'budget/coupled/alpha-beta') { + record.error = 'boom'; + record.oracle = null; + } + }); + + expect(run(records).aggregate.A4_interlock.unsafeJointState.numerator).toBe(1); + }); +}); + +describe('a failing family is not hidden inside an aggregate', () => { + it('surfaces the failure per family and in the aggregate', () => { + const records = buildRecords((record, scenario) => { + if (record.arm === 'A4_interlock' && scenario.family === 'registry' && scenario.label === 'COUPLED') { + record.oracle = { holds: false, exitCode: 1, verifierSha256: 'a'.repeat(64) }; + } + }); + + const report = run(records); + + expect(report.perFamily.budget.A4_interlock.unsafeJointState.numerator).toBe(0); + expect(report.perFamily.registry.A4_interlock.unsafeJointState.numerator).toBe(1); + // The aggregate must not read as clean while a family is failing. + expect(report.aggregate.A4_interlock.unsafeJointState.numerator).toBe(1); + expect(report.aggregate.A4_interlock.spr.qualified).toBe(false); + }); +}); + +describe('an evidence-blind arm cannot appear evidence-sensitive', () => { + it.each(['A1_uncoordinated', 'A2_global_lock', 'A3_per_target_lock'])( + 'fires the defect gate when %s changes decision under perturbation alone', + (arm) => { + const records = buildRecords((record, scenario) => { + if (record.arm === arm && scenario.label === 'EVIDENCE_PERTURBED') { + record.outcomes = record.outcomes.map((o) => ({ ...o, applied: false })); + } + }); + + const report = run(records); + const defect = report.defects.find((d) => d.gate === 'evidenceSensitivity' && d.arm === arm); + + expect(defect).toBeDefined(); + expect(defect.detail).toMatch(/consumes no evidence/); + }, + ); + + it('does not fire the gate for A4, which is supposed to be evidence-sensitive', () => { + const records = buildRecords((record, scenario) => { + if (record.arm === 'A4_interlock' && scenario.label === 'EVIDENCE_PERTURBED') { + record.concurrent = true; + record.outcomes = record.outcomes.map((o) => ({ ...o, applied: true })); + } + }); + + expect(run(records).defects.some((d) => d.gate === 'evidenceSensitivity')).toBe(false); + }); +}); + +describe('reporting conventions hold', () => { + it('renders an empty denominator as n/a rather than 0% or 100%', () => { + expect(rate(0, 0).display).toBe('n/a (0 cases)'); + expect(rate(0, 0).rate).toBeNull(); + }); + + it('never renders a bare percentage', () => { + expect(rate(1, 2).display).toBe('1/2 (50.0%)'); + }); + + it('makes decision signatures independent of intent ordering', () => { + const ab = { outcomes: [{ intentId: 'i0', applied: true }, { intentId: 'i1', applied: false }] }; + const ba = { outcomes: [{ intentId: 'i1', applied: false }, { intentId: 'i0', applied: true }] }; + + expect(decisionSignature(ab)).toBe(decisionSignature(ba)); + }); +}); diff --git a/experiments/hac-343/test/preflight.test.mjs b/experiments/hac-343/test/preflight.test.mjs new file mode 100644 index 0000000..8bd7eb7 --- /dev/null +++ b/experiments/hac-343/test/preflight.test.mjs @@ -0,0 +1,187 @@ +/** + * HAC-343 — preflight gates. These run before any result exists. + * + * Two things must be proven before an arm's output means anything: + * + * 1. **The oracle can fail.** Each family's `verify.mjs` is generated by the + * same generator that built its fixture. That makes it independent of the arm + * harness but not epistemically independent of fixture construction, so it is + * mutation-tested against constructed valid and invalid states rather than + * trusted because it reported success. + * + * 2. **The lock baselines actually lock.** An allow-all implementation would + * produce the same unsafe result on a cross-target coupling as a real + * per-target lock, and the finding would be worthless — a skeptical judge + * would correctly dismiss it as a strawman that never locked anything. A2 + * must serialize everything; A3 must serialize same-target intents and + * parallelise distinct-target ones. + * + * Neither gate needs mined evidence, so both run in CI without the pinned + * `workspacejson/cli` checkout. + * + * @see evidence/execution-semantics.json — oracleProtocol.discriminationGate + * @see evidence/metric-definitions.json — arms.*.validityGate + */ +import { mkdirSync } from 'node:fs'; +import { join, resolve } from 'node:path'; +import { beforeAll, describe, expect, it } from 'vitest'; + +import { buildFixture as buildBudget, FIXTURES as BUDGET_FIXTURES } from '../../hac-330/bin/build-fixtures.mjs'; +import { buildFixture as buildRegistry, FIXTURES as REGISTRY_FIXTURES } from '../lib/families/registry.mjs'; +import { SCENARIOS } from '../lib/corpus.mjs'; +import { runArm } from '../lib/arms.mjs'; +import { applyIntent, oracle, resetWorktree } from '../lib/executor.mjs'; + +const WORK = resolve(import.meta.dirname, '..', '.work', 'preflight'); +const repos = {}; + +beforeAll(() => { + mkdirSync(WORK, { recursive: true }); + buildBudget(join(WORK, 'budget'), BUDGET_FIXTURES.baseline); + buildRegistry(join(WORK, 'registry'), REGISTRY_FIXTURES.baseline); + repos.budget = join(WORK, 'budget'); + repos.registry = join(WORK, 'registry'); +}, 120_000); + +/** Apply a list of intents from the base snapshot, as a concurrent composition. */ +function composeFromBase(repo, family, intents) { + resetWorktree(repo); + for (const intent of intents) applyIntent(repo, family, intent); + return oracle(repo, family); +} + +const scenario = (id) => SCENARIOS.find((s) => s.id === id); + +/** + * Tag intents with the same stable ids the runner assigns. + * + * arms.mjs asserts rather than defaults them: an undefined id would collapse + * both intents onto one record and replay the wrong write in phase 2. + */ +const identify = (s) => s.intents.map((intent, index) => ({ ...intent, id: `i${index}` })); + +describe('the oracle can fail — verifier discrimination', () => { + describe.each([ + { + family: 'budget', + coupled: 'budget/coupled/alpha-beta', + // Invalid on one path alone: 200 + 40 + 20 is far past the 130 ceiling. + sameTargetInvalid: [{ op: 'set-reservation', path: 'services/alpha/reservation.json', service: 'alpha', reserved: 200 }], + }, + { + family: 'registry', + coupled: 'registry/coupled/retire-vs-route', + // Invalid on one path alone: a route to a service that was never declared. + sameTargetInvalid: [{ op: 'add-route', path: 'routing/routes.json', route: '/ghost', service: 'ghost' }], + }, + ])('$family', ({ family, coupled, sameTargetInvalid }) => { + it('reports the untouched fixture as valid', () => { + resetWorktree(repos[family]); + const verdict = oracle(repos[family], family); + + expect(verdict.spawnFailed).toBe(false); + expect(verdict.exitCode).toBe(0); + expect(verdict.holds).toBe(true); + // The verdict must be the exit code, and the record must carry the + // provenance the packet cites. + expect(verdict.verifierSha256).toMatch(/^[0-9a-f]{64}$/); + expect(verdict.stateSha256).toMatch(/^[0-9a-f]{64}$/); + }); + + it('reports the coupled composition as a violation', () => { + const verdict = composeFromBase(repos[family], family, scenario(coupled).intents); + + expect(verdict.spawnFailed).toBe(false); + expect(verdict.exitCode).not.toBe(0); + expect(verdict.holds).toBe(false); + }); + + it('reports a single-path invalid state as a violation', () => { + const verdict = composeFromBase(repos[family], family, sameTargetInvalid); + + expect(verdict.holds).toBe(false); + expect(verdict.exitCode).not.toBe(0); + }); + + it('returns to valid once the worktree is reset, so violations are not sticky', () => { + composeFromBase(repos[family], family, scenario(coupled).intents); + resetWorktree(repos[family]); + + expect(oracle(repos[family], family).holds).toBe(true); + }); + }); +}); + +describe('the lock baselines actually lock', () => { + const sameTarget = SCENARIOS.filter((s) => s.label === 'SAME_TARGET_CONTENTION'); + const crossTarget = SCENARIOS.filter((s) => s.label === 'COUPLED' || s.label === 'INDEPENDENT'); + + it.each(sameTarget.map((s) => [s.id, s]))('A2 serializes %s', (_id, s) => { + const run = runArm({ arm: 'A2_global_lock', repo: repos[s.family], family: s.family, scenario: s, intents: identify(s) }); + + expect(run.concurrent).toBe(false); + expect(run.lockGroups).toEqual(['GLOBAL']); + }); + + it.each(crossTarget.map((s) => [s.id, s]))('A2 serializes %s too — the lock is global, not per target', (_id, s) => { + const run = runArm({ arm: 'A2_global_lock', repo: repos[s.family], family: s.family, scenario: s, intents: identify(s) }); + + expect(run.concurrent).toBe(false); + expect(run.lockGroups).toEqual(['GLOBAL']); + }); + + it.each(sameTarget.map((s) => [s.id, s]))('A3 serializes %s — same target, one lock key', (_id, s) => { + const run = runArm({ arm: 'A3_per_target_lock', repo: repos[s.family], family: s.family, scenario: s, intents: identify(s) }); + + expect(run.lockGroups).toHaveLength(1); + expect(run.concurrent).toBe(false); + }); + + it.each(crossTarget.map((s) => [s.id, s]))('A3 parallelises %s — distinct targets, distinct lock keys', (_id, s) => { + const run = runArm({ arm: 'A3_per_target_lock', repo: repos[s.family], family: s.family, scenario: s, intents: identify(s) }); + + // The other half of the gate: an A3 that serialized everything would be a + // global lock wearing a per-target label, and its blindness to cross-target + // coupling would prove nothing about per-target locking. + expect(run.lockGroups).toHaveLength(2); + expect(run.concurrent).toBe(true); + }); + + it('A2 re-reads inside the critical section rather than replaying approvals', () => { + // This must use a CROSS-target pair to discriminate. On a same-target pair + // (alpha->60 then alpha->55) the second projection is 115 whether or not the + // first write was observed, because the second intent overwrites the same + // key — so that assertion would pass vacuously and prove nothing. + // + // Cross-target separates the two behaviours cleanly. alpha->60 lands, then + // beta->60 re-reads and projects 60+60+20 = 140, which does not fit and is + // rejected. Had it replayed against the base snapshot it would have + // projected 40+60+20 = 120, fitted, applied, and overshot to 140 — which is + // exactly the strawman a lock baseline must not be. + const s = scenario('budget/coupled/alpha-beta'); + const run = runArm({ arm: 'A2_global_lock', repo: repos.budget, family: 'budget', scenario: s, intents: identify(s) }); + + expect(run.outcomes).toHaveLength(2); + expect(run.outcomes[0].applied).toBe(true); + expect(run.outcomes[0].detail).toContain('projected total 120'); + + expect(run.outcomes[1].applied).toBe(false); + expect(run.outcomes[1].reason).toBe('LOCAL_PRECONDITION_FAILED'); + expect(run.outcomes[1].detail).toContain('projected total 140'); + + // And the state the oracle sees must be the safe one. + expect(oracle(repos.budget, 'budget').holds).toBe(true); + }); + + it('A3 re-reads too, on a same-target pair where its lock is the one contending', () => { + // A3's critical section is the same code, but only same-target intents + // contend for it. Both fit here, so both apply and the last write wins — + // what is being asserted is that the second ran inside the section at all. + const s = scenario('budget/same-target/alpha-alpha'); + const run = runArm({ arm: 'A3_per_target_lock', repo: repos.budget, family: 'budget', scenario: s, intents: identify(s) }); + + expect(run.lockGroups).toHaveLength(1); + expect(run.outcomes.every((o) => o.applied)).toBe(true); + expect(oracle(repos.budget, 'budget').holds).toBe(true); + }); +}); diff --git a/media/hac-335/captures/IL-COCK-010-run-local-treatment-1440x724-runhac330local.png b/media/hac-335/captures/IL-COCK-010-run-local-treatment-1440x724-runhac330local.png deleted file mode 100644 index a1c3c83..0000000 Binary files a/media/hac-335/captures/IL-COCK-010-run-local-treatment-1440x724-runhac330local.png and /dev/null differ diff --git a/media/hac-335/captures/IL-COCK-010-run-local-treatment-1440x774-runhac330local.png b/media/hac-335/captures/IL-COCK-010-run-local-treatment-1440x774-runhac330local.png new file mode 100644 index 0000000..3c900da Binary files /dev/null and b/media/hac-335/captures/IL-COCK-010-run-local-treatment-1440x774-runhac330local.png differ diff --git a/media/hac-335/captures/IL-COCK-011-run-local-perturbed-1440x866-runhac330local.png b/media/hac-335/captures/IL-COCK-011-run-local-perturbed-1440x866-runhac330local.png deleted file mode 100644 index c9c5fa1..0000000 Binary files a/media/hac-335/captures/IL-COCK-011-run-local-perturbed-1440x866-runhac330local.png and /dev/null differ diff --git a/media/hac-335/captures/IL-COCK-011-run-local-perturbed-1440x887-runhac330local.png b/media/hac-335/captures/IL-COCK-011-run-local-perturbed-1440x887-runhac330local.png new file mode 100644 index 0000000..7321add Binary files /dev/null and b/media/hac-335/captures/IL-COCK-011-run-local-perturbed-1440x887-runhac330local.png differ diff --git a/media/hac-335/captures/IL-COCK-012-run-cloud-overview-1440x653-runilkhac340cloud1786730369123.png b/media/hac-335/captures/IL-COCK-012-run-cloud-overview-1440x653-runilkhac340cloud1786730369123.png index 3e9dfe8..7718207 100644 Binary files a/media/hac-335/captures/IL-COCK-012-run-cloud-overview-1440x653-runilkhac340cloud1786730369123.png and b/media/hac-335/captures/IL-COCK-012-run-cloud-overview-1440x653-runilkhac340cloud1786730369123.png differ diff --git a/media/hac-335/captures/IL-COCK-013-run-cloud-evidence-1440x817-runilkhac340cloud1786730369123.png b/media/hac-335/captures/IL-COCK-013-run-cloud-evidence-1440x817-runilkhac340cloud1786730369123.png index 1f80405..97b4527 100644 Binary files a/media/hac-335/captures/IL-COCK-013-run-cloud-evidence-1440x817-runilkhac340cloud1786730369123.png and b/media/hac-335/captures/IL-COCK-013-run-cloud-evidence-1440x817-runilkhac340cloud1786730369123.png differ diff --git a/media/hac-335/devpost/screenshot-order.json b/media/hac-335/devpost/screenshot-order.json index 1822f0a..dd4af94 100644 --- a/media/hac-335/devpost/screenshot-order.json +++ b/media/hac-335/devpost/screenshot-order.json @@ -38,7 +38,7 @@ { "order": 3, "assetId": "IL-COCK-010", - "file": "media/hac-335/captures/IL-COCK-010-run-local-treatment-1440x724-runhac330local.png", + "file": "media/hac-335/captures/IL-COCK-010-run-local-treatment-1440x774-runhac330local.png", "proofClass": "A", "judgeQuestion": "Can I verify this myself?", "caption": "The Run — a real capture of the judge verification surface at ?run=hac330-local&proof=local&state=run.local.treatment. Every value is read from the frozen record; nothing executes in the browser." diff --git a/media/hac-335/evidence/asset-registry.json b/media/hac-335/evidence/asset-registry.json index f2aa547..03192a2 100644 --- a/media/hac-335/evidence/asset-registry.json +++ b/media/hac-335/evidence/asset-registry.json @@ -496,17 +496,17 @@ "canonicalMasterIssue": null, "authoredBy": "HAC-335", "capturedFrom": "HAC-341 merged cockpit", - "capturedFromSha": "2f742a42fbb1410fa47ec6a0e758be2c12818ec1", + "capturedFromSha": "c4654821b6e1a443ab4ef6218ddde4daaf1ee20c", "sourceUrl": "/media/hac-341/cockpit.html?run=hac330-local&proof=local&state=run.local.treatment&static=1", "sourceFormat": "live surface (html)", "exportFormat": "png", "exports": [ { "surface": "readme+devpost-screenshot", - "file": "media/hac-335/captures/IL-COCK-010-run-local-treatment-1440x724-runhac330local.png", + "file": "media/hac-335/captures/IL-COCK-010-run-local-treatment-1440x774-runhac330local.png", "width": 1440, - "height": 724, - "sha256": "1c629a77a3753d81abb61a2855bd0491cebb396a5772b8cc60e58841debb923c", + "height": 774, + "sha256": "ae84c67ba8655bb31f60a44092f542e8d0f6e95e0f37cf6d6187f23e1e88b65b", "cropAnchor": "main#app", "cropRule": "measured bounding box of the rendered content; unused canvas only" } @@ -540,17 +540,17 @@ "canonicalMasterIssue": null, "authoredBy": "HAC-335", "capturedFrom": "HAC-341 merged cockpit", - "capturedFromSha": "2f742a42fbb1410fa47ec6a0e758be2c12818ec1", + "capturedFromSha": "c4654821b6e1a443ab4ef6218ddde4daaf1ee20c", "sourceUrl": "/media/hac-341/cockpit.html?run=hac330-local&proof=local&state=run.local.perturbed&static=1", "sourceFormat": "live surface (html)", "exportFormat": "png", "exports": [ { "surface": "readme+devpost-screenshot", - "file": "media/hac-335/captures/IL-COCK-011-run-local-perturbed-1440x866-runhac330local.png", + "file": "media/hac-335/captures/IL-COCK-011-run-local-perturbed-1440x887-runhac330local.png", "width": 1440, - "height": 866, - "sha256": "87257c8bd33cfbba447ba500665b04b99c59d8fb3db2487262460d2126511b84", + "height": 887, + "sha256": "63aca750f302d45db2ed3d06a21fe77e98fa1224198f136099da11e18033f219", "cropAnchor": "main#app", "cropRule": "measured bounding box of the rendered content; unused canvas only" } @@ -584,7 +584,7 @@ "canonicalMasterIssue": null, "authoredBy": "HAC-335", "capturedFrom": "HAC-341 merged cockpit", - "capturedFromSha": "2f742a42fbb1410fa47ec6a0e758be2c12818ec1", + "capturedFromSha": "c4654821b6e1a443ab4ef6218ddde4daaf1ee20c", "sourceUrl": "/media/hac-341/cockpit.html?run=hac340-cloud&proof=cloud&state=run.cloud.overview&static=1", "sourceFormat": "live surface (html)", "exportFormat": "png", @@ -594,7 +594,7 @@ "file": "media/hac-335/captures/IL-COCK-012-run-cloud-overview-1440x653-runilkhac340cloud1786730369123.png", "width": 1440, "height": 653, - "sha256": "bb17de71f12be372b75eb2bcfe4f6512d607a1ad6bd0ed96910cbd37db916802", + "sha256": "e32bf8c880232914aa17f7d3aac882039871ccc7af351d316e87a2ad123ce47d", "cropAnchor": "main#app", "cropRule": "measured bounding box of the rendered content; unused canvas only" } @@ -644,7 +644,7 @@ "canonicalMasterIssue": null, "authoredBy": "HAC-335", "capturedFrom": "HAC-341 merged cockpit", - "capturedFromSha": "2f742a42fbb1410fa47ec6a0e758be2c12818ec1", + "capturedFromSha": "c4654821b6e1a443ab4ef6218ddde4daaf1ee20c", "sourceUrl": "/media/hac-341/cockpit.html?run=hac340-cloud&proof=cloud&state=run.cloud.overview&static=1", "sourceFormat": "live surface (html)", "exportFormat": "png", @@ -654,7 +654,7 @@ "file": "media/hac-335/captures/IL-COCK-013-run-cloud-evidence-1440x817-runilkhac340cloud1786730369123.png", "width": 1440, "height": 817, - "sha256": "bb24f64ec7c5ecfc909de64b328954d5e83d987e63564a880b215f082ba8a84e", + "sha256": "319ab8eabeba0b4d7d03c69840d6d2898b540f2e0dfd2194923fb13c12c460c9", "cropAnchor": "union:main#app,aside#drawer", "cropRule": "measured bounding box of the rendered content; unused canvas only" } diff --git a/media/hac-335/evidence/capture-manifest.json b/media/hac-335/evidence/capture-manifest.json index 3b82bcb..7fe3432 100644 --- a/media/hac-335/evidence/capture-manifest.json +++ b/media/hac-335/evidence/capture-manifest.json @@ -2,11 +2,11 @@ "manifestId": "HAC-335-capture-manifest", "issue": "HAC-335", "generator": "media/hac-335/bin/capture-cockpit.mjs", - "capturedFromSha": "2f742a42fbb1410fa47ec6a0e758be2c12818ec1", + "capturedFromSha": "c5fa5b8d0d8860c8215d888748e53fb62ef8f95b", "capturedSurface": "media/hac-341/cockpit.html (merged executable surface)", "servedFrom": "repository root — the cockpit resolves shared identity from /assets", "viewport": "1440x900", - "captureSourceDigest": "250cb8e7b0eb84faf3ae1ee384092ddadac30fb50f3d9be225b84d0bb9aa66ba", + "captureSourceDigest": "3aca7f6ac5a1ddb7aa8353b28fcad1e9ca47fe158e78c498e3c27f47349dd295", "captureSourceFiles": [ "assets/fonts/geist-mono-variable.woff2", "assets/fonts/geist-variable.woff2", @@ -20,14 +20,16 @@ "assets/tokens/typography.css", "media/hac-341/cockpit.html", "media/hac-341/evidence/view-model.json", - "media/hac-341/lib/arm-view.mjs" + "media/hac-341/lib/arm-view.mjs", + "media/hac-341/lib/comparison.mjs", + "media/hac-341/lib/guide.mjs" ], "note": "Real captures of the merged cockpit. Pixels inside each frame are unmodified. Crops drop unused canvas only; the crop height is the measured bounding box of main#app, never a hand-typed number. captureSourceDigest binds these frames to the render sources they came from; verify-package.mjs fails if they drift apart.", "captures": [ { "assetId": "IL-COCK-010", - "file": "media/hac-335/captures/IL-COCK-010-run-local-treatment-1440x724-runhac330local.png", - "sha256": "1c629a77a3753d81abb61a2855bd0491cebb396a5772b8cc60e58841debb923c", + "file": "media/hac-335/captures/IL-COCK-010-run-local-treatment-1440x774-runhac330local.png", + "sha256": "ae84c67ba8655bb31f60a44092f542e8d0f6e95e0f37cf6d6187f23e1e88b65b", "judgeQuestion": "Can I verify the causal claim myself?", "proofClass": "A", "proofClassLabel": "CONTROLLED LOCAL EXPERIMENT", @@ -38,7 +40,7 @@ "sourceUrl": "/media/hac-341/cockpit.html?run=hac330-local&proof=local&state=run.local.treatment&static=1", "viewport": "1440x900", "width": 1440, - "height": 724, + "height": 774, "cropAnchor": "main#app", "reducedMotion": true, "staticCapture": true, @@ -68,8 +70,8 @@ }, { "assetId": "IL-COCK-011", - "file": "media/hac-335/captures/IL-COCK-011-run-local-perturbed-1440x866-runhac330local.png", - "sha256": "87257c8bd33cfbba447ba500665b04b99c59d8fb3db2487262460d2126511b84", + "file": "media/hac-335/captures/IL-COCK-011-run-local-perturbed-1440x887-runhac330local.png", + "sha256": "63aca750f302d45db2ed3d06a21fe77e98fa1224198f136099da11e18033f219", "judgeQuestion": "What happens if the evidence changes?", "proofClass": "A", "proofClassLabel": "CONTROLLED LOCAL EXPERIMENT", @@ -80,7 +82,7 @@ "sourceUrl": "/media/hac-341/cockpit.html?run=hac330-local&proof=local&state=run.local.perturbed&static=1", "viewport": "1440x900", "width": 1440, - "height": 866, + "height": 887, "cropAnchor": "main#app", "reducedMotion": true, "staticCapture": true, @@ -105,7 +107,7 @@ { "assetId": "IL-COCK-012", "file": "media/hac-335/captures/IL-COCK-012-run-cloud-overview-1440x653-runilkhac340cloud1786730369123.png", - "sha256": "bb17de71f12be372b75eb2bcfe4f6512d607a1ad6bd0ed96910cbd37db916802", + "sha256": "e32bf8c880232914aa17f7d3aac882039871ccc7af351d316e87a2ad123ce47d", "judgeQuestion": "What actually ran on Google Cloud?", "proofClass": "B", "proofClassLabel": "GOOGLE CLOUD PARTICIPATION", @@ -149,7 +151,7 @@ { "assetId": "IL-COCK-013", "file": "media/hac-335/captures/IL-COCK-013-run-cloud-evidence-1440x817-runilkhac340cloud1786730369123.png", - "sha256": "bb24f64ec7c5ecfc909de64b328954d5e83d987e63564a880b215f082ba8a84e", + "sha256": "319ab8eabeba0b4d7d03c69840d6d2898b540f2e0dfd2194923fb13c12c460c9", "judgeQuestion": "Where is the immutable evidence, and what is withheld?", "proofClass": "B", "proofClassLabel": "GOOGLE CLOUD PARTICIPATION", diff --git a/media/hac-341/README.md b/media/hac-341/README.md index 8bcc6f4..83d044f 100644 --- a/media/hac-341/README.md +++ b/media/hac-341/README.md @@ -12,6 +12,8 @@ simulation. Nothing here executes; every value is a recorded result. | --- | --- | | `cockpit.html` | The Run. Renders the view model; derives no meaning of its own. | | `lib/arm-view.mjs` | What one *selected arm* shows. Pure, so the gate can assert it without a browser. | +| `lib/guide.mjs` | The guided walk: six beats, and what the ablation held constant versus changed. Pure, for the same reason. | +| `lib/comparison.mjs` | The coordination-strategy comparison, bound field-by-field to HAC-343. | | `evidence/view-model.json` | Normalized view model, generated — not hand-written. | | `bin/build-view-model.mjs` | Adapter. Derives the view model from frozen evidence. | | `bin/verify-cockpit.mjs` | Mechanical gate. | @@ -144,10 +146,200 @@ reporting changed evidence, or if an arm records a coupling its own `decisionReason` denies. Those are bindings, not strings: rewiring the derivation fails them, and so does editing the frozen arms. +## Walk the proof — an attention layer, not a second cockpit + +A judge arriving cold has to answer *what changed because Interlock existed?* +before they can decide whether to check it. The cockpit answers that in one +frame, but it answers it all at once. **Walk the proof** is an optional guided +pass over the same geometry: same shell, same four-stage spine, same controls in +the same places. It moves emphasis and adds two things — the entry choice, and +the ablation's held-constant / changed markers. It adds no run, no arm, no value +and no claim. + +On local entry the reader is offered both paths and **neither is preselected**: + +> **Inspect the run** +> Two actions can be valid alone and unsafe together. Follow the recorded proof, +> or inspect everything yourself. +> +> `Walk the proof` · `Explore freely` + +`Walk the proof` takes ordinary primary emphasis. `Explore freely` keeps a +full-weight border and the same tap target, because it is the expert path rather +than a decline. The module is not modal, and it does not sit over run identity, +frozen state, the checks or the proof-class switch. + +The six beats: + +| Step | State | What it emphasises | +| --- | --- | --- | +| 01 | `guide.local.validity` | both intents, equally | +| 02 | `guide.local.shared-environment` | the intents and the bounded environment they converge on | +| 03 | `guide.local.evidence-decision` | the frozen coupling evidence and the decision it produced | +| 04 | `guide.local.outcome` | the decision beside the baseline and treatment outcomes | +| 05 | `guide.local.ablation` | evidence, decision, outcome, and the arm control | +| 06 | `guide.local.handoff` | nothing — the run returns to full emphasis | + +### Emphasis is positive, and it is not paid for out of legibility + +The first implementation receded non-current stages with `opacity: 0.62`. It was +wrong twice over, and only measurement showed it. `opacity` composites *text* +toward whatever is behind it, and it **multiplies through every ancestor that +also sets it** — this surface already muted small labels at `.6`, inside rows at +`.7`. Composed, `Baseline · no coordination` rendered at an effective 0.26 and +measured **1.81:1** against a 4.5:1 floor, on text that is still content. Twelve +labels that passed AA in the free cockpit failed inside the walk. + +No opacity floor fixes that. Measured across the range, `0.95` still introduced a +failure and only `1.0` — no recession at all — reached zero, because the worst +affected label sits at 4.60:1 unrecessed and any multiplier pushes it under. + +So the mechanism was replaced rather than tuned. The current stage is marked +**up**; the others are simply unmarked: + +| Channel | Current stage | Other stages | +| --- | --- | --- | +| Accent edge | 3px inset shadow in the coupled hue | none | +| Surface | card | sunken | +| Border | left edge in the coupled hue | `--border-default` | +| Stage number | filled chip | plain numeral | +| Lift | `0 1px 3px` | none | +| Connector | full stroke, coupled hue, only when *both* joined stages are current | grammar default | + +Every one of those is colour, background or `box-shadow`. None participates in +layout, so **no step moves anything** — asserted in a browser by comparing the +box of all five stages across all six steps. Text colour is untouched at every +step, so a non-current stage reads exactly as well as the current one. + +The gate refuses the whole mechanism, not a particular value of it: no `opacity` +and no `filter` may appear in any `[data-guide-em]` rule, at any value; nothing +may be hidden or made unreachable; and nothing may change a layout property. + +### One floor for the whole surface + +Hierarchy is carried by two measured colour tiers rather than by opacity, which +cannot be measured once and trusted because it depends on every ancestor: + +| Tier | Light field | Dark field | +| --- | --- | --- | +| `--text-body` | `--ink` | `--paper` | +| `--text-muted` | `--n60` — **6.84:1** on sunken, 7.27:1 on card | `--n40` — **6.89:1** on sunken, 7.25:1 on card | + +There is deliberately no third tier: `--n50` was the natural next step down and +measures **4.07:1** on the sunken surface, under the floor. The gate fails if it +colours text again. + +The audit that produced those numbers found eight failures already present in the +free cockpit before any of this work — down to 2.81:1 — and one more in a place +nobody had measured: **the Google Cloud raw-proof panel rendered paper text on +`--surface-code`, a light surface, at 1.03:1.** The L3 packet was on screen and +could not be read. The code surface now follows its field. + +Resolving colour by string was itself a defect in the audit harness: `fillStyle` +round-trips `oklch()` unchanged and this design is oklch throughout, so the first +run silently mis-measured every semantic state colour. Colour is now resolved by +painting a pixel and reading it back. + +Current state, measured across **13,344 text nodes in 136 scenarios** — both +modes, every step, every arm, both proof classes, every panel, the degraded +states, 1440px and 320px, and both reduced-motion resolutions: + +``` +muted by opacity 0 +AA failures 0 +min ratio 5.15 : 1 +``` + +### The step owns its action + +While the walk runs, the persistent verification row is **demoted** — 1px chrome +and normal weight instead of 2px and semibold — so the step's own action leads. +Its text colour, tap target and tab position are unchanged: a demoted control is +quieter, never less readable. + +On the handoff step the two actions the step panel itself offers are dropped from +that row outright; the same control twice, eighty pixels apart, reads as two +different things. The row is never emptied — `Show me the raw proof` and `What is +not claimed?` appear in no step panel, and no step may take away the expert path. + +Nothing auto-advances: the gate fails on a timer that moves a step. No step +change scrolls — focus is restored with `preventScroll`, and `scrollIntoView` is +refused outright. + +### The ablation is a claim, so it is derived + +Step 5 states that four things were held constant and four changed. That is the +load-bearing sentence of the whole walk, and it is the one a reader cannot check +by eye across a state change. So it is not a sentence: `ablationDelta` in +`lib/guide.mjs` reads **both frozen arms** and reports what actually moved. + +| Held constant | Read from | +| --- | --- | +| Intent A, Intent B | `run.actors` — properties of the experiment | +| Shared environment | `environmentEvidence[0].source` | +| Joint bound `130` | each arm's own `outcome.bound` | + +| Changed | Treatment | Perturbed | +| --- | --- | --- | +| Evidence basis | `eb67a6f5…` | `db8a63ec…` | +| Evidence finding | `COUPLED` | `NO QUALIFYING COUPLING` | +| Coordination decision | `WITHHOLD_SERIALIZE` | `ALLOW_PARALLEL` | +| Bounded outcome | `120 <= 130` | `140 > 130` | + +The bound is deliberately read off each arm rather than off the shared +constraint. Read off the constraint it is held constant by construction and the +marker proves nothing; read off the arms it is a claim that both arms were +judged against the same bound, and editing one arm's `outcome.bound` fails the +gate. A marker whose claim stops matching the record is **dropped rather than +drawn**, and refused by `verify-cockpit.mjs`. + +`Remove or perturb the evidence` selects the recorded perturbed arm. It does not +edit, delete or recompute anything, and `Restore the original evidence` names +the action it will perform in the other direction. The rail's own disclaimer — +*each arm is a recorded result … nothing is executed in the browser* — stays on +screen throughout. + +### Motion + +The walk reuses the one explanatory transition this surface already had: +switching arms steps evidence → decision → outcome so a reader sees which parts +moved together. `--dur-base` at delays `0`, `--delay-step`, `2 × --delay-step` is +**400ms**, and the gate derives that from `assets/tokens/motion.css` and fails if +it passes the `--dur-hold` 700ms budget. No new keyframe, no new dependency, no +Lottie, nothing pre-rendered. + +Reduced motion is resolved, not toggled: + +- **The system asks for it** → the manual control is *withdrawn* and replaced by + a non-interactive `Reduced motion · system preference` status. Offering an + `Enable motion` button the preference would immediately override is a lie + about who is in charge, and the gate fails on one. +- **The system does not** → the control names the action available: + `Reduce motion`, or `Enable motion` once manually reduced, with `aria-pressed`. + +Either way the substitution is transitions for immediate state changes. The step +sequence, the copy, the changed and held-constant markers, the selected arm and +the announcements are identical — asserted by comparing the two derivations +field for field. The preference is **not persisted**: this repository has no +preference-storage pattern, and inventing one would put a second invisible +authority beside the OS setting. + +### Keyboard + +Tab and Shift+Tab reach everything; Enter and Space activate. **Back and Next are +the universal path and never depend on an arrow key.** Escape is the only key +bound globally, because closing the open panel is the only action that makes +sense wherever focus is. + +Left/Right/Home/End are scoped to two roving-tabindex groups — the step rail and +the strategy control — by asking where the event came from *first*. They do not +move a step from inside a scrollable proof block, a code sample or any other +control. The gate fails if that guard is removed. + ## Deep links ``` -?run=&proof=&state= +?run=&proof=&state=[&guide=] ``` `run.local.overview` aliases to `run.local.treatment` and is the default. Rules, @@ -167,6 +359,78 @@ three. Cockpit-specific additions: `run.missing`, `run.unavailable`, `run.cloud.partial`, `run.evidence.invalid-link`, `evaluation.unbound`. +The guided layer is a **second axis on the same address**, not a second address +space: `state` still names the recorded arm and `guide` names which beat is +emphasised, so the two compose. `guide.local.choice` is the default, and +`guide.local.free` is the expert path — declared rather than left implicit, so +an unknown value can be refused instead of resolving to something. An unknown +guided state renders `run.missing`; so does a guided state asked for under the +cloud proof class. Both echo the address that earned the refusal, and neither is +corrected to step one. + +## Coordination strategies — bound to HAC-343, not transcribed + +The judge's next question after *what changed?* is *compared with what?*. The +approved prototype rendered that panel as a scaffold, because HAC-343 had no +frozen artifact when it was drawn. **It does now**, so the panel binds rather +than scaffolds — every cell reads a named field out of a frozen HAC-343 artifact +and renders the path it came from beside the value. + +Six dimensions × four strategies. Strategy labels come from +`judge-export.json#panel1.rows[].label` rather than being written here. + +| Dimension | Bound to | +| --- | --- | +| Safety result | `results.json#report.aggregate..unsafeJointState.display` | +| Concurrency cost | `results.json#report.aggregate..spr.rendering` | +| Scope of coordination | `execution-semantics.json#arms..note` | +| Evidence sensitivity | `results.json#report.aggregate..evidenceSensitivity.display` | +| Recorded outcome | `results.json#report.aggregate..permit.display` | +| Limitation | per arm — see below | + +`Limitation` is the one asymmetric row, because HAC-343 records each arm's +limitation under the key that fits that arm: `canFail` for the arms that could +have falsified the thesis, `knownWeakness` where the weakness is structural, +`namingRule` for the arm most likely to be mis-described, and the export's own +`forbiddenRendering` for Interlock. Flattening them to one key would have meant +writing three sentences HAC-343 never wrote. + +| Arm | Limitation field | +| --- | --- | +| `A1_uncoordinated` | `metric-definitions.json#arms.A1_uncoordinated.canFail` | +| `A2_global_lock` | `metric-definitions.json#arms.A2_global_lock.knownWeakness` | +| `A3_per_target_lock` | `metric-definitions.json#arms.A3_per_target_lock.namingRule` | +| `A4_interlock` | `judge-export.json#panel2.forbiddenRendering` | + +Each dimension also carries the question it answers, bound to +`metric-definitions.json#metrics.*.question` — a bare `2/2 (100.0%)` does not say +whether two out of two is good. + +**A3 is not renamed "credible".** HAC-343's own `namingRule` forbids describing +that arm loosely, so the panel shows the frozen figure that earns the word +instead: same-target contention serialized +`judge-export.json#panel1.perTargetLockCredibility.serializedSameTargetContention`. +A skeptical judge can see the lock was real before reading what it missed. + +**Two experiments, two panels.** HAC-343 evaluates four strategies over its own +sixteen-scenario corpus; HAC-330 is the single bounded counterfactual on screen +beside it. No value crosses. The gate fails if `140 > 130`, `120 <= 130`, +`WITHHOLD_SERIALIZE` or `hac330` appears inside the comparison — the same +refusal that keeps the two proof classes apart, applied to a third experiment. + +### When it cannot bind + +The adapter reads the HAC-343 artifacts optionally, so this surface builds +without them. Absent, every cell they fed renders as +`[BIND: experiments/hac-343/evidence/.json#]`, the panel labels +itself `Unresolved binding scaffold · not evidence`, and no substitute value is +derived from HAC-330 or anywhere else. The banner is conditional in both +directions: it may not appear over bound evidence, and it may not be missing +when something is genuinely unbound. `verify-cockpit.mjs` rebuilds the +comparison from the artifacts it cites and fails if the committed one is not +what they produce — so a hand-edited cell fails whether it was edited toward a +plausible value or away from one. + ## Public evidence All links pin to commit `75253e38791e69f7e2a4bb3a041044a9114c32f0` — never a @@ -278,6 +542,26 @@ node media/hac-341/bin/build-view-model.mjs # rebuild from frozen evidence node media/hac-341/bin/verify-cockpit.mjs # gate ``` +This surface now binds twenty-four comparison cells into HAC-343, so the packet +those cells come from is verified in the same pass — `check:packet:eval` was +added to `pnpm run check`, ahead of `check:cockpit`, so the evidence is checked +before the surface that renders it. That gate builds first: unlike the other +packet verifiers, `experiments/hac-343/lib/arms.mjs` loads the compiled decision +core from `dist/`, so it cannot assume a clean checkout has one. + +```sh +pnpm run check:packet:eval # HAC-343 packet, then the cockpit gate +``` + +The seam is proved in both directions in `test/hac-343-check-wiring.test.mjs`: +an invalid HAC-343 packet fails `check:packet:eval`, and a HAC-343 field moving +*underneath* the committed view model fails `check:cockpit`. One boundary is +pinned rather than assumed there — `check:packet:eval` reports an absent +`results.json` as *machinery verified; the experiment has not been executed* and +exits zero, which is correct for HAC-343 alone and insufficient for anything +binding to it. The cockpit gate is what refuses that case, and both run in +`check`. + The browser-level visual contract is separate because the deterministic package does not carry a browser dependency. It measures both proof classes at **1440×900** and **1280×800**: the local run must show its identity, causal @@ -322,6 +606,23 @@ looping animation. and evidence-panel invariants add **16 more**, and the corrective pass adds **17 more**, in `test/hac-341-identity-gates.test.mjs`. +The guided layer adds **71 tests** in `test/hac-341-guided-walk.test.mjs`: the +derivations directly, and **31 negative cases** proving each new gate bites — +emphasis paid for out of text opacity or a filter, emphasis that starts moving +the run between steps, a label returning to opacity or to the sub-floor grey, the +cloud field losing its muted tier, the cloud raw-proof surface un-following its +field, the walk failing to demote the persistent row, the handoff duplicating its +own actions, demotion dimming control text — +a held-constant marker that stops matching the arms, a perturbation that stops +perturbing, an entry that preselects a path, a step that hides the cockpit or +advances on its own, an arrow key that escapes its group, a second side panel, +a motion control that survives the system preference, an unbound cell dressed up +as a value, and a binding placeholder escaping the comparison scaffold. + +The browser gate adds the guided walk, the ablation, the side panels, both +reduced-motion resolutions and a **320 CSS px** frame to the viewports it +measures. + ## Downstream use — this is a verification surface, not the hero The cockpit answers one judge question: *can I verify this?* It is not the diff --git a/media/hac-341/bin/build-view-model.mjs b/media/hac-341/bin/build-view-model.mjs index 3c0d892..34b2050 100644 --- a/media/hac-341/bin/build-view-model.mjs +++ b/media/hac-341/bin/build-view-model.mjs @@ -19,9 +19,34 @@ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { codeToHtml } from 'shiki'; +import { buildComparison } from '../lib/comparison.mjs'; +import { GUIDE_STATES, GUIDE_CHOICE_STATE, GUIDE_FREE_STATE, GUIDE_STEPS } from '../lib/guide.mjs'; const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..'); const read = (...p) => JSON.parse(readFileSync(join(repoRoot, ...p), 'utf8')); +/** + * A sibling experiment's artifact, or `undefined` when it is not in the tree. + * + * HAC-343 is a separate experiment on a separate branch cadence, so this + * surface has to build with or without it. Absent, every cell it would have + * bound renders as a visible `[BIND: ...]` rather than as a plausible value — + * which is the same refusal the degraded states make everywhere else. + */ +const readOptional = (...p) => { + try { + return read(...p); + } catch (error) { + // Absent is a legitimate state: HAC-343 is a separate experiment and this + // surface has to build without it. Corrupt is not. A truncated or malformed + // artifact took the same branch and silently unbound ten judge-facing + // values — four of them the strategy labels — while every gate stayed + // green. Only ENOENT may pass; anything else is a build defect and says so. + if (error?.code !== 'ENOENT') { + throw new Error(`${p.join('/')} exists but could not be read as JSON: ${error.message}`, { cause: error }); + } + return undefined; + } +}; const arms = read('experiments', 'hac-330', 'evidence', 'arms.json'); const results = read('experiments', 'hac-330', 'evidence', 'results.json'); @@ -259,9 +284,44 @@ const reserved = { rule: 'Labels only. No value, no mark, no proportional geometry until HAC-319 supplies a frozen evaluation packet.', }; +/* --- coordination-strategy comparison: bound to HAC-343 ---------------- */ + +/** + * A different experiment from the run this cockpit shows. Bound here rather + * than transcribed, and left visibly unbound when the packet is absent. + */ +const hac343 = Object.fromEntries( + [ + 'experiments/hac-343/evidence/results.json', + 'experiments/hac-343/evidence/execution-semantics.json', + 'experiments/hac-343/evidence/metric-definitions.json', + 'experiments/hac-343/evidence/judge-export.json', + ] + .map((rel) => [rel, readOptional(...rel.split('/'))]) + .filter(([, value]) => value !== undefined), +); +const comparison = buildComparison(hac343); + +/* --- the guided inspection layer -------------------------------------- */ + +/** + * Declared here so the routing contract has one home. The copy lives with the + * derivation in `lib/guide.mjs`; what the view model owns is the *vocabulary* — + * which addresses exist, so an address that is not one of them can be refused. + */ +const guide = { + proofClass: 'A', + states: GUIDE_STATES, + choiceState: GUIDE_CHOICE_STATE, + freeState: GUIDE_FREE_STATE, + steps: GUIDE_STEPS.map((s) => ({ no: s.no, stateId: s.stateId, name: s.name })), + classification: 'EDITORIAL — an attention layer over the recorded run; it adds no arm, value or claim', + rule: 'Guided steps change emphasis only. Every control the free cockpit offers stays reachable at every step, and no step recomputes anything.', +}; + const model = { contract: 'HAC-341 normalized cockpit view model', - revision: 'r01', + revision: 'r02', generatedFrom: [ 'experiments/hac-330/evidence/arms.json', 'experiments/hac-330/evidence/results.json', @@ -269,6 +329,7 @@ const model = { 'experiments/hac-342/evidence/publication-bindings.json', 'experiments/hac-342/evidence/redaction-manifest.json', 'experiments/hac-342/evidence/runtime-source-snapshot.json', + ...comparison.artifacts.filter((a) => a in hac343), ], fieldClassification: { universalRequired: ['runIdentity', 'proofClass', 'proofLabel', 'frozen', 'editorial', 'claimBoundary'], @@ -283,12 +344,23 @@ const model = { runIds: ['hac330-local', 'hac340-cloud'], proofClasses: ['local', 'cloud'], aliases: { 'run.local.overview': 'run.local.treatment' }, + // The guided layer is addressable on its own axis: `state` still names the + // recorded arm, and `guide` names which beat of the walk is emphasised. An + // unknown value on either axis is refused rather than corrected. + guideParam: 'guide', + guideStates: GUIDE_STATES, + guideDefault: GUIDE_CHOICE_STATE, + guideProofClass: 'local', + unknownGuideState: 'run.missing', + guideUnderWrongProofClass: 'run.missing', invalidRun: 'run.missing', unreadableEvidence: 'run.unavailable', mismatchedProofAndState: 'run.missing', silentSubstitution: 'forbidden', }, runs: { local, cloud: cloudRun }, + guide, + comparison, reserved, degradedStates: [ { id: 'run.loading', message: 'Loading frozen evidence.', forbiddenInference: 'that a run exists or passed' }, @@ -307,5 +379,7 @@ mkdirSync(join(repoRoot, 'media', 'hac-341', 'evidence'), { recursive: true }); writeFileSync(join(repoRoot, 'media', 'hac-341', 'evidence', 'view-model.json'), JSON.stringify(model, null, 2) + '\n'); process.stdout.write( `cockpit view model built\n local arms ${local.arms.length}, checks ${local.checks.label}, receipt ${local.receipt ? 'PRESENT' : 'absent'}\n` - + ` cloud hops ${cloudRun.events.length}, controls ${cloudRun.negativeControls.map((c) => c.status).join('/')}, arms ${cloudRun.arms ? 'PRESENT' : 'absent'}\n`, + + ` cloud hops ${cloudRun.events.length}, controls ${cloudRun.negativeControls.map((c) => c.status).join('/')}, arms ${cloudRun.arms ? 'PRESENT' : 'absent'}\n` + + ` guide ${guide.steps.length} steps, ${guide.states.length} addressable states\n` + + ` compare HAC-343 ${comparison.resolved ? 'bound' : `UNBOUND (${comparison.unresolved.length} bindings)`}, ${comparison.strategies.length} strategies x ${comparison.dimensions.length} dimensions\n`, ); diff --git a/media/hac-341/bin/lib/workflow.mjs b/media/hac-341/bin/lib/workflow.mjs new file mode 100644 index 0000000..f31f2fd --- /dev/null +++ b/media/hac-341/bin/lib/workflow.mjs @@ -0,0 +1,515 @@ +/** + * A structural reader for the CI workflow, sufficient to assert that a gate is + * actually wired rather than merely mentioned. + * + * It lives beside the gate rather than in `media/hac-341/lib/` because nothing + * in the browser imports it. That directory is swept by + * `media/hac-335/bin/lib/capture-source.mjs` as a *render source* set, so a + * gate helper placed there would invalidate every committed cockpit capture on + * every edit to a file that cannot change a pixel. + * + * The check this replaces was `ci.yml.includes('')`, which a comment + * satisfies. Commenting out one line disabled the only enforcement of the + * HAC-343 judge-export reproduction while every gate stayed green — so the + * assertion has to know the difference between a `run:` step and a `#`. + * + * Deliberately not a YAML parser and deliberately not a dependency: this + * repository ships none, and the deterministic core must stay installable + * without one. It understands exactly the subset this workflow uses — + * two-space job keys, `- uses:`/`- name:`/`- run:` steps, `with:` maps, + * `run: |` block scalars, and the two step controls that decide whether a step + * runs at all and whether its failure counts — and it strips comment-only + * lines first, so a commented command is invisible to every accessor below. + * + * It does not try to decide which lines of a shell script execute, because that + * is not decidable by reading: `if false; then`, a heredoc and an open quoted + * string each put a command at the start of a line without running it, and a + * line-anchored match accepted all three. The contract is a *shape* instead — + * one enforcement operation per step, whose `run` is exactly the expected + * command — and this module reports the shape rather than guessing at + * semantics. Callers assert; nothing here evaluates a GitHub expression. + */ + +/** Lines with a `#` in the first non-space position carry no configuration. */ +const uncommented = (yaml) => yaml + .split('\n') + .filter((line) => !/^\s*#/.test(line)) + .join('\n'); + +const indentOf = (line) => line.length - line.trimStart().length; + +/** + * A `key: value` line. + * + * The value group starts at a non-space, so it cannot re-consume what the + * separator already matched: `\\s*` and `\\S` are disjoint and there is no + * backtracking left to be super-linear about. The group is undefined for a bare + * `key:`, which every use site coalesces to ''. + */ +const KV = /^([a-zA-Z-]+):\s*(\S.*)?$/; + +/** `KV`, with the optional value normalised to '' so callers read `m[2]` as before. */ +const kvExec = (line) => { const m = KV.exec(line); if (m) m[2] ??= ''; return m; }; + +/** A `- ` list item and its payload, disjoint for the same reason. */ +const ITEM = /^(\s*)-\s+(\S.*)?$/; + +/** `ITEM`, normalised the same way. */ +const itemExec = (line) => { const m = ITEM.exec(line); if (m) m[2] ??= ''; return m; }; + +/** Job-level keys that decide whether a job runs, or whether its failure counts. */ +const JOB_KEYS = new Set(['if', 'continue-on-error', 'needs', 'runs-on']); + +/** + * The shell lines a `run:` body actually executes. + * + * Comment lines and blank lines are dropped, and each remaining line is + * trimmed, so a caller can anchor a required command to the *start* of a line. + * That is the difference between running `pnpm run check:packet:eval` and + * printing its name inside an `echo` — the failure-summary step documents every + * command this gate requires, and an unanchored substring test could not tell + * the two apart. + */ +export const executableLines = (runBody) => String(runBody ?? '') + .split('\n') + .map((line) => line.trim()) + .filter((line) => line !== '' && !line.startsWith('#')); + +/** + * The lines belonging to one job, by indentation: the ` :` key and every + * following line indented deeper than it. + */ +export function jobBlock(yaml, jobName) { + const lines = uncommented(yaml).split('\n'); + const start = lines.findIndex((l) => new RegExp(String.raw`^ ${jobName}:\s*$`).test(l)); + if (start < 0) return null; + const out = []; + for (const line of lines.slice(start + 1)) { + if (line.trim() === '') { out.push(line); continue; } + if (indentOf(line) <= 2) break; + out.push(line); + } + return out; +} + +/** + * The steps of a job, as `{ uses, name, run, with, if, continueOnError }`. + * `run: |` block scalars are gathered so a multi-line script is one step's + * `run`, not a fragment. + */ +export function jobSteps(yaml, jobName) { + const block = jobBlock(yaml, jobName); + if (!block) return null; + const steps = []; + let current = null; + let blockScalar = null; + for (const line of block) { + const trimmed = line.trim(); + if (blockScalar !== null) { + // A block scalar ends at the first line indented no deeper than its key. + if (trimmed !== '' && indentOf(line) <= blockScalar.indent) blockScalar = null; + else { current.run += `${trimmed}\n`; continue; } + } + const item = itemExec(line); + if (item) { + current = { + uses: null, name: null, run: null, with: {}, + if: null, continueOnError: null, shell: null, workingDirectory: null, + // Every key the step declares, so the caller can allowlist rather than + // enumerate what to forbid. `keyIndent` is where a step's own keys sit; + // anything deeper under `with:` is an input, not a step key. + keys: [], keyIndent: item[1].length + 2, + }; + steps.push(current); + const kv = kvExec(item[2]); + if (kv) { + current.keys.push(kv[1]); + applyKey(kv[1], kv[2], current, indentOf(line), (b) => { blockScalar = b; }); + } + continue; + } + if (!current) continue; + const kv = kvExec(trimmed); + if (!kv) continue; + const ind = indentOf(line); + if (current.inWith && ind > current.keyIndent) { + current.with[kv[1]] = kv[2]; + continue; + } + if (ind !== current.keyIndent) continue; + current.keys.push(kv[1]); + if (kv[1] === 'with') { current.inWith = true; continue; } + current.inWith = false; + applyKey(kv[1], kv[2], current, ind, (b) => { blockScalar = b; }); + } + return steps; +} + +function applyKey(key, value, step, indent, setBlock) { + if (key === 'uses') { step.uses = value; return; } + if (key === 'name') { step.name = value; return; } + // A step control, never a `with:` input: both decide whether the step's + // failure can reach the job, which is the whole point of asserting on it. + if (key === 'if') { step.if = value; step.inWith = false; return; } + if (key === 'continue-on-error') { step.continueOnError = value; step.inWith = false; return; } + // A custom shell or a different working directory changes what the command + // means; an enforcement step must run the command as written, where written. + if (key === 'shell') { step.shell = value; step.inWith = false; return; } + if (key === 'working-directory') { step.workingDirectory = value; step.inWith = false; return; } + if (key !== 'run') return; + step.inWith = false; + if (value === '|' || value === '|-' || value === '>') { + step.run = ''; + setBlock({ indent }); + return; + } + step.run = value; +} + +/** Every command a job actually executes — never anything it merely mentions. */ +export const runCommands = (yaml, jobName) => (jobSteps(yaml, jobName) ?? []) + .map((s) => s.run) + .filter((r) => typeof r === 'string' && r.trim() !== ''); + +/** + * The step whose entire `run` payload is `command`, or `null`. + * + * Equality after trimming, not a search. A step that runs the command plus + * anything else is not an enforcement step: whether the rest of the script + * reaches the command cannot be read off the text, which is exactly how + * `if false; then`, a heredoc and an open quoted string each defeated a + * line-anchored match. + */ +export function enforcementStep(yaml, jobName, command) { + return (jobSteps(yaml, jobName) ?? []) + .find((step) => typeof step.run === 'string' && step.run.trim() === command) ?? null; +} + +/** `continue-on-error` is trustworthy only when absent or the literal `false`. */ +function continueOnErrorDefect(value, subject) { + if (value === null || value === undefined) return null; + if (String(value).trim() === 'false') return null; + return `${subject} sets \`continue-on-error: ${value}\`; only an absent or literally \`false\` value can be trusted`; +} + +/** + * Why a step could fail to enforce what it appears to enforce, or `null`. + * + * Deliberately not an expression evaluator. An evidence gate should be + * unconditional, should run its command as written and where written, and + * should propagate its failure — so any `if:`, any `shell:`, any + * `working-directory:` and any non-literal-`false` `continue-on-error:` is + * refused rather than interpreted. The explanatory step that runs + * `if: failure()` is not an enforcement step and never reaches this. + */ +export function stepEnforcementDefect(step) { + if (!step) return 'no step runs exactly that command'; + if (step.if !== null && step.if !== undefined) { + return `the step is conditional on \`if: ${step.if}\`; an evidence gate must be unconditional`; + } + const coe = continueOnErrorDefect(step.continueOnError, 'the step'); + if (coe) return coe; + if (step.shell !== null && step.shell !== undefined) { + return `the step sets \`shell: ${step.shell}\`; an enforcement step must run its command as written`; + } + if (step.workingDirectory !== null && step.workingDirectory !== undefined) { + return `the step sets \`working-directory: ${step.workingDirectory}\`; an enforcement step must run where written`; + } + return null; +} + +/** + * The job-level controls. + * + * Every step can be unconditional and failure-propagating while the job around + * them is skipped (`if: false`) or has its failure discarded + * (`continue-on-error: true`). Both leave every step-level assertion satisfied + * and enforce nothing. + */ +export function jobControls(yaml, jobName) { + const block = jobBlock(yaml, jobName); + if (!block) return null; + const out = { if: null, continueOnError: null, needs: null, runsOn: null }; + for (const line of block) { + // Job-level keys sit at exactly four spaces; deeper belongs to a step. + if (indentOf(line) !== 4) continue; + const kv = kvExec(line.trim()); + if (!kv || !JOB_KEYS.has(kv[1])) continue; + if (kv[1] === 'if') out.if = kv[2]; + if (kv[1] === 'continue-on-error') out.continueOnError = kv[2]; + if (kv[1] === 'needs') out.needs = kv[2]; + if (kv[1] === 'runs-on') out.runsOn = kv[2]; + } + return out; +} + +/** Why the job could fail to enforce what its steps enforce, or `null`. */ +export function jobEnforcementDefect(controls, expectedRunner) { + if (!controls) return 'the job does not exist'; + if (controls.if !== null && controls.if !== undefined) { + return `the job is conditional on \`if: ${controls.if}\`; an evidence gate must be unconditional`; + } + const coe = continueOnErrorDefect(controls.continueOnError, 'the job'); + if (coe) return coe; + if (controls.needs !== null && controls.needs !== undefined) { + return `the job declares \`needs: ${controls.needs}\`; a skipped or failed dependency would silently skip this gate`; + } + if (controls.runsOn !== expectedRunner) { + return `the job runs on \`${controls.runsOn}\`, not \`${expectedRunner}\``; + } + return null; +} + +/** + * A `defaults.run` map's `shell` and `working-directory`, at one indent level. + * + * Read, never resolved. GitHub inherits these into every `run` step of every + * job in scope, so a step can carry no `shell:` and no `working-directory:` of + * its own and still run under a different shell in a different directory — the + * step-level assertions all pass and the guarantee is gone. This grammar does + * not model that precedence; it forbids the keys outright for this gate. + */ +function inheritedRunDefaults(lines, baseIndent) { + const out = { shell: null, workingDirectory: null }; + const start = lines.findIndex((l) => indentOf(l) === baseIndent && l.trim() === 'defaults:'); + if (start < 0) return out; + let runIndent = null; + for (const line of lines.slice(start + 1)) { + if (line.trim() === '') continue; + const ind = indentOf(line); + if (ind <= baseIndent) break; + const kv = kvExec(line.trim()); + if (!kv) continue; + if (runIndent === null) { + if (kv[1] === 'run') runIndent = ind; + continue; + } + if (ind <= runIndent) break; + if (kv[1] === 'shell') out.shell = kv[2]; + if (kv[1] === 'working-directory') out.workingDirectory = kv[2]; + } + return out; +} + +/** + * Why an inherited `defaults.run` could change what a required step does, or + * `null`. Both the workflow-level and the job-level map are refused; which one + * would win is precedence this grammar deliberately does not compute. + */ +export function runDefaultsDefect(yaml, jobName) { + const documentLines = uncommented(yaml).split('\n'); + const scopes = [ + { scope: 'the workflow', defaults: inheritedRunDefaults(documentLines, 0) }, + { scope: 'the job', defaults: inheritedRunDefaults(jobBlock(yaml, jobName) ?? [], 4) }, + ]; + for (const { scope, defaults } of scopes) { + if (defaults.shell !== null) { + return `${scope} sets \`defaults.run.shell: ${defaults.shell}\`; every required step would inherit it`; + } + if (defaults.workingDirectory !== null) { + return `${scope} sets \`defaults.run.working-directory: ${defaults.workingDirectory}\`; every required step would inherit it`; + } + } + return null; +} + +/** Every key the job declares, in order. */ +export function jobKeys(yaml, jobName) { + const block = jobBlock(yaml, jobName); + if (!block) return null; + const keys = []; + for (const line of block) { + if (indentOf(line) !== 4) continue; + const kv = kvExec(line.trim()); + if (kv) keys.push(kv[1]); + } + return keys; +} + +/** + * Why the job declares a key outside the accepted grammar, or `null`. + * + * An allowlist, not a blacklist. A list of forbidden keys is only ever as + * complete as the last review that extended it — `defaults`, `strategy`, + * `container` and `services` were each added after someone found them — and the + * next one is whatever nobody has thought of yet. The canonical job declares + * three keys; anything else is a defect whether or not its effect is understood. + */ +export function jobKeyDefect(yaml, jobName, allowed) { + const keys = jobKeys(yaml, jobName); + if (keys === null) return 'the job does not exist'; + const extra = keys.filter((k) => !allowed.includes(k)); + if (extra.length) { + return `the job declares \`${extra.join('`, `')}\`; the accepted shape declares only \`${allowed.join('`, `')}\``; + } + for (const key of allowed) { + if (!keys.includes(key)) return `the job does not declare \`${key}\``; + } + return null; +} + +/** + * Why the workflow inherits state into the job from above it, or `null`. + * + * `env` is refused for the same reason `defaults.run` is: the job's steps would + * carry it without naming it, and an evidence gate that has to reason about + * what it inherited has already lost. + */ +export function workflowEnvDefect(yaml) { + for (const line of uncommented(yaml).split('\n')) { + if (indentOf(line) !== 0) continue; + if (/^env:\s*/.test(line.trim())) return 'the workflow declares `env`, which every job would inherit'; + } + return null; +} + +/** Every top-level key the workflow declares, in order. */ +export const workflowKeys = (yaml) => uncommented(yaml).split('\n') + .filter((line) => indentOf(line) === 0 && /^[a-zA-Z-]+:/.test(line)) + .map((line) => /^([a-zA-Z-]+):/.exec(line)[1]); + +/** + * The lines of one top-level block, by indentation. + * + * Used to project `on:`, `permissions:` and `concurrency:` exactly rather than + * accept whatever they happen to say. + */ +export function workflowBlock(yaml, key) { + const lines = uncommented(yaml).split('\n'); + const start = lines.findIndex((l) => indentOf(l) === 0 && new RegExp(`^${key}:`).test(l)); + if (start < 0) return null; + const out = []; + for (const line of lines.slice(start + 1)) { + if (line.trim() === '') continue; + if (indentOf(line) === 0) break; + out.push(line); + } + return out; +} + +/** + * Why the workflow's execution contract departs from the canonical one, or + * `null`. + * + * The job and its steps can be exactly right and never run. A workflow whose + * trigger is narrowed to `workflow_dispatch` still contains a perfectly valid + * evaluation gate, and enforcement on the submission path is simply gone. So + * the top level is pinned the same way everything below it is: an allowlist of + * keys, and an exact projection of the blocks that decide when and with what + * the gate runs. Nothing here interprets an event or an expression — the lines + * either match the canonical ones or they do not. + */ +export function workflowShapeDefects(yaml, expected) { + const out = []; + const keys = workflowKeys(yaml); + const extra = keys.filter((k) => !expected.keys.includes(k)); + if (extra.length) { + out.push(`the workflow declares \`${extra.join('`, `')}\`; the accepted shape declares only \`${expected.keys.join('`, `')}\``); + } + for (const key of expected.keys) { + if (!keys.includes(key)) out.push(`the workflow does not declare \`${key}\``); + } + for (const [key, lines] of Object.entries(expected.blocks ?? {})) { + const actual = workflowBlock(yaml, key); + if (actual === null) { out.push(`the workflow has no \`${key}\` block`); continue; } + const normalise = (xs) => xs.map((l) => l.trimEnd()).join('\n'); + if (normalise(actual) !== normalise(lines)) { + out.push(`the workflow's \`${key}\` block is \`${actual.map((l) => l.trim()).join(' ')}\`; the accepted shape is \`${lines.map((l) => l.trim()).join(' ')}\``); + } + } + return out; +} + +/** + * Compare a job's steps against an exact expected sequence. + * + * Presence-based verification was not enough: every required step could be + * exact, unconditional and failure-propagating while the byte assertion ran + * *before* the rebuild it asserts about, or while an interposed step undid the + * rebuild first. Both left all four operations present and one of them vacuous. + * A sequence has no gaps to hide in — and `rebuild index + 1 === assertion + * index` falls out of it rather than being a rule of its own. + * + * Each expectation may pin `uses`, `name`, an exact `run`, `with` entries, and + * `conditional` (the only permitted `if`). Anything the shape does not name is + * refused by position: an extra step is a step the shape has no slot for. + */ +export function shapeDefects(steps, expected) { + const out = []; + const label = (step, i) => `step ${i + 1}` + (step ? ` (${step.uses ?? step.name ?? 'unnamed'})` : ''); + if (steps.length !== expected.length) { + out.push(`the job has ${steps.length} step(s); the accepted shape has exactly ${expected.length}`); + } + for (let i = 0; i < Math.max(steps.length, expected.length); i += 1) { + const step = steps[i]; + const want = expected[i]; + if (!want) { out.push(`${label(step, i)} is not part of the accepted shape`); continue; } + if (!step) { out.push(`step ${i + 1} is missing; the shape expects ${want.uses ?? want.name}`); continue; } + if (want.uses && step.uses !== want.uses) { + out.push(`${label(step, i)} uses \`${step.uses}\`; the shape expects \`${want.uses}\``); + } + if (want.name && step.name !== want.name) { + out.push(`step ${i + 1} is named \`${step.name}\`; the shape expects \`${want.name}\``); + } + if (want.run !== undefined && String(step.run ?? '').trim() !== want.run) { + out.push(`step ${i + 1} runs \`${String(step.run ?? '(none)').trim().slice(0, 52)}\`; the shape expects exactly \`${want.run}\``); + } + for (const [key, value] of Object.entries(want.with ?? {})) { + if (step.with?.[key] !== value) { + out.push(`step ${i + 1} sets \`${key}: ${step.with?.[key] ?? '(absent)'}\`; the shape expects \`${value}\``); + } + } + // The `with` projection is exact: an extra input is as much a departure as + // a wrong one. + for (const key of Object.keys(step.with ?? {})) { + if (!(key in (want.with ?? {}))) { + out.push(`step ${i + 1} passes \`with.${key}\`, which the shape does not project`); + } + } + // And the step declares exactly the keys the shape names — allowlisted, so + // a key nobody has thought to forbid is refused by default. + const wantKeys = want.keys ?? []; + const extraKeys = (step.keys ?? []).filter((k) => !wantKeys.includes(k)); + if (extraKeys.length) { + out.push(`step ${i + 1} declares \`${extraKeys.join('`, `')}\`; the shape declares only \`${wantKeys.join('`, `')}\``); + } + for (const key of wantKeys) { + if (!(step.keys ?? []).includes(key)) out.push(`step ${i + 1} does not declare \`${key}\``); + } + const wantIf = want.conditional ?? null; + if ((step.if ?? null) !== wantIf) { + out.push(wantIf + ? `step ${i + 1} is conditional on \`${step.if ?? '(absent)'}\`; the shape expects \`${wantIf}\`` + : `step ${i + 1} is conditional on \`if: ${step.if}\`; only the failure explanation may be conditional`); + } + const control = stepEnforcementDefect({ ...step, if: null }); + if (control) out.push(`step ${i + 1}: ${control}`); + } + return out; +} + +/** Every `actions/checkout` step in a job, in order. */ +export const checkoutSteps = (yaml, jobName) => (jobSteps(yaml, jobName) ?? []) + .filter((s) => String(s.uses ?? '').includes('actions/checkout')); + +/** + * Why the job's checkout could leave the workspace at the wrong depth, or + * `null`. + * + * Exactly one checkout, at `fetch-depth: 0`. Reading the *first* checkout was + * not enough: a later re-checkout at depth 1 leaves the workspace shallow while + * the first still declares 0, so the count is part of the invariant rather than + * an afterthought. A second checkout is a defect whatever depth it asks for — + * the grammar does not model which one wins. + */ +export function checkoutDefect(yaml, jobName) { + const steps = checkoutSteps(yaml, jobName); + if (steps.length !== 1) { + return `the job runs ${steps.length} checkout step(s); an evidence gate must check out exactly once, at a known depth`; + } + const depth = steps[0].with?.['fetch-depth']; + if (String(depth) !== '0') { + return `the checkout requests \`fetch-depth: ${depth ?? '(absent)'}\`; the freeze-commit checks cannot resolve`; + } + return null; +} diff --git a/media/hac-341/bin/verify-cockpit-visual.mjs b/media/hac-341/bin/verify-cockpit-visual.mjs index 0013e98..bb67f3d 100644 --- a/media/hac-341/bin/verify-cockpit-visual.mjs +++ b/media/hac-341/bin/verify-cockpit-visual.mjs @@ -42,6 +42,14 @@ function assert(condition, message) { if (!condition) throw new Error(message); } +/** A guided address. The walk is a second axis on the same deep link. */ +async function gotoGuided(page, guide, arm = 'treatment', query = '') { + await page.goto(`${cockpit}?run=hac330-local&proof=local&state=run.local.${arm}&guide=${guide}&static=1${query}`, { + waitUntil: 'networkidle', + }); + await page.waitForSelector('.arm-switcher'); +} + async function goto(page, query = '') { await page.goto(`${cockpit}?run=hac330-local&proof=local&state=run.local.treatment&static=1${query}`, { waitUntil: 'networkidle', @@ -149,6 +157,402 @@ async function assertLongValues(page, name) { await assertNoHorizontalOverflow(page, `${name}: adversarial recorded values`); } +/** + * The guided layer, in a browser. + * + * Source-level checks can prove the markup says the right thing; only a browser + * can prove that the emphasised region is still on screen, that the run behind + * the walk is still operable, and that a step change did not scroll the + * evidence away. + */ +async function assertGuidedWalk(page, name) { + // The entry offers a choice and preselects neither path. + await goto(page); + await page.waitForSelector('.guide-choice'); + await whollyVisible(page, '.guide-choice', `${name}: entry choice`, 0); + const picks = page.locator('.guide-choice button'); + assert(await picks.count() === 2, `${name}: the entry does not offer exactly two paths`); + for (const attr of ['aria-pressed', 'aria-current']) { + assert(await picks.evaluateAll((els, a) => els.every((el) => !el.getAttribute(a)), attr), + `${name}: the entry preselects a path`); + } + // The choice may not obscure run identity, frozen state, checks or the switch. + for (const [selector, label] of [ + ['.run-header__facts', 'run identity and checks'], + ['.switch', 'proof-class switch'], + ['.run-thesis', 'causal claim'], + ]) await whollyVisible(page, selector, `${name}: ${label} behind the entry choice`, 0); + await assertNoHorizontalOverflow(page, `${name}: entry choice`); + + // Walking starts at one, and the cockpit is still a cockpit. + await page.getByRole('button', { name: 'Walk the proof' }).click(); + await page.waitForSelector('.guide-bar'); + assert((await page.locator('.guide-count').first().innerText()).includes('01'), + `${name}: the walk did not start at step one`); + assert(await page.locator('[data-guide-rail] button').count() === 6, + `${name}: the step rail does not offer all six steps`); + assert(await page.locator('.arms button[aria-pressed="true"]').isEnabled(), + `${name}: the arm selector is not operable during the walk`); + + // Every step keeps the four stages and the expert controls on screen, and no + // step scrolls the reader. + for (const [step, id] of [ + [1, 'guide.local.validity'], [2, 'guide.local.shared-environment'], + [3, 'guide.local.evidence-decision'], [4, 'guide.local.outcome'], + [5, 'guide.local.ablation'], [6, 'guide.local.handoff'], + ]) { + await gotoGuided(page, id); + assert(await page.evaluate(() => scrollY === 0), `${name}: step ${step} scrolled the reader`); + for (const sel of ['#inputs-title', '#evidence-title', '#decision-title', '#outcome-title']) { + assert(await page.locator(sel).isVisible(), `${name}: step ${step} removed ${sel} from the run`); + } + // Emphasis is positive and non-textual: the current stage is marked, the + // others are simply unmarked. Nothing anywhere is composited. + const emphasis = await page.evaluate(() => { + const rows = [...document.querySelectorAll('[data-guide-em]')].map((el) => ({ + state: el.dataset.guideEm, + opacity: Number(getComputedStyle(el).opacity), + filter: getComputedStyle(el).filter, + shadow: getComputedStyle(el).boxShadow, + })); + return { rows, focused: rows.filter((r) => r.state === 'focus').length }; + }); + assert(emphasis.rows.every((r) => r.opacity === 1), `${name}: step ${step} composites a stage with opacity`); + assert(emphasis.rows.every((r) => r.filter === 'none'), `${name}: step ${step} filters a stage`); + assert(step === 6 || emphasis.focused > 0, `${name}: step ${step} marks no stage as current`); + assert(emphasis.rows.filter((r) => r.state === 'focus').every((r) => r.shadow !== 'none'), + `${name}: the current stage carries no positive emphasis`); + await assertNoHorizontalOverflow(page, `${name}: step ${step}`); + } + + // No step may move the run. Emphasis is colour, background and shadow only, + // so every stage must occupy the same box at every step. + const geometry = {}; + for (const id of ['guide.local.validity', 'guide.local.shared-environment', 'guide.local.evidence-decision', + 'guide.local.outcome', 'guide.local.ablation', 'guide.local.handoff']) { + await gotoGuided(page, id); + geometry[id] = await page.evaluate(() => Object.fromEntries( + ['.causal-stage', '.evidence-band', '.decision-bar', '.outcome-stage', '.arm-switcher'] + .map((sel) => { + const b = document.querySelector(sel).getBoundingClientRect(); + return [sel, `${Math.round(b.x)},${Math.round(b.width)},${Math.round(b.height)}`]; + }))); + } + const steps = Object.keys(geometry); + for (const sel of Object.keys(geometry[steps[0]])) { + const seen = new Set(steps.map((k) => geometry[k][sel])); + // The ablation and handoff steps legitimately change content height; only + // the horizontal box is compared there, and the rest must not move at all. + const boxes = [...seen].map((v) => v.split(',').slice(0, 2).join(',')); + assert(new Set(boxes).size === 1, + `${name}: ${sel} moves between steps (${[...seen].join(' | ')}); emphasis must not participate in layout`); + } + + // Back and Next work without an arrow key, and neither wraps. + await gotoGuided(page, 'guide.local.validity'); + assert(await page.locator('[data-guide-back]').isDisabled(), `${name}: Back is live on the first step`); + await page.locator('[data-guide-next]').click(); + assert((await page.locator('.guide-count').first().innerText()).includes('02'), + `${name}: Next did not advance the walk`); + await page.locator('[data-guide-back]').click(); + assert((await page.locator('.guide-count').first().innerText()).includes('01'), + `${name}: Back did not return the walk`); + + // Arrows move a step only inside the rail. + await page.locator('button[data-drawer="verify"]').focus(); + await page.keyboard.press('ArrowRight'); + assert((await page.locator('.guide-count').first().innerText()).includes('01'), + `${name}: an arrow key outside the rail moved the walk`); + await page.keyboard.press('Escape'); + await page.locator('[data-guide-rail] button[aria-current="step"]').focus(); + await page.keyboard.press('ArrowRight'); + assert((await page.locator('.guide-count').first().innerText()).includes('02'), + `${name}: an arrow key inside the rail did not move the walk`); +} + +/** The ablation swaps a recorded arm and says what moved and what did not. */ +async function assertAblation(page, name) { + await gotoGuided(page, 'guide.local.ablation'); + const evidenceBefore = await box(page, '.evidence-band', `${name}: evidence before ablation`); + await page.locator('[data-guide-ablate]').click(); + await page.waitForSelector('.mk[data-mk="changed"]'); + const evidenceAfter = await box(page, '.evidence-band', `${name}: evidence after ablation`); + assert(Math.abs(evidenceAfter.y - evidenceBefore.y) < 240, + `${name}: the ablation moved the evidence it is about by ${Math.round(Math.abs(evidenceAfter.y - evidenceBefore.y))}px`); + assert(await page.evaluate(() => scrollY === 0), `${name}: the ablation scrolled the reader`); + + const marks = await page.evaluate(() => ({ + held: [...document.querySelectorAll('.mk[data-mk="held"]')].map((el) => el.textContent.trim()), + changed: [...document.querySelectorAll('.mk[data-mk="changed"]')].map((el) => el.textContent.trim()), + })); + assert(marks.held.length === 4, `${name}: ${marks.held.length} held-constant markers, expected 4`); + assert(marks.changed.length === 4, `${name}: ${marks.changed.length} changed markers, expected 4`); + const text = await page.locator('.causal-layout').innerText(); + for (const value of ['ALLOW_PARALLEL', '140 > 130', 'db8a63ec', 'no qualifying coupling']) { + assert(text.toLowerCase().includes(value.toLowerCase()), + `${name}: the perturbed arm does not show its recorded ${value}`); + } + assert(text.includes('Nothing is executed in the browser'), + `${name}: the recorded-arm disclaimer was lost during the ablation`); + await assertNoHorizontalOverflow(page, `${name}: ablation`); + + // Reversible: the same control restores the original evidence. + await page.locator('[data-guide-ablate]').click(); + await page.waitForSelector('.evidence-band .chip[data-s="coupled"]'); + const restored = await page.locator('.causal-layout').innerText(); + assert(restored.includes('WITHHOLD_SERIALIZE') && restored.includes('120 <= 130'), + `${name}: restoring the evidence did not return the treatment arm`); + assert(await page.locator('.mk').count() === 0, + `${name}: changed markers survived the restore`); +} + +/** One side panel, non-modal, focus in and back out. */ +async function assertPanels(page, name) { + await gotoGuided(page, 'guide.local.handoff'); + const verify = page.locator('.guide-step .acts button[data-drawer="verify"]'); + const compare = page.locator('.guide-step .acts button[data-drawer="compare"]'); + await verify.click(); + await page.waitForSelector('#drawer[data-open="true"]'); + assert((await page.locator('.drawer .sub').first().innerText()).includes('selected arm'), + `${name}: the verification panel does not name the arm it explains`); + assert(await page.locator('#evidence-title').isVisible() && await page.locator('#decision-title').isVisible(), + `${name}: opening verification hid the causal context it explains`); + await compare.click(); + await page.waitForSelector('#drawer[data-panel="compare"]'); + assert(await page.locator('aside[data-open="true"]').count() === 1, + `${name}: two side panels were open at once`); + assert(await page.locator('#drawer-title').innerText() === 'Coordination strategies', + `${name}: the comparison panel did not replace the verification panel`); + assert(!(await page.locator('main#app').evaluate((el) => el.hasAttribute('inert'))), + `${name}: the run was made inert while a panel was open`); + // Arrows move strategies only inside the strategy group. + const first = await page.locator('.cmp-strats button[aria-pressed="true"]').innerText(); + await page.locator('.cmp-strats button[aria-pressed="true"]').focus(); + await page.keyboard.press('ArrowRight'); + assert(await page.locator('.cmp-strats button[aria-pressed="true"]').innerText() !== first, + `${name}: an arrow inside the strategy group did not change the strategy`); + // Every rendered cell names the field it came from. + const cells = await page.evaluate(() => [...document.querySelectorAll('.cmp-dim')] + .map((el) => ({ value: el.querySelector('dd').textContent.trim(), src: el.querySelector('.cmp-src').textContent.trim() }))); + assert(cells.length === 6, `${name}: the comparison shows ${cells.length} dimensions, expected 6`); + for (const cell of cells) { + assert(cell.src.startsWith('experiments/hac-343/evidence/'), + `${name}: a comparison cell cites "${cell.src}" rather than a HAC-343 field`); + const unbound = cell.value.startsWith('[BIND:'); + const scaffold = await page.locator('.cmp-scaffold').count(); + assert(!unbound || scaffold === 1, + `${name}: an unresolved binding is shown without the not-evidence label`); + } + await page.keyboard.press('Escape'); + await page.waitForSelector('#drawer[data-open="false"]', { state: 'attached' }); + assert(await compare.evaluate((el) => document.activeElement === el), + `${name}: focus did not return to the control that opened the panel`); +} + +/** The reduced-motion control describes the state actually in force. */ +async function assertReducedMotion(browser, viewport, name) { + const os = await browser.newPage({ viewport, reducedMotion: 'reduce' }); + try { + await gotoGuided(os, 'guide.local.ablation', 'perturbed'); + assert(await os.locator('span.guide-motion[role="status"]').count() === 1, + `${name}: the OS reduced-motion state is not reported`); + assert(await os.locator('button.guide-motion').count() === 0, + `${name}: a manual motion control survives the OS preference`); + assert((await os.locator('.guide-motion').first().innerText()).toLowerCase().includes('system preference'), + `${name}: the reduced-motion status does not name the system preference`); + // Reduction changes the transition, never the information. + assert(await os.locator('.mk').count() === 8, + `${name}: reduced motion dropped the held/changed markers`); + assert(await os.locator('[data-guide-rail] button').count() === 6, + `${name}: reduced motion dropped a step`); + const durations = await os.evaluate(() => [...document.querySelectorAll('[data-guide-em],[data-il-motion]')] + .map((el) => getComputedStyle(el).transitionDuration)); + assert(durations.every((d) => d === '0s'), `${name}: a staged transition survived reduced motion`); + } finally { await os.close(); } + + const manual = await browser.newPage({ viewport, reducedMotion: 'no-preference' }); + try { + await manual.goto(`${cockpit}?run=hac330-local&proof=local&state=run.local.treatment&guide=guide.local.validity`, { waitUntil: 'networkidle' }); + await manual.waitForSelector('.guide-bar'); + assert((await manual.locator('button.guide-motion').innerText()).toLowerCase() === 'reduce motion', + `${name}: the motion control does not name the action it will perform`); + await manual.locator('button.guide-motion').click(); + assert((await manual.locator('button.guide-motion').innerText()).toLowerCase() === 'enable motion', + `${name}: the motion control does not name the action available after reducing`); + assert(await manual.locator('button.guide-motion').getAttribute('aria-pressed') === 'true', + `${name}: the manual motion control does not expose its state`); + assert(await manual.evaluate(() => document.documentElement.dataset.reducedMotion) === 'true', + `${name}: manual reduction did not take effect`); + } finally { await manual.close(); } +} + +/** 320 CSS px: one column, causal order, no sideways scroll. */ +async function assertNarrow(browser) { + const page = await browser.newPage({ viewport: { width: 320, height: 900 } }); + try { + await goto(page); + await page.waitForSelector('.guide-choice'); + const taps = await page.evaluate(() => [...document.querySelectorAll('.guide-choice button')] + .map((el) => el.getBoundingClientRect().height)); + assert(taps.every((h) => h >= 44), `320px: an entry action is under the 44px tap target`); + await assertNoHorizontalOverflow(page, '320px entry'); + + await gotoGuided(page, 'guide.local.ablation', 'perturbed'); + const order = await page.evaluate(() => { + const top = (s) => document.querySelector(s).getBoundingClientRect().top + scrollY; + return { evidence: top('.evidence-band'), decision: top('.decision-bar'), outcome: top('.outcome-stage') }; + }); + assert(order.evidence < order.decision && order.decision < order.outcome, + '320px: the causal reading order is not evidence, decision, outcome'); + assert(await page.locator('[data-guide-rail]').isVisible() === false, + '320px: the full step rail is drawn where it cannot fit'); + assert((await page.locator('.guide-count').first().innerText()).includes('05'), + '320px: the step count is not shown in place of the rail'); + for (const control of ['[data-guide-back]', '[data-guide-next]']) { + assert(await page.locator(control).isVisible(), `320px: ${control} is not reachable`); + } + assert(await page.locator('.mk').count() === 8, '320px: an ablation marker was dropped'); + const clipped = await page.evaluate(() => [...document.querySelectorAll('.mk, .res .v')] + .filter((el) => el.scrollWidth > el.clientWidth + 1).length); + assert(clipped === 0, '320px: an ablation marker or recorded value is clipped'); + await assertNoHorizontalOverflow(page, '320px ablation'); + } finally { await page.close(); } +} + +/** + * WCAG AA, measured rather than asserted. + * + * This surface's whole thesis is that a displayed claim resolves to something + * checkable. The contrast floor was the one claim that did not: "no text muted + * by opacity, zero AA failures, minimum 5.15:1" lived in a README sentence and + * a scratch script, so it was true when someone ran it and unfalsifiable + * afterwards. It is a gate now. + * + * Colour is resolved by painting one pixel and reading it back. `fillStyle` + * round-trips `oklch()` unchanged and this design is oklch throughout, so + * string parsing silently mis-measured every semantic state colour — the first + * version of this audit reported four failures that were its own bug. + */ +const MEASURE = () => { + const cv = document.createElement('canvas').getContext('2d', { willReadFrequently: true }); + cv.globalCompositeOperation = 'copy'; + const cache = new Map(); + const parse = (c) => { + if (!c) return null; + if (cache.has(c)) return cache.get(c); + let v = null; + try { + cv.fillStyle = c; + cv.fillRect(0, 0, 1, 1); + const d = cv.getImageData(0, 0, 1, 1).data; + v = { r: d[0], g: d[1], b: d[2], a: d[3] / 255 }; + } catch { v = null; } + cache.set(c, v); + return v; + }; + const over = (f, b) => ({ + r: f.r * f.a + b.r * (1 - f.a), g: f.g * f.a + b.g * (1 - f.a), b: f.b * f.a + b.b * (1 - f.a), a: 1, + }); + const lum = (c) => { + const f = (v) => { v /= 255; return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; }; + return 0.2126 * f(c.r) + 0.7152 * f(c.g) + 0.0722 * f(c.b); + }; + const ratio = (a, b) => { const l1 = lum(a), l2 = lum(b); const [hi, lo] = l1 > l2 ? [l1, l2] : [l2, l1]; return (hi + 0.05) / (lo + 0.05); }; + const pageBg = parse(getComputedStyle(document.body).backgroundColor) || { r: 255, g: 255, b: 255, a: 1 }; + // Opacity composites the whole subtree, so it accumulates down the ancestry. + const chain = (el) => { + let o = 1; + for (let n = el; n && n !== document.documentElement; n = n.parentElement) { + o *= Number(getComputedStyle(n).opacity); + } + return o; + }; + const backdrop = (el) => { + for (let n = el; n && n !== document.documentElement; n = n.parentElement) { + const c = parse(getComputedStyle(n).backgroundColor); + if (c && c.a > 0.999) return c; + } + return pageBg; + }; + const out = []; + for (const el of document.querySelectorAll('body *')) { + if (![...el.childNodes].some((n) => n.nodeType === 3 && n.textContent.trim())) continue; + const r = el.getBoundingClientRect(); + if (!r.width || !r.height) continue; + const cs = getComputedStyle(el); + if (cs.visibility === 'hidden' || cs.display === 'none' || el.classList.contains('sr')) continue; + // Closed panels are `inert`; 1.4.3 exempts inactive components. + if (el.closest('[inert]') || el.matches(':disabled') || el.closest('button:disabled')) continue; + const fg = parse(cs.color); + if (!fg) continue; + const fs = Number.parseFloat(cs.fontSize); + const weight = Number(cs.fontWeight) || 400; + const total = chain(el); + const bd = backdrop(el); + const ownBg = parse(cs.backgroundColor); + const eBg = ownBg && ownBg.a > 0.999 ? over({ ...ownBg, a: total }, bd) : over({ ...bd, a: total }, bd); + const eFg = over({ ...fg, a: fg.a * total }, eBg); + const large = fs >= 24 || (fs >= 18.66 && weight >= 700); + out.push({ + sel: (el.className || el.tagName).toString().split(/\s+/)[0].slice(0, 28), + text: el.textContent.trim().slice(0, 30).replace(/\s+/g, ' '), + fs: +fs.toFixed(1), opacity: +total.toFixed(3), + ratio: +ratio(eFg, eBg).toFixed(2), required: large ? 3 : 4.5, + }); + } + return out; +}; + +async function assertContrastFloor(browser) { + const GUIDES = ['guide.local.choice', 'guide.local.free', 'guide.local.validity', 'guide.local.shared-environment', + 'guide.local.evidence-decision', 'guide.local.outcome', 'guide.local.ablation', 'guide.local.handoff']; + const local = `${cockpit}?run=hac330-local&proof=local`; + const cloudUrl = `${cockpit}?run=hac340-cloud&proof=cloud&state=run.cloud.overview&static=1`; + const rows = []; + for (const viewport of [{ width: 1440, height: 900 }, { width: 320, height: 900 }]) { + const page = await browser.newPage({ viewport }); + try { + for (const guide of GUIDES) { + for (const arm of ['treatment', 'perturbed']) { + await page.goto(`${local}&state=run.local.${arm}&guide=${guide}&static=1`, { waitUntil: 'networkidle' }); + await page.waitForSelector('main#app *'); + for (const r of await page.evaluate(MEASURE)) rows.push({ where: `${viewport.width}px ${guide}/${arm}`, ...r }); + } + } + for (const [where, url] of [['cloud', cloudUrl], ['degraded', `${local}&state=run.local.nope&static=1`]]) { + await page.goto(url, { waitUntil: 'networkidle' }); + await page.waitForSelector('main#app *'); + for (const r of await page.evaluate(MEASURE)) rows.push({ where: `${viewport.width}px ${where}`, ...r }); + } + // Panels carry the densest small text on the surface, in both classes. + for (const [url, names] of [ + [`${local}&state=run.local.perturbed&guide=guide.local.free&static=1`, + ['Verify this decision', 'Compare coordination strategies', 'Show me the raw proof', 'What is not claimed?']], + [cloudUrl, ['Verify this run', 'Show me the raw proof', 'What is not claimed?']], + ]) { + for (const name of names) { + await page.goto(url, { waitUntil: 'networkidle' }); + await page.getByRole('button', { name }).first().click(); + await page.waitForSelector('#drawer[data-open="true"]'); + for (const r of await page.evaluate(MEASURE)) rows.push({ where: `${viewport.width}px panel:${name}`, ...r }); + } + } + } finally { await page.close(); } + } + + const composited = rows.filter((r) => r.opacity < 0.999); + assert(composited.length === 0, + `text is muted by opacity, which composites it toward the background and compounds through ancestors: ${ + [...new Set(composited.map((r) => `${r.sel} "${r.text}"`))].slice(0, 5).join('; ')}`); + const failures = rows.filter((r) => r.ratio < r.required); + const distinct = [...new Map(failures.map((f) => [`${f.sel}|${f.text}`, f])).values()]; + assert(failures.length === 0, + `${failures.length} text node(s) under the WCAG AA floor: ${ + distinct.slice(0, 6).map((f) => `${f.sel} "${f.text}" ${f.fs}px ${f.ratio}:1 < ${f.required} [${f.where}]`).join('; ')}`); + const min = Math.min(...rows.map((r) => r.ratio)); + console.log(` contrast floor: ${rows.length} text nodes, ${new Set(rows.map((r) => r.where)).size} scenarios, 0 failures, min ${min.toFixed(2)}:1`); +} + async function main() { const { chromium } = await loadPlaywright(); const browser = await chromium.launch({ headless: true }); @@ -171,6 +575,9 @@ async function main() { await assertDrawer(page, name); await goto(page); await assertRawProof(page, name); + await assertGuidedWalk(page, `${name} walk`); + await assertAblation(page, `${name} ablation`); + await assertPanels(page, `${name} panels`); assert(offOrigin.length === 0, `${name}: unexpected off-origin request(s): ${offOrigin.join(', ')}`); } catch (error) { failures.push(error.message); } await page.close(); @@ -183,6 +590,13 @@ async function main() { await cloud.close(); } + for (const viewport of [{ width: 1440, height: 900 }]) { + try { await assertReducedMotion(browser, viewport, `${viewport.width}x${viewport.height} motion`); } + catch (error) { failures.push(error.message); } + } + try { await assertNarrow(browser); } catch (error) { failures.push(error.message); } + try { await assertContrastFloor(browser); } catch (error) { failures.push(error.message); } + const mobile = await browser.newPage({ viewport: { width: 390, height: 844 } }); try { await goto(mobile); @@ -196,7 +610,8 @@ async function main() { await browser.close(); } if (failures.length) throw new Error(`Cockpit visual contract failed:\n- ${failures.join('\n- ')}`); - console.log('HAC-341 cockpit visual contract verified (1440x900, 1280x800, 390x844).'); + console.log('HAC-341 cockpit visual contract verified (1440x900, 1280x800, 390x844, 320x900),' + + '\n including the guided walk, the ablation, the side panels and both reduced-motion resolutions.'); } main().catch((error) => { console.error(error.stack || error.message); process.exit(1); }); diff --git a/media/hac-341/bin/verify-cockpit.mjs b/media/hac-341/bin/verify-cockpit.mjs index db99bfd..b169146 100644 --- a/media/hac-341/bin/verify-cockpit.mjs +++ b/media/hac-341/bin/verify-cockpit.mjs @@ -15,6 +15,10 @@ import { existsSync, readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { armView } from '../lib/arm-view.mjs'; +import { ablationDelta, guideRoute, GUIDE_STATES, GUIDE_STEPS } from '../lib/guide.mjs'; +import { jobSteps, jobControls, jobEnforcementDefect, jobKeyDefect, workflowEnvDefect, + runDefaultsDefect, checkoutDefect, shapeDefects, workflowShapeDefects } from './lib/workflow.mjs'; +import { buildComparison, judgeFacing, JUDGE_FACING_FIELDS, DIMENSIONS, STRATEGY_ARMS, BINDINGS } from '../lib/comparison.mjs'; const here = dirname(fileURLToPath(import.meta.url)); const repoRoot = join(here, '..', '..', '..'); @@ -116,6 +120,28 @@ if (local.verification.hac330VerifierUrl) fail('a HAC-330 verifier URL was inven * These run through `armView`, the derivation the cockpit itself renders from, * so a rewiring of the binding fails here rather than a changed string. */ +/** + * The evidence basis is the value the whole ablation rests on — step 5 says + * "only the frozen evidence differs: basis X replaces Y" — and it was the one + * arm field compared view-model-to-view-model rather than against the frozen + * record. Totals, `holds` and decisions were bound; `basisRevision` was not, so + * editing it in `arms.json` reached the judge with every gate green. + */ +const FROZEN_ARM_KEY = { baseline: 'baseline', treatment: 'treatment', perturbed: 'perturbedControl' }; +for (const [armId, frozenKey] of Object.entries(FROZEN_ARM_KEY)) { + const modelArm = local.arms.find((a) => a.armId === armId); + const frozenArm = arms[frozenKey]; + if (!modelArm || !frozenArm) { fail(`arm ${armId} has no frozen counterpart at arms.${frozenKey}`); continue; } + const frozenBasis = frozenArm.decision?.basisRevision ?? null; + if ((modelArm.basisRevision ?? null) !== frozenBasis) { + fail(`arm ${armId} records basis ${modelArm.basisRevision} but the frozen arm records ${frozenBasis}`); + } + const frozenReason = frozenArm.decision?.reason ?? null; + if ((modelArm.decisionReason ?? null) !== frozenReason) { + fail(`arm ${armId} records reason ${modelArm.decisionReason} but the frozen arm records ${frozenReason}`); + } +} + for (const arm of local.arms) { const v = armView(local, arm.armId); if (v.arm.armId !== arm.armId) { fail(`selecting arm ${arm.armId} resolves to ${v.arm.armId}`); continue; } @@ -186,8 +212,13 @@ if (/environmentEvidence\[0\][^\n]*basisRevision|basisRevision[^\n]*environmentE fail('cockpit reads a basis revision off the environment, bypassing the selected arm'); } // One proof class, one name: the switch and the heading may not drift apart. +// Scoped to `switchHtml` rather than to the whole file — the label is legitimately +// read elsewhere now, and a check that any occurrence exists would stop noticing +// a hard-coded name in the switch itself. +const switchSource = /const switchHtml = [\s\S]*?`;\n/.exec(cockpit)?.[0] ?? ''; +if (!switchSource) fail('cannot locate the proof switch; its naming cannot be checked'); for (const cls of ['local', 'cloud']) { - if (!new RegExp(String.raw`MODEL\.runs\.${cls}\.proofLabel`).test(cockpit)) { + if (!new RegExp(String.raw`MODEL\.runs\.${cls}\.proofLabel`).test(switchSource)) { fail(`the proof switch does not name the ${cls} class from its own proofLabel`); } } @@ -334,6 +365,661 @@ if (!/Substitution refused/i.test(cockpit)) fail('cockpit does not refuse substi if (model.reserved.metricsWithheld.length === 0) fail('HAC-319 metrics are not declared withheld'); if (/\b(SPR|precision|recall)\s*[:=]\s*[\d.]/.test(cockpit)) fail('cockpit renders a HAC-319 metric value'); + +/* --- the guided layer may only move emphasis ---------------------------- */ + +/** + * The walk is an attention layer. Everything below refuses the ways it could + * quietly stop being one: by hiding the cockpit behind it, by advancing on its + * own, by recomputing a value, or by claiming something about the ablation that + * the frozen arms do not support. + */ + +// The vocabulary is one list, declared once. A cockpit that routed a state the +// model does not declare would be addressable but unverifiable. +if (!Array.isArray(model.guide?.states)) fail('the view model declares no guided state vocabulary'); +else { + for (const id of GUIDE_STATES) { + if (!model.guide.states.includes(id)) fail(`guided state ${id} is not declared in the view model`); + } + for (const id of model.guide.states) { + if (!guideRoute(id)) fail(`the view model declares ${id}, which the router refuses`); + } +} +for (const id of GUIDE_STATES) { + if (!id.startsWith('guide.local.')) fail(`guided state ${id} is not namespaced to the local proof class`); +} +if (GUIDE_STEPS.length !== 6) fail(`the walk has ${GUIDE_STEPS.length} steps; the approved sequence has six`); +if (model.deepLink.unknownGuideState !== 'run.missing') fail('an unknown guided state is not refused'); +if (model.deepLink.guideUnderWrongProofClass !== 'run.missing') { + fail('a guided state under the wrong proof class is not refused'); +} +if (!/guideRoute\(/.test(cockpit)) fail('cockpit does not route the guided axis through the shared derivation'); +if (!/return \{ missing: `guide=/.test(cockpit)) fail('cockpit does not refuse an unknown guided address'); +if (!/return \{ missing: `proof=\$\{proof\}&guide=/.test(cockpit)) { + fail('cockpit does not refuse a guided address under the wrong proof class'); +} + +/** + * The ablation's markers are the load-bearing claim of step 5: four things were + * held constant and four changed. `ablationDelta` reads both frozen arms and + * reports what actually moved, so this fails if a marker has stopped being true + * — including if the frozen arms are edited so that the perturbation no longer + * perturbs anything. + */ +const delta = ablationDelta(local); +if (!delta.truthful) { + for (const row of delta.rows) { + const truthful = row.kind === 'held' ? !row.differs : row.differs; + if (!truthful) { + fail(row.kind === 'held' + ? `the ablation marks ${row.id} held constant, but it changes between the ${delta.fromArmId} and ${delta.toArmId} arms` + : `the ablation marks ${row.id} changed, but it is identical in the ${delta.fromArmId} and ${delta.toArmId} arms`); + } + } +} +for (const id of ['intent.a', 'intent.b', 'environment', 'bound']) { + if (!delta.held.some((r) => r.id === id)) fail(`the ablation does not hold ${id} constant`); +} +// "Held constant: the joint bound" is only true if every arm was judged against +// the constraint the run declares. Reading it off the arms rather than off the +// constraint is what makes the marker falsifiable. +const declaredBound = local.constraints?.[0]?.bound; +for (const arm of local.arms) { + if (arm.outcome.bound !== declaredBound) { + fail(`arm ${arm.armId} was judged against bound ${arm.outcome.bound}, not the declared ${declaredBound}`); + } +} +for (const id of ['evidence.basis', 'evidence.finding', 'decision', 'outcome']) { + if (!delta.changed.some((r) => r.id === id)) fail(`the ablation does not report ${id} as changed`); +} +// A marker is drawn only where the derivation supports it. +if (!/function marker\(/.test(cockpit) || !/ablationDelta|g\.delta\.rows/.test(cockpit)) { + fail('cockpit draws held/changed markers without consulting the ablation derivation'); +} +// The ablation selects a recorded arm. It must not edit, delete or recompute one. +if (/data-guide-ablate/.test(cockpit) && !/go\(\{ state: `run\.local\.\$\{t\.dataset\.guideAblate\}` \}\)/.test(cockpit)) { + fail('the ablation control does something other than select a recorded arm'); +} + +// The walk never advances on its own: no timer may change the step or the arm. +for (const re of [/setInterval\s*\(/, /setTimeout\s*\([^)]*goStep/, /setTimeout\s*\([^)]*guideView/, /requestAnimationFrame\s*\([^)]*goStep/]) { + if (re.test(cockpit)) fail('the walk can advance without the reader; no step may auto-advance'); +} + +/** + * Emphasis may not be paid for out of legibility. + * + * The first implementation receded non-current stages with `opacity`, which + * composites *text* toward the background and multiplies through every ancestor + * that also sets it. Measured, labels already muted at 0.6 inside a row at 0.7 + * inside a stage at 0.62 rendered at 0.26 and 1.81:1. These refuse the whole + * mechanism rather than a particular value of it: no `opacity` and no `filter` + * may appear in any `[data-guide-em]` rule, at any value, ever. + */ +for (const rule of cockpit.match(/\[data-guide-em[^\]]*\][^{]*\{[^}]*\}/g) ?? []) { + if (/(^|[^-\w])opacity\s*:/.test(rule)) { + fail(`a guided-emphasis rule sets opacity, which composites text below the contrast floor: ${rule.slice(0, 80)}`); + } + if (/filter\s*:/.test(rule)) { + fail(`a guided-emphasis rule sets a filter, which alters rendered text colour: ${rule.slice(0, 80)}`); + } + if (/display\s*:\s*none|visibility\s*:\s*hidden|pointer-events\s*:\s*none/.test(rule)) { + fail(`a non-current stage is hidden or made unreachable: ${rule.slice(0, 80)}`); + } + // Emphasis is colour, background and shadow. Anything that participates in + // layout would move the run as the step advances. + if (/(^|;|\{)\s*(width|height|padding|margin|border-width|border-left-width|font-size|inset|top|left|right|bottom|transform)\s*:/.test(rule)) { + fail(`a guided-emphasis rule changes layout, which shifts the run between steps: ${rule.slice(0, 80)}`); + } +} +/** + * Guided copy may not out-run the record. + * + * Every value on this surface is derived except one: step 5's copy names + * `ALLOW_PARALLEL` in prose, because the approved sequence states it. It is + * true today and nothing enforced that it stays true — if the frozen perturbed + * arm's decision ever changed, the copy would quietly lie while every binding + * check still passed. Copy may name a decision only if a frozen arm records it. + */ +const recordedDecisions = new Set(local.arms.map((a) => a.decision).filter(Boolean)); +const DECISION_TOKEN = /\b(WITHHOLD_SERIALIZE|ALLOW_PARALLEL|ALLOW_SERIALIZED|INSUFFICIENT_EVIDENCE)\b/g; +for (const step of GUIDE_STEPS) { + for (const [, token] of step.copy.matchAll(DECISION_TOKEN)) { + if (!recordedDecisions.has(token)) { + fail(`guided copy for ${step.stateId} names "${token}", which no frozen arm records`); + } + } +} + +// A guided state id may not be stamped on the cloud proof class. +if (/dataset\.guideState = GUIDE_CHOICE_STATE/.test(cockpit)) { + fail('the cloud render stamps a local-namespaced guided state id on the document'); +} + +// The mechanism must be positive: the current stage is marked up. +if (!/\[data-guide-em="focus"\]/.test(cockpit)) fail('no positive emphasis exists for the current stage'); +if (/data-guide-em="recede"/.test(cockpit)) fail('the opacity-based recession is still present'); +if (/--il-recede/.test(cockpit)) fail('the recession opacity token survives; emphasis must not be opacity'); + +/** + * No text is muted with opacity anywhere on this surface. + * + * Opacity cannot be measured once and trusted: it depends on every ancestor and + * on whatever happens to be behind. Hierarchy is carried by the two measured + * colour tiers instead. Rules that dim a rule, a stroke, a decorative grid or a + * disabled control are unaffected — none of them are readable text. + */ +/** + * Strokes, rules and a decorative grid — none of them readable text. + * + * `:disabled` controls are deliberately *not* on this list. WCAG 1.4.3 exempts + * an inactive component, so dimming the Back button on step one is permitted — + * but it is text, and calling it "not text" to keep a blanket claim tidy is how + * the next real offender gets waved through. It is named separately below so + * the exemption is visible as an exemption. + */ +const NON_TEXT_OPACITY = new Set([ + '.cxn .e-intent', '.cxn .e-couple', '.res .cause .cxl', '.evidence-band::before', + '.hops::before', + // The emphasised connector: the same two strokes, restored to full. + '.cxn[data-guide-edge="on"] .e-intent', '.cxn[data-guide-edge="on"] .e-couple', +]); +/** Text, but on an inactive control, which 1.4.3 exempts from the floor. */ +const INACTIVE_CONTROL_OPACITY = new Set(['.guide-bar__controls button[disabled]']); +// Comments are stripped first: a comment sitting above a rule would otherwise +// be read as part of its selector. +const styleBlock = (/