Files
msd-core/gsd-core/bin/shared/exit-codes.sh
Tom Boucher 1e67ec9737 enhance(#3908): the scanners distinguish an empty diff from one they could not compute (#3937)
* 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>
2026-08-27 13:11:13 -04:00

21 lines
766 B
Bash

#!/bin/sh
# GENERATED FILE — DO NOT EDIT BY HAND.
# Source of truth: gsd-core/bin/shared/exit-codes.json. Regenerate with:
# node scripts/gen-exit-code-registry.cjs --write
#
# One `export EXIT_<NAME>=<code>` per gsd-core/bin/shared/exit-codes.json
# entry (ADR-3889 §2, #3905/#3906/#3908). POSIX sh, safe under `set -u`:
# sourcing this file only ever ASSIGNS variables, never reads one, so it
# cannot trip an unset-variable check regardless of the caller's existing
# environment.
#
# Usage (from a scanner under scripts/):
# . "$(dirname "$0")/../gsd-core/bin/shared/exit-codes.sh"
# exit "$EXIT_UNAVAILABLE"
export EXIT_HOOK_DENY=2
export EXIT_USAGE=64
export EXIT_NO_INPUT=66
export EXIT_UNAVAILABLE=69
export EXIT_INTERNAL=70
export EXIT_DEGRADED=80