mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
* 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.
155 lines
5.1 KiB
TypeScript
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();
|