Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

### Changes

- **Each baseline verifies against its own audit rows, the override tier gate reads the severity a finding was emitted at, and an optional policy pack's pin is fatal.** Every baseline in a directory appends to one `baseline-audit.log`, and verification compared a baseline with the log's last row whoever wrote it: saving `baseline-prod.json` turned an untouched `baseline.json` into a critical `SHIP-BASELINE-INTEGRITY-MISMATCH` (`baseline verify --strict` exit 6, `scan` `blocked`), while a copy of the baseline saved last verified clean. Verification now uses the latest row recorded for that baseline's file name; a log with no row for it is `missing_audit_log`, naming the files the log does record and the `baseline save` that records it. Audit logs written by released versions verify unchanged. The baseline hash reads CRLF as LF, as it always has (a Windows checkout converts line endings on an untouched file); its docstring, which said line endings were detected, now says so. `checks.severity_overrides: {SHIP-SIDEFX-IDEMPOTENCY-MISSING: high}` lowered that check's `critical` finding (emitted when a retry policy is known) with no acknowledgement, because the tier gate compared with the `high` catalog default; it now compares with the strongest severity among the findings the override would lower, so the override needs an `acknowledge_overrides` entry (exit 2 otherwise), and the same holds through a legacy alias such as `SHIP-API-OPERATIONAL-READINESS`. An `optional: true` policy pack whose `sha256` pin did not match, or that had been replaced by a malformed file, a directory or a link, was a warning and its rules were dropped; `optional` now tolerates only a missing pack, and anything else is a config error. Migration: [STABILITY.md](STABILITY.md#trust-config-1030). (#1030)
- **`checks.ignore` no longer turns a configured advisory gate green while the decision says `blocked`.** A suppression cannot waive a mandatory current-surface control (the four dedicated control families, such as `SHIP-ACTION-FINANCIAL-WRITE-CONTROL-MISSING`, and engine-raised control-pack obligations), so the release decision kept such a finding as a blocker; the exit code dropped it with every other suppressed finding before matching `fail_on`. `samples/support_refund_agent` in advisory mode with `fail_on: [critical]` and three `checks.ignore` entries printed `Decision: blocked`, `would_fail_ci=false` and exited `0`; without the entries it exited `20`. The exit code now also reads the decision's `blockers[]`: a blocker whose severity is in `fail_on` (including `verify`'s base `ci.fail_on` floor) exits `20` in either mode, for `scan`, `verify` and the Action, and `fail_policy.exit_code` / `would_fail_ci` in `report.json` and `verifier.json` say the same. Strict-mode exit codes are unchanged, because a blocker already meant a `blocked` decision, which strict mode fails; advisory mode without `fail_on` still exits `0`; and a suppression still waives every other finding. A report whose blocker in `fail_on` would not fail CI is refused when it is written. Separately, `verify-run.json`'s outcome, the verification receipt and the safety qualification script now read the decision-to-merge-verdict projection from `schemas/verifier.py` instead of carrying their own copies of its table, which would have refused every verify run the day that table changed; a test refuses a new copy. Migration: [STABILITY.md](STABILITY.md#release-decision-truth-table). (#1029)
- **A Claude Code setting that runs a command, or an `env` entry that redirects requests or loads code, is a host-grant row, and a settings change no row shows is no longer "no change".** Adding `"statusLine": {"command": "curl … | sh"}`, `"apiKeyHelper": "curl … | sh"`, `env.ANTHROPIC_BASE_URL` or `env.NODE_OPTIONS` to `.claude/settings.json` gave no row: `diff` (text and `--json`), `verify`'s host comparison and the PR comment headlined `No static host-grant changes detected. No verdict is implied.`, and `audit --host` reported Claude Code coverage `complete`. Host-grants `0.9` (extended in place) now publishes `command_setting` grants for `statusLine`, `subagentStatusLine`, `fileSuggestion`, `apiKeyHelper`, `otelHeadersHelper`, `awsAuthRefresh` and `awsCredentialExport` (`high`, the command shown as a hook's is: name, digest and shape, never text) and `env` grants for endpoint, proxy, certificate, search-path and loader names (`*_BASE_URL`, OpenTelemetry exporter endpoints, `HTTPS_PROXY` and the other proxies, `NODE_EXTRA_CA_CERTS`, `NODE_TLS_REJECT_UNAUTHORIZED`, `SSL_CERT_FILE`, `NODE_OPTIONS`, `BASH_ENV`, `PATH`, `PYTHONPATH`, `LD_PRELOAD`, `GIT_SSH_COMMAND` and the other names `docs/host-boundary-support.md` lists, read without regard to case; `high`, the value digested and never published), so adding one is a `⚠ high added` row and editing one a `changed` row naming the two digests. A settings file's other keys are published as display-only `unmodelled_keys` with digests; when one changes with no row, the coverage item names it in `detail` and the headline reads `Changed, not compared: .claude/settings.json (keys this entry does not compare: …)`, as it does, without keys, for any settings, MCP or hook file that changed with no row. A key no reader models that is shaped as a command (an object with a `command`, or a `…Helper` string) is a blocking limit, so `audit --host` reports coverage `partial`, as for `policyHelper`. The cold-start benchmark's oracle moves those seven settings and those `env` names from unsupported to supported. Migration: [STABILITY.md](STABILITY.md#host-commands-env-1027). (#1027)
- **An n8n MCP Client Tool that exposes every tool of its server is read as doing so.** n8n's node selects its tools with `include` (`all`, `selected` or `except`) and leaves it out of an exported workflow at its default, `all`; the reader recognised only `toolSelection`, `toolsToInclude` and `toolSelectionMode`, which n8n does not write. So the default export, `include: all` and `include: except` each entered the catalog as one concretely named tool, with no wildcard, no dynamic surface and no `SHIP-N8N-MCP-CLIENT-TOOLSET-UNFILTERED`. Each is now one `<node>.*` action, which raises `SHIP-INVENTORY-WILDCARD-TOOLS` and, with no local inventory declared, `SHIP-N8N-DYNAMIC-TOOL-SURFACE-NOT-ENUMERABLE` and the unfiltered finding (`high` in `production_like` and `production`, `medium` otherwise); an `except` node's `excludeTools` are listed in the finding's `excluded_tools`; `selected` names its `includeTools`, and a `selected` node naming none is read as `all`, as n8n runs it. The earlier spellings remain aliases, and `include` decides when both are present. A workflow whose MCP Client Tool keeps n8n's default now reports these findings where it passed; select explicit tools in the node, or declare a local MCP inventory. Migration: [STABILITY.md](STABILITY.md#mcp-reader-conformance-1025). (#1025)
Expand Down
Loading
Loading