enhance(#3913): docs, and the guards come down (#3994)

ADR-3889 terminal phase. Generated docs/reference/exit-codes.md from the exit-code declaration with a --check drift arm; deleted the inert soft-error-exit-zero oracle; promoted untyped-success from SMELL to VIOLATION so it can fail a build; pruned all 5 smell-baseline entries.

Fixed inline: two mis-scoped oracles (routing-validity, value-hygiene), a second source behind the band table, unescaped declaration strings reaching Markdown, and a pre-existing Windows 8.3 short-name path-comparison defect.

Guard ledger corrected from a claimed net -4 to a measured net -1.

Closes #3913
This commit is contained in:
Tom Boucher
2026-08-28 13:10:20 -04:00
committed by GitHub
parent c3e667df34
commit 12f9d1d9a0
16 changed files with 1517 additions and 152 deletions

View File

@@ -65,6 +65,12 @@
const nodePath = require('node:path');
const { KIND } = require('./result.cjs');
const { resolveForCompare, isUnderProjectDir } = require('./paths.cjs');
const {
LIVE_COMMAND_TOKEN_PREFIXES,
getLiveCommandTokens,
firstToken,
isLiveCommandToken,
} = require('../helpers/live-command-registry.cjs');
/** Severity of a failed oracle outcome. A SMELL is evidence, not a verdict — it never fails a build. */
const SEVERITY = Object.freeze({ VIOLATION: 'violation', SMELL: 'smell' });
@@ -92,6 +98,20 @@ const SENTINEL_STRINGS = new Set(['undefined', 'null', 'NaN', '[object Object]']
*/
const EXTERNAL_PATH_ALLOWED_KEYS = Object.freeze(new Set(['agents_dir']));
/**
* Strip the walk()-root prefix (`"$."`) from a `walk()`-built path, e.g.
* `"$.next.command"` -> `"next.command"`, `"$.actions[0].command"` ->
* `"actions[0].command"` — the field-naming convention `routing-validity`'s
* `subject.key` has always used, kept stable across the switch to a generic
* `walk()`-based scan (#3913 P9 SEC-2).
*
* @param {string} walkPath
* @returns {string}
*/
function stripRootPrefix(walkPath) {
return walkPath.startsWith('$.') ? walkPath.slice(2) : walkPath;
}
/**
* Extract the leaf key name from a `walk()`-built path (e.g. `"$.agents_dir"`
* -> `"agents_dir"`, `"$.foo.bar[3]"` -> `"bar"` for the array element itself,
@@ -313,8 +333,13 @@ const ORACLES = Object.freeze([
'result.json must not contain a NaN number or a string exactly equal to a coercion-artifact sentinel ' +
'(VIOLATION). When ctx.projectDir is supplied, an absolute-path string outside it is reported as a SMELL — ' +
'never a violation — UNLESS its leaf key name is in EXTERNAL_PATH_ALLOWED_KEYS (a field whose contract is to ' +
'point outside the project, e.g. agents_dir), which is skipped entirely: not a smell, not a violation. Without ' +
'ctx.projectDir the path check is skipped rather than guessed.',
'point outside the project, e.g. agents_dir) or the string\'s FIRST WHITESPACE-DELIMITED WORD is an EXACT ' +
'member of getLiveCommandTokens() (e.g. `/gsd:progress`, or `/gsd-plan-phase 2` where the token carries ' +
'trailing arguments — see isLiveCommandToken in tests/helpers/live-command-registry.cjs, the single predicate ' +
'shared with routing-validity), either of which is skipped entirely: not a smell, not a violation. Exact ' +
'membership, never a `startsWith` prefix test with an unconstrained remainder — a string merely SHARING a ' +
'prefix with a real token (e.g. `/gsd-x/../../../etc/passwd`) is not exempted and still surfaces as a SMELL. ' +
'Without ctx.projectDir the path check is skipped rather than guessed.',
check(ctx) {
try {
/** @type {{message: string, key: string, value: unknown}[]} */
@@ -327,6 +352,9 @@ const ORACLES = Object.freeze([
// Resolved once per runOracles call (this check runs exactly once per ctx), not once
// per candidate string below — projectDir is the same value for every leaf in the walk.
const resolvedProjectDir = projectDir !== null ? resolveForCompare(projectDir) : null;
// Resolved once per check() call, not once per leaf — getLiveCommandTokens() is itself
// memoized, but there is no reason to re-look-up the memo per candidate string.
const liveTokens = getLiveCommandTokens();
walk(json, (leaf, path) => {
if (typeof leaf === 'number' && Number.isNaN(leaf)) {
violations.push({ message: `NaN at ${path}`, key: path, value: leaf });
@@ -337,7 +365,8 @@ const ORACLES = Object.freeze([
typeof leaf === 'string' &&
nodePath.isAbsolute(leaf) &&
!isUnderProjectDir(resolveForCompare(leaf), resolvedProjectDir) &&
!EXTERNAL_PATH_ALLOWED_KEYS.has(leafKeyOf(path))
!EXTERNAL_PATH_ALLOWED_KEYS.has(leafKeyOf(path)) &&
!isLiveCommandToken(leaf, liveTokens)
) {
smells.push({
message: `absolute path ${JSON.stringify(leaf)} at ${path} is outside ctx.projectDir ${JSON.stringify(projectDir)}`,
@@ -523,25 +552,44 @@ const ORACLES = Object.freeze([
Object.freeze({
id: 'routing-validity',
describe:
'A result.json.recommended / recommended_command token must name a command present in ctx.liveCommands.',
'Every command token actually carried by the payload must name a command present in ctx.liveCommands. ' +
'Rather than enumerating fixed field paths (`actions[].command` / `next.command`), this walks the ENTIRE ' +
'payload for any string whose first whitespace-delimited word starts with one of LIVE_COMMAND_TOKEN_PREFIXES ' +
'(`/gsd-`, `/gsd:`, `$gsd-`) — catching the token wherever it is actually carried: `recommended_command`, a ' +
'bare-string `next`, `next` as an array of `{command}`, `actions` as an object map, `steps[].command`, ' +
'`actions[].next.command`, etc (#3913 P9 SEC-2 — the fixed-path version missed all of these). A candidate ' +
'passes only when its first word is an EXACT member of ctx.liveCommands (via the shared ' +
'`isLiveCommandToken` predicate, tests/helpers/live-command-registry.cjs — the SAME predicate ' +
'value-hygiene\'s command-token exemption uses, so the two can never drift on what a live token is); a ' +
'pleasing consequence is that value-hygiene therefore exempts exactly the strings routing-validity vouches ' +
'for. A bare `recommended` id (no `/gsd-`-style prefix — e.g. `discuss-phase`, by design ' +
'`src/smart-entry.cts` `actions.find(a => a.recommended)?.id`, an action id, not a command token) is never a ' +
'candidate. Engages only when at least one prefix-shaped string is present; a payload with no such string is ' +
'not engaged (ok).',
check(ctx) {
try {
const json = ctx && ctx.result ? ctx.result.json : undefined;
if (json === null || typeof json !== 'object' || Array.isArray(json)) return { ok: true };
const field = Object.prototype.hasOwnProperty.call(json, 'recommended')
? 'recommended'
: Object.prototype.hasOwnProperty.call(json, 'recommended_command')
? 'recommended_command'
: null;
if (field === null) return { ok: true };
const value = json[field];
if (typeof value !== 'string') return { ok: true };
const liveCommands = ctx && Array.isArray(ctx.liveCommands) ? ctx.liveCommands : [];
if (!liveCommands.includes(value)) {
const liveTokens = new Set(liveCommands);
const seen = new WeakSet();
/** @type {{field: string, value: string} | null} */
let violation = null;
walk(json, (leaf, path) => {
if (violation !== null || typeof leaf !== 'string') return;
const word = firstToken(leaf);
const looksLikeCommandToken = LIVE_COMMAND_TOKEN_PREFIXES.some((prefix) => word.startsWith(prefix));
if (!looksLikeCommandToken) return;
if (!isLiveCommandToken(leaf, liveTokens)) {
violation = { field: stripRootPrefix(path), value: leaf };
}
}, seen, '$');
if (violation !== null) {
return {
ok: false,
severity: SEVERITY.VIOLATION,
detail: `${field}=${JSON.stringify(value)} is not in ctx.liveCommands`,
subject: { key: violation.field, value: violation.value },
detail: `${violation.field}=${JSON.stringify(violation.value)} is not in ctx.liveCommands`,
};
}
return { ok: true };
@@ -571,38 +619,14 @@ const ORACLES = Object.freeze([
},
}),
Object.freeze({
id: 'soft-error-exit-zero',
describe:
'SMELL, not a violation: result.kind === KIND.SOFT_ERROR means the operation reported failure through a ' +
'payload key ({error:...}) while exiting 0. Every shell caller\'s `if ! cmd; then` is blind to that failure. ' +
'This is legal today (42 call sites use output({error:...})) and is deliberately NOT changed here — the ' +
'blast radius of changing it is CRITICAL — but it is reported so the trade stays visible instead of invisible.',
check(ctx) {
try {
const result = ctx && ctx.result;
if (!result || result.kind !== KIND.SOFT_ERROR) return { ok: true };
const argv = Array.isArray(result.argv) ? result.argv : [];
const argvDisplay = argv.length ? argv.join(' ') : '(no argv)';
const errorValue = result.json && typeof result.json === 'object' ? result.json.error : undefined;
return {
ok: false,
severity: SEVERITY.SMELL,
subject: { argv },
detail: `command "${argvDisplay}" exited 0 but carries result.json.error = ${JSON.stringify(errorValue)}`,
};
} catch (err) {
return { ok: false, severity: SEVERITY.SMELL, detail: `soft-error-exit-zero threw: ${err && err.message}` };
}
},
}),
Object.freeze({
id: 'untyped-success',
describe:
'SMELL, not a violation: result.kind === KIND.PROSE means the command only emits rendered text with no ' +
'typed surface to assert on. CONTRIBUTING.md forbids asserting on rendered text, so a prose-only command is ' +
'permanently unassertable by this harness — the gap is in the product, not the test.',
'VIOLATION: result.kind === KIND.PROSE means the command only emits rendered text with no typed surface to ' +
'assert on. CONTRIBUTING.md forbids asserting on rendered text, so a prose-only command is permanently ' +
'unassertable by this harness. This is enforced, not merely tracked: the corpus PROSE count is 0 (#3913) — ' +
'every executed step across every scenario emits a typed surface, so this oracle can now fire without ever ' +
'having fired against the shipped corpus. A future prose-only step reddens the build immediately.',
check(ctx) {
try {
const result = ctx && ctx.result;
@@ -611,12 +635,12 @@ const ORACLES = Object.freeze([
const argvDisplay = argv.length ? argv.join(' ') : '(no argv)';
return {
ok: false,
severity: SEVERITY.SMELL,
severity: SEVERITY.VIOLATION,
subject: { argv },
detail: `command "${argvDisplay}" emits KIND.PROSE only; no typed surface exists to assert on`,
};
} catch (err) {
return { ok: false, severity: SEVERITY.SMELL, detail: `untyped-success threw: ${err && err.message}` };
return { ok: false, severity: SEVERITY.VIOLATION, detail: `untyped-success threw: ${err && err.message}` };
}
},
}),