Files
qwen-code/scripts/review-audit-layers.mts
Shaojin Wen 187637449b feat(review): cover modeled-system defect layers in the reverse audit (#8956)
* feat(review): cover modeled-system defect layers in the reverse audit

A diff that models how an external system executes — a shell/git guard, a
sandbox, a permission interpreter — has a defect class the fixed dimensions do
not name: divergence between the model and the real system's STATE semantics
(what survives a function/eval/subshell/substitution boundary), not just its
syntax. That class is non-local and needs a differential oracle, so a static
single-model pass under-covers it, and the reverse audit's "two dry rounds"
stop rule is silent about any layer nobody walked.

Add a defect-layer lens across three sides, each independently revertible:

- Finder: the security pass gains a model-of-execution divergence hunt (run the
  real system as an oracle to discover it), and the invariant checklist gains a
  recursive-evaluator state-return contract for the cross-chunk half.
- Coverage: the reverse-audit brief asks each defect layer be walked and
  receipted on its own line; a taxonomy parses those receipts into per-layer
  coverage, and a standalone command reports it for A/B measurement.
- Cap: a deterministic gate emits one unreviewed-dimension entry per unwalked
  layer, capping a would-be Approve. It is opt-in via a repository-context
  domain, model out of the loop, and one-directional — it never ends the loop,
  blocks a Request changes, or changes convergence.

Extending the convergence rule itself so an unwalked layer keeps the loop
running is deferred behind an A/B on real modeled-system PRs.

* fix(review): fail-open the layer gate on a missing transcript dir

Address the review on this PR.

- Blocker: the gate's transcript read wrapped only the plan stat, not
  readTranscripts, which throws when the transcript directory is absent — so a
  manifest-marked diff in a transcript-less environment (a sandbox, a read-only
  HOME, a re-compose on a clean machine) crashed compose and posted nothing,
  contradicting the gate's own fail-open header. Wrap the whole read; add a test
  that exercises the real reader against a missing dir, which the injected-reader
  tests could not reach.

- Scope the automated cap to the shell/git model honestly: the brief and header
  promised a manifest-declared layer taxonomy for non-shell modeled systems that
  no channel supplies, so arming the sentinel on such a diff would owe the shell
  layers forever. Narrow the prose to what ships and name the manifest-taxonomy
  wiring as the follow-up that lifts the limit.

- Prefix the owed entry `reverse-audit layer coverage — ` so compose-review's
  caller-echo dedup cannot shadow the per-layer disclosures behind a `reverse
  audit` coverage subject; the verdict cap was already unaffected.

- Use the depended `glob` package in the measurement script instead of the
  experimental node:fs/promises glob.

* fix(review): corroborate and identity-anchor the layer-coverage reader

Address the second review round. The gate measured coverage from auditor prose
too readily, so a modeled-system diff with unwalked layers could release Approve
— the exact failure this feature prevents. Three probe-verified holes, all
closed, plus an end-to-end test that pins the cap through the real reader.

- Corroborate before a receipt counts: a transcript's receipts are read only
  when the harness's tool-call record shows it actually read the diff
  (diffToolCalls > 0, retirement's bar). A brief-only parrot holds every layer
  id from its own brief and can emit all six receipts without walking a layer;
  it has diffToolCalls === 0 and is dropped. successfulToolCalls > 0 would not
  drop it — the brief read is a successful call.

- Anchor the auditor selector on the launch IDENTITY line rather than a bare
  `reverse-audit` substring, which counted any transcript merely mentioning the
  role — a verifier inlining reverse-audit findings and quoting their receipt
  lines, a nested subagent — and pulled its finalText into the pool.

- Harden the receipt parser: a marker inside an inline code span or an indented
  code block is quoted, not used, and no longer parses as a live receipt. Allow
  a digit in a layer id so a custom taxonomy is not silently truncated.

- Refresh the now-stale module header (the cap ships, it is no longer "the next
  increment"), scope the arming docs to the shell/git layer set, and qualify the
  3B coverage claim (invariant-c runs only on heavy files; the cross-chunk
  contract backstops on the reverse-audit receipts).

The new compose-review cases exercise the real reader end to end: a partial-walk
auditor caps Approve to Comment, a full walk stays Approve, and neither a
diff-blind parrot nor a mis-identified verifier is counted.

* fix(review): track fences the CommonMark way in the receipt parser

Address the third review round.

- Critical: the receipt parser's symmetric fence toggle diverged from CommonMark
  three probe-verified ways, each releasing a QUOTED `Layer walked:` marker as a
  live receipt — a mismatched fence line (`~~~` inside a ``` block, or a shorter
  run) closed early, a list-item fence never opened, and a fence line with
  trailing content closed a block GitHub keeps open. Replace it with fence
  tracking that records the opening character and length, opens generously (0-3
  spaces, optional list prefix) and closes strictly (same char, >= length,
  whitespace only), biased toward skipping. The shared `usedLines` walk now backs
  both the parser and the `--infer` estimate, so neither credits a layer from
  quoted text.

- Correct the docs that overclaimed 3B coverage: Agent 2 does not run on a
  territory fan-out and the chunk agents do not inherit its brief, so the
  execution-model lens is 3A-only; on a huge diff the reverse-audit receipts and
  the cap carry the class, with invariant-c a heavy-file backstop.

- Fix the module header (coverage is the receipt, not a bare finding) and the
  test name that echoed it.

- Pin the gate's identity anchor against the real launch-prompt builder, so
  rewording the header cannot silently stop the gate selecting an auditor. Allow
  a digit in a layer id.

Deferred (non-blocking test gaps, noted in the thread): a test for the run-epoch
mtime fence, and extracting the measurement script's round-sort for a unit test.

* feat(review): carry the execution-model lens into 3B chunk agents

Two gaps a modeled-system review left open, both surfaced by the ongoing review
of the cross-worktree guard.

- The finder-side execution-model lens ran only on a 3A dimension fan-out; on a
  3B territory fan-out Agent 2 does not run and the chunk agents did not inherit
  its brief, so a huge guard/interpreter diff — the band this class lives in —
  got no finder coverage. Extract the lens into one exported constant and attach
  it to each chunk agent when the manifest declares the diff a modeled executable
  system, scoped to the chunk. Agent 2 still carries the same constant on 3A, so
  there is one source for both topologies. The cross-chunk contract still falls
  to the reverse-audit receipts and invariant-c.

- The state layers named only the ESTABLISH side of shell state. A model that
  grows an add-only map of function/alias definitions, export attributes, or
  options and never removes an entry diverges the moment the real shell removes
  one (`unset -f`, `unalias`, `export -n -f`, `set +a`). Name the removal side in
  the resolution-order and inheritance layer hints, in the lens's second bug
  shape, in the reverse-audit walk, and in invariant-a's collection check, so an
  auditor is led to check the removal path for every add path — the exact class a
  reviewer found when the guard's `definedBodies` map gained entries but modeled
  no removal.

* fix(review): close the layer gate's corroboration and fence leaks

Address the fourth review round — three release-direction gaps and cleanups.

- Critical: the receipt parser's fence tracker closed a list-item fence at any
  0-3 space indent, so a shallower closer released the quoted markers after it.
  Record the opener's indent (its content column) and close only at that column
  or up to three past it; a shallower or unrecognised closer keeps the fence
  open, biasing every remaining indent corner toward skipping.

- The corroboration bar was range-blind: `diffToolCalls > 0` passed an auditor
  that read a far chunk and then parroted its receipts. Add retirement's other
  half — the diff read must overlap the territory the launch prompt baked
  (`openedTheTerritory` + `bakedRanges`, now exported); a whole-diff auditor
  bakes none and still passes on the read floor.

- The empty-pool branch deferred to the reverse-audit-ran floor, but that floor
  has no diff-read requirement — so a run whose auditors all ran yet none read
  the diff went uncapped. Distinguish "could not measure" (fail-open) from
  "measured: auditors ran, none corroborated" (owe every layer).

- Cleanups: rehome the parseLayerReceipts JSDoc that had stranded above the
  fence helpers, drop the unused exported `coveredBy`, and pin the invariant-a
  removal clause with a test.

* fix(review): locate quoted regions with a real CommonMark parser

The receipt parser's hand-rolled fence/quote scanner diverged from CommonMark
round after round — each review pass probed another corner (mismatched fences,
list-item containers, trailing content, tab stops, HTML blocks, nested
blockquotes), and each gap released a quoted `Layer walked:` marker as a live
receipt. A second parser is a divergence hunt, and this skill's own rule is that
the oracle must come from the authority the code models, not a self-consistent
re-implementation.

Replace the scanner with `markdown-it` (already a workspace dependency, the
parser GitHub's own family uses): tokenize the return and treat every line inside
a fenced/indented code block, an HTML block, or a blockquote as quoted, reading
the block tokens' own line ranges. The receipt regex still guards inline
code spans (no leading backtick). This ends the divergence class outright —
HTML blocks, tab-indented code and nested blockquotes now quote their markers
with no new code, and the obsolete hand-rolled-closer test (which pinned a
divergent expectation) is replaced by cases verified against the parser.

Also (R4-5): stop attaching the modeled-system lens to an unreachable chunk,
whose one instruction is to return `Uncoverable:` and stop.
2026-08-12 18:15:11 +00:00

155 lines
5.1 KiB
TypeScript

/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
// Measure defect-LAYER coverage of a reverse-audit run — the A/B instrument for
// the layer-aware convergence change.
//
// The reverse audit converges on "two dry rounds": no auditor found a new gap.
// This script answers the question that stop rule cannot — *which defect layers
// did the auditors actually walk* — so you can compare a run before the auditor
// brief asked for `Layer walked:` receipts against one after, and see the deep
// layers (scope-propagation, resolution-order, inheritance) go from uncovered to
// covered. On PR #8687 the token layer filled every round while the state layer
// went untouched; this is what makes that visible instead of hidden behind a
// green "converged".
//
// Usage:
// tsx scripts/review-audit-layers.mts <file-or-glob> [<file> ...] [--infer]
//
// Each argument is a text file (or a glob) holding one auditor's final return
// — dump the reverse-audit agents' returns to files, one per agent, named so
// the round is recoverable (e.g. round-1-chunk-3.txt). One JSON file whose
// top level is an array of {round?, chunk?, text} objects also works.
//
// --infer turn on the keyword estimate for MARKER-LESS baseline transcripts
// (a run recorded before the brief change emits no `Layer walked:`
// lines, so structured-only coverage reads as zero — the estimate is
// the best baseline signal, approximate by construction).
//
// Reads nothing but the files you pass; it never touches a PR or the network.
import { readFileSync } from 'node:fs';
import { basename } from 'node:path';
import { glob } from 'glob';
import {
SHELL_MODEL_LAYERS,
layerCoverage,
} from '../packages/cli/src/commands/review/lib/audit-layers.ts';
interface Return {
round: number | null;
label: string;
text: string;
}
function roundOf(name: string): number | null {
const m = /round[-_ ]?(\d+)/i.exec(name);
return m ? Number(m[1]) : null;
}
async function collectPaths(args: string[]): Promise<string[]> {
const out: string[] = [];
for (const arg of args) {
if (/[*?[\]]/.test(arg)) {
out.push(...(await glob(arg)));
} else {
out.push(arg);
}
}
return out;
}
function loadReturns(paths: string[]): Return[] {
const returns: Return[] = [];
for (const path of paths) {
const raw = readFileSync(path, 'utf8');
if (path.endsWith('.json')) {
const parsed = JSON.parse(raw) as unknown;
if (Array.isArray(parsed)) {
parsed.forEach((e, i) => {
const o = (e ?? {}) as Record<string, unknown>;
returns.push({
round: typeof o.round === 'number' ? o.round : null,
label:
typeof o.chunk === 'number'
? `${basename(path)}#${o.chunk}`
: `${basename(path)}#${i}`,
text: typeof o.text === 'string' ? o.text : '',
});
});
continue;
}
}
returns.push({
round: roundOf(basename(path)),
label: basename(path),
text: raw,
});
}
return returns;
}
function report(returns: Return[], keywordFallback: boolean): void {
const texts = returns.map((r) => r.text);
const overall = layerCoverage(texts, { keywordFallback });
// Group by round so you can watch a layer stay uncovered across the loop.
const rounds = [...new Set(returns.map((r) => r.round))].sort((a, b) => {
if (a === null) return 1;
if (b === null) return -1;
return a - b;
});
process.stdout.write(
`\nDefect-layer coverage — ${returns.length} auditor return(s), ` +
`${keywordFallback ? 'keyword estimate ON' : 'structured receipts only'}\n\n`,
);
for (const round of rounds) {
const inRound = returns.filter((r) => r.round === round);
const cov = layerCoverage(
inRound.map((r) => r.text),
{ keywordFallback },
);
const label = round === null ? 'unlabelled' : `round ${round}`;
const covered = SHELL_MODEL_LAYERS.filter((l) => cov.covered[l.id]).map(
(l) => l.id,
);
process.stdout.write(
` ${label.padEnd(12)} covered: ${covered.join(', ') || '(none)'}\n`,
);
}
process.stdout.write('\n Whole run:\n');
for (const layer of SHELL_MODEL_LAYERS) {
const mark = overall.covered[layer.id] ? '✓' : '✗ OWED';
process.stdout.write(` [${mark}] ${layer.id} — ${layer.label}\n`);
}
if (overall.uncovered.length > 0) {
process.stdout.write(
`\n ${overall.uncovered.length} layer(s) never walked: ` +
`${overall.uncovered.join(', ')}\n` +
` A "two dry rounds" stop would certify the diff with these unreviewed.\n`,
);
} else {
process.stdout.write('\n Every layer was walked.\n');
}
}
async function main(): Promise<void> {
const argv = process.argv.slice(2);
const keywordFallback = argv.includes('--infer');
const paths = await collectPaths(argv.filter((a) => a !== '--infer'));
if (paths.length === 0) {
process.stderr.write(
'usage: tsx scripts/review-audit-layers.mts <file-or-glob> [...] [--infer]\n',
);
process.exit(2);
}
report(loadReturns(paths), keywordFallback);
}
await main();