* chore(#1892): delete orphan verify-phase workflow, migrate live gates to verifier reference * test(#1892): retarget structural suites from verify-phase.md to verifier-phase-gates.md * chore(#1892): reword retired-workflow mentions for removed-but-needed lint * test(#1892): correct stale surface labels in retargeted suites * docs(#1892): add verifier-phase-gates row to locale inventories * chore(#3421): backfill changeset pr number --------- Co-authored-by: sim <sim@local>
288 lines
13 KiB
JavaScript
288 lines
13 KiB
JavaScript
// allow-test-rule: source-text-is-the-product (see #1955)
|
|
// allow-test-rule: docs-parity (see #1955)
|
|
// Two categories from CONTRIBUTING.md's exception matrix, deliberately both:
|
|
// source-text-is-the-product — agents/gsd-verifier.md and
|
|
// gsd-core/templates/verification-report.md are shipped .md whose text IS
|
|
// what the runtime loads, so asserting on the text asserts the deployed
|
|
// contract. Same category tests/verifier-behavior-unverified.test.cjs uses
|
|
// for the sibling #966 axis.
|
|
// docs-parity — the single docs/AGENTS.md assertion is not runtime-loaded
|
|
// text; it holds human documentation in sync with the shipped contract,
|
|
// which has no runtime enumeration API. Claiming the first category for it
|
|
// would overstate what that category covers.
|
|
|
|
'use strict';
|
|
|
|
/**
|
|
* Issue #1955 — the verifier grades THAT a truth holds, never WHY.
|
|
*
|
|
* This suite locks the `coincidental-reliance` advisory: for a truth that
|
|
* already reached ✓ VERIFIED, the verifier additionally classifies whether it
|
|
* holds for a guaranteed reason or an incidental one, and names the incidental
|
|
* ones so they can be hardened instead of shipping invisibly.
|
|
*
|
|
* The load-bearing assertions are the INVARIANTS, not the feature:
|
|
* - the advisory changes neither the verified score nor the overall status;
|
|
* - it never emits a human-verification item (Step 9 rule 2 would escalate a
|
|
* passing phase to `human_needed`, contradicting "the phase can still pass");
|
|
* - ⚠️ PRESENT_BEHAVIOR_UNVERIFIED (#966) stays the ONLY truth state excluded
|
|
* from `verified_truths`;
|
|
* - the per-truth token never enters the overall-status vocabulary;
|
|
* - the qualifier SUFFIXES `✓ VERIFIED` rather than replacing it, so every
|
|
* substring matcher on the old verdict still hits (Hyrum's Law).
|
|
*
|
|
* Deliberately absent: any assertion on the model's verdict. ADR-550 Decision 5
|
|
* rejects that as vacuous — the CI-testable surface is the deterministic
|
|
* contract, which for a prose rubric is the shipped text.
|
|
*/
|
|
|
|
const { test, describe } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const ROOT = path.join(__dirname, '..');
|
|
|
|
// Canonical sources of truth (CONTRIBUTING.md "Source of truth for agents") —
|
|
// never .claude/agents/, which is a gitignored install-sync output and would
|
|
// false-pass against a stale local sync.
|
|
const read = (...p) => fs.readFileSync(path.join(ROOT, ...p), 'utf-8');
|
|
|
|
const verifier = read('agents', 'gsd-verifier.md');
|
|
const template = read('gsd-core', 'templates', 'verification-report.md');
|
|
const agentsDoc = read('docs', 'AGENTS.md');
|
|
const verifyPhase = read('gsd-core', 'references', 'verifier-phase-gates.md');
|
|
|
|
const QUALIFIER = '✓ VERIFIED (coincidental-reliance)';
|
|
const REASONS = ['undeclared-precondition', 'incidental-ordering', 'fixture-only'];
|
|
|
|
/**
|
|
* Return a window of `source` centred on the first occurrence of `needle`,
|
|
* extending `radius` characters either side. Used to assert that a clause sits
|
|
* WITH the rule rather than anywhere in a 47 KB file — a bare
|
|
* `assert.match(verifier, /score/)` would pass on unrelated prose.
|
|
*/
|
|
function sectionAround(source, needle, radius) {
|
|
const at = source.indexOf(needle);
|
|
assert.notEqual(at, -1, `expected to find '${needle}' in the source`);
|
|
return source.slice(Math.max(0, at - radius), at + needle.length + radius);
|
|
}
|
|
|
|
describe('#1955: coincidental-reliance advisory — the rule', () => {
|
|
test('defines the coincidental-reliance advisory', () => {
|
|
assert.match(verifier, /coincidental-reliance/);
|
|
});
|
|
|
|
test('names the three reliance triggers', () => {
|
|
for (const reason of REASONS) {
|
|
assert.ok(
|
|
verifier.includes(reason),
|
|
`verifier must name the '${reason}' trigger`,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('advisory applies only to truths that reached VERIFIED', () => {
|
|
// The advisory explains why a PASSING truth passes. A truth that never
|
|
// reached ✓ VERIFIED has nothing to explain. Asserted on the window around
|
|
// the rule rather than a forward-only regex: the scoping clause precedes
|
|
// the first `coincidental-reliance` token, so `token[\s\S]{0,N}?✓ VERIFIED`
|
|
// structurally cannot see it.
|
|
const window = sectionAround(verifier, 'coincidental-reliance', 1400);
|
|
assert.match(window, /✓ VERIFIED truth/);
|
|
});
|
|
|
|
test('does not double-report truths already routed to human verification', () => {
|
|
// PRESENT_BEHAVIOR_UNVERIFIED (#966) and insufficient_spec (honest-verifier)
|
|
// already route to human_needed; re-flagging them is noise.
|
|
const window = sectionAround(verifier, 'coincidental-reliance', 1400);
|
|
assert.match(window, /PRESENT_BEHAVIOR_UNVERIFIED/);
|
|
assert.match(window, /insufficient_spec/);
|
|
});
|
|
|
|
test('an accepted override is not re-flagged', () => {
|
|
const window = sectionAround(verifier, 'coincidental-reliance', 1400);
|
|
assert.match(window, /override/i);
|
|
});
|
|
|
|
test('carries a do-not-flag list bounding the false-positive surface', () => {
|
|
const window = sectionAround(verifier, 'coincidental-reliance', 1400);
|
|
assert.match(window, /(do not flag|don't flag|never flag)/i);
|
|
// The negative space must name at least the two cases that most look like
|
|
// the trigger and are not: code-established preconditions and enforced
|
|
// ordering.
|
|
assert.match(window, /(enforce|await|explicit sequenc)/i);
|
|
assert.match(window, /default/i);
|
|
});
|
|
|
|
test('rule routes on recorded evidence, not self-rated confidence', () => {
|
|
// The .out-of-scope/general-purpose-agent-prompt-skills.md reason-4 bar:
|
|
// self-rated confidence is measured weak (honest-verifier.md:25-29). The
|
|
// shipped prose must carry that constraint, not just the design doc.
|
|
const window = sectionAround(verifier, 'coincidental-reliance', 1400);
|
|
assert.match(window, /honest-verifier/);
|
|
assert.match(window, /confiden/i);
|
|
});
|
|
});
|
|
|
|
describe('#1955: coincidental-reliance advisory — the invariants', () => {
|
|
test('advisory does not change the verified score', () => {
|
|
const window = sectionAround(verifier, 'coincidental-reliance', 1400);
|
|
assert.match(window, /score/i);
|
|
assert.match(window, /(not the score|never the score|does not (change|affect) the score|no score)/i);
|
|
});
|
|
|
|
test('advisory does not change the overall status', () => {
|
|
const window = sectionAround(verifier, 'coincidental-reliance', 1400);
|
|
assert.match(window, /(not the status|never the status|does not (change|affect) the status|status is unchanged)/i);
|
|
});
|
|
|
|
test('advisory never emits a human-verification item', () => {
|
|
// Step 9 rule 2: ANY human-verification item forces `human_needed`. An
|
|
// advisory that emitted one would stop a passing phase from passing, which
|
|
// is precisely what issue #1955 says must NOT happen.
|
|
const window = sectionAround(verifier, 'coincidental-reliance', 1400);
|
|
assert.match(window, /never[^.]{0,80}human-verification item/i);
|
|
});
|
|
|
|
test('PRESENT_BEHAVIOR_UNVERIFIED remains the only score-excluded truth state', () => {
|
|
// #966's invariant, asserted here so #1955 cannot silently erode it.
|
|
assert.match(
|
|
verifier,
|
|
/PRESENT_BEHAVIOR_UNVERIFIED truths are the \*only\* ones excluded from `verified_truths`/,
|
|
);
|
|
});
|
|
|
|
test('PARITY: the per-truth advisory never leaks into the status vocabulary', () => {
|
|
const unionLines = verifier.match(/^status:\s+[a-z_]+(?:\s*\|\s*[a-z_]+)+\s*$/gm) || [];
|
|
assert.ok(unionLines.length > 0, 'expected at least one status union line');
|
|
for (const line of unionLines) {
|
|
assert.doesNotMatch(line, /coincidental/i);
|
|
for (const s of ['passed', 'gaps_found', 'human_needed']) {
|
|
assert.ok(line.includes(s), `status union must still contain ${s}: ${line}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('overall-status enum in verification.cts is unchanged', () => {
|
|
const cts = read('src', 'verification.cts');
|
|
const m = cts.match(/VERIFIER_STATUSES[^=]*=\s*\[([^\]]*)\]/);
|
|
assert.ok(m, 'VERIFIER_STATUSES array must be present');
|
|
assert.doesNotMatch(m[1], /coincidental/i);
|
|
for (const s of ['passed', 'gaps_found', 'human_needed']) {
|
|
assert.match(m[1], new RegExp(`'${s}'`));
|
|
}
|
|
});
|
|
|
|
test('qualifier suffixes VERIFIED, never replaces it (Hyrum)', () => {
|
|
// Every rendered verdict carrying the qualifier must keep the `✓ VERIFIED`
|
|
// token verbatim and leading, so a consumer matching the old verdict as a
|
|
// substring still hits. A bare `(coincidental-reliance)` verdict cell, or
|
|
// any form that puts the qualifier before the token, is a break.
|
|
for (const source of [verifier, template]) {
|
|
const verdictCells = source.match(/\|\s*[^|\n]*coincidental-reliance[^|\n]*\|/g) || [];
|
|
for (const cell of verdictCells) {
|
|
assert.ok(
|
|
cell.includes(QUALIFIER),
|
|
`verdict cell must render as "${QUALIFIER}": ${cell.trim()}`,
|
|
);
|
|
}
|
|
}
|
|
assert.ok(
|
|
verifier.includes(QUALIFIER),
|
|
'the agent must show the suffixed verdict form at least once',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('#1955: coincidental-reliance advisory — the report surface', () => {
|
|
test('report frontmatter carries coincidental_reliance_items', () => {
|
|
assert.match(verifier, /coincidental_reliance_items/);
|
|
});
|
|
|
|
test('items list is omitted when nothing is flagged', () => {
|
|
// 0-flag boundary: the key follows the `overrides:` / `deferred:` "only if"
|
|
// convention rather than emitting an empty array.
|
|
assert.match(verifier, /coincidental_reliance_items:[^\n]*(only if|Only if)/);
|
|
});
|
|
|
|
test('item shape names the truth, the reason, and the hardening', () => {
|
|
// 1-flag boundary: an advisory that names no fix is not actionable.
|
|
// `coincidental_reliance_items: #` anchors the FRONTMATTER block. The bare
|
|
// key also appears earlier in the rule prose, and a window around that
|
|
// occurrence contains none of the item fields.
|
|
const window = sectionAround(verifier, 'coincidental_reliance_items: #', 500);
|
|
assert.match(window, /truth:/);
|
|
assert.match(window, /reason:/);
|
|
assert.match(window, /(harden|fix|precondition)/i);
|
|
});
|
|
|
|
test('items survive a gaps_found phase', () => {
|
|
// many-flag / mixed boundary: same survival rule behavior_unverified_items
|
|
// carries, so an advisory is not lost when the phase also has gaps.
|
|
const window = sectionAround(verifier, 'coincidental_reliance_items: #', 500);
|
|
assert.match(window, /(regardless of (overall )?status|survive|never lost)/i);
|
|
});
|
|
});
|
|
|
|
describe('#1955: cross-surface parity (agent, template, verifier gate reference)', () => {
|
|
test('PARITY: agent and standalone template agree on the advisory vocabulary', () => {
|
|
// Generative-fix-divergence gate: two surfaces render the same report, so a
|
|
// token added to one and not the other is the defect this test exists for.
|
|
for (const token of ['coincidental-reliance', ...REASONS]) {
|
|
assert.ok(verifier.includes(token), `agent must carry '${token}'`);
|
|
assert.ok(template.includes(token), `standalone template must carry '${token}'`);
|
|
}
|
|
assert.ok(
|
|
template.includes('coincidental_reliance_items'),
|
|
'standalone template must carry the frontmatter items list',
|
|
);
|
|
});
|
|
|
|
test('template guidelines document the advisory per-truth state', () => {
|
|
const guidelines = template.slice(template.indexOf('**Per-truth states'));
|
|
assert.ok(guidelines.length > 0, 'template must keep its per-truth states guideline');
|
|
assert.match(guidelines, /coincidental-reliance/);
|
|
});
|
|
|
|
test('verifier gate reference reaches the rule through the canonical template', () => {
|
|
// The third surface. `gsd-core/references/verifier-phase-gates.md` is the
|
|
// verifier agent's eagerly-imported gate reference (migrated from the
|
|
// retired workflows/verify-phase.md in #1892). It does NOT reimplement the
|
|
// truth rubric — the rule is NOT duplicated into it. It reaches the rule
|
|
// instead through its pointer to the canonical report template, whose
|
|
// Guidelines carry the instruction — not merely the output shape. Both
|
|
// halves of that claim are asserted here, because either one silently
|
|
// failing turns the reference surface into an undetected divergence.
|
|
assert.match(
|
|
verifyPhase,
|
|
/@[^\n]*gsd-core\/templates\/verification-report\.md/,
|
|
'verifier-phase-gates.md must point at the verification-report template',
|
|
);
|
|
const guidelines = template.slice(template.indexOf('**Per-truth states'));
|
|
assert.match(
|
|
guidelines,
|
|
/apply the reliance check to every `✓ VERIFIED` truth/i,
|
|
'the template Guidelines must carry the imperative check, not just the row shape',
|
|
);
|
|
});
|
|
|
|
test('the workflow surface carries no divergent copy of the rule', () => {
|
|
// Characterization, not aspiration: verifier-phase-gates.md deliberately
|
|
// holds NO copy of the detection prose today. If a future change adds one,
|
|
// this assertion fails and forces a decision — duplicate it deliberately
|
|
// and update this test, or keep the single template-carried source. Silent
|
|
// partial duplication across the surfaces is the failure mode
|
|
// (generative fix divergence) this locks out.
|
|
assert.doesNotMatch(
|
|
verifyPhase,
|
|
/coincidental-reliance/,
|
|
'verifier-phase-gates.md must not grow a second copy of the rule without a deliberate decision',
|
|
);
|
|
});
|
|
|
|
test('docs/AGENTS.md documents the coincidental-reliance advisory', () => {
|
|
assert.match(agentsDoc, /coincidental-reliance/);
|
|
});
|
|
});
|