* feat(#3908): the scanners distinguish an empty diff from one they could not compute collect_files ended 2>/dev/null || true, which destroyed the evidence three ways: the redirect discarded git's diagnostic, the pipe replaced git's status with grep's, and || true forced success regardless. Four distinct conditions - an established-empty diff, a bad ref, no repository, and a repository with no commits - all reported clean, and a secret scanner reporting clean because git failed is indistinguishable from an all-clear to any gate consuming it. git now runs separately from the filter so its status and diagnostic both survive. An established-empty diff exits NO_INPUT; a scope that could not be established exits UNAVAILABLE; the usage sites move off 2 to USAGE. || true is retained on the filter alone, where it is correct: a diff of only images is empty, not failed. Codes are sourced from a generated shell fragment rather than written into three scripts, so a re-allocation cannot desync them, and a missing fragment fails loudly instead of falling back to literals. The security workflow is updated in the same change: without it, a docs-only PR would newly fail the job. * fix(#3908): keep scanner stderr out of the file list, and drop try/finally from test bodies Capturing git and find output with 2>&1 was right for the failure path but wrong for the success path: a warning emitted alongside a successful diff flowed into the file list and was treated as a filename. stderr is now captured separately, forwarded as a warning on success and as the diagnostic on failure, and never folded into the list. Also converts the control tests' try/finally blocks to t.after(), which CONTRIBUTING bans inside a test body because it masks failures. * chore(#3908): backfill changeset pr number * docs(#3908): record the scanners' four-outcome exit contract SECURITY.md is root-level, so the docs gate correctly held: a Changed fragment owes a file under docs/. The contract also belongs where the feature is described, as REQ-SCAN-INJ-05. docs/FEATURES.md is GENERATED from per-feature fragments (#3840) - the first edit went into the generated file and gen-features --check caught it, which is the same edit-the-output drift this epic exists to close. The fragment is the source; FEATURES.md is regenerated. --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -55,16 +55,19 @@ function makeEntry(overrides) {
|
||||
/**
|
||||
* #3906 (ADR-3889 Phase 2): the generator now emits THREE artifacts — a
|
||||
* primary (gsd-core/bin/lib), a secondary (scripts/lib), and the ambient
|
||||
* `.d.cts` type declaration (src/exit-code-registry.d.cts). Every existing
|
||||
* call site below only overrides the PRIMARY path via `--out`; without
|
||||
* matching `--scripts-out`/`--dts-out` overrides, a `--write` here would
|
||||
* clobber the real committed `scripts/lib/exit-code-registry.cjs` and
|
||||
* `src/exit-code-registry.d.cts` — dangerous since test files in this repo
|
||||
* run in parallel. Rather than touch every call site, this single seam
|
||||
* derives co-located, per-call-unique secondary/dts paths from whatever
|
||||
* `.d.cts` type declaration (src/exit-code-registry.d.cts). #3908 (Phase 4)
|
||||
* added a FOURTH: the shell-sourceable fragment (gsd-core/bin/shared/
|
||||
* exit-codes.sh). Every existing call site below only overrides the PRIMARY
|
||||
* path via `--out`; without matching `--scripts-out`/`--dts-out`/`--sh-out`
|
||||
* overrides, a `--write` here would clobber the real committed
|
||||
* `scripts/lib/exit-code-registry.cjs`, `src/exit-code-registry.d.cts`, and
|
||||
* `gsd-core/bin/shared/exit-codes.sh` — dangerous since test files in this
|
||||
* repo run in parallel. Rather than touch every call site, this single seam
|
||||
* derives co-located, per-call-unique secondary/dts/sh paths from whatever
|
||||
* `--out` value the test already supplies, whenever the caller has not
|
||||
* already supplied its own `--scripts-out`/`--dts-out`. Calls with no
|
||||
* explicit `--out` (the "real committed set" checks) are left untouched.
|
||||
* already supplied its own `--scripts-out`/`--dts-out`/`--sh-out`. Calls
|
||||
* with no explicit `--out` (the "real committed set" checks) are left
|
||||
* untouched.
|
||||
*/
|
||||
function ensureScriptsOut(args) {
|
||||
const outIdx = args.indexOf('--out');
|
||||
@@ -73,6 +76,7 @@ function ensureScriptsOut(args) {
|
||||
const extra = [];
|
||||
if (!args.includes('--scripts-out')) extra.push('--scripts-out', `${outValue}.secondary.cjs`);
|
||||
if (!args.includes('--dts-out')) extra.push('--dts-out', `${outValue}.d.cts`);
|
||||
if (!args.includes('--sh-out')) extra.push('--sh-out', `${outValue}.sh`);
|
||||
return extra.length === 0 ? args : [...args, ...extra];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user