Skip to content

v0.22.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 15:35
· 9 commits to main since this release
Immutable release. Only release title and notes can be modified.
v0.22.0
204cdcf

Upgrading to v0.22.0

Default changes

  • stub-bodies: equality-made-constant on a changed production method at warning at most (exit 0) → at the gate's severity (error by default, exit 1) (stricter). Restores v0.19.0 blocking for a substantive equality body made constant Restore: allow-stub: <Type> <reason>, or [gates.stub-bodies] severity = "warning" (whole gate).
  • citation-anchors: no minimum quote length → min_quote_chars = 8 (stricter). A one-character quote is contained in almost any line and verified anything Restore: min_quote_chars = 0.

Behaviour changes

  • library discipline::guards::perf::bounds::sample_size_for_interval (stricter): Returns an error when the required n is not finite or exceeds the new MAX_SAMPLE_SIZE (1e9) instead of saturating to usize::MAX; adds pub const MAX_SAMPLE_SIZE Migration: Callers passing a vanishing half-width handle the Err; values at or below 1e9 are unchanged
  • gate-command-lint/unfailable-command (stricter): Read from the bash syntax tree: also reports || echo …, || /bin/true, ||true, || exit 00, || { true; }, (cmd || true) and || printf 'lit'; || true inside a string or comment, an a || b whose left side only tests a condition, and an || echo default inside $(...), are not reported; a command that does not parse is named in the notes Migration: Remove the mask, or end the fallback with a failing command (|| { echo failed; exit 1; })
  • gate-command-lint/pipeline-exit-masked (stricter): Any pipeline with a failable stage before the last is reported without pipefail, whatever the filter (| xargs, | python3); pipefail counts only from a real set command, not text such as echo "set -o pipefail" Migration: Add set -o pipefail; before the pipeline, or set allow_pipeline_without_pipefail = true
  • ci-integrity/exit-code-masked (GitHub run:, GitLab script) (stricter): Read from the bash syntax tree on both forges: also reports ||true, || exit 0, || echo …, || /bin/true, || { true; }, set +o errexit; a mask only mentioned in a comment or string, an a || b whose left side only tests a condition ([ -f x ] || echo missing, a function made of conditions), or an || echo default inside $(...), is not reported; the GitHub finding is reported at the masking line; a changed script that does not parse, or a non-bash/sh shell:, is named in the notes Migration: Remove the mask or excuse it with allow-ci-weakening: <step name> <reason> (GitLab subject unchanged: || true)
  • stub-bodies/equality-made-constant (stricter): An existing production equality method whose substantive body is replaced by constant true is reported at the gate's severity (it was capped at warning since v0.20.0; v0.19.0 reported it as body-replaced-by-stub at gate severity). Added equality methods, ones already constant on the base side, and test code stay capped at warning Migration: Implement a real comparison, or allow-stub: <Type> <reason>
  • stub-bodies/equality-made-constant carve-outs (stricter (looser for test-double modules)): Read from syntax nodes: a comment naming a derive, a mock import or @Generated no longer exempts; a hand-written equality method in a @dataclass, attrs or pydantic class, a Lombok or record class, or a Kotlin/Scala data/case class is reported; test doubles in fakes/mocks/stubs/doubles modules, and in tests/ files judged as production code, are exempt Migration: allow-stub: <Type> <reason> for a deliberate constant equality
  • stub-bodies constant bodies (stricter): return Boolean.TRUE; and parenthesised constants (return (true);, return (null)) are trivial bodies, for both equality-made-constant and body-replaced-by-stub Migration: none
  • suppression-delta report text (quote_line = false) (stricter): The message and remediation name a fixed tool and the rule identifiers only; descriptions, justifications, reason =, msg= and other free text are no longer echoed Migration: none; quote_line = true still quotes the line
  • suppression-delta JavaScript sites (stricter): v8 ignore, node:coverage ignore and node:coverage disable comments are suppression sites when they open the comment Migration: allow-suppression: coverage <reason> or a path
  • unsafe-budget (stricter): A changed file whose head side does not parse stops the run with exit 2 (could not check) instead of a note and a skip, restoring the behaviour before v0.20.1 Migration: Fix the file, or exempt the path
  • config-integrity (stricter): Under --disable <gate>, a change whose own file leaves the gate off, including by deleting an enabled = true table so a default-off gate falls back to off, is reported as gate-weakened; a head config read or parse error stops the gate instead of skipping Migration: allow-gate-weakening: <gate> <reason>
  • mechanism-sections (bug fix): An untagged claim whose 57th byte falls inside a multi-byte character is reported instead of crashing the run (exit 101) Migration: none
  • mechanism-sections (looser (false positive removed)): ~~~ fenced blocks are code, not claims Migration: none
  • mechanism-sections (stricter): A heading that starts with a designated heading (Root cause (summary), 2. Root cause) is in scope; #word with no blank is no longer a heading Migration: Tag the claims, or rename the heading
  • mechanism-sections (stricter): A (verified: <path>) target must be tracked in the index, not only present on disk Migration: git add the cited file
  • citation-anchors (stricter): A quote with fewer than min_quote_chars (default 8) non-whitespace characters is reported as the new citation-anchors/quote-too-short Migration: Quote more of the line, or set min_quote_chars
  • citation-anchors (stricter): A cited commit must be the base or one of its ancestors; a commit only the change or another branch holds is commit-unresolvable Migration: Cite a commit already on the base branch
  • citation-anchors (tracker titles) (bug fix): DISCIPLINE_NO_NETWORK=0 or false no longer skips the check, matching the forge client; a tracker-title-mismatch at the start of a line reports the correct line (it was one short) Migration: Baseline entries keyed on the old line: discipline baseline --write
  • scope-confinement (stricter): A rename is judged at its source as well as its destination; symlinks added, retargeted or removed under a forbidden path (or outside allowed_paths) are reported Migration: allow-scope: <path> <reason>
  • claim-labels (stricter): A label needs a separator and real content; (RUN), [READ], `RUN`, **RUN**, RUN:, -- RUN, (RUN: x) and markers inside code spans no longer satisfy a claim Migration: Write RUN: <command> or (READ: path:line)
  • hook run --event post-tool ([hooks.verified-stop] witnessed runs) (bug fix (stricter)): A run counts as passing only when its exit code is stated: a numeric field, an Exit code: N statement (Codex tool_response string, Qwen llmContent, Copilot <exited with exit code N>; any non-zero statement wins), or a Claude Code PostToolUse that was not interrupted. A payload with a result but no stated code was read as exit 0 and is no longer witnessed; Copilot resultType: "success" alone no longer counts Migration: none; a passing run in an unrecognised shape now needs run_at_stop = true
  • hook run ([hooks.verified-stop], refuse mode) (bug fix (stricter)): A stop whose refusal cannot be counted against max_per_session (no session id, an unwritable session store, or sessions/.lock held) is refused (exit 2) with the reason on stderr instead of being let through; stop_hook_active and agy executionNum still let it through Migration: none
  • hook run ([hooks.verified-stop] default commands) (bug fix (looser for single-ecosystem repositories)): With no command configured, only the default runners whose ecosystem marker is a file at the base tree's root are vouched (cargo test/Cargo.toml, pytest/pyproject.toml, setup.py or pytest.ini, npm test/package.json, go test ./.../go.mod); before, all four were vouched and run_at_stop required all four to pass. With none applicable, the refusal says so Migration: Set [hooks.verified-stop].commands on the base branch when no marker applies
  • hook install (all agents but Cursor and Aider) (additive): Generated hook files run hook run --agent <name> --event post-tool after the agent's shell tool (Bash, run_shell_command, bash, run_command, OpenCode bash/shell), so [hooks.verified-stop] can witness verification runs from installed hooks; it never blocks Migration: discipline hook install --agent <name> [--observe] --upgrade
  • session store (<common git dir>/discipline/sessions/) (bug fix): Session files and the lock file are created 0600 on Unix (were 0644 under the usual umask) Migration: none
  • hook install --agent opencode (2.x execute.after) (bug fix): In enforcing mode, a failed check whose call.result has neither text content nor a string output is written to stderr instead of being dropped Migration: discipline hook install --agent opencode --upgrade
  • release tarballs (bug fix): Archives record owner and group 0 with no names, directories and files at 0755/0644 and the binary at 0755, on both GNU tar and bsdtar runners Migration: none

The full ledger is in docs/ROADMAP.md.

What's Changed

  • fix: close the v0.19.0..v0.21.2 audit findings by @orieg in #768
  • chore(release): bump version to 0.22.0 with its ledger rows by @orieg in #770

Full Changelog: v0.21.2...v0.22.0