diff --git a/.well-known/agents-shipgate.json b/.well-known/agents-shipgate.json index e4bb39d..efd6a90 100644 --- a/.well-known/agents-shipgate.json +++ b/.well-known/agents-shipgate.json @@ -40,7 +40,7 @@ "trust_model": "static_by_default", "schemas": { "manifest": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/manifest-v0.1.json", - "report": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/report-schema.v0.20.json", + "report": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/report-schema.v0.21.json", "packet": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/packet-schema.v0.6.json", "checks_catalog": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/checks.json" }, diff --git a/AGENTS.md b/AGENTS.md index 5913ed0..e9820b5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -259,8 +259,9 @@ Other stable top-level fields: - `release_decision.contribution_rules[]` (v0.17+, per-finding audit of how each finding contributed to the decision; one row per `report.findings` entry, with `category` ∈ `{blocker, review_item, excluded}` and `rule` ∈ `{policy_block_new, severity_block_new, policy_baseline_accepted, severity_baseline_accepted, review_required, sub_threshold, suppressed}`) - `policy_audit.severity_overrides_applied[]` (v0.17+, top-of-report audit envelope listing every manifest-driven severity override with `{check_id, default_severity, applied_severity, manifest_path, reason, tier_crossed, direction, expires}`) - `privacy_audit` (v0.18+, top-level audit proving default redaction ran before public artifacts were written; `redacted_paths[]` contains counts and structural paths only, never raw values or raw hashes) +- `heuristics_filter` (v0.21+, top-level audit envelope describing the `--no-heuristics` CLI filter pass; `enabled` is `False` and counts are zero when the flag is unset, so the field shape is stable across runs. When enabled, findings whose `provenance_kind` is `keyword_heuristic` or `regex_heuristic` are marked `suppressed=True` with `suppression_reason="filtered by --no-heuristics"` before the release decision is built — they remain in `findings[]` for transparency but do not gate release.) -The full schema is at [`docs/report-schema.v0.20.json`](docs/report-schema.v0.20.json) (current; emitted reports carry `report_schema_version: "0.20"`, adding the top-level `reviewer_summary` block — a deterministic projection of reviewer-lens surfaces and audit envelopes). v0.19 (frozen at [`docs/report-schema.v0.19.json`](docs/report-schema.v0.19.json)) adds `Finding.policy_evidence_source` and `ReleaseDecisionItem.{source, policy_evidence_source}` for reviewer-grade dual-source provenance on top of v0.18's `privacy_audit`, v0.17's `policy_audit`, and `release_decision.contribution_rules[]` audit fields. What's-stable is documented in [STABILITY.md](STABILITY.md). +The full schema is at [`docs/report-schema.v0.21.json`](docs/report-schema.v0.21.json) (current; emitted reports carry `report_schema_version: "0.21"`, adding the top-level `heuristics_filter` audit envelope alongside v0.20's `reviewer_summary` block). v0.20 (frozen at [`docs/report-schema.v0.20.json`](docs/report-schema.v0.20.json)) added the `reviewer_summary` deterministic projection of reviewer-lens surfaces and audit envelopes. v0.19 added `Finding.policy_evidence_source` and `ReleaseDecisionItem.{source, policy_evidence_source}` for reviewer-grade dual-source provenance on top of v0.18's `privacy_audit`, v0.17's `policy_audit`, and `release_decision.contribution_rules[]` audit fields. What's-stable is documented in [STABILITY.md](STABILITY.md). **Release gating signal**: prefer `release_decision.decision` (`"blocked" | "review_required" | "insufficient_evidence" | "passed"`) over `summary.status`. The new field is **baseline-aware** — a baseline-matched critical surfaces in `release_decision.review_items` (accepted debt), not `release_decision.blockers`. `summary.status` stays baseline-blind for v0.7 compatibility, so a baseline-matched-only critical produces both `summary.status = "release_blockers_detected"` AND `release_decision.decision = "review_required"` (intentional divergence — see [STABILITY.md](STABILITY.md#release_decisiondecision-vs-summarystatus)). `insufficient_evidence` (added v0.14) signals that the scan saw too many low-confidence tools or source-loader warnings to be trustworthy; consumers that switch on the enum must fall back to `review_required` for unknown future values. @@ -326,7 +327,7 @@ validation and [`docs/manifest-v0.1.md`](docs/manifest-v0.1.md) for prose. ### Where is the report schema? Parse `agents-shipgate-reports/report.json` and validate against -[`docs/report-schema.v0.20.json`](docs/report-schema.v0.20.json) (current). +[`docs/report-schema.v0.21.json`](docs/report-schema.v0.21.json) (current). Older reports (`report_schema_version: "0.10"`) validate against the frozen [`docs/report-schema.v0.10.json`](docs/report-schema.v0.10.json). Do not scrape Markdown when JSON is available. @@ -364,7 +365,8 @@ For the short, current statement of "which fields to read", see [`docs/agent-con | What | Path | Stable | |---|---|---| | Manifest schema | [`docs/manifest-v0.1.json`](docs/manifest-v0.1.json) | `0.1` | -| Report schema (current) | [`docs/report-schema.v0.20.json`](docs/report-schema.v0.20.json) | `0.20` | +| Report schema (current) | [`docs/report-schema.v0.21.json`](docs/report-schema.v0.21.json) | `0.21` | +| Report schema (v0.20 frozen reference) | [`docs/report-schema.v0.20.json`](docs/report-schema.v0.20.json) | `0.20` | | Report schema (v0.19 frozen reference) | [`docs/report-schema.v0.19.json`](docs/report-schema.v0.19.json) | `0.19` | | Report schema (v0.18 frozen reference) | [`docs/report-schema.v0.18.json`](docs/report-schema.v0.18.json) | `0.18` | | Report schema (v0.17 frozen reference) | [`docs/report-schema.v0.17.json`](docs/report-schema.v0.17.json) | `0.17` | @@ -399,7 +401,7 @@ Promised to not break in `0.x` minor versions. See [STABILITY.md](STABILITY.md) | Command | Stable flags | |---|---| -| `agents-shipgate scan` | `-c`, `--out`, `--format`, `--ci-mode`, `--fail-on`, `--baseline`, `--diff-from`, `--no-plugins`, `--verbose`, `--packet`/`--no-packet`, `--packet-format` | +| `agents-shipgate scan` | `-c`, `--out`, `--format`, `--ci-mode`, `--fail-on`, `--baseline`, `--diff-from`, `--no-plugins`, `--no-heuristics`, `--verbose`, `--packet`/`--no-packet`, `--packet-format` | | `agents-shipgate evidence-packet` | `--from`, `--out`, `--format`, `--json` | | `agents-shipgate init` | `--workspace`, `--write`, `--json` | | `agents-shipgate doctor` | `-c`, `--workspace`, `--json`, `--verbose` | diff --git a/CHANGELOG.md b/CHANGELOG.md index 95e309c..1fe6419 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,60 @@ ## Unreleased +- **v0.21 — `--no-heuristics` CLI flag closes the round-3 / round-4 E5 + carryover.** `Finding.provenance_kind` has shipped on every report since + v0.15 as required+non-nullable wire metadata but had no consumer for + four review cycles. v0.21 lands the consumer the field was always + designed for: a security/GRC-friendly filter that excludes findings + whose provenance is `keyword_heuristic` or `regex_heuristic` from the + active release-gating set. + - New `--no-heuristics` flag on `agents-shipgate scan` (stable in + 0.x). When set, findings whose `provenance_kind` is in + `NO_HEURISTICS_EXCLUDED_PROVENANCE_KINDS` (today: `keyword_heuristic` + and `regex_heuristic`) are marked `suppressed=True` with + `suppression_reason="filtered by --no-heuristics"` BEFORE the + release decision is built. Filtered findings remain in `findings[]` + for transparency; they no longer gate release. The KEEP list is + `static_declaration`, `ast_extraction`, and `policy_pack` — + declared/parsed-shape findings and explicit external rules stay in + scope. + - New top-level `report.heuristics_filter` audit envelope. Required + + always present on emitted scans regardless of whether the flag was + set (parallel to `privacy_audit` shape). Fields: `enabled`, + `excluded_provenance_kinds: list[str]`, `filtered_finding_count`, + `filtered_by_kind: dict[str, int]`. Earns the contract weight of + `Finding.provenance_kind` by giving it a first-class consumer. + - Manifest-driven suppression wins on overlap: a finding the user + explicitly suppressed via `checks.ignore` keeps the user's reason + text even when its provenance_kind would have triggered the + filter. The audit envelope still counts the overlap so reviewers + see the filter's effective scope. + - `ReviewerSummary` lens/audit counts already reflect the post-filter + active set (the filter runs before `build_reviewer_summary`); no + new field added to `ReviewerSummary` — the dedicated envelope is + the right audit home. + - Schema bump: `report_schema_version: "0.20"` → `"0.21"`. v0.20 moves + to frozen-reference; existing v0.20 consumers ignore the new field. + - Contract-stamp pin in `docs/architecture.md` bumped to date + `2026-05-23`, report `v0.21`, packet `v0.6` (unchanged). The + `test_architecture_doc_contract_stamp_matches_runtime` regression + test moves in lockstep. + - 11 new tests in `tests/test_no_heuristics.py` covering: pure- + function filter semantics (KEEP / FILTER classifications per + provenance_kind), envelope shape parity across enabled=True/False, + manifest-suppression preservation, contract-list completeness + (every value in `NO_HEURISTICS_EXCLUDED_PROVENANCE_KINDS` is a + real `ProvenanceKind`; KEEP+EXCLUDE partition is exact), end-to- + end `run_scan(no_heuristics=True)`, CLI subprocess smoke test, + monotone-non-increasing reviewer-summary lens counts under + filtering. + - **Decision recorded.** Round-4 review's E5 carryover offered ship- + or-retire on `provenance_kind`. We ship. Retiring would have forced + a deprecation cycle on a stable-contract field used by every + report since v0.15; shipping the flag earns the weight and serves + a real audience (security/GRC reviewers triaging declared-only + findings before promotion). + - **v0.21 — CI coverage gate raised from 75% → 85% (E7 from round-4 review).** Both `.github/workflows/ci.yml` and `.github/workflows/release.yml` now pass `--cov-fail-under=85`. Aggregate coverage on `main` at the time of diff --git a/README.md b/README.md index a6aea80..e4d1a60 100644 --- a/README.md +++ b/README.md @@ -220,7 +220,7 @@ Set `pr_comment: "true"` to post a compact PR summary: ## What it produces -- **Tool-Use Readiness Report** — `agents-shipgate-reports/report.{md,json,sarif}`. Markdown for human release review, JSON for tools and coding agents (current schema [v0.20](docs/report-schema.v0.20.json); gating signal is `release_decision.decision`; v0.20 adds the top-level `reviewer_summary` block — a deterministic projection of reviewer lens + audit surfaces parallel to `agent_summary`; v0.19 added reviewer-grade dual-source provenance on top of v0.18's privacy audit), SARIF for GitHub code-scanning workflows. +- **Tool-Use Readiness Report** — `agents-shipgate-reports/report.{md,json,sarif}`. Markdown for human release review, JSON for tools and coding agents (current schema [v0.21](docs/report-schema.v0.21.json); gating signal is `release_decision.decision`; v0.21 adds the top-level `heuristics_filter` envelope — the audit pass for the new `--no-heuristics` CLI flag — alongside v0.20's `reviewer_summary` block; v0.19 added reviewer-grade dual-source provenance on top of v0.18's privacy audit), SARIF for GitHub code-scanning workflows. - **Release Evidence Packet** — `agents-shipgate-reports/packet.{md,json,html}` (and `packet.pdf` with the `[pdf]` extras). Reviewer-shaped synthesis with fixed sections, including the compact evidence matrix plus tool-surface and action-surface diffs when available. Packet outputs are locally redacted by default and governed by [packet schema v0.6](docs/packet-schema.v0.6.json) — see [STABILITY.md §Release Evidence Packet](STABILITY.md#release-evidence-packet-v06). ## Exit codes @@ -256,7 +256,7 @@ Agents Shipgate is designed to be agent-friendly. If you're a coding agent (Clau - **[`prompts/`](prompts/)** — reusable prompts for common workflows - **[`skills/agents-shipgate/`](skills/agents-shipgate/)** + **[`.claude/commands/shipgate.md`](.claude/commands/shipgate.md)** — self-contained Claude Code skill (bundled prompts and CI recipe) and `/shipgate` slash command. See [`docs/agents/use-with-claude-code.md`](docs/agents/use-with-claude-code.md) to install in your own project. - **[`docs/ai-search-summary.md`](docs/ai-search-summary.md)** — human-readable summary for AI search, answer engines, and coding agents -- **[`docs/manifest-v0.1.json`](docs/manifest-v0.1.json)** + **[`docs/report-schema.v0.20.json`](docs/report-schema.v0.20.json)** — JSON Schemas for live editor validation (current; emitted reports carry `report_schema_version: "0.20"`). v0.20 adds the top-level `reviewer_summary` block (lens + audit activity counts plus a `first_recommended_surface` pointer, parallel to `agent_summary` for the reviewer side); v0.19 added `Finding.policy_evidence_source` and `ReleaseDecisionItem.{source, policy_evidence_source}` for reviewer-grade dual-source provenance; v0.18 added `privacy_audit`; v0.17 added `policy_audit` and `release_decision.contribution_rules[]`. Read `release_decision.decision` for release gating in new consumers; read `agent_summary.first_recommended_action` for a deterministic next agent step and `reviewer_summary.first_recommended_surface` for the recommended human-review entry point. +- **[`docs/manifest-v0.1.json`](docs/manifest-v0.1.json)** + **[`docs/report-schema.v0.21.json`](docs/report-schema.v0.21.json)** — JSON Schemas for live editor validation (current; emitted reports carry `report_schema_version: "0.21"`). v0.21 adds the top-level `heuristics_filter` envelope — the audit pass for the new `--no-heuristics` CLI flag — alongside v0.20's `reviewer_summary` block (lens + audit activity counts plus a `first_recommended_surface` pointer, parallel to `agent_summary` for the reviewer side); v0.19 added `Finding.policy_evidence_source` and `ReleaseDecisionItem.{source, policy_evidence_source}` for reviewer-grade dual-source provenance; v0.18 added `privacy_audit`; v0.17 added `policy_audit` and `release_decision.contribution_rules[]`. Read `release_decision.decision` for release gating in new consumers; read `agent_summary.first_recommended_action` for a deterministic next agent step and `reviewer_summary.first_recommended_surface` for the recommended human-review entry point. - **[`docs/checks.json`](docs/checks.json)** — machine-readable check catalog Every command has a `--json` form. Errors emit a structured `next_action` line on stderr when `AGENTS_SHIPGATE_AGENT_MODE=1`. @@ -444,7 +444,7 @@ Agents Shipgate is a static, manifest-first scanner. It is intentionally narrow: - It does not verify runtime behavior, latency, prompt quality, or routing decisions. - It does not replace dynamic security testing or human security review of the underlying systems. - It only inspects what is declared in `shipgate.yaml`, local OpenAPI specs, MCP exports, Anthropic/OpenAI API artifacts, optional SDK AST metadata, static Google ADK/LangChain/CrewAI/n8n inputs, and static Codex plugin package metadata; tools that are not declared or statically discoverable are not scanned. -- The manifest remains `version: "0.1"` so existing configs keep working. Current reports carry `report_schema_version: "0.20"` (additive over v0.19's dual-source provenance, adding the `reviewer_summary` top-level block — a deterministic projection of reviewer lens activity and audit envelope counts plus a `first_recommended_surface` pointer) while preserving the stable payload contract documented in the report schema. +- The manifest remains `version: "0.1"` so existing configs keep working. Current reports carry `report_schema_version: "0.21"` (additive over v0.20's `reviewer_summary`, adding the `heuristics_filter` top-level audit envelope — the deterministic projection of the `--no-heuristics` CLI filter pass) while preserving the stable payload contract documented in the report schema. See [ROADMAP.md](ROADMAP.md) for what is planned next. @@ -521,7 +521,7 @@ readers and AI search ingest. - [Check catalog](docs/checks.md) - [Policy packs](docs/policy-packs.md) - [Baseline workflow](docs/baseline.md) -- [JSON report schema v0.20](docs/report-schema.v0.20.json) +- [JSON report schema v0.21](docs/report-schema.v0.21.json) - [Privacy and redaction](docs/privacy.md) - [Trust model](docs/trust-model.md) - [AI search summary](docs/ai-search-summary.md) diff --git a/STABILITY.md b/STABILITY.md index c54b0ad..04ba84a 100644 --- a/STABILITY.md +++ b/STABILITY.md @@ -14,7 +14,7 @@ These commands and flags are stable across all `0.x.y` releases. They will only | Command | Stable flags | |---|---| -| `agents-shipgate scan` | `-c`, `--config`, `--out`, `--format`, `--ci-mode`, `--fail-on`, `--baseline`, `--diff-from`, `--no-plugins`, `--strict-plugins`, `--verbose`, `--workspace`, `--packet`/`--no-packet`, `--packet-format` | +| `agents-shipgate scan` | `-c`, `--config`, `--out`, `--format`, `--ci-mode`, `--fail-on`, `--baseline`, `--diff-from`, `--no-plugins`, `--strict-plugins`, `--no-heuristics`, `--verbose`, `--workspace`, `--packet`/`--no-packet`, `--packet-format` | | `agents-shipgate evidence-packet` | `--from`, `--out`, `--format`, `--json` | | `agents-shipgate scenario suggest` | `--from`, `--out` | | `agents-shipgate init` | `--workspace`, `--write`, `--json` | @@ -106,6 +106,7 @@ In `agents-shipgate-reports/report.json`, the following are guaranteed: - `policy_audit.severity_overrides_applied[].{check_id, default_severity, applied_severity, manifest_path, reason, tier_crossed, direction, expires}` (v0.17+ / M1) — top-of-report audit envelope for severity overrides applied during scan. Always present on emitted scans (empty when no overrides applied); required + non-nullable on the wire. `direction` is one of `downgrade | upgrade | same`. `tier_crossed=true` indicates the override crossed a severity tier boundary (critical / high / medium-low); tier-crossing downgrades require a matching `checks.acknowledge_overrides` entry, which is reflected in `reason`. `expires` is an ISO-8601 date carried from the matching acknowledgement (or the rich-form override entry); on/past this date the manifest fails to load with exit 2. - `privacy_audit.{enabled, rules_version, sensitive_field_inventory_version, redacted_occurrence_count, redacted_paths, output_surfaces, notes}` (v0.18+) — top-level audit envelope proving the default-on privacy layer ran before public artifacts were emitted. `redacted_paths[]` contains `{path, count, kinds}` aggregate rows only; it never includes raw values or raw-value hashes. Redaction is best-effort pattern/key based and does not claim complete secret-scanner coverage. - `reviewer_summary.{verdict, headline, tool_surface_changes, capability_misalignments, action_surface_changes, evidence_matrix_gaps, severity_overrides_applied, severity_overrides_tier_crossed, privacy_redactions, baseline_integrity_issues, first_recommended_surface}` (v0.20+) — top-level deterministic projection of the reviewer lens surfaces and audit envelopes; the reviewer-side parallel to `agent_summary`. Required + always present on emitted scans (mirroring the `agent_summary` contract). `verdict` mirrors `release_decision.decision` and is added/removed in lockstep with `AgentSummary.verdict` and `ReleaseDecisionStatus`. `first_recommended_surface` is `{kind, name, path, why}` where `kind` ∈ `{release_decision, lens, audit, evidence_matrix}` and `name` ∈ `{tool_surface_diff, capability_intent_diff, action_surface_diff, evidence_matrix, policy_audit, privacy_audit, baseline_integrity, release_decision}`; the pointer is `null` only when verdict is `passed` AND every count above is zero. The priority order encoded by `first_recommended_surface` is documented in [`docs/agent-contract-current.md`](docs/agent-contract-current.md). Same inputs always produce the same output; this block cannot disagree with the underlying lens/audit data. +- `heuristics_filter.{enabled, excluded_provenance_kinds, filtered_finding_count, filtered_by_kind}` (v0.21+) — top-level audit envelope describing the `--no-heuristics` CLI filter pass. Required + always present on emitted scans regardless of whether the flag was set (envelope shape is stable). When `enabled` is `False` the count fields are zero and no findings have been mutated by the filter. When `enabled` is `True`, every finding whose `provenance_kind` is in `excluded_provenance_kinds` has been marked `suppressed=True` with `suppression_reason="filtered by --no-heuristics"` BEFORE the release decision is built — those findings remain in `findings[]` for transparency but no longer gate release. `excluded_provenance_kinds` is the stable list `["keyword_heuristic", "regex_heuristic"]` (the only two `ProvenanceKind` values describing token/regex matches; `static_declaration`, `ast_extraction`, and `policy_pack` are never filtered). The filter never un-suppresses a finding; manifest-driven suppression reasons are preserved verbatim when they overlap with the filter (the envelope still counts the overlap so reviewers see the filter's effective scope). ### Privacy and redaction diff --git a/docs/INDEX.md b/docs/INDEX.md index e45cc42..b8478f8 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -21,10 +21,11 @@ A single entry point for human readers and AI agents walking the `docs/` tree. - [`checks.md`](checks.md) — full check catalog (human-readable) - [`checks.json`](checks.json) — machine-readable check catalog (regenerated each release) - [`manifest-v0.1.json`](manifest-v0.1.json) — JSON Schema for `shipgate.yaml` -- [`report-schema.v0.20.json`](report-schema.v0.20.json) — JSON Schema for `report.json` (current; emitted reports carry `report_schema_version: "0.20"`, adding the top-level `reviewer_summary` block — a deterministic projection of reviewer lens + audit surfaces parallel to v0.12's `agent_summary` — on top of v0.19's dual-source provenance and v0.18's privacy audit) +- [`report-schema.v0.21.json`](report-schema.v0.21.json) — JSON Schema for `report.json` (current; emitted reports carry `report_schema_version: "0.21"`, adding the top-level `heuristics_filter` envelope — the audit pass for the new `--no-heuristics` CLI flag — alongside v0.20's `reviewer_summary` block) - [`privacy.md`](privacy.md) and [`report-sensitive-fields.json`](report-sensitive-fields.json) — redaction behavior and report sensitive-field inventory - [`agent-action-guide.md`](agent-action-guide.md) — per-category recipe for what to do with a finding (canonical fix per check category, last-resort suppression rules) - [`upstream-integrations.md`](upstream-integrations.md) — per-framework 60-second drop-in for adding Shipgate to an existing project (OpenAI Agents SDK, LangChain, CrewAI, ADK, MCP-only, OpenAPI-only, OpenAI Messages API, Anthropic Messages API) +- [`report-schema.v0.20.json`](report-schema.v0.20.json) — frozen v0.20 reference schema; pre-v0.21 reports validate against this - [`report-schema.v0.19.json`](report-schema.v0.19.json) — frozen v0.19 reference schema; pre-v0.20 reports validate against this - [`report-schema.v0.18.json`](report-schema.v0.18.json) — frozen v0.18 reference schema; pre-v0.19 reports validate against this - [`report-schema.v0.17.json`](report-schema.v0.17.json) — frozen v0.17 reference schema; pre-v0.18 reports validate against this diff --git a/docs/agent-contract-current.md b/docs/agent-contract-current.md index 05ee6df..e64fd50 100644 --- a/docs/agent-contract-current.md +++ b/docs/agent-contract-current.md @@ -12,9 +12,9 @@ agents-shipgate contract --json - Latest release: `v0.10.0` (see [pyproject.toml](../pyproject.toml) for the in-tree version) - Runtime contract: `1` -- Current report schema: `0.20` — [`docs/report-schema.v0.20.json`](report-schema.v0.20.json) +- Current report schema: `0.21` — [`docs/report-schema.v0.21.json`](report-schema.v0.21.json) - Current packet schema: `0.6` — [`docs/packet-schema.v0.6.json`](packet-schema.v0.6.json) -- Frozen-reference report schemas: [`v0.19`](report-schema.v0.19.json), [`v0.18`](report-schema.v0.18.json), [`v0.17`](report-schema.v0.17.json), [`v0.16`](report-schema.v0.16.json), [`v0.15`](report-schema.v0.15.json), [`v0.14`](report-schema.v0.14.json), [`v0.13`](report-schema.v0.13.json), [`v0.12`](report-schema.v0.12.json), [`v0.11`](report-schema.v0.11.json), [`v0.10`](report-schema.v0.10.json), [`v0.9`](report-schema.v0.9.json), [`v0.8`](report-schema.v0.8.json), [`v0.7`](report-schema.v0.7.json), [`v0.6`](report-schema.v0.6.json), older +- Frozen-reference report schemas: [`v0.20`](report-schema.v0.20.json), [`v0.19`](report-schema.v0.19.json), [`v0.18`](report-schema.v0.18.json), [`v0.17`](report-schema.v0.17.json), [`v0.16`](report-schema.v0.16.json), [`v0.15`](report-schema.v0.15.json), [`v0.14`](report-schema.v0.14.json), [`v0.13`](report-schema.v0.13.json), [`v0.12`](report-schema.v0.12.json), [`v0.11`](report-schema.v0.11.json), [`v0.10`](report-schema.v0.10.json), [`v0.9`](report-schema.v0.9.json), [`v0.8`](report-schema.v0.8.json), [`v0.7`](report-schema.v0.7.json), [`v0.6`](report-schema.v0.6.json), older - Frozen-reference packet schemas live in [`docs/INDEX.md`](INDEX.md#reference). ## Read these first for release gating @@ -29,6 +29,7 @@ In `agents-shipgate-reports/report.json`: - `release_decision.contribution_rules[]` (v0.17+) — deterministic per-finding audit explaining how each `report.findings` entry was classified. Exactly one row per finding (including suppressed). Each row carries `{finding_id, fingerprint, check_id, category, rule, rationale}`. `category` ∈ `{blocker, review_item, excluded}`; `rule` ∈ `{policy_block_new, severity_block_new, policy_baseline_accepted, severity_baseline_accepted, review_required, sub_threshold, suppressed}`. Reading the contribution rule is sufficient to predict the gate outcome for that finding without re-deriving the decision logic — the closed grammar of `(rule, category)` pairs is documented in [STABILITY.md "Release decision truth table"](../STABILITY.md#release-decision-truth-table). The audit cannot disagree with `blockers[]` / `review_items[]` (the same classification powers both). - `privacy_audit` (v0.18+) — confirms the default redaction pass ran before public artifacts were written. Read `enabled`, `rules_version`, `sensitive_field_inventory_version`, `redacted_occurrence_count`, `redacted_paths[]`, and `output_surfaces[]`. `redacted_paths[]` contains structural paths and counts only, never raw values or raw hashes. - `reviewer_summary` (v0.20+) — deterministic projection of the reviewer lens surfaces and audit envelopes; the reviewer-side parallel to `agent_summary`. Read this block first when triaging a scan for a human reviewer. Carries `verdict` (mirrors `release_decision.decision`), `headline` (≤200 chars, PR-comment-friendly), per-lens activity counts (`tool_surface_changes`, `capability_misalignments`, `action_surface_changes`, `evidence_matrix_gaps`), per-audit-envelope counts (`severity_overrides_applied`, `severity_overrides_tier_crossed`, `privacy_redactions`, `baseline_integrity_issues`), and `first_recommended_surface: ReviewerSurfacePointer | None` — a deterministic pointer naming which lens/audit to open first (`{kind, name, path, why}` where `kind` ∈ `{release_decision, lens, audit, evidence_matrix}` and `name` ∈ `{tool_surface_diff, capability_intent_diff, action_surface_diff, evidence_matrix, policy_audit, privacy_audit, baseline_integrity, release_decision}`). Same inputs always produce the same output; this block cannot disagree with the underlying lens/audit data. +- `heuristics_filter` (v0.21+) — top-level audit envelope describing the `--no-heuristics` CLI filter pass. Always present, even when the flag is unset (`enabled: False` with zero counts), so the report shape is stable. Carries `enabled: bool`, `excluded_provenance_kinds: list[str]` (`["keyword_heuristic", "regex_heuristic"]`), `filtered_finding_count: int`, and `filtered_by_kind: dict[str, int]` (per-kind breakdown). When `enabled: True`, findings whose `provenance_kind` is in the excluded list have been marked `suppressed=True` with `suppression_reason="filtered by --no-heuristics"` BEFORE the release decision was built — they remain in `findings[]` for transparency but no longer gate release. The filter never un-suppresses a finding; manifest-driven suppression reasons are preserved when they overlap with the filter. Useful for security/GRC reviewers who want declared-only findings. The action exposes these as outputs `decision`, `blocker_count`, `review_item_count`, `ci_would_fail` (v0.8+). @@ -143,7 +144,7 @@ Companion prompt: [`prompts/explain-finding-to-user.md`](../prompts/explain-find - [STABILITY.md](../STABILITY.md) — full 0.x stability contract. Source of truth for everything above. - [AGENTS.md](../AGENTS.md) — agent-facing instructions: install, run, single-turn flow, error semantics. -- [`docs/report-schema.v0.20.json`](report-schema.v0.20.json) — machine-validatable JSON Schema for the current report. +- [`docs/report-schema.v0.21.json`](report-schema.v0.21.json) — machine-validatable JSON Schema for the current report. - [`docs/privacy.md`](privacy.md) and [`docs/report-sensitive-fields.json`](report-sensitive-fields.json) — default redaction behavior and sensitive-field inventory. - [`docs/packet-schema.v0.6.json`](packet-schema.v0.6.json) — machine-validatable JSON Schema for the current packet. - [`docs/checks.json`](checks.json) — check catalog. diff --git a/docs/ai-search-summary.md b/docs/ai-search-summary.md index 89f670f..c6bcb30 100644 --- a/docs/ai-search-summary.md +++ b/docs/ai-search-summary.md @@ -119,6 +119,6 @@ shipgate, and Agents-Shipgate. - Agent instructions: [`../AGENTS.md`](../AGENTS.md) - Machine-readable summary: [`../llms.txt`](../llms.txt) - Discovery metadata: [`../.well-known/agents-shipgate.json`](../.well-known/agents-shipgate.json) -- Report schema (current): [`report-schema.v0.20.json`](report-schema.v0.20.json) (v0.19 frozen at [`report-schema.v0.19.json`](report-schema.v0.19.json)) +- Report schema (current): [`report-schema.v0.21.json`](report-schema.v0.21.json) (v0.20 frozen at [`report-schema.v0.20.json`](report-schema.v0.20.json)) - Packet schema (current): [`packet-schema.v0.6.json`](packet-schema.v0.6.json) - Check catalog: [`checks.json`](checks.json) diff --git a/docs/architecture.md b/docs/architecture.md index 27857d5..cd217da 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,8 +2,8 @@ A single-page summary of the `agents-shipgate` codebase for new contributors and AI coding agents extending the project. Current as of -2026-05-22; auto-checked against `agents-shipgate contract --json`: -runtime contract `1`, report schema `v0.20`, packet schema `v0.6`. +2026-05-23; auto-checked against `agents-shipgate contract --json`: +runtime contract `1`, report schema `v0.21`, packet schema `v0.6`. For the per-field stability contract, see [`../STABILITY.md`](../STABILITY.md). For the agent-facing field index, @@ -101,6 +101,16 @@ core/findings.apply_* severity overrides, suppressions, v0.7 remediation annotation, v0.12 agent_action projection ↓ +apply_no_heuristics_filter v0.21: when --no-heuristics is set, + mark heuristic-provenance findings + suppressed BEFORE build_release_decision + runs, so excluded findings cannot + gate release. Runs after + apply_suppressions so manifest + intent wins on overlap. Always emits + the heuristics_filter envelope + (enabled=false when flag is unset). + ↓ core/privacy piecemeal redaction sanitize_model + redact_data on every public field; stats accumulate ↓ @@ -114,13 +124,16 @@ core/findings.build_report assemble ReadinessReport; internally calls build_release_decision ({blocked, insufficient_evidence, review_required, passed} + - contribution_rules[] audit); populates - agent_summary + policy_audit + privacy_audit + contribution_rules[] audit) over the + post-filter active set; populates + agent_summary + policy_audit + + privacy_audit + heuristics_filter ↓ apply_capability_diff mutate report from public tools ↓ build_reviewer_summary populate v0.20 reviewer_summary from - final lens/audit data + final lens/audit data (already + post-filter) ↓ report/{markdown,json,sarif} formatters write to agents-shipgate-reports/ packet/builder.build_packet Release Evidence Packet (v0.6) including @@ -223,6 +236,17 @@ For reviewer triage, `reviewer_summary` (v0.20) mirrors `release_decision.decision` and projects lens/audit activity counts plus `first_recommended_surface`. +For security/GRC reviewers who want declared-only findings, +`agents-shipgate scan --no-heuristics` (v0.21) marks +`keyword_heuristic` and `regex_heuristic` findings as suppressed +before the release decision is built. Filtered findings stay in +`findings[]` for audit but no longer gate release. The +`report.heuristics_filter` envelope records `enabled`, +`excluded_provenance_kinds`, `filtered_finding_count`, and a +per-kind breakdown — the audit pass for the filter. Earns the +contract weight of `Finding.provenance_kind` (shipped v0.15) by +giving it a first-class CLI consumer. + ## Determinism Two non-negotiable invariants: @@ -439,7 +463,7 @@ contract. Headlines: - **Manifest schema** stable across `0.x` (`version: "0.1"`). - **Report JSON shape** is additive across the `0.x` line. Current - `report_schema_version: "0.20"`; older schemas frozen as + `report_schema_version: "0.21"`; older schemas frozen as `docs/report-schema.v0.N.json`. - **Packet JSON shape** is additive across the `0.x` line. Current `packet_schema_version: "0.6"`; older schemas frozen. diff --git a/docs/autofix-policy.md b/docs/autofix-policy.md index 39ee23d..2576193 100644 --- a/docs/autofix-policy.md +++ b/docs/autofix-policy.md @@ -220,7 +220,7 @@ from the hash so toggling `--suggest-patches` doesn't shift it. - [`checks.md`](checks.md) — full check catalog with rationale. - [`minimal-real-configs.md`](minimal-real-configs.md) — per-framework minimal manifests to build from. -- [`report-schema.v0.20.json`](report-schema.v0.20.json) — current JSON +- [`report-schema.v0.21.json`](report-schema.v0.21.json) — current JSON Schema for `report.json`. - [`AGENTS.md`](../AGENTS.md) — top-level agent instructions, install, trigger table. diff --git a/docs/baseline.md b/docs/baseline.md index 866ba94..48d6f7e 100644 --- a/docs/baseline.md +++ b/docs/baseline.md @@ -37,7 +37,7 @@ fail CI. Reports keep the v0.1 payload contract and add baseline fields: -- `report_schema_version: "0.20"` in current reports +- `report_schema_version: "0.21"` in current reports - `baseline.path` - `baseline.matched_count` - `baseline.new_count` diff --git a/docs/examples.md b/docs/examples.md index ab733d6..6a48401 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -47,8 +47,8 @@ The canonical fixture writes: - `agents-shipgate-reports/report.sarif` when requested or when using the GitHub Action The JSON output is the stable contract for tools and coding agents. See -[report-schema.v0.20.json](report-schema.v0.20.json) (current; emitted reports -carry `report_schema_version: "0.20"`, adding the top-level `reviewer_summary` -block — a deterministic projection of reviewer-lens surfaces and audit envelopes -on top of v0.19's dual-source provenance fields; v0.19 frozen at -[report-schema.v0.19.json](report-schema.v0.19.json)). +[report-schema.v0.21.json](report-schema.v0.21.json) (current; emitted reports +carry `report_schema_version: "0.21"`, adding the top-level `heuristics_filter` +envelope — the audit pass for the new `--no-heuristics` CLI flag — alongside +v0.20's `reviewer_summary` block; v0.20 frozen at +[report-schema.v0.20.json](report-schema.v0.20.json)). diff --git a/docs/faq.md b/docs/faq.md index 41ca061..4eed321 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -94,7 +94,7 @@ schema. - **Markdown** — `agents-shipgate-reports/report.md`, for human review. - **JSON** — `agents-shipgate-reports/report.json`, machine-readable - (schema v0.20, current). Always parse this for programmatic use. + (schema v0.21, current). Always parse this for programmatic use. For release gating, read `release_decision.decision`; the legacy `summary.status` field is baseline-blind (kept for v0.7 callers). - **SARIF** — `agents-shipgate-reports/report.sarif`, compatible with diff --git a/docs/report-reading-for-agents.md b/docs/report-reading-for-agents.md index a3c99c9..407d24a 100644 --- a/docs/report-reading-for-agents.md +++ b/docs/report-reading-for-agents.md @@ -167,7 +167,7 @@ Surface the `next_action` to the user rather than scraping prose. The full diagn | Schema | Current | Frozen references | File | |---|---|---|---| -| Report | `0.20` | `0.19`, `0.18`, `0.17`, `0.16`, `0.15`, `0.14`, `0.13`, `0.12`, `0.11`, `0.10`, `0.9`, `0.8`, `0.7`, `0.6`, `0.5`, `0.4`, `0.3`, `0.2`, `0.1` | [`report-schema.v0.20.json`](report-schema.v0.20.json) | +| Report | `0.21` | `0.20`, `0.19`, `0.18`, `0.17`, `0.16`, `0.15`, `0.14`, `0.13`, `0.12`, `0.11`, `0.10`, `0.9`, `0.8`, `0.7`, `0.6`, `0.5`, `0.4`, `0.3`, `0.2`, `0.1` | [`report-schema.v0.21.json`](report-schema.v0.21.json) | | Packet | `0.6` | `0.5`, `0.4`, `0.3`, `0.2`, `0.1` | [`packet-schema.v0.6.json`](packet-schema.v0.6.json) | | Manifest | `0.1` | — | [`manifest-v0.1.json`](manifest-v0.1.json) | | CLI contract | `1` | — | `agents-shipgate contract --json` | diff --git a/docs/report-schema.v0.21.json b/docs/report-schema.v0.21.json new file mode 100644 index 0000000..359cd53 --- /dev/null +++ b/docs/report-schema.v0.21.json @@ -0,0 +1,4473 @@ +{ + "$defs": { + "ActionApprovalFact": { + "additionalProperties": false, + "properties": { + "required": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Required" + }, + "threshold": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Threshold" + } + }, + "required": [ + "required", + "threshold" + ], + "title": "ActionApprovalFact", + "type": "object" + }, + "ActionEvidenceFact": { + "additionalProperties": false, + "properties": { + "approval_ticket": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Approval Ticket" + }, + "owner": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Owner" + }, + "runbook": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Runbook" + } + }, + "required": [ + "approval_ticket", + "owner", + "runbook" + ], + "title": "ActionEvidenceFact", + "type": "object" + }, + "ActionFact": { + "additionalProperties": false, + "properties": { + "action_id": { + "title": "Action Id", + "type": "string" + }, + "agent_id": { + "title": "Agent Id", + "type": "string" + }, + "approval_policy": { + "$ref": "#/$defs/ActionApprovalFact" + }, + "effect": { + "enum": [ + "read", + "write", + "destructive", + "external_communication", + "financial_write", + "production_operation", + "privileged_data_access", + "code_execution", + "identity_access" + ], + "title": "Effect", + "type": "string" + }, + "evidence": { + "$ref": "#/$defs/ActionEvidenceFact" + }, + "hashes": { + "$ref": "#/$defs/ActionSurfaceHashes" + }, + "input_fields": { + "items": { + "type": "string" + }, + "title": "Input Fields", + "type": "array" + }, + "input_schema_hash": { + "title": "Input Schema Hash", + "type": "string" + }, + "operation": { + "title": "Operation", + "type": "string" + }, + "provider": { + "title": "Provider", + "type": "string" + }, + "required_input_fields": { + "items": { + "type": "string" + }, + "title": "Required Input Fields", + "type": "array" + }, + "required_scopes": { + "items": { + "type": "string" + }, + "title": "Required Scopes", + "type": "array" + }, + "risk_tags": { + "items": { + "type": "string" + }, + "title": "Risk Tags", + "type": "array" + }, + "safeguards": { + "$ref": "#/$defs/ActionSafeguardsFact" + }, + "source_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Id" + }, + "source_type": { + "title": "Source Type", + "type": "string" + }, + "tool_id": { + "title": "Tool Id", + "type": "string" + }, + "tool_name": { + "title": "Tool Name", + "type": "string" + } + }, + "required": [ + "action_id", + "agent_id", + "approval_policy", + "effect", + "evidence", + "hashes", + "input_fields", + "input_schema_hash", + "operation", + "provider", + "required_input_fields", + "required_scopes", + "risk_tags", + "safeguards", + "source_id", + "source_type", + "tool_id", + "tool_name" + ], + "title": "ActionFact", + "type": "object" + }, + "ActionSafeguardsFact": { + "additionalProperties": false, + "properties": { + "audit_log": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Audit Log" + }, + "dry_run": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Dry Run" + }, + "idempotency": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Idempotency" + }, + "rollback": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Rollback" + } + }, + "required": [ + "audit_log", + "dry_run", + "idempotency", + "rollback" + ], + "title": "ActionSafeguardsFact", + "type": "object" + }, + "ActionSurfaceChange": { + "additionalProperties": false, + "properties": { + "action_id": { + "title": "Action Id", + "type": "string" + }, + "added": { + "items": { + "type": "string" + }, + "title": "Added", + "type": "array" + }, + "after": { + "default": null, + "title": "After" + }, + "agent_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Id" + }, + "before": { + "default": null, + "title": "Before" + }, + "operation": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Operation" + }, + "reason": { + "title": "Reason", + "type": "string" + }, + "removed": { + "items": { + "type": "string" + }, + "title": "Removed", + "type": "array" + }, + "severity": { + "default": "info", + "enum": [ + "info", + "low", + "medium", + "high", + "critical" + ], + "title": "Severity", + "type": "string" + }, + "source_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Path" + }, + "source_start_line": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Start Line" + }, + "tool_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Tool Name" + }, + "type": { + "enum": [ + "ACTION_ADDED", + "ACTION_REMOVED", + "ACTION_MODIFIED", + "SCOPE_EXPANDED", + "EFFECT_ESCALATED", + "RISK_TAG_ADDED", + "APPROVAL_REMOVED", + "SAFEGUARD_REMOVED", + "INPUT_SCHEMA_EXPANDED" + ], + "title": "Type", + "type": "string" + } + }, + "required": [ + "action_id", + "added", + "after", + "agent_id", + "before", + "operation", + "reason", + "removed", + "severity", + "tool_name", + "type" + ], + "title": "ActionSurfaceChange", + "type": "object" + }, + "ActionSurfaceDiff": { + "additionalProperties": false, + "properties": { + "added": { + "items": { + "$ref": "#/$defs/ActionSurfaceChange" + }, + "title": "Added", + "type": "array" + }, + "base": { + "$ref": "#/$defs/ToolSurfaceDiffBase" + }, + "enabled": { + "default": false, + "title": "Enabled", + "type": "boolean" + }, + "modified": { + "items": { + "$ref": "#/$defs/ActionSurfaceChange" + }, + "title": "Modified", + "type": "array" + }, + "notes": { + "items": { + "type": "string" + }, + "title": "Notes", + "type": "array" + }, + "removed": { + "items": { + "$ref": "#/$defs/ActionSurfaceChange" + }, + "title": "Removed", + "type": "array" + }, + "summary": { + "$ref": "#/$defs/ActionSurfaceDiffSummary" + } + }, + "required": [ + "added", + "base", + "enabled", + "modified", + "notes", + "removed", + "summary" + ], + "title": "ActionSurfaceDiff", + "type": "object" + }, + "ActionSurfaceDiffSummary": { + "additionalProperties": false, + "properties": { + "actions_added": { + "default": 0, + "title": "Actions Added", + "type": "integer" + }, + "actions_modified": { + "default": 0, + "title": "Actions Modified", + "type": "integer" + }, + "actions_removed": { + "default": 0, + "title": "Actions Removed", + "type": "integer" + }, + "approvals_removed": { + "default": 0, + "title": "Approvals Removed", + "type": "integer" + }, + "blocking_findings": { + "default": 0, + "title": "Blocking Findings", + "type": "integer" + }, + "effect_escalations": { + "default": 0, + "title": "Effect Escalations", + "type": "integer" + }, + "input_schema_expansions": { + "default": 0, + "title": "Input Schema Expansions", + "type": "integer" + }, + "risk_tags_added": { + "default": 0, + "title": "Risk Tags Added", + "type": "integer" + }, + "safeguards_removed": { + "default": 0, + "title": "Safeguards Removed", + "type": "integer" + }, + "scope_expansions": { + "default": 0, + "title": "Scope Expansions", + "type": "integer" + } + }, + "required": [ + "actions_added", + "actions_modified", + "actions_removed", + "approvals_removed", + "blocking_findings", + "effect_escalations", + "input_schema_expansions", + "risk_tags_added", + "safeguards_removed", + "scope_expansions" + ], + "title": "ActionSurfaceDiffSummary", + "type": "object" + }, + "ActionSurfaceFacts": { + "additionalProperties": false, + "properties": { + "actions": { + "items": { + "$ref": "#/$defs/ActionFact" + }, + "title": "Actions", + "type": "array" + }, + "snapshot_version": { + "default": "0.1", + "title": "Snapshot Version", + "type": "string" + } + }, + "required": [ + "actions", + "snapshot_version" + ], + "title": "ActionSurfaceFacts", + "type": "object" + }, + "ActionSurfaceHashes": { + "additionalProperties": false, + "properties": { + "identity_hash": { + "title": "Identity Hash", + "type": "string" + }, + "policy_hash": { + "title": "Policy Hash", + "type": "string" + }, + "risk_hash": { + "title": "Risk Hash", + "type": "string" + }, + "schema_hash": { + "title": "Schema Hash", + "type": "string" + } + }, + "required": [ + "identity_hash", + "policy_hash", + "risk_hash", + "schema_hash" + ], + "title": "ActionSurfaceHashes", + "type": "object" + }, + "AgentSummary": { + "additionalProperties": false, + "description": "Top-level summary block shaped for one-fetch agent consumption.\n\nDeterministic projection of (``release_decision``, ``findings[].agent_action``).\nA coding agent that wants the headline numbers can read this block\ninstead of traversing arrays. All fields are derived; this block\ncannot disagree with the underlying data.", + "properties": { + "auto_appliable_patches": { + "default": 0, + "title": "Auto Appliable Patches", + "type": "integer" + }, + "blocker_count": { + "default": 0, + "title": "Blocker Count", + "type": "integer" + }, + "first_recommended_action": { + "anyOf": [ + { + "$ref": "#/$defs/AgentSummaryAction" + }, + { + "type": "null" + } + ], + "default": null + }, + "headline": { + "title": "Headline", + "type": "string" + }, + "needs_human_review": { + "default": 0, + "title": "Needs Human Review", + "type": "integer" + }, + "review_item_count": { + "default": 0, + "title": "Review Item Count", + "type": "integer" + }, + "verdict": { + "enum": [ + "blocked", + "review_required", + "insufficient_evidence", + "passed" + ], + "title": "Verdict", + "type": "string" + } + }, + "required": [ + "auto_appliable_patches", + "blocker_count", + "first_recommended_action", + "headline", + "needs_human_review", + "review_item_count", + "verdict" + ], + "title": "AgentSummary", + "type": "object" + }, + "AgentSummaryAction": { + "additionalProperties": false, + "description": "A single recommended next step shaped for direct agent consumption.\n\nMirrors the ``next_actions[]`` shape used elsewhere in the contract\n(kind/command/why) so callers that already handle diagnostic\nnext_actions can reuse the same renderer here.", + "properties": { + "command": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Command" + }, + "kind": { + "default": "command", + "enum": [ + "command", + "info" + ], + "title": "Kind", + "type": "string" + }, + "why": { + "title": "Why", + "type": "string" + } + }, + "required": [ + "command", + "kind", + "why" + ], + "title": "AgentSummaryAction", + "type": "object" + }, + "AppendPointerPatch": { + "additionalProperties": false, + "description": "Append a value to the list at a JSON pointer.", + "properties": { + "confidence": { + "enum": [ + "low", + "medium", + "high" + ], + "title": "Confidence", + "type": "string" + }, + "kind": { + "const": "append_pointer", + "default": "append_pointer", + "title": "Kind", + "type": "string" + }, + "pointer": { + "title": "Pointer", + "type": "string" + }, + "rationale": { + "title": "Rationale", + "type": "string" + }, + "target_file": { + "title": "Target File", + "type": "string" + }, + "target_format": { + "enum": [ + "yaml", + "json" + ], + "title": "Target Format", + "type": "string" + }, + "target_sha256": { + "title": "Target Sha256", + "type": "string" + }, + "value": { + "title": "Value" + } + }, + "required": [ + "target_file", + "pointer", + "value", + "target_format", + "confidence", + "rationale", + "target_sha256" + ], + "title": "AppendPointerPatch", + "type": "object" + }, + "BaselineDelta": { + "properties": { + "enabled": { + "title": "Enabled", + "type": "boolean" + }, + "matched_count": { + "default": 0, + "title": "Matched Count", + "type": "integer" + }, + "new_count": { + "default": 0, + "title": "New Count", + "type": "integer" + }, + "path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Path" + }, + "resolved_count": { + "default": 0, + "title": "Resolved Count", + "type": "integer" + } + }, + "required": [ + "enabled", + "matched_count", + "new_count", + "resolved_count" + ], + "title": "BaselineDelta", + "type": "object" + }, + "BaselineSummary": { + "properties": { + "matched_count": { + "default": 0, + "title": "Matched Count", + "type": "integer" + }, + "new_count": { + "default": 0, + "title": "New Count", + "type": "integer" + }, + "path": { + "title": "Path", + "type": "string" + }, + "resolved_count": { + "default": 0, + "title": "Resolved Count", + "type": "integer" + } + }, + "required": [ + "path" + ], + "title": "BaselineSummary", + "type": "object" + }, + "CapabilityFact": { + "properties": { + "auth_scopes": { + "items": { + "type": "string" + }, + "title": "Auth Scopes", + "type": "array" + }, + "capability": { + "title": "Capability", + "type": "string" + }, + "control_status": { + "enum": [ + "missing", + "partial", + "present", + "unknown" + ], + "title": "Control Status", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "included_reason": { + "enum": [ + "high_risk_tag", + "wildcard_exposure", + "referenced_by_critical_finding", + "referenced_by_high_finding", + "referenced_by_medium_finding" + ], + "title": "Included Reason", + "type": "string" + }, + "owner": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Owner" + }, + "related_findings": { + "items": { + "type": "string" + }, + "title": "Related Findings", + "type": "array" + }, + "risk_tags": { + "items": { + "type": "string" + }, + "title": "Risk Tags", + "type": "array" + }, + "source_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Ref" + }, + "source_type": { + "title": "Source Type", + "type": "string" + }, + "tool_name": { + "title": "Tool Name", + "type": "string" + } + }, + "required": [ + "auth_scopes", + "capability", + "control_status", + "id", + "included_reason", + "owner", + "related_findings", + "risk_tags", + "source_ref", + "source_type", + "tool_name" + ], + "title": "CapabilityFact", + "type": "object" + }, + "CodexPluginAppSummary": { + "additionalProperties": true, + "properties": { + "connector_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Connector Id" + }, + "location": { + "$ref": "#/$defs/CodexPluginSourceLocation" + }, + "name": { + "title": "Name", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "plugin": { + "title": "Plugin", + "type": "string" + } + }, + "required": [ + "plugin", + "name", + "path" + ], + "title": "CodexPluginAppSummary", + "type": "object" + }, + "CodexPluginComponentPathIssue": { + "additionalProperties": true, + "properties": { + "component": { + "title": "Component", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "plugin": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Plugin" + }, + "reason": { + "title": "Reason", + "type": "string" + }, + "source_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Ref" + } + }, + "required": [ + "component", + "path", + "reason" + ], + "title": "CodexPluginComponentPathIssue", + "type": "object" + }, + "CodexPluginHookStub": { + "additionalProperties": true, + "properties": { + "command": { + "title": "Command", + "type": "string" + }, + "confidence": { + "default": "medium", + "enum": [ + "low", + "medium", + "high" + ], + "title": "Confidence", + "type": "string" + }, + "location": { + "$ref": "#/$defs/CodexPluginSourceLocation" + }, + "name": { + "title": "Name", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "plugin": { + "title": "Plugin", + "type": "string" + }, + "risk_tags": { + "items": { + "type": "string" + }, + "title": "Risk Tags", + "type": "array" + } + }, + "required": [ + "plugin", + "name", + "command", + "path" + ], + "title": "CodexPluginHookStub", + "type": "object" + }, + "CodexPluginMarketplaceSummary": { + "additionalProperties": true, + "properties": { + "missing_policy_entries": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Missing Policy Entries", + "type": "array" + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Name" + }, + "path": { + "title": "Path", + "type": "string" + }, + "plugin_count": { + "default": 0, + "title": "Plugin Count", + "type": "integer" + }, + "skipped_entries": { + "items": { + "additionalProperties": true, + "type": "object" + }, + "title": "Skipped Entries", + "type": "array" + }, + "source_id": { + "title": "Source Id", + "type": "string" + } + }, + "required": [ + "source_id", + "path" + ], + "title": "CodexPluginMarketplaceSummary", + "type": "object" + }, + "CodexPluginMcpServerStub": { + "additionalProperties": true, + "properties": { + "command": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Command" + }, + "inventory_loaded": { + "default": false, + "title": "Inventory Loaded", + "type": "boolean" + }, + "inventory_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inventory Path" + }, + "location": { + "$ref": "#/$defs/CodexPluginSourceLocation" + }, + "path": { + "title": "Path", + "type": "string" + }, + "plugin": { + "title": "Plugin", + "type": "string" + }, + "server": { + "title": "Server", + "type": "string" + } + }, + "required": [ + "plugin", + "server", + "path" + ], + "title": "CodexPluginMcpServerStub", + "type": "object" + }, + "CodexPluginSkillSummary": { + "additionalProperties": true, + "properties": { + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "duplicate": { + "default": false, + "title": "Duplicate", + "type": "boolean" + }, + "location": { + "$ref": "#/$defs/CodexPluginSourceLocation" + }, + "missing_fields": { + "items": { + "type": "string" + }, + "title": "Missing Fields", + "type": "array" + }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Name" + }, + "path": { + "title": "Path", + "type": "string" + }, + "plugin": { + "title": "Plugin", + "type": "string" + } + }, + "required": [ + "plugin", + "path" + ], + "title": "CodexPluginSkillSummary", + "type": "object" + }, + "CodexPluginSourceLocation": { + "additionalProperties": false, + "properties": { + "source_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Path" + }, + "source_pointer": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Pointer" + }, + "source_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Ref" + }, + "source_start_column": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Start Column" + }, + "source_start_line": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Start Line" + } + }, + "title": "CodexPluginSourceLocation", + "type": "object" + }, + "CodexPluginSummary": { + "additionalProperties": true, + "properties": { + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "duplicate_name": { + "default": false, + "title": "Duplicate Name", + "type": "boolean" + }, + "duplicate_root": { + "default": false, + "title": "Duplicate Root", + "type": "boolean" + }, + "location": { + "$ref": "#/$defs/CodexPluginSourceLocation" + }, + "manifest_path": { + "title": "Manifest Path", + "type": "string" + }, + "marketplace": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Marketplace" + }, + "missing_fields": { + "items": { + "type": "string" + }, + "title": "Missing Fields", + "type": "array" + }, + "name": { + "title": "Name", + "type": "string" + }, + "name_mismatch": { + "default": false, + "title": "Name Mismatch", + "type": "boolean" + }, + "root_path": { + "title": "Root Path", + "type": "string" + }, + "source_id": { + "title": "Source Id", + "type": "string" + }, + "version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Version" + } + }, + "required": [ + "source_id", + "name", + "root_path", + "manifest_path" + ], + "title": "CodexPluginSummary", + "type": "object" + }, + "CodexPluginSurface": { + "additionalProperties": true, + "properties": { + "app_count": { + "default": 0, + "title": "App Count", + "type": "integer" + }, + "apps": { + "items": { + "$ref": "#/$defs/CodexPluginAppSummary" + }, + "title": "Apps", + "type": "array" + }, + "component_path_issues": { + "items": { + "$ref": "#/$defs/CodexPluginComponentPathIssue" + }, + "title": "Component Path Issues", + "type": "array" + }, + "hook_stub_count": { + "default": 0, + "title": "Hook Stub Count", + "type": "integer" + }, + "hook_stubs": { + "items": { + "$ref": "#/$defs/CodexPluginHookStub" + }, + "title": "Hook Stubs", + "type": "array" + }, + "marketplace_count": { + "default": 0, + "title": "Marketplace Count", + "type": "integer" + }, + "marketplaces": { + "items": { + "$ref": "#/$defs/CodexPluginMarketplaceSummary" + }, + "title": "Marketplaces", + "type": "array" + }, + "mcp_inventory_file_count": { + "default": 0, + "title": "Mcp Inventory File Count", + "type": "integer" + }, + "mcp_inventory_files": { + "items": { + "type": "string" + }, + "title": "Mcp Inventory Files", + "type": "array" + }, + "mcp_server_stub_count": { + "default": 0, + "title": "Mcp Server Stub Count", + "type": "integer" + }, + "mcp_server_stubs": { + "items": { + "$ref": "#/$defs/CodexPluginMcpServerStub" + }, + "title": "Mcp Server Stubs", + "type": "array" + }, + "plugin_count": { + "default": 0, + "title": "Plugin Count", + "type": "integer" + }, + "plugins": { + "items": { + "$ref": "#/$defs/CodexPluginSummary" + }, + "title": "Plugins", + "type": "array" + }, + "skill_count": { + "default": 0, + "title": "Skill Count", + "type": "integer" + }, + "skills": { + "items": { + "$ref": "#/$defs/CodexPluginSkillSummary" + }, + "title": "Skills", + "type": "array" + }, + "warnings": { + "items": { + "type": "string" + }, + "title": "Warnings", + "type": "array" + } + }, + "title": "CodexPluginSurface", + "type": "object" + }, + "ContributionRule": { + "additionalProperties": false, + "description": "Per-finding audit row explaining how a finding contributed to the\nrelease decision.\n\nAdditive in v0.17. Every finding in `report.findings` produces\nexactly one ContributionRule. Reading the contribution rule is\nsufficient to predict the gate outcome for that finding without\nre-deriving the decision logic; the set of valid `(rule, category)`\npairs is the contract documented in STABILITY.md \"Release decision\ntruth table\".", + "properties": { + "category": { + "enum": [ + "blocker", + "review_item", + "excluded" + ], + "title": "Category", + "type": "string" + }, + "check_id": { + "title": "Check Id", + "type": "string" + }, + "finding_id": { + "title": "Finding Id", + "type": "string" + }, + "fingerprint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Fingerprint" + }, + "rationale": { + "title": "Rationale", + "type": "string" + }, + "rule": { + "enum": [ + "policy_block_new", + "severity_block_new", + "policy_baseline_accepted", + "severity_baseline_accepted", + "review_required", + "sub_threshold", + "suppressed" + ], + "title": "Rule", + "type": "string" + } + }, + "required": [ + "category", + "check_id", + "finding_id", + "fingerprint", + "rationale", + "rule" + ], + "title": "ContributionRule", + "type": "object" + }, + "DeclaredIntention": { + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "intent_tags": { + "items": { + "type": "string" + }, + "title": "Intent Tags", + "type": "array" + }, + "kind": { + "enum": [ + "declared_purpose", + "prohibited_action", + "instruction_preview" + ], + "title": "Kind", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "text": { + "title": "Text", + "type": "string" + } + }, + "required": [ + "id", + "intent_tags", + "kind", + "source", + "text" + ], + "title": "DeclaredIntention", + "type": "object" + }, + "EvidenceCoverageDecision": { + "properties": { + "human_review_recommended": { + "title": "Human Review Recommended", + "type": "boolean" + }, + "level": { + "title": "Level", + "type": "string" + }, + "low_confidence_tool_count": { + "title": "Low Confidence Tool Count", + "type": "integer" + }, + "source_warning_count": { + "title": "Source Warning Count", + "type": "integer" + } + }, + "required": [ + "human_review_recommended", + "level", + "low_confidence_tool_count", + "source_warning_count" + ], + "title": "EvidenceCoverageDecision", + "type": "object" + }, + "FailPolicy": { + "properties": { + "ci_mode": { + "title": "Ci Mode", + "type": "string" + }, + "exit_code": { + "title": "Exit Code", + "type": "integer" + }, + "fail_on": { + "items": { + "enum": [ + "info", + "low", + "medium", + "high", + "critical" + ], + "type": "string" + }, + "title": "Fail On", + "type": "array" + }, + "new_findings_only": { + "default": false, + "title": "New Findings Only", + "type": "boolean" + }, + "would_fail_ci": { + "title": "Would Fail Ci", + "type": "boolean" + } + }, + "required": [ + "ci_mode", + "exit_code", + "fail_on", + "new_findings_only", + "would_fail_ci" + ], + "title": "FailPolicy", + "type": "object" + }, + "Finding": { + "additionalProperties": true, + "properties": { + "agent_action": { + "enum": [ + "auto_apply", + "propose_patch_for_review", + "escalate_to_human", + "suppress_with_reason", + "informational" + ], + "type": "string" + }, + "agent_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Agent Id" + }, + "autofix_safe": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Autofix Safe" + }, + "baseline_status": { + "anyOf": [ + { + "enum": [ + "new", + "matched", + "resolved" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline Status" + }, + "blocks_release": { + "default": false, + "title": "Blocks Release", + "type": "boolean" + }, + "category": { + "title": "Category", + "type": "string" + }, + "check_id": { + "title": "Check Id", + "type": "string" + }, + "confidence": { + "default": "medium", + "enum": [ + "low", + "medium", + "high" + ], + "title": "Confidence", + "type": "string" + }, + "docs_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Docs Url" + }, + "evidence": { + "additionalProperties": true, + "title": "Evidence", + "type": "object" + }, + "fingerprint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Fingerprint" + }, + "id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Id" + }, + "patches": { + "anyOf": [ + { + "items": { + "discriminator": { + "mapping": { + "append_pointer": "#/$defs/AppendPointerPatch", + "manual": "#/$defs/ManualPatch", + "remove_pointer": "#/$defs/RemovePointerPatch", + "set_pointer": "#/$defs/SetPointerPatch" + }, + "propertyName": "kind" + }, + "oneOf": [ + { + "$ref": "#/$defs/SetPointerPatch" + }, + { + "$ref": "#/$defs/AppendPointerPatch" + }, + { + "$ref": "#/$defs/RemovePointerPatch" + }, + { + "$ref": "#/$defs/ManualPatch" + } + ] + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Patches" + }, + "policy_evidence_source": { + "anyOf": [ + { + "$ref": "#/$defs/SourceReference" + }, + { + "type": "null" + } + ], + "default": null + }, + "provenance_kind": { + "enum": [ + "static_declaration", + "ast_extraction", + "keyword_heuristic", + "regex_heuristic", + "policy_pack" + ], + "type": "string" + }, + "recommendation": { + "title": "Recommendation", + "type": "string" + }, + "requires_human_review": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Requires Human Review" + }, + "severity": { + "enum": [ + "info", + "low", + "medium", + "high", + "critical" + ], + "title": "Severity", + "type": "string" + }, + "source": { + "anyOf": [ + { + "$ref": "#/$defs/SourceReference" + }, + { + "type": "null" + } + ], + "default": null + }, + "suggested_patch_kind": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Suggested Patch Kind" + }, + "suppressed": { + "default": false, + "title": "Suppressed", + "type": "boolean" + }, + "suppression_reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Suppression Reason" + }, + "title": { + "title": "Title", + "type": "string" + }, + "tool_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Tool Id" + }, + "tool_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Tool Name" + } + }, + "required": [ + "agent_action", + "baseline_status", + "blocks_release", + "category", + "check_id", + "confidence", + "evidence", + "fingerprint", + "id", + "provenance_kind", + "recommendation", + "severity", + "suppressed", + "title" + ], + "title": "Finding", + "type": "object" + }, + "HeuristicsFilter": { + "additionalProperties": false, + "description": "v0.21: top-level envelope describing the ``--no-heuristics``\nfilter pass.\n\nEmitted on every report regardless of whether the flag was set, so\nconsumers always read the same shape. When the flag is unset,\n``enabled=False`` and the count fields are zero \u2014 the active\nfinding set is unchanged. When the flag is set, every finding whose\n``provenance_kind`` is in ``excluded_provenance_kinds`` is marked\n``suppressed=True`` with ``suppression_reason=\"filtered by\n--no-heuristics\"`` BEFORE the release decision is built, so heuristic\nfindings can no longer gate release. Filtered findings stay in\n``findings[]`` for transparency; the audit envelope here records\naggregate counts.\n\nEarns the contract weight of ``Finding.provenance_kind`` (shipped\nv0.15) by giving it a first-class consumer. Same shape pattern as\n``PrivacyAudit``: an envelope that proves the filter ran and tells\na reviewer/agent which findings were excluded and why.", + "properties": { + "enabled": { + "default": false, + "title": "Enabled", + "type": "boolean" + }, + "excluded_provenance_kinds": { + "items": { + "type": "string" + }, + "title": "Excluded Provenance Kinds", + "type": "array" + }, + "filtered_by_kind": { + "additionalProperties": { + "type": "integer" + }, + "title": "Filtered By Kind", + "type": "object" + }, + "filtered_finding_count": { + "default": 0, + "title": "Filtered Finding Count", + "type": "integer" + } + }, + "required": [ + "enabled", + "excluded_provenance_kinds", + "filtered_by_kind", + "filtered_finding_count" + ], + "title": "HeuristicsFilter", + "type": "object" + }, + "LoadedPolicyPack": { + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "rule_count": { + "title": "Rule Count", + "type": "integer" + }, + "version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Version" + } + }, + "required": [ + "id", + "name", + "path", + "rule_count" + ], + "title": "LoadedPolicyPack", + "type": "object" + }, + "ManualPatch": { + "additionalProperties": false, + "description": "No machine-applicable change. Carries human-readable instructions.\n\nUsed for every finding whose check ID has no v0.6 non-manual generator\nand for findings (like trace flips, per C6) that are intentionally\nnever auto-patched.", + "properties": { + "instructions": { + "title": "Instructions", + "type": "string" + }, + "kind": { + "const": "manual", + "default": "manual", + "title": "Kind", + "type": "string" + } + }, + "required": [ + "instructions" + ], + "title": "ManualPatch", + "type": "object" + }, + "Misalignment": { + "properties": { + "capability_refs": { + "items": { + "type": "string" + }, + "title": "Capability Refs", + "type": "array" + }, + "finding_refs": { + "items": { + "type": "string" + }, + "title": "Finding Refs", + "type": "array" + }, + "gap": { + "title": "Gap", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "intention_refs": { + "items": { + "type": "string" + }, + "title": "Intention Refs", + "type": "array" + }, + "kind": { + "enum": [ + "policy_gap", + "scope_drift", + "prohibited_action_present", + "control_missing", + "intent_mismatch", + "undetected_gap" + ], + "title": "Kind", + "type": "string" + }, + "policy_requirement": { + "title": "Policy Requirement", + "type": "string" + }, + "release_implication": { + "title": "Release Implication", + "type": "string" + }, + "severity": { + "enum": [ + "info", + "low", + "medium", + "high", + "critical" + ], + "title": "Severity", + "type": "string" + }, + "tool_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Tool Name" + } + }, + "required": [ + "capability_refs", + "finding_refs", + "gap", + "id", + "intention_refs", + "kind", + "policy_requirement", + "release_implication", + "severity", + "tool_name" + ], + "title": "Misalignment", + "type": "object" + }, + "PolicyAudit": { + "additionalProperties": false, + "description": "v0.17 (M1) top-of-report audit envelope for policy decisions\napplied during scan.\n\nCarries severity-override audit today; M2 (baseline integrity) and\nM5 (plugin validation) will land sibling fields here so the audit\nenvelope stays stable across the trust-hardening releases.", + "properties": { + "severity_overrides_applied": { + "items": { + "$ref": "#/$defs/SeverityOverrideAuditEntry" + }, + "title": "Severity Overrides Applied", + "type": "array" + } + }, + "title": "PolicyAudit", + "type": "object" + }, + "PrivacyAudit": { + "additionalProperties": false, + "description": "Top-level audit envelope proving the default redaction pass ran.", + "properties": { + "enabled": { + "default": true, + "title": "Enabled", + "type": "boolean" + }, + "notes": { + "items": { + "type": "string" + }, + "title": "Notes", + "type": "array" + }, + "output_surfaces": { + "items": { + "type": "string" + }, + "title": "Output Surfaces", + "type": "array" + }, + "redacted_occurrence_count": { + "default": 0, + "title": "Redacted Occurrence Count", + "type": "integer" + }, + "redacted_paths": { + "items": { + "$ref": "#/$defs/RedactedPathSummary" + }, + "title": "Redacted Paths", + "type": "array" + }, + "rules_version": { + "title": "Rules Version", + "type": "string" + }, + "sensitive_field_inventory_version": { + "title": "Sensitive Field Inventory Version", + "type": "string" + } + }, + "required": [ + "enabled", + "notes", + "output_surfaces", + "redacted_occurrence_count", + "redacted_paths", + "rules_version", + "sensitive_field_inventory_version" + ], + "title": "PrivacyAudit", + "type": "object" + }, + "RedactedPathSummary": { + "additionalProperties": false, + "description": "One privacy-audit row for a structural output path.\n\nThe row intentionally carries only aggregate counts and secret kinds,\nnever the original value or a hash/verifier of that value.", + "properties": { + "count": { + "default": 0, + "title": "Count", + "type": "integer" + }, + "kinds": { + "items": { + "type": "string" + }, + "title": "Kinds", + "type": "array" + }, + "path": { + "title": "Path", + "type": "string" + } + }, + "required": [ + "count", + "kinds", + "path" + ], + "title": "RedactedPathSummary", + "type": "object" + }, + "ReleaseConsequence": { + "properties": { + "blocker_misalignment_count": { + "default": 0, + "title": "Blocker Misalignment Count", + "type": "integer" + }, + "decision": { + "enum": [ + "blocked", + "review_required", + "insufficient_evidence", + "passed" + ], + "title": "Decision", + "type": "string" + }, + "fail_policy": { + "$ref": "#/$defs/FailPolicy" + }, + "review_misalignment_count": { + "default": 0, + "title": "Review Misalignment Count", + "type": "integer" + }, + "summary": { + "title": "Summary", + "type": "string" + } + }, + "required": [ + "blocker_misalignment_count", + "decision", + "fail_policy", + "review_misalignment_count", + "summary" + ], + "title": "ReleaseConsequence", + "type": "object" + }, + "ReleaseDecision": { + "properties": { + "baseline_delta": { + "$ref": "#/$defs/BaselineDelta" + }, + "blockers": { + "items": { + "$ref": "#/$defs/ReleaseDecisionItem" + }, + "title": "Blockers", + "type": "array" + }, + "contribution_rules": { + "items": { + "$ref": "#/$defs/ContributionRule" + }, + "title": "Contribution Rules", + "type": "array" + }, + "decision": { + "enum": [ + "blocked", + "review_required", + "insufficient_evidence", + "passed" + ], + "title": "Decision", + "type": "string" + }, + "evidence_coverage": { + "$ref": "#/$defs/EvidenceCoverageDecision" + }, + "fail_policy": { + "$ref": "#/$defs/FailPolicy" + }, + "reason": { + "title": "Reason", + "type": "string" + }, + "review_items": { + "items": { + "$ref": "#/$defs/ReleaseDecisionItem" + }, + "title": "Review Items", + "type": "array" + } + }, + "required": [ + "baseline_delta", + "blockers", + "contribution_rules", + "decision", + "evidence_coverage", + "fail_policy", + "reason", + "review_items" + ], + "title": "ReleaseDecision", + "type": "object" + }, + "ReleaseDecisionItem": { + "properties": { + "baseline_status": { + "anyOf": [ + { + "enum": [ + "new", + "matched", + "resolved" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline Status" + }, + "blocks_release": { + "default": false, + "title": "Blocks Release", + "type": "boolean" + }, + "check_id": { + "title": "Check Id", + "type": "string" + }, + "fingerprint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Fingerprint" + }, + "id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Id" + }, + "policy_evidence_source": { + "anyOf": [ + { + "$ref": "#/$defs/SourceReference" + }, + { + "type": "null" + } + ], + "default": null + }, + "severity": { + "enum": [ + "info", + "low", + "medium", + "high", + "critical" + ], + "title": "Severity", + "type": "string" + }, + "source": { + "anyOf": [ + { + "$ref": "#/$defs/SourceReference" + }, + { + "type": "null" + } + ], + "default": null + }, + "title": { + "title": "Title", + "type": "string" + } + }, + "required": [ + "baseline_status", + "blocks_release", + "check_id", + "fingerprint", + "id", + "severity", + "title" + ], + "title": "ReleaseDecisionItem", + "type": "object" + }, + "RemovePointerPatch": { + "additionalProperties": false, + "description": "Remove the node at a JSON pointer.", + "properties": { + "confidence": { + "enum": [ + "low", + "medium", + "high" + ], + "title": "Confidence", + "type": "string" + }, + "kind": { + "const": "remove_pointer", + "default": "remove_pointer", + "title": "Kind", + "type": "string" + }, + "pointer": { + "title": "Pointer", + "type": "string" + }, + "rationale": { + "title": "Rationale", + "type": "string" + }, + "target_file": { + "title": "Target File", + "type": "string" + }, + "target_format": { + "enum": [ + "yaml", + "json" + ], + "title": "Target Format", + "type": "string" + }, + "target_sha256": { + "title": "Target Sha256", + "type": "string" + } + }, + "required": [ + "target_file", + "pointer", + "target_format", + "confidence", + "rationale", + "target_sha256" + ], + "title": "RemovePointerPatch", + "type": "object" + }, + "ReportSummary": { + "properties": { + "critical_count": { + "default": 0, + "title": "Critical Count", + "type": "integer" + }, + "evidence_coverage": { + "default": "static", + "title": "Evidence Coverage", + "type": "string" + }, + "high_count": { + "default": 0, + "title": "High Count", + "type": "integer" + }, + "human_review_recommended": { + "default": false, + "title": "Human Review Recommended", + "type": "boolean" + }, + "info_count": { + "default": 0, + "title": "Info Count", + "type": "integer" + }, + "low_count": { + "default": 0, + "title": "Low Count", + "type": "integer" + }, + "medium_count": { + "default": 0, + "title": "Medium Count", + "type": "integer" + }, + "status": { + "title": "Status", + "type": "string" + }, + "suppressed_count": { + "default": 0, + "title": "Suppressed Count", + "type": "integer" + } + }, + "required": [ + "status" + ], + "title": "ReportSummary", + "type": "object" + }, + "ReviewerSummary": { + "additionalProperties": false, + "description": "Top-level summary block shaped for one-fetch reviewer consumption.\n\nDeterministic projection of the reviewer lens surfaces\n(``tool_surface_diff``, capability/intent diff, ``action_surface_diff``,\nevidence matrix) and the audit envelopes (``policy_audit``,\n``privacy_audit``, baseline integrity findings). A reviewer who wants\nheadline activity counts and a recommended starting surface can read\nthis block instead of opening every lens / audit envelope.\n\nParallels ``AgentSummary`` but for the audit/lens dimensions:\n``AgentSummary`` answers \"what should an agent do next?\" and\n``ReviewerSummary`` answers \"what should a reviewer look at first?\".\n\nAll fields are derived; this block cannot disagree with the\nunderlying lens/audit data.", + "properties": { + "action_surface_changes": { + "default": 0, + "title": "Action Surface Changes", + "type": "integer" + }, + "baseline_integrity_issues": { + "default": 0, + "title": "Baseline Integrity Issues", + "type": "integer" + }, + "capability_misalignments": { + "default": 0, + "title": "Capability Misalignments", + "type": "integer" + }, + "evidence_matrix_gaps": { + "default": 0, + "title": "Evidence Matrix Gaps", + "type": "integer" + }, + "first_recommended_surface": { + "anyOf": [ + { + "$ref": "#/$defs/ReviewerSurfacePointer" + }, + { + "type": "null" + } + ], + "default": null + }, + "headline": { + "title": "Headline", + "type": "string" + }, + "privacy_redactions": { + "default": 0, + "title": "Privacy Redactions", + "type": "integer" + }, + "severity_overrides_applied": { + "default": 0, + "title": "Severity Overrides Applied", + "type": "integer" + }, + "severity_overrides_tier_crossed": { + "default": 0, + "title": "Severity Overrides Tier Crossed", + "type": "integer" + }, + "tool_surface_changes": { + "default": 0, + "title": "Tool Surface Changes", + "type": "integer" + }, + "verdict": { + "enum": [ + "blocked", + "review_required", + "insufficient_evidence", + "passed" + ], + "title": "Verdict", + "type": "string" + } + }, + "required": [ + "action_surface_changes", + "baseline_integrity_issues", + "capability_misalignments", + "evidence_matrix_gaps", + "first_recommended_surface", + "headline", + "privacy_redactions", + "severity_overrides_applied", + "severity_overrides_tier_crossed", + "tool_surface_changes", + "verdict" + ], + "title": "ReviewerSummary", + "type": "object" + }, + "ReviewerSurfacePointer": { + "additionalProperties": false, + "description": "A single recommended reviewer entry point.\n\nMirrors the ``next_actions[]`` shape (kind/path/why) used by other\ncontract surfaces so the same renderer pattern works here. ``kind``\nclassifies the surface family (``release_decision`` /``lens`` /\n``audit`` /``evidence_matrix``); ``name`` is the canonical\nmachine-readable identifier; ``path`` is a dotted JSON path the\nreviewer can use to navigate. ``why`` is one sentence of reviewer\nrationale, suitable for a PR comment lead.", + "properties": { + "kind": { + "enum": [ + "release_decision", + "lens", + "audit", + "evidence_matrix" + ], + "title": "Kind", + "type": "string" + }, + "name": { + "enum": [ + "tool_surface_diff", + "capability_intent_diff", + "action_surface_diff", + "evidence_matrix", + "policy_audit", + "privacy_audit", + "baseline_integrity", + "release_decision" + ], + "title": "Name", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "why": { + "title": "Why", + "type": "string" + } + }, + "required": [ + "kind", + "name", + "path", + "why" + ], + "title": "ReviewerSurfacePointer", + "type": "object" + }, + "SetPointerPatch": { + "additionalProperties": false, + "description": "Set the value at a JSON pointer inside a YAML or JSON file.", + "properties": { + "confidence": { + "enum": [ + "low", + "medium", + "high" + ], + "title": "Confidence", + "type": "string" + }, + "kind": { + "const": "set_pointer", + "default": "set_pointer", + "title": "Kind", + "type": "string" + }, + "pointer": { + "title": "Pointer", + "type": "string" + }, + "rationale": { + "title": "Rationale", + "type": "string" + }, + "target_file": { + "title": "Target File", + "type": "string" + }, + "target_format": { + "enum": [ + "yaml", + "json" + ], + "title": "Target Format", + "type": "string" + }, + "target_sha256": { + "title": "Target Sha256", + "type": "string" + }, + "value": { + "title": "Value" + } + }, + "required": [ + "target_file", + "pointer", + "value", + "target_format", + "confidence", + "rationale", + "target_sha256" + ], + "title": "SetPointerPatch", + "type": "object" + }, + "SeverityOverrideAuditEntry": { + "additionalProperties": false, + "description": "One row in ``ReadinessReport.policy_audit.severity_overrides_applied``.\n\nv0.17 (M1). Surfaces every manifest-driven severity override so a\nreviewer can see what was downgraded (or upgraded) without diving\ninto per-finding evidence. Emitted regardless of whether the override\nmatched any active finding \u2014 entries for checks that did not fire\nstill document reviewer intent.", + "properties": { + "applied_severity": { + "enum": [ + "info", + "low", + "medium", + "high", + "critical" + ], + "title": "Applied Severity", + "type": "string" + }, + "check_id": { + "title": "Check Id", + "type": "string" + }, + "default_severity": { + "enum": [ + "info", + "low", + "medium", + "high", + "critical" + ], + "title": "Default Severity", + "type": "string" + }, + "direction": { + "default": "same", + "enum": [ + "downgrade", + "upgrade", + "same" + ], + "title": "Direction", + "type": "string" + }, + "expires": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires" + }, + "manifest_path": { + "title": "Manifest Path", + "type": "string" + }, + "reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Reason" + }, + "tier_crossed": { + "default": false, + "title": "Tier Crossed", + "type": "boolean" + } + }, + "required": [ + "check_id", + "default_severity", + "applied_severity", + "manifest_path" + ], + "title": "SeverityOverrideAuditEntry", + "type": "object" + }, + "SourceReference": { + "additionalProperties": true, + "properties": { + "end_line": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "End Line" + }, + "location": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Location" + }, + "path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Path" + }, + "pointer": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Pointer" + }, + "ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Ref" + }, + "start_column": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Start Column" + }, + "start_line": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Start Line" + }, + "type": { + "title": "Type", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "SourceReference", + "type": "object" + }, + "SuggestedScenario": { + "properties": { + "expected_control": { + "title": "Expected Control", + "type": "string" + }, + "given": { + "title": "Given", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "scenario_type": { + "enum": [ + "approval", + "confirmation", + "idempotency_retry", + "least_privilege_scope", + "prohibited_action", + "wildcard_inventory", + "schema_boundary", + "prompt_scope_alignment", + "test_case_coverage" + ], + "title": "Scenario Type", + "type": "string" + }, + "source_findings": { + "items": { + "type": "string" + }, + "title": "Source Findings", + "type": "array" + }, + "source_misalignments": { + "items": { + "type": "string" + }, + "title": "Source Misalignments", + "type": "array" + }, + "title": { + "title": "Title", + "type": "string" + } + }, + "required": [ + "expected_control", + "given", + "id", + "scenario_type", + "source_findings", + "source_misalignments", + "title" + ], + "title": "SuggestedScenario", + "type": "object" + }, + "ToolSurfaceControlChange": { + "additionalProperties": false, + "properties": { + "control": { + "enum": [ + "approval_policy", + "confirmation_policy", + "idempotency_evidence" + ], + "title": "Control", + "type": "string" + }, + "kind": { + "enum": [ + "added", + "removed", + "changed" + ], + "title": "Kind", + "type": "string" + }, + "reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Reason" + }, + "source": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source" + }, + "source_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Path" + }, + "source_start_line": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Start Line" + }, + "tool": { + "title": "Tool", + "type": "string" + } + }, + "required": [ + "control", + "kind", + "reason", + "source", + "tool" + ], + "title": "ToolSurfaceControlChange", + "type": "object" + }, + "ToolSurfaceControlFact": { + "additionalProperties": false, + "properties": { + "kind": { + "enum": [ + "approval_policy", + "confirmation_policy", + "idempotency_evidence" + ], + "title": "Kind", + "type": "string" + }, + "reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Reason" + }, + "source": { + "title": "Source", + "type": "string" + }, + "tool": { + "title": "Tool", + "type": "string" + } + }, + "required": [ + "kind", + "reason", + "source", + "tool" + ], + "title": "ToolSurfaceControlFact", + "type": "object" + }, + "ToolSurfaceDiff": { + "additionalProperties": false, + "properties": { + "base": { + "$ref": "#/$defs/ToolSurfaceDiffBase" + }, + "controls": { + "items": { + "$ref": "#/$defs/ToolSurfaceControlChange" + }, + "title": "Controls", + "type": "array" + }, + "enabled": { + "default": false, + "title": "Enabled", + "type": "boolean" + }, + "finding_deltas": { + "$ref": "#/$defs/ToolSurfaceFindingDeltas" + }, + "high_risk_effects": { + "items": { + "$ref": "#/$defs/ToolSurfaceHighRiskEffectChange" + }, + "title": "High Risk Effects", + "type": "array" + }, + "metadata_changes": { + "items": { + "$ref": "#/$defs/ToolSurfaceMetadataChange" + }, + "title": "Metadata Changes", + "type": "array" + }, + "notes": { + "items": { + "type": "string" + }, + "title": "Notes", + "type": "array" + }, + "policy_drift": { + "items": { + "$ref": "#/$defs/ToolSurfacePolicyDrift" + }, + "title": "Policy Drift", + "type": "array" + }, + "scopes": { + "items": { + "$ref": "#/$defs/ToolSurfaceScopeChange" + }, + "title": "Scopes", + "type": "array" + }, + "summary": { + "$ref": "#/$defs/ToolSurfaceDiffSummary" + }, + "tools": { + "items": { + "$ref": "#/$defs/ToolSurfaceToolChange" + }, + "title": "Tools", + "type": "array" + } + }, + "required": [ + "base", + "controls", + "enabled", + "finding_deltas", + "high_risk_effects", + "metadata_changes", + "notes", + "policy_drift", + "scopes", + "summary", + "tools" + ], + "title": "ToolSurfaceDiff", + "type": "object" + }, + "ToolSurfaceDiffBase": { + "additionalProperties": false, + "properties": { + "baseline_schema_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline Schema Version" + }, + "kind": { + "default": "none", + "enum": [ + "none", + "report", + "baseline" + ], + "title": "Kind", + "type": "string" + }, + "path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Path" + }, + "report_schema_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Report Schema Version" + } + }, + "required": [ + "baseline_schema_version", + "kind", + "path", + "report_schema_version" + ], + "title": "ToolSurfaceDiffBase", + "type": "object" + }, + "ToolSurfaceDiffSummary": { + "additionalProperties": false, + "properties": { + "accepted_debt": { + "default": 0, + "title": "Accepted Debt", + "type": "integer" + }, + "controls_added": { + "default": 0, + "title": "Controls Added", + "type": "integer" + }, + "controls_removed": { + "default": 0, + "title": "Controls Removed", + "type": "integer" + }, + "metadata_changes": { + "default": 0, + "title": "Metadata Changes", + "type": "integer" + }, + "new_findings": { + "default": 0, + "title": "New Findings", + "type": "integer" + }, + "new_high_risk_effects": { + "default": 0, + "title": "New High Risk Effects", + "type": "integer" + }, + "new_scopes": { + "default": 0, + "title": "New Scopes", + "type": "integer" + }, + "policy_drift_items": { + "default": 0, + "title": "Policy Drift Items", + "type": "integer" + }, + "removed_high_risk_effects": { + "default": 0, + "title": "Removed High Risk Effects", + "type": "integer" + }, + "removed_scopes": { + "default": 0, + "title": "Removed Scopes", + "type": "integer" + }, + "resolved_findings": { + "default": 0, + "title": "Resolved Findings", + "type": "integer" + }, + "tools_added": { + "default": 0, + "title": "Tools Added", + "type": "integer" + }, + "tools_changed": { + "default": 0, + "title": "Tools Changed", + "type": "integer" + }, + "tools_removed": { + "default": 0, + "title": "Tools Removed", + "type": "integer" + }, + "unchanged_findings": { + "default": 0, + "title": "Unchanged Findings", + "type": "integer" + } + }, + "required": [ + "accepted_debt", + "controls_added", + "controls_removed", + "metadata_changes", + "new_findings", + "new_high_risk_effects", + "new_scopes", + "policy_drift_items", + "removed_high_risk_effects", + "removed_scopes", + "resolved_findings", + "tools_added", + "tools_changed", + "tools_removed", + "unchanged_findings" + ], + "title": "ToolSurfaceDiffSummary", + "type": "object" + }, + "ToolSurfaceFacts": { + "additionalProperties": false, + "properties": { + "controls": { + "items": { + "$ref": "#/$defs/ToolSurfaceControlFact" + }, + "title": "Controls", + "type": "array" + }, + "policies": { + "items": { + "$ref": "#/$defs/ToolSurfacePolicyFact" + }, + "title": "Policies", + "type": "array" + }, + "scopes": { + "items": { + "$ref": "#/$defs/ToolSurfaceScopeFact" + }, + "title": "Scopes", + "type": "array" + }, + "tools": { + "items": { + "$ref": "#/$defs/ToolSurfaceToolFact" + }, + "title": "Tools", + "type": "array" + } + }, + "required": [ + "controls", + "policies", + "scopes", + "tools" + ], + "title": "ToolSurfaceFacts", + "type": "object" + }, + "ToolSurfaceFieldChange": { + "additionalProperties": false, + "properties": { + "after": { + "default": null, + "title": "After" + }, + "before": { + "default": null, + "title": "Before" + }, + "field": { + "title": "Field", + "type": "string" + } + }, + "required": [ + "after", + "before", + "field" + ], + "title": "ToolSurfaceFieldChange", + "type": "object" + }, + "ToolSurfaceFindingDeltaItem": { + "additionalProperties": false, + "properties": { + "baseline_status": { + "anyOf": [ + { + "enum": [ + "new", + "matched", + "resolved" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline Status" + }, + "check_id": { + "title": "Check Id", + "type": "string" + }, + "fingerprint": { + "title": "Fingerprint", + "type": "string" + }, + "severity": { + "enum": [ + "info", + "low", + "medium", + "high", + "critical" + ], + "title": "Severity", + "type": "string" + }, + "title": { + "title": "Title", + "type": "string" + }, + "tool_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Tool Name" + } + }, + "required": [ + "baseline_status", + "check_id", + "fingerprint", + "severity", + "title", + "tool_name" + ], + "title": "ToolSurfaceFindingDeltaItem", + "type": "object" + }, + "ToolSurfaceFindingDeltas": { + "additionalProperties": false, + "properties": { + "accepted_debt": { + "items": { + "$ref": "#/$defs/ToolSurfaceFindingDeltaItem" + }, + "title": "Accepted Debt", + "type": "array" + }, + "new_findings": { + "items": { + "$ref": "#/$defs/ToolSurfaceFindingDeltaItem" + }, + "title": "New Findings", + "type": "array" + }, + "resolved_findings": { + "items": { + "$ref": "#/$defs/ToolSurfaceFindingDeltaItem" + }, + "title": "Resolved Findings", + "type": "array" + }, + "unchanged_findings": { + "items": { + "$ref": "#/$defs/ToolSurfaceFindingDeltaItem" + }, + "title": "Unchanged Findings", + "type": "array" + } + }, + "required": [ + "accepted_debt", + "new_findings", + "resolved_findings", + "unchanged_findings" + ], + "title": "ToolSurfaceFindingDeltas", + "type": "object" + }, + "ToolSurfaceHashes": { + "additionalProperties": false, + "properties": { + "annotations": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Annotations" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Description" + }, + "input_schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Input Schema" + }, + "output_schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Output Schema" + }, + "parameters": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Parameters" + }, + "source_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Ref" + } + }, + "required": [ + "annotations", + "description", + "input_schema", + "output_schema", + "parameters", + "source_ref" + ], + "title": "ToolSurfaceHashes", + "type": "object" + }, + "ToolSurfaceHighRiskEffectChange": { + "additionalProperties": false, + "properties": { + "kind": { + "enum": [ + "added", + "removed", + "changed" + ], + "title": "Kind", + "type": "string" + }, + "source_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Path" + }, + "source_start_line": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Start Line" + }, + "tag": { + "title": "Tag", + "type": "string" + }, + "tool": { + "title": "Tool", + "type": "string" + } + }, + "required": [ + "kind", + "tag", + "tool" + ], + "title": "ToolSurfaceHighRiskEffectChange", + "type": "object" + }, + "ToolSurfaceMetadataChange": { + "additionalProperties": false, + "properties": { + "after": { + "default": null, + "title": "After" + }, + "before": { + "default": null, + "title": "Before" + }, + "kind": { + "enum": [ + "added", + "removed", + "changed" + ], + "title": "Kind", + "type": "string" + }, + "metadata": { + "title": "Metadata", + "type": "string" + }, + "source_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Path" + }, + "source_start_line": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Start Line" + }, + "tool": { + "title": "Tool", + "type": "string" + } + }, + "required": [ + "after", + "before", + "kind", + "metadata", + "tool" + ], + "title": "ToolSurfaceMetadataChange", + "type": "object" + }, + "ToolSurfacePolicyDrift": { + "additionalProperties": false, + "properties": { + "after_hash": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "After Hash" + }, + "after_summary": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "After Summary" + }, + "before_hash": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Before Hash" + }, + "before_summary": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Before Summary" + }, + "key": { + "title": "Key", + "type": "string" + }, + "kind": { + "enum": [ + "added", + "removed", + "changed" + ], + "title": "Kind", + "type": "string" + }, + "policy_kind": { + "title": "Policy Kind", + "type": "string" + } + }, + "required": [ + "after_hash", + "after_summary", + "before_hash", + "before_summary", + "key", + "kind", + "policy_kind" + ], + "title": "ToolSurfacePolicyDrift", + "type": "object" + }, + "ToolSurfacePolicyFact": { + "additionalProperties": false, + "properties": { + "key": { + "title": "Key", + "type": "string" + }, + "kind": { + "title": "Kind", + "type": "string" + }, + "summary": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Summary" + }, + "value_hash": { + "title": "Value Hash", + "type": "string" + } + }, + "required": [ + "key", + "kind", + "summary", + "value_hash" + ], + "title": "ToolSurfacePolicyFact", + "type": "object" + }, + "ToolSurfaceScopeChange": { + "additionalProperties": false, + "properties": { + "broad": { + "default": false, + "title": "Broad", + "type": "boolean" + }, + "kind": { + "enum": [ + "added", + "removed", + "changed" + ], + "title": "Kind", + "type": "string" + }, + "scope": { + "title": "Scope", + "type": "string" + }, + "scope_kind": { + "enum": [ + "tool_required", + "manifest_declared" + ], + "title": "Scope Kind", + "type": "string" + }, + "tool_names": { + "items": { + "type": "string" + }, + "title": "Tool Names", + "type": "array" + } + }, + "required": [ + "broad", + "kind", + "scope", + "scope_kind", + "tool_names" + ], + "title": "ToolSurfaceScopeChange", + "type": "object" + }, + "ToolSurfaceScopeFact": { + "additionalProperties": false, + "properties": { + "broad": { + "default": false, + "title": "Broad", + "type": "boolean" + }, + "kind": { + "enum": [ + "tool_required", + "manifest_declared" + ], + "title": "Kind", + "type": "string" + }, + "scope": { + "title": "Scope", + "type": "string" + }, + "tool_names": { + "items": { + "type": "string" + }, + "title": "Tool Names", + "type": "array" + } + }, + "required": [ + "broad", + "kind", + "scope", + "tool_names" + ], + "title": "ToolSurfaceScopeFact", + "type": "object" + }, + "ToolSurfaceSummary": { + "properties": { + "high_risk_tools": { + "title": "High Risk Tools", + "type": "integer" + }, + "missing_descriptions": { + "default": 0, + "title": "Missing Descriptions", + "type": "integer" + }, + "sources": { + "additionalProperties": { + "type": "integer" + }, + "title": "Sources", + "type": "object" + }, + "total_tools": { + "title": "Total Tools", + "type": "integer" + }, + "wildcard_tools": { + "default": 0, + "title": "Wildcard Tools", + "type": "integer" + } + }, + "required": [ + "total_tools", + "high_risk_tools" + ], + "title": "ToolSurfaceSummary", + "type": "object" + }, + "ToolSurfaceToolChange": { + "additionalProperties": false, + "properties": { + "changes": { + "items": { + "$ref": "#/$defs/ToolSurfaceFieldChange" + }, + "title": "Changes", + "type": "array" + }, + "kind": { + "enum": [ + "added", + "removed", + "changed" + ], + "title": "Kind", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "source_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Id" + }, + "source_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Path" + }, + "source_start_line": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Start Line" + }, + "source_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Type" + } + }, + "required": [ + "changes", + "kind", + "name", + "source_id", + "source_type" + ], + "title": "ToolSurfaceToolChange", + "type": "object" + }, + "ToolSurfaceToolFact": { + "additionalProperties": false, + "properties": { + "auth_scopes": { + "items": { + "type": "string" + }, + "title": "Auth Scopes", + "type": "array" + }, + "extraction_confidence": { + "default": "low", + "enum": [ + "low", + "medium", + "high" + ], + "title": "Extraction Confidence", + "type": "string" + }, + "has_description": { + "default": false, + "title": "Has Description", + "type": "boolean" + }, + "hashes": { + "$ref": "#/$defs/ToolSurfaceHashes" + }, + "name": { + "title": "Name", + "type": "string" + }, + "owner": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Owner" + }, + "risk_tags": { + "items": { + "type": "string" + }, + "title": "Risk Tags", + "type": "array" + }, + "source_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Id" + }, + "source_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Ref" + }, + "source_type": { + "title": "Source Type", + "type": "string" + } + }, + "required": [ + "auth_scopes", + "extraction_confidence", + "has_description", + "hashes", + "name", + "owner", + "risk_tags", + "source_id", + "source_ref", + "source_type" + ], + "title": "ToolSurfaceToolFact", + "type": "object" + } + }, + "$id": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/report-schema.v0.21.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": true, + "description": "JSON Schema for the Agents Shipgate Tool-Use Readiness Report. Generated from agents_shipgate.schemas.report.ReadinessReport with post-processing to preserve the v0.5 public contract. Do not edit by hand.", + "properties": { + "action_surface_diff": { + "$ref": "#/$defs/ActionSurfaceDiff" + }, + "action_surface_facts": { + "$ref": "#/$defs/ActionSurfaceFacts" + }, + "agent": { + "additionalProperties": true, + "title": "Agent", + "type": "object" + }, + "agent_summary": { + "$ref": "#/$defs/AgentSummary" + }, + "anthropic_surface": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Anthropic Surface" + }, + "api_surface": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Api Surface" + }, + "baseline": { + "anyOf": [ + { + "$ref": "#/$defs/BaselineSummary" + }, + { + "type": "null" + } + ], + "default": null + }, + "capability_facts": { + "items": { + "$ref": "#/$defs/CapabilityFact" + }, + "title": "Capability Facts", + "type": "array" + }, + "codex_plugin_surface": { + "anyOf": [ + { + "$ref": "#/$defs/CodexPluginSurface" + }, + { + "type": "null" + } + ], + "default": null + }, + "declared_intentions": { + "items": { + "$ref": "#/$defs/DeclaredIntention" + }, + "title": "Declared Intentions", + "type": "array" + }, + "environment": { + "additionalProperties": true, + "title": "Environment", + "type": "object" + }, + "findings": { + "items": { + "$ref": "#/$defs/Finding" + }, + "title": "Findings", + "type": "array" + }, + "frameworks": { + "additionalProperties": true, + "properties": { + "crewai": { + "additionalProperties": true, + "required": [ + "agent_count", + "class_tool_count", + "crew_count", + "dynamic_tool_surface_count", + "function_tool_count", + "prebuilt_tool_count", + "python_entrypoint_count", + "tool_inventory_file_count", + "warnings" + ], + "type": "object" + }, + "google_adk": { + "additionalProperties": true, + "required": [ + "agent_config_count", + "agent_count", + "callback_count", + "dynamic_toolset_count", + "eval_file_count", + "function_tool_count", + "long_running_tool_count", + "plugin_count", + "python_entrypoint_count", + "sub_agent_count", + "tool_inventory_file_count", + "toolset_count", + "trace_sample_count", + "warnings" + ], + "type": "object" + }, + "langchain": { + "additionalProperties": true, + "required": [ + "agent_tool_binding_count", + "dynamic_tool_surface_count", + "function_tool_count", + "python_entrypoint_count", + "structured_tool_count", + "tool_inventory_file_count", + "tool_node_count", + "warnings" + ], + "type": "object" + } + }, + "title": "Frameworks", + "type": "object" + }, + "generated_reports": { + "additionalProperties": { + "type": "string" + }, + "title": "Generated Reports", + "type": "object" + }, + "heuristics_filter": { + "$ref": "#/$defs/HeuristicsFilter" + }, + "loaded_adapters": { + "items": { + "additionalProperties": true, + "required": [ + "distribution", + "name", + "runtime_errors", + "source_type", + "validation_errors", + "validation_status", + "value", + "version" + ], + "type": "object" + }, + "title": "Loaded Adapters", + "type": "array" + }, + "loaded_plugins": { + "items": { + "additionalProperties": true, + "required": [ + "check_id", + "distribution", + "name", + "runtime_errors", + "validation_errors", + "validation_status", + "value", + "version" + ], + "type": "object" + }, + "title": "Loaded Plugins", + "type": "array" + }, + "loaded_policy_packs": { + "items": { + "$ref": "#/$defs/LoadedPolicyPack" + }, + "title": "Loaded Policy Packs", + "type": "array" + }, + "manifest_dir": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Manifest Dir" + }, + "misalignments": { + "items": { + "$ref": "#/$defs/Misalignment" + }, + "title": "Misalignments", + "type": "array" + }, + "policy_audit": { + "$ref": "#/$defs/PolicyAudit" + }, + "privacy_audit": { + "$ref": "#/$defs/PrivacyAudit" + }, + "project": { + "additionalProperties": true, + "title": "Project", + "type": "object" + }, + "recommended_actions": { + "items": { + "type": "string" + }, + "title": "Recommended Actions", + "type": "array" + }, + "release_consequence": { + "$ref": "#/$defs/ReleaseConsequence" + }, + "release_decision": { + "$ref": "#/$defs/ReleaseDecision" + }, + "report_schema_version": { + "const": "0.21" + }, + "reviewer_summary": { + "$ref": "#/$defs/ReviewerSummary" + }, + "run_id": { + "title": "Run Id", + "type": "string" + }, + "schema_version": { + "const": "0.1" + }, + "source_warnings": { + "items": { + "type": "string" + }, + "title": "Source Warnings", + "type": "array" + }, + "suggested_scenarios": { + "items": { + "$ref": "#/$defs/SuggestedScenario" + }, + "title": "Suggested Scenarios", + "type": "array" + }, + "summary": { + "$ref": "#/$defs/ReportSummary" + }, + "tool_inventory": { + "items": { + "additionalProperties": true, + "required": [ + "auth_scopes", + "confidence", + "name", + "risk_tags", + "source_type" + ], + "type": "object" + }, + "title": "Tool Inventory", + "type": "array" + }, + "tool_surface": { + "$ref": "#/$defs/ToolSurfaceSummary" + }, + "tool_surface_diff": { + "$ref": "#/$defs/ToolSurfaceDiff" + }, + "tool_surface_facts": { + "$ref": "#/$defs/ToolSurfaceFacts" + } + }, + "required": [ + "action_surface_diff", + "action_surface_facts", + "agent", + "agent_summary", + "capability_facts", + "codex_plugin_surface", + "declared_intentions", + "environment", + "findings", + "frameworks", + "generated_reports", + "heuristics_filter", + "loaded_adapters", + "loaded_plugins", + "loaded_policy_packs", + "misalignments", + "policy_audit", + "privacy_audit", + "project", + "recommended_actions", + "release_consequence", + "release_decision", + "report_schema_version", + "reviewer_summary", + "run_id", + "schema_version", + "source_warnings", + "suggested_scenarios", + "summary", + "tool_inventory", + "tool_surface", + "tool_surface_diff", + "tool_surface_facts" + ], + "title": "Agents Shipgate Readiness Report v0.21", + "type": "object" +} diff --git a/docs/report-sensitive-fields.json b/docs/report-sensitive-fields.json index 553e9c9..ac8d326 100644 --- a/docs/report-sensitive-fields.json +++ b/docs/report-sensitive-fields.json @@ -46,6 +46,7 @@ {"surface": "report", "path": "agent_summary", "classification": "free_text"}, {"surface": "report", "path": "policy_audit", "classification": "free_text"}, {"surface": "report", "path": "privacy_audit", "classification": "public_control_metadata"}, + {"surface": "report", "path": "heuristics_filter", "classification": "public_control_metadata"}, {"surface": "report", "path": "reviewer_summary", "classification": "public_control_metadata"}, {"surface": "report", "path": "findings[].evidence", "classification": "free_text"}, {"surface": "report", "path": "findings[].title", "classification": "free_text"}, diff --git a/llms-full.txt b/llms-full.txt index 85b6e87..349b1fa 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -284,8 +284,9 @@ Other stable top-level fields: - `release_decision.contribution_rules[]` (v0.17+, per-finding audit of how each finding contributed to the decision; one row per `report.findings` entry, with `category` ∈ `{blocker, review_item, excluded}` and `rule` ∈ `{policy_block_new, severity_block_new, policy_baseline_accepted, severity_baseline_accepted, review_required, sub_threshold, suppressed}`) - `policy_audit.severity_overrides_applied[]` (v0.17+, top-of-report audit envelope listing every manifest-driven severity override with `{check_id, default_severity, applied_severity, manifest_path, reason, tier_crossed, direction, expires}`) - `privacy_audit` (v0.18+, top-level audit proving default redaction ran before public artifacts were written; `redacted_paths[]` contains counts and structural paths only, never raw values or raw hashes) +- `heuristics_filter` (v0.21+, top-level audit envelope describing the `--no-heuristics` CLI filter pass; `enabled` is `False` and counts are zero when the flag is unset, so the field shape is stable across runs. When enabled, findings whose `provenance_kind` is `keyword_heuristic` or `regex_heuristic` are marked `suppressed=True` with `suppression_reason="filtered by --no-heuristics"` before the release decision is built — they remain in `findings[]` for transparency but do not gate release.) -The full schema is at [`docs/report-schema.v0.20.json`](docs/report-schema.v0.20.json) (current; emitted reports carry `report_schema_version: "0.20"`, adding the top-level `reviewer_summary` block — a deterministic projection of reviewer-lens surfaces and audit envelopes). v0.19 (frozen at [`docs/report-schema.v0.19.json`](docs/report-schema.v0.19.json)) adds `Finding.policy_evidence_source` and `ReleaseDecisionItem.{source, policy_evidence_source}` for reviewer-grade dual-source provenance on top of v0.18's `privacy_audit`, v0.17's `policy_audit`, and `release_decision.contribution_rules[]` audit fields. What's-stable is documented in [STABILITY.md](STABILITY.md). +The full schema is at [`docs/report-schema.v0.21.json`](docs/report-schema.v0.21.json) (current; emitted reports carry `report_schema_version: "0.21"`, adding the top-level `heuristics_filter` audit envelope alongside v0.20's `reviewer_summary` block). v0.20 (frozen at [`docs/report-schema.v0.20.json`](docs/report-schema.v0.20.json)) added the `reviewer_summary` deterministic projection of reviewer-lens surfaces and audit envelopes. v0.19 added `Finding.policy_evidence_source` and `ReleaseDecisionItem.{source, policy_evidence_source}` for reviewer-grade dual-source provenance on top of v0.18's `privacy_audit`, v0.17's `policy_audit`, and `release_decision.contribution_rules[]` audit fields. What's-stable is documented in [STABILITY.md](STABILITY.md). **Release gating signal**: prefer `release_decision.decision` (`"blocked" | "review_required" | "insufficient_evidence" | "passed"`) over `summary.status`. The new field is **baseline-aware** — a baseline-matched critical surfaces in `release_decision.review_items` (accepted debt), not `release_decision.blockers`. `summary.status` stays baseline-blind for v0.7 compatibility, so a baseline-matched-only critical produces both `summary.status = "release_blockers_detected"` AND `release_decision.decision = "review_required"` (intentional divergence — see [STABILITY.md](STABILITY.md#release_decisiondecision-vs-summarystatus)). `insufficient_evidence` (added v0.14) signals that the scan saw too many low-confidence tools or source-loader warnings to be trustworthy; consumers that switch on the enum must fall back to `review_required` for unknown future values. @@ -351,7 +352,7 @@ validation and [`docs/manifest-v0.1.md`](docs/manifest-v0.1.md) for prose. ### Where is the report schema? Parse `agents-shipgate-reports/report.json` and validate against -[`docs/report-schema.v0.20.json`](docs/report-schema.v0.20.json) (current). +[`docs/report-schema.v0.21.json`](docs/report-schema.v0.21.json) (current). Older reports (`report_schema_version: "0.10"`) validate against the frozen [`docs/report-schema.v0.10.json`](docs/report-schema.v0.10.json). Do not scrape Markdown when JSON is available. @@ -389,7 +390,8 @@ For the short, current statement of "which fields to read", see [`docs/agent-con | What | Path | Stable | |---|---|---| | Manifest schema | [`docs/manifest-v0.1.json`](docs/manifest-v0.1.json) | `0.1` | -| Report schema (current) | [`docs/report-schema.v0.20.json`](docs/report-schema.v0.20.json) | `0.20` | +| Report schema (current) | [`docs/report-schema.v0.21.json`](docs/report-schema.v0.21.json) | `0.21` | +| Report schema (v0.20 frozen reference) | [`docs/report-schema.v0.20.json`](docs/report-schema.v0.20.json) | `0.20` | | Report schema (v0.19 frozen reference) | [`docs/report-schema.v0.19.json`](docs/report-schema.v0.19.json) | `0.19` | | Report schema (v0.18 frozen reference) | [`docs/report-schema.v0.18.json`](docs/report-schema.v0.18.json) | `0.18` | | Report schema (v0.17 frozen reference) | [`docs/report-schema.v0.17.json`](docs/report-schema.v0.17.json) | `0.17` | @@ -424,7 +426,7 @@ Promised to not break in `0.x` minor versions. See [STABILITY.md](STABILITY.md) | Command | Stable flags | |---|---| -| `agents-shipgate scan` | `-c`, `--out`, `--format`, `--ci-mode`, `--fail-on`, `--baseline`, `--diff-from`, `--no-plugins`, `--verbose`, `--packet`/`--no-packet`, `--packet-format` | +| `agents-shipgate scan` | `-c`, `--out`, `--format`, `--ci-mode`, `--fail-on`, `--baseline`, `--diff-from`, `--no-plugins`, `--no-heuristics`, `--verbose`, `--packet`/`--no-packet`, `--packet-format` | | `agents-shipgate evidence-packet` | `--from`, `--out`, `--format`, `--json` | | `agents-shipgate init` | `--workspace`, `--write`, `--json` | | `agents-shipgate doctor` | `-c`, `--workspace`, `--json`, `--verbose` | @@ -832,9 +834,9 @@ agents-shipgate contract --json - Latest release: `v0.10.0` (see [pyproject.toml](../pyproject.toml) for the in-tree version) - Runtime contract: `1` -- Current report schema: `0.20` — [`docs/report-schema.v0.20.json`](report-schema.v0.20.json) +- Current report schema: `0.21` — [`docs/report-schema.v0.21.json`](report-schema.v0.21.json) - Current packet schema: `0.6` — [`docs/packet-schema.v0.6.json`](packet-schema.v0.6.json) -- Frozen-reference report schemas: [`v0.19`](report-schema.v0.19.json), [`v0.18`](report-schema.v0.18.json), [`v0.17`](report-schema.v0.17.json), [`v0.16`](report-schema.v0.16.json), [`v0.15`](report-schema.v0.15.json), [`v0.14`](report-schema.v0.14.json), [`v0.13`](report-schema.v0.13.json), [`v0.12`](report-schema.v0.12.json), [`v0.11`](report-schema.v0.11.json), [`v0.10`](report-schema.v0.10.json), [`v0.9`](report-schema.v0.9.json), [`v0.8`](report-schema.v0.8.json), [`v0.7`](report-schema.v0.7.json), [`v0.6`](report-schema.v0.6.json), older +- Frozen-reference report schemas: [`v0.20`](report-schema.v0.20.json), [`v0.19`](report-schema.v0.19.json), [`v0.18`](report-schema.v0.18.json), [`v0.17`](report-schema.v0.17.json), [`v0.16`](report-schema.v0.16.json), [`v0.15`](report-schema.v0.15.json), [`v0.14`](report-schema.v0.14.json), [`v0.13`](report-schema.v0.13.json), [`v0.12`](report-schema.v0.12.json), [`v0.11`](report-schema.v0.11.json), [`v0.10`](report-schema.v0.10.json), [`v0.9`](report-schema.v0.9.json), [`v0.8`](report-schema.v0.8.json), [`v0.7`](report-schema.v0.7.json), [`v0.6`](report-schema.v0.6.json), older - Frozen-reference packet schemas live in [`docs/INDEX.md`](INDEX.md#reference). ## Read these first for release gating @@ -849,6 +851,7 @@ In `agents-shipgate-reports/report.json`: - `release_decision.contribution_rules[]` (v0.17+) — deterministic per-finding audit explaining how each `report.findings` entry was classified. Exactly one row per finding (including suppressed). Each row carries `{finding_id, fingerprint, check_id, category, rule, rationale}`. `category` ∈ `{blocker, review_item, excluded}`; `rule` ∈ `{policy_block_new, severity_block_new, policy_baseline_accepted, severity_baseline_accepted, review_required, sub_threshold, suppressed}`. Reading the contribution rule is sufficient to predict the gate outcome for that finding without re-deriving the decision logic — the closed grammar of `(rule, category)` pairs is documented in [STABILITY.md "Release decision truth table"](../STABILITY.md#release-decision-truth-table). The audit cannot disagree with `blockers[]` / `review_items[]` (the same classification powers both). - `privacy_audit` (v0.18+) — confirms the default redaction pass ran before public artifacts were written. Read `enabled`, `rules_version`, `sensitive_field_inventory_version`, `redacted_occurrence_count`, `redacted_paths[]`, and `output_surfaces[]`. `redacted_paths[]` contains structural paths and counts only, never raw values or raw hashes. - `reviewer_summary` (v0.20+) — deterministic projection of the reviewer lens surfaces and audit envelopes; the reviewer-side parallel to `agent_summary`. Read this block first when triaging a scan for a human reviewer. Carries `verdict` (mirrors `release_decision.decision`), `headline` (≤200 chars, PR-comment-friendly), per-lens activity counts (`tool_surface_changes`, `capability_misalignments`, `action_surface_changes`, `evidence_matrix_gaps`), per-audit-envelope counts (`severity_overrides_applied`, `severity_overrides_tier_crossed`, `privacy_redactions`, `baseline_integrity_issues`), and `first_recommended_surface: ReviewerSurfacePointer | None` — a deterministic pointer naming which lens/audit to open first (`{kind, name, path, why}` where `kind` ∈ `{release_decision, lens, audit, evidence_matrix}` and `name` ∈ `{tool_surface_diff, capability_intent_diff, action_surface_diff, evidence_matrix, policy_audit, privacy_audit, baseline_integrity, release_decision}`). Same inputs always produce the same output; this block cannot disagree with the underlying lens/audit data. +- `heuristics_filter` (v0.21+) — top-level audit envelope describing the `--no-heuristics` CLI filter pass. Always present, even when the flag is unset (`enabled: False` with zero counts), so the report shape is stable. Carries `enabled: bool`, `excluded_provenance_kinds: list[str]` (`["keyword_heuristic", "regex_heuristic"]`), `filtered_finding_count: int`, and `filtered_by_kind: dict[str, int]` (per-kind breakdown). When `enabled: True`, findings whose `provenance_kind` is in the excluded list have been marked `suppressed=True` with `suppression_reason="filtered by --no-heuristics"` BEFORE the release decision was built — they remain in `findings[]` for transparency but no longer gate release. The filter never un-suppresses a finding; manifest-driven suppression reasons are preserved when they overlap with the filter. Useful for security/GRC reviewers who want declared-only findings. The action exposes these as outputs `decision`, `blocker_count`, `review_item_count`, `ci_would_fail` (v0.8+). @@ -963,7 +966,7 @@ Companion prompt: [`prompts/explain-finding-to-user.md`](../prompts/explain-find - [STABILITY.md](../STABILITY.md) — full 0.x stability contract. Source of truth for everything above. - [AGENTS.md](../AGENTS.md) — agent-facing instructions: install, run, single-turn flow, error semantics. -- [`docs/report-schema.v0.20.json`](report-schema.v0.20.json) — machine-validatable JSON Schema for the current report. +- [`docs/report-schema.v0.21.json`](report-schema.v0.21.json) — machine-validatable JSON Schema for the current report. - [`docs/privacy.md`](privacy.md) and [`docs/report-sensitive-fields.json`](report-sensitive-fields.json) — default redaction behavior and sensitive-field inventory. - [`docs/packet-schema.v0.6.json`](packet-schema.v0.6.json) — machine-validatable JSON Schema for the current packet. - [`docs/checks.json`](checks.json) — check catalog. @@ -1974,7 +1977,7 @@ from the hash so toggling `--suggest-patches` doesn't shift it. - [`checks.md`](checks.md) — full check catalog with rationale. - [`minimal-real-configs.md`](minimal-real-configs.md) — per-framework minimal manifests to build from. -- [`report-schema.v0.20.json`](report-schema.v0.20.json) — current JSON +- [`report-schema.v0.21.json`](report-schema.v0.21.json) — current JSON Schema for `report.json`. - [`AGENTS.md`](../AGENTS.md) — top-level agent instructions, install, trigger table. diff --git a/llms.txt b/llms.txt index a030bb6..aba70c5 100644 --- a/llms.txt +++ b/llms.txt @@ -57,7 +57,7 @@ - Markdown report: `agents-shipgate-reports/report.md`. - JSON report: `agents-shipgate-reports/report.json`. -- JSON report schema (current): https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/report-schema.v0.20.json +- JSON report schema (current): https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/report-schema.v0.21.json - Release Evidence Packet (Markdown / JSON / HTML, optional PDF): `agents-shipgate-reports/packet.{md,json,html}`. - Packet schema (current): https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/packet-schema.v0.6.json - SARIF report: `agents-shipgate-reports/report.sarif`. @@ -112,7 +112,7 @@ - Zero-install detector (stdlib-only Python; same structural verdict as `agents-shipgate detect --json` — emits the canonical `DetectResult` fields plus `script_version`, but NOT the CLI's `diagnostics` or `next_actions` arrays): https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/tools/shipgate-detect.py - Zero-install paths overview (single-file detector, uvx, GitHub Action): https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/zero-install.md - Manifest schema: https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/manifest-v0.1.json -- Report schema (current): https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/report-schema.v0.20.json +- Report schema (current): https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/report-schema.v0.21.json - Privacy/redaction docs: https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/privacy.md - Packet schema (current): https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/packet-schema.v0.6.json - Current agent contract: https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/agent-contract-current.md diff --git a/samples/simple_crewai_agent/expected/report.json b/samples/simple_crewai_agent/expected/report.json index e7c9478..76881a7 100644 --- a/samples/simple_crewai_agent/expected/report.json +++ b/samples/simple_crewai_agent/expected/report.json @@ -1,6 +1,6 @@ { "schema_version": "0.1", - "report_schema_version": "0.20", + "report_schema_version": "0.21", "run_id": "agents_shipgate_75f9c3e73779b880", "manifest_dir": "/samples/simple_crewai_agent", "project": { @@ -406,6 +406,7 @@ }, "loaded_policy_packs": [], "loaded_plugins": [], + "loaded_adapters": [], "tool_inventory": [ { "name": "FileReadTool", @@ -485,6 +486,15 @@ "Redaction audit paths contain counts and secret kinds only; raw values and raw hashes are not emitted." ] }, + "heuristics_filter": { + "enabled": false, + "excluded_provenance_kinds": [ + "keyword_heuristic", + "regex_heuristic" + ], + "filtered_finding_count": 0, + "filtered_by_kind": {} + }, "reviewer_summary": { "verdict": "insufficient_evidence", "headline": "Evidence coverage below threshold: no reviewer signals (lenses + audits all clean). Start at release_decision.", diff --git a/samples/simple_langchain_agent/expected/report.json b/samples/simple_langchain_agent/expected/report.json index 93c9bd2..98ac840 100644 --- a/samples/simple_langchain_agent/expected/report.json +++ b/samples/simple_langchain_agent/expected/report.json @@ -1,6 +1,6 @@ { "schema_version": "0.1", - "report_schema_version": "0.20", + "report_schema_version": "0.21", "run_id": "agents_shipgate_e6307029b0ddfba5", "manifest_dir": "/samples/simple_langchain_agent", "project": { @@ -345,6 +345,7 @@ }, "loaded_policy_packs": [], "loaded_plugins": [], + "loaded_adapters": [], "tool_inventory": [ { "name": "lookup_case", @@ -409,6 +410,15 @@ "Redaction audit paths contain counts and secret kinds only; raw values and raw hashes are not emitted." ] }, + "heuristics_filter": { + "enabled": false, + "excluded_provenance_kinds": [ + "keyword_heuristic", + "regex_heuristic" + ], + "filtered_finding_count": 0, + "filtered_by_kind": {} + }, "reviewer_summary": { "verdict": "insufficient_evidence", "headline": "Evidence coverage below threshold: no reviewer signals (lenses + audits all clean). Start at release_decision.", diff --git a/samples/simple_openai_api_agent/expected/report.json b/samples/simple_openai_api_agent/expected/report.json index 03d2735..3006ede 100644 --- a/samples/simple_openai_api_agent/expected/report.json +++ b/samples/simple_openai_api_agent/expected/report.json @@ -1,6 +1,6 @@ { "schema_version": "0.1", - "report_schema_version": "0.20", + "report_schema_version": "0.21", "run_id": "agents_shipgate_529a14acc6ebdc81", "manifest_dir": "/samples/simple_openai_api_agent", "project": { @@ -2410,6 +2410,7 @@ }, "loaded_policy_packs": [], "loaded_plugins": [], + "loaded_adapters": [], "tool_inventory": [ { "name": "create_refund", @@ -2484,6 +2485,15 @@ "Redaction audit paths contain counts and secret kinds only; raw values and raw hashes are not emitted." ] }, + "heuristics_filter": { + "enabled": false, + "excluded_provenance_kinds": [ + "keyword_heuristic", + "regex_heuristic" + ], + "filtered_finding_count": 0, + "filtered_by_kind": {} + }, "reviewer_summary": { "verdict": "review_required", "headline": "Review required: 23 lens changes. Start at capability_intent_diff.", diff --git a/samples/support_refund_agent/expected/report.json b/samples/support_refund_agent/expected/report.json index d76a6f7..7e40abc 100644 --- a/samples/support_refund_agent/expected/report.json +++ b/samples/support_refund_agent/expected/report.json @@ -1,6 +1,6 @@ { "schema_version": "0.1", - "report_schema_version": "0.20", + "report_schema_version": "0.21", "run_id": "agents_shipgate_ebb71d7248235cc3", "manifest_dir": "/samples/support_refund_agent", "project": { @@ -2885,6 +2885,7 @@ }, "loaded_policy_packs": [], "loaded_plugins": [], + "loaded_adapters": [], "tool_inventory": [ { "name": "gmail.send_customer_email", @@ -3071,6 +3072,15 @@ "Redaction audit paths contain counts and secret kinds only; raw values and raw hashes are not emitted." ] }, + "heuristics_filter": { + "enabled": false, + "excluded_provenance_kinds": [ + "keyword_heuristic", + "regex_heuristic" + ], + "filtered_finding_count": 0, + "filtered_by_kind": {} + }, "reviewer_summary": { "verdict": "blocked", "headline": "Release blocked: 18 lens changes. Start at release_decision.", diff --git a/scripts/generate_schemas.py b/scripts/generate_schemas.py index 9699477..d578e6a 100644 --- a/scripts/generate_schemas.py +++ b/scripts/generate_schemas.py @@ -244,6 +244,15 @@ def build_report_schema() -> tuple[Path, str]: # we mark it required + non-nullable on the wire. "policy_audit", "privacy_audit", + # v0.21: heuristics_filter is the top-of-report audit envelope + # for the --no-heuristics CLI flag. Optional in Python for + # back-compat with older fixtures; emitted scans always + # populate it (empty envelope with enabled=false when the + # flag is not set), so we mark it required + non-nullable + # on the wire. Consumers can read + # ``heuristics_filter.filtered_finding_count`` without a + # null check. + "heuristics_filter", # v0.20: reviewer_summary parallels agent_summary for the # audit/lens dimensions. Optional in Python for back-compat # with test helpers; emitted scans always populate it via @@ -282,6 +291,12 @@ def build_report_schema() -> tuple[Path, str]: # v0.18: same tightening for privacy_audit. Emitted scans always # carry the default-on privacy envelope after public output redaction. properties["privacy_audit"] = {"$ref": "#/$defs/PrivacyAudit"} + # v0.21: same tightening for heuristics_filter. Emitted scans always + # carry the envelope (with enabled=false when --no-heuristics was + # not set), so the wire shape is HeuristicsFilter, never null. The + # const + non-nullable form lets consumers read + # ``heuristics_filter.filtered_finding_count`` without a null check. + properties["heuristics_filter"] = {"$ref": "#/$defs/HeuristicsFilter"} # v0.20: same tightening for reviewer_summary. Parallel to v0.12 # agent_summary — Python-Optional for test helpers, required + # non-nullable on the wire so consumers can read @@ -311,6 +326,21 @@ def build_report_schema() -> tuple[Path, str]: defs["RedactedPathSummary"]["required"] = sorted( ["path", "count", "kinds"] ) + # v0.21: pin every HeuristicsFilter field as required. Pydantic + # auto-required is empty here because every field has a default + # (enabled=False, lists/dicts default-factory). On the wire every + # emitted scan carries all four fields — `apply_no_heuristics_filter` + # always populates them — so consumers can rely on the full shape + # without conditional key checks. + if "HeuristicsFilter" in defs: + defs["HeuristicsFilter"]["required"] = sorted( + [ + "enabled", + "excluded_provenance_kinds", + "filtered_finding_count", + "filtered_by_kind", + ] + ) if "Finding" in defs: defs["Finding"]["required"] = sorted( [ diff --git a/skills/agents-shipgate/SKILL.md b/skills/agents-shipgate/SKILL.md index d7dfd75..c230f0a 100644 --- a/skills/agents-shipgate/SKILL.md +++ b/skills/agents-shipgate/SKILL.md @@ -63,7 +63,7 @@ For non-GitHub CI (GitLab, CircleCI, Jenkins, Azure Pipelines, Buildkite, Bitbuc - **CLI surface** is frozen for `0.x` — see https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/STABILITY.md. - **Installed CLI contract**: when available, run `agents-shipgate contract --json` to verify local schema versions, `release_decision.decision`, and manual-review signal fields. Older installs should use [`docs/agent-contract-current.md`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/agent-contract-current.md) or upgrade before automating against the local contract command. -- **Report JSON**: `report_schema_version: "0.20"`. Read `release_decision.decision` (`"blocked" | "review_required" | "insufficient_evidence" | "passed"`) **first** for release gating — it is baseline-aware. `insufficient_evidence` (added v0.14) fires when evidence coverage is degraded past threshold (at least half of scanned tools low-confidence — `ceil(N × 0.5)` with a minimum of 1 — or 4+ source warnings); switch on the enum with a `review_required` fallback for unknown values. For privacy audit read `privacy_audit` (v0.18+) to confirm default redaction ran before public artifacts were written; `redacted_paths[]` contains structural paths and counts only. For severity-override audit read the top-level `policy_audit.severity_overrides_applied[]` block (v0.17+) — every manifest-driven severity change carries `{check_id, default_severity, applied_severity, manifest_path, reason, tier_crossed, direction, expires}`. For per-finding decision audit read `release_decision.contribution_rules[]` (v0.17+) — one row per `report.findings` entry with `category` ∈ `{blocker, review_item, excluded}` and `rule` ∈ `{policy_block_new, severity_block_new, policy_baseline_accepted, severity_baseline_accepted, review_required, sub_threshold, suppressed}`. For Action Surface Diff read `action_surface_facts`, `action_surface_diff`, and `findings[].blocks_release` (v0.16+) to understand added/removed/modified external actions and explicit release-policy blockers. For one-fetch summarization read the top-level `agent_summary` block (v0.12+) — `{verdict, headline, blocker_count, review_item_count, auto_appliable_patches, needs_human_review, first_recommended_action}`. For per-finding routing read `findings[].agent_action` (v0.12+; `auto_apply | propose_patch_for_review | escalate_to_human | suppress_with_reason | informational`) instead of synthesizing one from `autofix_safe`/`requires_human_review`/`suggested_patch_kind`. To filter findings by source reliability read `findings[].provenance_kind` (v0.15+; `static_declaration | ast_extraction | keyword_heuristic | regex_heuristic | policy_pack`) — independent of `confidence`. Codex plugin facts, when present, live under `codex_plugin_surface` (v0.13+). Do not gate on `summary.status` for new consumers; it is preserved for v0.7 callers and is baseline-blind. The full field list lives in [`docs/agent-contract-current.md`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/agent-contract-current.md#read-these-first-for-release-gating); this skill links there instead of restating it. v0.11 adds optional `findings[].source.{path, start_line, end_line, start_column, pointer}` provenance keys (kept in v0.19). v0.19 adds the optional `Finding.policy_evidence_source` and `ReleaseDecisionItem.{source, policy_evidence_source}` fields for reviewer-grade dual-source provenance — high-risk findings that fire because of a missing manifest mitigation can carry both the tool location AND the manifest-pointer line. Reports validate against [`docs/report-schema.v0.20.json`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.20.json) (active emitted version). Frozen-reference older schemas (kept for legacy/pre-v0.19 reports): [`v0.18`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.18.json), [`v0.17`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.17.json), [`v0.16`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.16.json), [`v0.15`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.15.json), [`v0.14`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.14.json), [`v0.13`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.13.json), [`v0.12`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.12.json), [`v0.11` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.11.json), [`v0.10` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.10.json), [`v0.9` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.9.json), [`v0.8` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.8.json), and [`v0.7` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.7.json). +- **Report JSON**: `report_schema_version: "0.21"`. Read `release_decision.decision` (`"blocked" | "review_required" | "insufficient_evidence" | "passed"`) **first** for release gating — it is baseline-aware. `insufficient_evidence` (added v0.14) fires when evidence coverage is degraded past threshold (at least half of scanned tools low-confidence — `ceil(N × 0.5)` with a minimum of 1 — or 4+ source warnings); switch on the enum with a `review_required` fallback for unknown values. For privacy audit read `privacy_audit` (v0.18+) to confirm default redaction ran before public artifacts were written; `redacted_paths[]` contains structural paths and counts only. For severity-override audit read the top-level `policy_audit.severity_overrides_applied[]` block (v0.17+) — every manifest-driven severity change carries `{check_id, default_severity, applied_severity, manifest_path, reason, tier_crossed, direction, expires}`. For per-finding decision audit read `release_decision.contribution_rules[]` (v0.17+) — one row per `report.findings` entry with `category` ∈ `{blocker, review_item, excluded}` and `rule` ∈ `{policy_block_new, severity_block_new, policy_baseline_accepted, severity_baseline_accepted, review_required, sub_threshold, suppressed}`. For Action Surface Diff read `action_surface_facts`, `action_surface_diff`, and `findings[].blocks_release` (v0.16+) to understand added/removed/modified external actions and explicit release-policy blockers. For one-fetch summarization read the top-level `agent_summary` block (v0.12+) — `{verdict, headline, blocker_count, review_item_count, auto_appliable_patches, needs_human_review, first_recommended_action}`. For per-finding routing read `findings[].agent_action` (v0.12+; `auto_apply | propose_patch_for_review | escalate_to_human | suppress_with_reason | informational`) instead of synthesizing one from `autofix_safe`/`requires_human_review`/`suggested_patch_kind`. To filter findings by source reliability read `findings[].provenance_kind` (v0.15+; `static_declaration | ast_extraction | keyword_heuristic | regex_heuristic | policy_pack`) — independent of `confidence`. Codex plugin facts, when present, live under `codex_plugin_surface` (v0.13+). Do not gate on `summary.status` for new consumers; it is preserved for v0.7 callers and is baseline-blind. The full field list lives in [`docs/agent-contract-current.md`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/agent-contract-current.md#read-these-first-for-release-gating); this skill links there instead of restating it. v0.11 adds optional `findings[].source.{path, start_line, end_line, start_column, pointer}` provenance keys (kept in v0.19). v0.19 adds the optional `Finding.policy_evidence_source` and `ReleaseDecisionItem.{source, policy_evidence_source}` fields for reviewer-grade dual-source provenance — high-risk findings that fire because of a missing manifest mitigation can carry both the tool location AND the manifest-pointer line. Reports validate against [`docs/report-schema.v0.21.json`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.21.json) (active emitted version). Frozen-reference older schemas (kept for legacy/pre-v0.21 reports): [`v0.20`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.20.json), [`v0.18`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.18.json), [`v0.17`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.17.json), [`v0.16` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.16.json), [`v0.15`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.15.json), [`v0.14`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.14.json), [`v0.13`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.13.json), [`v0.12`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.12.json), [`v0.11` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.11.json), [`v0.10` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.10.json), [`v0.9` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.9.json), [`v0.8` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.8.json), and [`v0.7` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.7.json). - **Release Evidence Packet**: `agents-shipgate-reports/packet.{md,json,html}` (and `packet.pdf` with the `[pdf]` extras) is emitted alongside the report by default. The packet has fixed reviewer sections governed by [`docs/packet-schema.v0.6.json`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/packet-schema.v0.6.json) (latest; v0.6 adds the top-level `evidence_matrix` compact review section AND `ReleaseDecisionItem.{source, policy_evidence_source}` for reviewer-grade dual-source provenance over the v0.5 baseline). See [STABILITY.md §Release Evidence Packet](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/STABILITY.md#release-evidence-packet-v06). Use the packet for reviewer-shaped output; use the report for finding details. - **Single source of truth for the contract**: [`docs/agent-contract-current.md`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/agent-contract-current.md). When the schema bumps, that file updates first. - **Exit codes**: `0` pass, `2` config error, `3` parse error, `4` other error, `20` strict-mode gate failure. diff --git a/src/agents_shipgate/cli/_helpers.py b/src/agents_shipgate/cli/_helpers.py index b89dd9e..f5d59a9 100644 --- a/src/agents_shipgate/cli/_helpers.py +++ b/src/agents_shipgate/cli/_helpers.py @@ -315,6 +315,7 @@ def _run_multi_scan( packet_enabled: bool | None = None, packet_formats: list[str] | None = None, strict_plugins: bool = False, + no_heuristics: bool = False, ) -> int: typer.echo(f"Agents Shipgate {__version__}") typer.echo(f"Scanning {len(config_paths)} manifests") @@ -341,6 +342,7 @@ def _run_multi_scan( suggest_patches=suggest_patches, packet_enabled=packet_enabled, packet_formats=packet_formats, + no_heuristics=no_heuristics, ) except ConfigError as exc: scan_exit_code = 2 diff --git a/src/agents_shipgate/cli/_register_scan.py b/src/agents_shipgate/cli/_register_scan.py index 0bc1f9a..4f329dc 100644 --- a/src/agents_shipgate/cli/_register_scan.py +++ b/src/agents_shipgate/cli/_register_scan.py @@ -114,6 +114,20 @@ def scan( "apply them; the report stays read-only." ), ), + no_heuristics: bool = typer.Option( + False, + "--no-heuristics", + help=( + "Filter out findings whose provenance_kind is " + "keyword_heuristic or regex_heuristic (v0.21+). Filtered " + "findings stay in findings[] marked suppressed; they no " + "longer gate release. static_declaration, ast_extraction, " + "and policy_pack findings are unaffected. The " + "report.heuristics_filter envelope records aggregate " + "counts. Useful for security/GRC reviewers who want " + "declared-only findings." + ), + ), packet: bool | None = typer.Option( None, "--packet/--no-packet", @@ -187,6 +201,7 @@ def scan( suggest_patches=suggest_patches, packet_enabled=packet, packet_formats=parsed_packet_formats, + no_heuristics=no_heuristics, ) exit_code = _apply_strict_plugins( report, exit_code, strict_plugins=strict_plugins @@ -210,6 +225,7 @@ def scan( packet_enabled=packet, packet_formats=parsed_packet_formats, strict_plugins=strict_plugins, + no_heuristics=no_heuristics, ) except ConfigError as exc: typer.echo(f"Config error: {exc}", err=True) diff --git a/src/agents_shipgate/cli/discovery/agent_instructions/renderers/claude_code_skill.py b/src/agents_shipgate/cli/discovery/agent_instructions/renderers/claude_code_skill.py index def6688..242a12b 100644 --- a/src/agents_shipgate/cli/discovery/agent_instructions/renderers/claude_code_skill.py +++ b/src/agents_shipgate/cli/discovery/agent_instructions/renderers/claude_code_skill.py @@ -41,7 +41,11 @@ def render_bundle_text() -> str: # `init --agent-instructions=claude-code-skill --write` can safely migrate # v(N-1) files. Leave the dict empty while there is no prior shipped Claude # Code skill bundle. -PRIOR_RENDER_SHA256: dict[str, tuple[str, ...]] = {} +PRIOR_RENDER_SHA256: dict[str, tuple[str, ...]] = { + ".claude/skills/agents-shipgate/SKILL.md": ( + "b17c53d9905f46b196be38e98cf71e53da6779e3a4f426ecff14f2b0f238aba9", + ), +} _ACTION_VERSION = __version__ @@ -112,7 +116,7 @@ def render_bundle_text() -> str: - **CLI surface** is frozen for `0.x` — see https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/STABILITY.md. - **Installed CLI contract**: when available, run `agents-shipgate contract --json` to verify local schema versions, `release_decision.decision`, and manual-review signal fields. Older installs should use [`docs/agent-contract-current.md`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/agent-contract-current.md) or upgrade before automating against the local contract command. -- **Report JSON**: `report_schema_version: "0.20"`. Read `release_decision.decision` (`"blocked" | "review_required" | "insufficient_evidence" | "passed"`) **first** for release gating — it is baseline-aware. `insufficient_evidence` (added v0.14) fires when evidence coverage is degraded past threshold (at least half of scanned tools low-confidence — `ceil(N × 0.5)` with a minimum of 1 — or 4+ source warnings); switch on the enum with a `review_required` fallback for unknown values. For privacy audit read `privacy_audit` (v0.18+) to confirm default redaction ran before public artifacts were written; `redacted_paths[]` contains structural paths and counts only. For severity-override audit read the top-level `policy_audit.severity_overrides_applied[]` block (v0.17+) — every manifest-driven severity change carries `{check_id, default_severity, applied_severity, manifest_path, reason, tier_crossed, direction, expires}`. For per-finding decision audit read `release_decision.contribution_rules[]` (v0.17+) — one row per `report.findings` entry with `category` ∈ `{blocker, review_item, excluded}` and `rule` ∈ `{policy_block_new, severity_block_new, policy_baseline_accepted, severity_baseline_accepted, review_required, sub_threshold, suppressed}`. For Action Surface Diff read `action_surface_facts`, `action_surface_diff`, and `findings[].blocks_release` (v0.16+) to understand added/removed/modified external actions and explicit release-policy blockers. For one-fetch summarization read the top-level `agent_summary` block (v0.12+) — `{verdict, headline, blocker_count, review_item_count, auto_appliable_patches, needs_human_review, first_recommended_action}`. For per-finding routing read `findings[].agent_action` (v0.12+; `auto_apply | propose_patch_for_review | escalate_to_human | suppress_with_reason | informational`) instead of synthesizing one from `autofix_safe`/`requires_human_review`/`suggested_patch_kind`. To filter findings by source reliability read `findings[].provenance_kind` (v0.15+; `static_declaration | ast_extraction | keyword_heuristic | regex_heuristic | policy_pack`) — independent of `confidence`. Codex plugin facts, when present, live under `codex_plugin_surface` (v0.13+). Do not gate on `summary.status` for new consumers; it is preserved for v0.7 callers and is baseline-blind. The full field list lives in [`docs/agent-contract-current.md`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/agent-contract-current.md#read-these-first-for-release-gating); this skill links there instead of restating it. v0.11 adds optional `findings[].source.{path, start_line, end_line, start_column, pointer}` provenance keys (kept in v0.19). v0.19 adds the optional `Finding.policy_evidence_source` and `ReleaseDecisionItem.{source, policy_evidence_source}` fields for reviewer-grade dual-source provenance — high-risk findings that fire because of a missing manifest mitigation can carry both the tool location AND the manifest-pointer line. Reports validate against [`docs/report-schema.v0.20.json`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.20.json) (active emitted version). Frozen-reference older schemas (kept for legacy/pre-v0.19 reports): [`v0.18`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.18.json), [`v0.17`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.17.json), [`v0.16`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.16.json), [`v0.15`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.15.json), [`v0.14`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.14.json), [`v0.13`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.13.json), [`v0.12`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.12.json), [`v0.11` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.11.json), [`v0.10` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.10.json), [`v0.9` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.9.json), [`v0.8` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.8.json), and [`v0.7` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.7.json). +- **Report JSON**: `report_schema_version: "0.21"`. Read `release_decision.decision` (`"blocked" | "review_required" | "insufficient_evidence" | "passed"`) **first** for release gating — it is baseline-aware. `insufficient_evidence` (added v0.14) fires when evidence coverage is degraded past threshold (at least half of scanned tools low-confidence — `ceil(N × 0.5)` with a minimum of 1 — or 4+ source warnings); switch on the enum with a `review_required` fallback for unknown values. For privacy audit read `privacy_audit` (v0.18+) to confirm default redaction ran before public artifacts were written; `redacted_paths[]` contains structural paths and counts only. For severity-override audit read the top-level `policy_audit.severity_overrides_applied[]` block (v0.17+) — every manifest-driven severity change carries `{check_id, default_severity, applied_severity, manifest_path, reason, tier_crossed, direction, expires}`. For per-finding decision audit read `release_decision.contribution_rules[]` (v0.17+) — one row per `report.findings` entry with `category` ∈ `{blocker, review_item, excluded}` and `rule` ∈ `{policy_block_new, severity_block_new, policy_baseline_accepted, severity_baseline_accepted, review_required, sub_threshold, suppressed}`. For Action Surface Diff read `action_surface_facts`, `action_surface_diff`, and `findings[].blocks_release` (v0.16+) to understand added/removed/modified external actions and explicit release-policy blockers. For one-fetch summarization read the top-level `agent_summary` block (v0.12+) — `{verdict, headline, blocker_count, review_item_count, auto_appliable_patches, needs_human_review, first_recommended_action}`. For per-finding routing read `findings[].agent_action` (v0.12+; `auto_apply | propose_patch_for_review | escalate_to_human | suppress_with_reason | informational`) instead of synthesizing one from `autofix_safe`/`requires_human_review`/`suggested_patch_kind`. To filter findings by source reliability read `findings[].provenance_kind` (v0.15+; `static_declaration | ast_extraction | keyword_heuristic | regex_heuristic | policy_pack`) — independent of `confidence`. Codex plugin facts, when present, live under `codex_plugin_surface` (v0.13+). Do not gate on `summary.status` for new consumers; it is preserved for v0.7 callers and is baseline-blind. The full field list lives in [`docs/agent-contract-current.md`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/agent-contract-current.md#read-these-first-for-release-gating); this skill links there instead of restating it. v0.11 adds optional `findings[].source.{path, start_line, end_line, start_column, pointer}` provenance keys (kept in v0.19). v0.19 adds the optional `Finding.policy_evidence_source` and `ReleaseDecisionItem.{source, policy_evidence_source}` fields for reviewer-grade dual-source provenance — high-risk findings that fire because of a missing manifest mitigation can carry both the tool location AND the manifest-pointer line. Reports validate against [`docs/report-schema.v0.21.json`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.21.json) (active emitted version). Frozen-reference older schemas (kept for legacy/pre-v0.21 reports): [`v0.20`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.20.json), [`v0.18`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.18.json), [`v0.17`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.17.json), [`v0.16` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.16.json), [`v0.15`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.15.json), [`v0.14`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.14.json), [`v0.13`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.13.json), [`v0.12`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.12.json), [`v0.11` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.11.json), [`v0.10` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.10.json), [`v0.9` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.9.json), [`v0.8` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.8.json), and [`v0.7` (frozen)](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.7.json). - **Release Evidence Packet**: `agents-shipgate-reports/packet.{md,json,html}` (and `packet.pdf` with the `[pdf]` extras) is emitted alongside the report by default. The packet has fixed reviewer sections governed by [`docs/packet-schema.v0.6.json`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/packet-schema.v0.6.json) (latest; v0.6 adds the top-level `evidence_matrix` compact review section AND `ReleaseDecisionItem.{source, policy_evidence_source}` for reviewer-grade dual-source provenance over the v0.5 baseline). See [STABILITY.md §Release Evidence Packet](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/STABILITY.md#release-evidence-packet-v06). Use the packet for reviewer-shaped output; use the report for finding details. - **Single source of truth for the contract**: [`docs/agent-contract-current.md`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/agent-contract-current.md). When the schema bumps, that file updates first. - **Exit codes**: `0` pass, `2` config error, `3` parse error, `4` other error, `20` strict-mode gate failure. diff --git a/src/agents_shipgate/cli/scan.py b/src/agents_shipgate/cli/scan.py index c55d2fa..17371c9 100644 --- a/src/agents_shipgate/cli/scan.py +++ b/src/agents_shipgate/cli/scan.py @@ -43,6 +43,7 @@ from agents_shipgate.core.errors import ConfigError, InputParseError from agents_shipgate.core.findings import ( annotate_remediation, + apply_no_heuristics_filter, apply_severity_overrides, apply_suppressions, assign_finding_ids, @@ -229,6 +230,10 @@ class _ChecksDecision: override_resolution: Any # SeverityOverrideResolution loaded_plugins: list[dict[str, str | None]] context: ScanContext + # v0.21: audit envelope describing the --no-heuristics filter pass. + # Always populated (enabled=False with zero counts when the flag is + # off) so the report shape is stable. + heuristics_filter: Any = None # HeuristicsFilter @dataclass(frozen=True) @@ -287,6 +292,12 @@ class _SanitizedSurfaces: tool_surface_diff: Any baseline_summary: Any privacy_audit: Any + # v0.21: --no-heuristics audit envelope. Always populated (with + # enabled=False and zero counts when the flag is off) so the + # downstream report shape is stable. Threaded straight from + # _ChecksDecision; no sanitization needed (the envelope contains + # only count fields and constant strings). + heuristics_filter: Any # ----------------------------------------------------------------------------- @@ -550,6 +561,7 @@ def _run_checks_and_decide( diffs: _DiffReferences, plugins_enabled: bool | None, suggest_patches: bool, + no_heuristics: bool = False, ) -> _ChecksDecision: """Phase 5: build internal action-surface facts, run all checks (built-in + plugin + policy-pack + action-surface policies), @@ -622,6 +634,13 @@ def _run_checks_and_decide( ) apply_severity_overrides(findings, override_resolution.override_by_check_id) apply_suppressions(findings, manifest.checks.ignore) + # v0.21: --no-heuristics filter runs AFTER manifest suppressions so a + # finding the user explicitly suppressed keeps the user's reason + # (manifest intent wins). The filter only flips findings that are + # still un-suppressed at this point. + heuristics_filter = apply_no_heuristics_filter( + findings, enabled=no_heuristics + ) if suggest_patches: _attach_patches( findings, @@ -655,6 +674,7 @@ def _run_checks_and_decide( override_resolution=override_resolution, loaded_plugins=loaded_plugins, context=context, + heuristics_filter=heuristics_filter, ) @@ -1042,6 +1062,7 @@ def _sanitize_for_output( tool_surface_diff=public_tool_surface_diff, baseline_summary=baseline_summary, privacy_audit=privacy_audit, + heuristics_filter=decision.heuristics_filter, ) @@ -1104,6 +1125,12 @@ def _build_final_report( privacy_audit=sanitized.privacy_audit, ) apply_capability_diff(report, sanitized.tools) + # v0.21: --no-heuristics audit envelope attached BEFORE reviewer_summary + # is built so the projection can include filtered_finding_count in + # the reviewer headline. The envelope itself is computed in Phase 5 + # (apply_no_heuristics_filter inside _run_checks_and_decide); we + # just route it onto the public report here. + report.heuristics_filter = sanitized.heuristics_filter # v0.20: reviewer_summary is built HERE — after apply_capability_diff # has populated misalignments / release_consequence / suggested_scenarios. # Building it inside build_report() would project from incomplete state @@ -1181,6 +1208,7 @@ def run_scan( packet_enabled: bool | None = None, packet_formats: list[str] | None = None, packet_generated_at: str | None = None, + no_heuristics: bool = False, ) -> tuple[ReadinessReport, int]: """Run a full scan pipeline. Returns ``(report, exit_code)``. @@ -1227,6 +1255,7 @@ def run_scan( diffs=diffs, plugins_enabled=plugins_enabled, suggest_patches=suggest_patches, + no_heuristics=no_heuristics, ) plan = _plan_outputs( manifest=resolved.manifest, diff --git a/src/agents_shipgate/core/findings.py b/src/agents_shipgate/core/findings.py index 0eab2dc..0ebd7eb 100644 --- a/src/agents_shipgate/core/findings.py +++ b/src/agents_shipgate/core/findings.py @@ -22,10 +22,12 @@ ) from agents_shipgate.schemas.patches import ManualPatch from agents_shipgate.schemas.report import ( + NO_HEURISTICS_EXCLUDED_PROVENANCE_KINDS, AgentSummary, AgentSummaryAction, BaselineSummary, Finding, + HeuristicsFilter, LoadedPolicyPack, PolicyAudit, PrivacyAudit, @@ -116,6 +118,71 @@ def apply_suppressions( return findings +# v0.21: stable, reviewer-readable suppression reason for the +# ``--no-heuristics`` filter. Distinguishable from manifest-driven +# suppressions (which carry user-provided reason text) so downstream +# tools and reviewers can filter on it without parsing free-form prose. +NO_HEURISTICS_SUPPRESSION_REASON = "filtered by --no-heuristics" + + +def apply_no_heuristics_filter( + findings: list[Finding], + *, + enabled: bool, +) -> HeuristicsFilter: + """v0.21: filter heuristic-provenance findings out of the active set + when ``--no-heuristics`` is enabled. + + Returns a ``HeuristicsFilter`` audit envelope describing the pass — + same shape regardless of ``enabled`` so consumers always read the + same field set. When ``enabled=False`` the envelope reports zero + activity and no findings are mutated. + + Filter semantics (locked in v0.21): + * ``static_declaration`` — KEEP (manifest / MCP / OpenAPI schema) + * ``ast_extraction`` — KEEP (parsed shape from user Python) + * ``policy_pack`` — KEEP (externally loaded rule, explicit) + * ``keyword_heuristic`` — FILTER (token-list match) + * ``regex_heuristic`` — FILTER (regex match, e.g. secrets/injection) + + The exact filter set lives in + ``schemas.report.NO_HEURISTICS_EXCLUDED_PROVENANCE_KINDS`` and is + pinned by ``tests/test_no_heuristics.py`` so the contract surface + cannot drift silently. + + Findings whose ``provenance_kind`` is in the excluded set are + mutated in place: ``suppressed=True`` and + ``suppression_reason=NO_HEURISTICS_SUPPRESSION_REASON``. Manifest- + driven suppressions that already matched the finding are preserved + (the existing reason wins, since the user explicitly opted in to + that suppression). The filter never un-suppresses a finding. + """ + excluded = set(NO_HEURISTICS_EXCLUDED_PROVENANCE_KINDS) + envelope = HeuristicsFilter( + enabled=enabled, + excluded_provenance_kinds=list(NO_HEURISTICS_EXCLUDED_PROVENANCE_KINDS), + ) + if not enabled: + return envelope + + counter: Counter[str] = Counter() + for finding in findings: + if finding.provenance_kind not in excluded: + continue + counter[finding.provenance_kind] += 1 + if finding.suppressed: + # Preserve manifest-driven suppression reason verbatim — the + # user already opted in to that decision. The audit envelope + # still counts the finding so the reviewer sees the overlap. + continue + finding.suppressed = True + finding.suppression_reason = NO_HEURISTICS_SUPPRESSION_REASON + + envelope.filtered_finding_count = sum(counter.values()) + envelope.filtered_by_kind = dict(counter) + return envelope + + def apply_severity_overrides( findings: list[Finding], overrides: dict[str, Severity] ) -> list[Finding]: diff --git a/src/agents_shipgate/schemas/report.py b/src/agents_shipgate/schemas/report.py index d886609..fe1ef4b 100644 --- a/src/agents_shipgate/schemas/report.py +++ b/src/agents_shipgate/schemas/report.py @@ -569,6 +569,53 @@ class PrivacyAudit(BaseModel): notes: list[str] = Field(default_factory=list) +# v0.21: stable list of ``provenance_kind`` values that ``--no-heuristics`` +# excludes from the active finding set. Pinned as a module-level constant +# so downstream consumers (tests, docs, the contract command) can read the +# same source of truth. ``static_declaration`` and ``ast_extraction`` +# describe how the *finding* was produced from declared/parsed-shape data; +# ``keyword_heuristic`` and ``regex_heuristic`` describe token/regex +# matches that are best-effort by nature. ``policy_pack`` stays in scope +# because the rule body is declared, even though the trigger may pattern- +# match — operators who load a policy pack want its findings. +NO_HEURISTICS_EXCLUDED_PROVENANCE_KINDS: tuple[str, ...] = ( + "keyword_heuristic", + "regex_heuristic", +) + + +class HeuristicsFilter(BaseModel): + """v0.21: top-level envelope describing the ``--no-heuristics`` + filter pass. + + Emitted on every report regardless of whether the flag was set, so + consumers always read the same shape. When the flag is unset, + ``enabled=False`` and the count fields are zero — the active + finding set is unchanged. When the flag is set, every finding whose + ``provenance_kind`` is in ``excluded_provenance_kinds`` is marked + ``suppressed=True`` with ``suppression_reason="filtered by + --no-heuristics"`` BEFORE the release decision is built, so heuristic + findings can no longer gate release. Filtered findings stay in + ``findings[]`` for transparency; the audit envelope here records + aggregate counts. + + Earns the contract weight of ``Finding.provenance_kind`` (shipped + v0.15) by giving it a first-class consumer. Same shape pattern as + ``PrivacyAudit``: an envelope that proves the filter ran and tells + a reviewer/agent which findings were excluded and why. + """ + + model_config = ConfigDict(extra="forbid") + + enabled: bool = False + excluded_provenance_kinds: list[str] = Field(default_factory=list) + filtered_finding_count: int = 0 + # Per-provenance-kind breakdown of filtered counts so a reviewer + # can tell "we filtered N regex_heuristic findings and M + # keyword_heuristic findings" without scanning ``findings[]``. + filtered_by_kind: dict[str, int] = Field(default_factory=dict) + + class ReadinessReport(BaseModel): model_config = ConfigDict(extra="allow") @@ -582,7 +629,7 @@ class ReadinessReport(BaseModel): # (manifest YAML path + line) for high-risk findings whose # triggering evidence also lives in the manifest. Old consumers # ignore the new fields. - report_schema_version: str = "0.20" + report_schema_version: str = "0.21" run_id: str # v0.6 (per C13): absolute path to the directory containing # shipgate.yaml. apply-patches uses this to enforce a containment @@ -648,6 +695,14 @@ class ReadinessReport(BaseModel): # envelope after the default-on redaction pass has sanitized public # outputs. Optional at Python level for older fixtures. privacy_audit: PrivacyAudit | None = None + # v0.21: top-level heuristics-filter envelope. Required + non-nullable + # on the wire (mirrors privacy_audit shape). When enabled=False the + # active finding set is unchanged; when enabled=True every finding + # whose ``provenance_kind`` is in ``excluded_provenance_kinds`` has + # been marked ``suppressed=True`` before the release decision was + # built. Optional at the Python level for older test helpers that + # construct minimal reports. + heuristics_filter: HeuristicsFilter | None = None # v0.20: top-level reviewer summary. Deterministic projection of # the reviewer lens surfaces (tool_surface_diff, capability/intent # diff, action_surface_diff, evidence matrix) and audit envelopes diff --git a/tests/test_agent_instructions_renderers.py b/tests/test_agent_instructions_renderers.py index eb57993..0417644 100644 --- a/tests/test_agent_instructions_renderers.py +++ b/tests/test_agent_instructions_renderers.py @@ -39,7 +39,7 @@ REPO_ROOT = Path(__file__).resolve().parent.parent EXPECTED_CLAUDE_CODE_SKILL_RENDER_SHA256 = { ".claude/skills/agents-shipgate/SKILL.md": ( - "b17c53d9905f46b196be38e98cf71e53da6779e3a4f426ecff14f2b0f238aba9" + "011c3c3daeb537566651ba230b94bc3128b2c51fc51e3d360e8a74cf11f7a48f" ), ".claude/skills/agents-shipgate/prompts/add-shipgate-to-repo.md": ( "1ea69b1d3d418080c76540fff3b20044f70ed6787418eb5e4d3d39e036b34014" diff --git a/tests/test_no_heuristics.py b/tests/test_no_heuristics.py new file mode 100644 index 0000000..d7eb7d2 --- /dev/null +++ b/tests/test_no_heuristics.py @@ -0,0 +1,462 @@ +"""v0.21 — ``--no-heuristics`` filter tests. + +Earns the contract weight of ``Finding.provenance_kind`` (shipped v0.15 +as required+non-nullable wire metadata but with no consumer until v0.21) +by giving it a first-class CLI surface and a HeuristicsFilter envelope. + +Scope of this test file: + +1. ``apply_no_heuristics_filter`` semantics (the pure projection). +2. End-to-end ``run_scan`` with ``no_heuristics=True``: heuristic + findings are suppressed, release decision is recomputed without + them, ``report.heuristics_filter`` envelope is populated. +3. Negative parity: with the flag OFF the report shape is unchanged + and the envelope reports zero activity. +4. Contract stability: every value in + ``NO_HEURISTICS_EXCLUDED_PROVENANCE_KINDS`` is a real + ``ProvenanceKind`` literal, and KEEP-list values + (``static_declaration``, ``ast_extraction``, ``policy_pack``) are + NOT filtered. +5. Suppression interaction: manifest-driven suppression reasons are + preserved when the same finding would also be filtered by the flag + (manifest intent wins). +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import get_args + +from agents_shipgate.cli.scan import run_scan +from agents_shipgate.core.findings import ( + NO_HEURISTICS_SUPPRESSION_REASON, + apply_no_heuristics_filter, +) +from agents_shipgate.schemas.common import ProvenanceKind, Severity +from agents_shipgate.schemas.report import ( + NO_HEURISTICS_EXCLUDED_PROVENANCE_KINDS, + Finding, +) + +SUPPORT_REFUND_FIXTURE = Path("samples/support_refund_agent/shipgate.yaml") + + +# --- Pure-function tests --------------------------------------------------- + + +def _finding( + check_id: str, + *, + provenance_kind: ProvenanceKind, + suppressed: bool = False, + suppression_reason: str | None = None, + severity: Severity = "medium", +) -> Finding: + return Finding( + check_id=check_id, + category="test", + severity=severity, + title=f"{check_id} title", + provenance_kind=provenance_kind, + recommendation="test recommendation", + suppressed=suppressed, + suppression_reason=suppression_reason, + ) + + +def test_disabled_filter_returns_zero_activity_envelope() -> None: + """``enabled=False`` is the no-op path: envelope shape is identical + to the enabled case, but counts are zero and no findings mutate.""" + findings = [ + _finding("SHIP-KW", provenance_kind="keyword_heuristic"), + _finding("SHIP-RX", provenance_kind="regex_heuristic"), + _finding("SHIP-DECL", provenance_kind="static_declaration"), + ] + envelope = apply_no_heuristics_filter(findings, enabled=False) + assert envelope.enabled is False + assert envelope.filtered_finding_count == 0 + assert envelope.filtered_by_kind == {} + # excluded_provenance_kinds is still populated so a consumer reading + # the envelope sees the contract regardless of whether the flag fired. + assert envelope.excluded_provenance_kinds == list( + NO_HEURISTICS_EXCLUDED_PROVENANCE_KINDS + ) + # No mutation + assert all(not f.suppressed for f in findings) + + +def test_enabled_filter_marks_heuristic_findings_suppressed() -> None: + """``keyword_heuristic`` and ``regex_heuristic`` findings are + mutated to ``suppressed=True`` with the canonical reason.""" + findings = [ + _finding("SHIP-KW", provenance_kind="keyword_heuristic"), + _finding("SHIP-RX", provenance_kind="regex_heuristic"), + _finding("SHIP-DECL", provenance_kind="static_declaration"), + _finding("SHIP-AST", provenance_kind="ast_extraction"), + _finding("SHIP-POL", provenance_kind="policy_pack"), + ] + envelope = apply_no_heuristics_filter(findings, enabled=True) + assert envelope.enabled is True + assert envelope.filtered_finding_count == 2 + assert envelope.filtered_by_kind == { + "keyword_heuristic": 1, + "regex_heuristic": 1, + } + by_id = {f.check_id: f for f in findings} + # Filtered: suppressed with canonical reason. + assert by_id["SHIP-KW"].suppressed is True + assert ( + by_id["SHIP-KW"].suppression_reason == NO_HEURISTICS_SUPPRESSION_REASON + ) + assert by_id["SHIP-RX"].suppressed is True + assert ( + by_id["SHIP-RX"].suppression_reason == NO_HEURISTICS_SUPPRESSION_REASON + ) + # KEEP-list: NOT touched. + assert by_id["SHIP-DECL"].suppressed is False + assert by_id["SHIP-AST"].suppressed is False + assert by_id["SHIP-POL"].suppressed is False + + +def test_manifest_suppression_reason_preserved_when_also_filterable() -> None: + """A finding the user already suppressed via manifest keeps the + user's reason; the filter still counts it in the envelope (for + audit overlap) but does not overwrite the reason.""" + findings = [ + _finding( + "SHIP-KW", + provenance_kind="keyword_heuristic", + suppressed=True, + suppression_reason="user said it's fine", + ), + ] + envelope = apply_no_heuristics_filter(findings, enabled=True) + # The finding was already suppressed → reason is preserved. + assert findings[0].suppressed is True + assert findings[0].suppression_reason == "user said it's fine" + # But the audit envelope STILL counts it so a reviewer sees the + # overlap. (Without this, a manifest that pre-suppresses all + # heuristic findings would hide the filter's effective scope.) + assert envelope.filtered_finding_count == 1 + + +def test_excluded_kinds_are_real_provenance_kinds() -> None: + """Contract: every value in ``NO_HEURISTICS_EXCLUDED_PROVENANCE_KINDS`` + must be a real ``ProvenanceKind`` literal. A typo here would silently + no-op the filter for that kind.""" + valid = set(get_args(ProvenanceKind)) + for kind in NO_HEURISTICS_EXCLUDED_PROVENANCE_KINDS: + assert kind in valid, f"unknown provenance_kind {kind!r}" + + +def test_keep_list_is_explicit_and_non_overlapping() -> None: + """Contract: the KEEP and EXCLUDE partitions of ``ProvenanceKind`` + are pinned EXACTLY. A future ``ProvenanceKind`` literal must trigger + a deliberate filter-list update — either add it to the EXCLUDE + constant in ``schemas/report.py`` or to the EXPECTED_KEEP set below. + + Pinning the literals (rather than deriving ``kept = valid - excluded``) + is what catches the regression. Derivation-style asserts would silently + pass for any newly-added literal because the derivation would include + it on whichever side the constant didn't, masking the missing decision. + """ + valid = set(get_args(ProvenanceKind)) + excluded = set(NO_HEURISTICS_EXCLUDED_PROVENANCE_KINDS) + expected_exclude = {"keyword_heuristic", "regex_heuristic"} + expected_keep = {"static_declaration", "ast_extraction", "policy_pack"} + assert excluded == expected_exclude, ( + f"NO_HEURISTICS_EXCLUDED_PROVENANCE_KINDS drifted from the pinned " + f"set: got {excluded}, expected {expected_exclude}. Update the " + f"constant in agents_shipgate.schemas.report (and this test) " + f"only with an explicit decision about classification." + ) + assert valid == expected_keep | expected_exclude, ( + f"ProvenanceKind literals changed without updating this test. " + f"Got valid={valid}, expected {expected_keep | expected_exclude}. " + f"Either add the new literal to NO_HEURISTICS_EXCLUDED_PROVENANCE_KINDS " + f"or to the EXPECTED_KEEP pin above — every literal must be " + f"classified by --no-heuristics." + ) + + +# --- End-to-end run_scan tests -------------------------------------------- + + +def test_run_scan_with_no_heuristics_emits_envelope(tmp_path) -> None: + """End-to-end: ``run_scan(no_heuristics=True)`` produces a report + whose ``heuristics_filter`` envelope is enabled+populated.""" + report, _ = run_scan( + config_path=SUPPORT_REFUND_FIXTURE, + output_dir=tmp_path, + formats=["json"], + ci_mode="advisory", + no_heuristics=True, + ) + assert report.heuristics_filter is not None + assert report.heuristics_filter.enabled is True + # The support_refund fixture is known to contain keyword_heuristic + # findings (description-injection and broad-free-text checks). Exact + # count varies as the catalog evolves; assert ≥ 1 so the test + # documents the live-fixture invariant without over-pinning. + assert report.heuristics_filter.filtered_finding_count >= 1 + assert "keyword_heuristic" in report.heuristics_filter.filtered_by_kind + + +def test_run_scan_without_flag_is_unaffected(tmp_path) -> None: + """Negative parity: with the flag OFF the envelope reports zero + activity, and no finding carries the canonical filter reason.""" + report, _ = run_scan( + config_path=SUPPORT_REFUND_FIXTURE, + output_dir=tmp_path, + formats=["json"], + ci_mode="advisory", + # no_heuristics defaults to False + ) + assert report.heuristics_filter is not None + assert report.heuristics_filter.enabled is False + assert report.heuristics_filter.filtered_finding_count == 0 + assert report.heuristics_filter.filtered_by_kind == {} + # No finding was suppressed by the filter. + assert not any( + f.suppression_reason == NO_HEURISTICS_SUPPRESSION_REASON + for f in report.findings + ) + + +def test_no_heuristics_changes_review_items_but_preserves_static_blockers( + tmp_path, +) -> None: + """The filter removes heuristic findings from the ACTIVE set + (review_items shrinks) but does NOT touch static-declaration + blockers. A finding that would have blocked release because of + static evidence still blocks release after filtering.""" + baseline_dir = tmp_path / "baseline" + filtered_dir = tmp_path / "filtered" + baseline_report, _ = run_scan( + config_path=SUPPORT_REFUND_FIXTURE, + output_dir=baseline_dir, + formats=["json"], + ci_mode="advisory", + ) + filtered_report, _ = run_scan( + config_path=SUPPORT_REFUND_FIXTURE, + output_dir=filtered_dir, + formats=["json"], + ci_mode="advisory", + no_heuristics=True, + ) + # Same decision (blocked) but fewer review_items because heuristic + # ones are now suppressed. + assert baseline_report.release_decision is not None + assert filtered_report.release_decision is not None + assert ( + filtered_report.release_decision.decision + == baseline_report.release_decision.decision + ) + assert len(filtered_report.release_decision.review_items) <= len( + baseline_report.release_decision.review_items + ) + # Blockers are static_declaration (approval policy / idempotency + # missing) — unchanged. + assert len(filtered_report.release_decision.blockers) == len( + baseline_report.release_decision.blockers + ) + + +def test_no_heuristics_via_cli_smoke(tmp_path) -> None: + """Smoke-test the CLI surface: invoke via subprocess to confirm + the Typer flag is plumbed end-to-end (catches argument-parser + breakage that pure Python kwarg tests would miss).""" + import subprocess + import sys + + result = subprocess.run( + [ + sys.executable, + "-m", + "agents_shipgate", + "scan", + "-c", + str(SUPPORT_REFUND_FIXTURE), + "--out", + str(tmp_path), + "--format", + "json", + "--no-heuristics", + ], + capture_output=True, + text=True, + check=False, + ) + assert result.returncode == 0, ( + f"scan failed: rc={result.returncode}\nstdout={result.stdout}\n" + f"stderr={result.stderr}" + ) + report = json.loads((tmp_path / "report.json").read_text()) + assert report["heuristics_filter"]["enabled"] is True + assert report["heuristics_filter"]["filtered_finding_count"] >= 1 + + +def test_filter_does_not_overwrite_manifest_suppression_reason_e2e( + tmp_path, +) -> None: + """Integration-level pin of the same rule as + ``test_manifest_suppression_reason_preserved_when_also_filterable``: + if a manifest already suppressed a heuristic finding, the user's + reason wins after the filter runs.""" + # support_refund_agent ships heuristic findings without manifest + # suppression; we'd need a custom fixture to test the overlap path + # end-to-end. The unit test above pins the semantic; this test + # documents the absence of regression by confirming the canonical + # reason appears verbatim on at least one filtered finding when + # NO manifest suppression overlaps. + report, _ = run_scan( + config_path=SUPPORT_REFUND_FIXTURE, + output_dir=tmp_path, + formats=["json"], + ci_mode="advisory", + no_heuristics=True, + ) + flagged = [ + f + for f in report.findings + if f.suppression_reason == NO_HEURISTICS_SUPPRESSION_REASON + ] + assert flagged, ( + "expected at least one finding with the canonical " + "--no-heuristics suppression reason in the support_refund fixture" + ) + # All flagged findings are heuristic provenance. + assert all( + f.provenance_kind in NO_HEURISTICS_EXCLUDED_PROVENANCE_KINDS + for f in flagged + ) + + +# --- Wire-schema enforcement ---------------------------------------------- + + +def test_v21_schema_requires_heuristics_filter_and_rejects_null(tmp_path) -> None: + """The v0.21 schema must REQUIRE the heuristics_filter envelope and + REJECT ``null`` — otherwise the contract that "every emitted report + carries a real HeuristicsFilter" is unenforceable. + + Parallel to ``test_v12_schema_requires_agent_summary_and_agent_action_non_nullable`` + (test_agent_action_summary.py): without this test, a schema edit + that omits the field from `required` or emits `anyOf: [..., null]` + would let a payload silently violate the documented stable shape. + """ + import jsonschema + import pytest as _pytest + + repo_root = Path(__file__).resolve().parent.parent + schema = json.loads( + (repo_root / "docs" / "report-schema.v0.21.json").read_text("utf-8") + ) + + # Top-level required list pins the field. + assert "heuristics_filter" in schema["required"], ( + "v0.21 schema must list heuristics_filter in the top-level required " + "block so payloads without the key fail validation." + ) + + # Direct $ref form — no anyOf-with-null. Otherwise a payload could + # ship `heuristics_filter: null` and validate. + hf_schema = schema["properties"]["heuristics_filter"] + assert hf_schema == {"$ref": "#/$defs/HeuristicsFilter"}, ( + "heuristics_filter must be a direct $ref (no anyOf with null) so " + f"null payloads are rejected at the schema level. Got: {hf_schema}" + ) + + # The HeuristicsFilter definition itself must require all 4 fields. + hf_def = schema["$defs"]["HeuristicsFilter"] + assert set(hf_def["required"]) == { + "enabled", + "excluded_provenance_kinds", + "filtered_finding_count", + "filtered_by_kind", + }, ( + f"HeuristicsFilter must require all 4 fields; got {hf_def['required']}" + ) + + # End-to-end: a real scan payload validates, but each negative mutation + # (strip field, set to null, drop a sub-field, swap to {}) must fail. + report, _ = run_scan( + config_path=SUPPORT_REFUND_FIXTURE, + output_dir=tmp_path, + formats=["json"], + ci_mode="advisory", + ) + payload = json.loads((tmp_path / "report.json").read_text("utf-8")) + + jsonschema.validate(payload, schema) # baseline: real payload validates + + # 1. Strip the entire field → must fail. + stripped = {k: v for k, v in payload.items() if k != "heuristics_filter"} + with _pytest.raises(jsonschema.ValidationError): + jsonschema.validate(stripped, schema) + + # 2. Set the field to null → must fail. + null_envelope = json.loads(json.dumps(payload)) + null_envelope["heuristics_filter"] = None + with _pytest.raises(jsonschema.ValidationError): + jsonschema.validate(null_envelope, schema) + + # 3. Set the field to an empty object {} → must fail (all four + # sub-fields are required). + empty_envelope = json.loads(json.dumps(payload)) + empty_envelope["heuristics_filter"] = {} + with _pytest.raises(jsonschema.ValidationError): + jsonschema.validate(empty_envelope, schema) + + # 4. Drop each required sub-field one at a time → each must fail. + for key in ("enabled", "excluded_provenance_kinds", "filtered_finding_count", "filtered_by_kind"): + bad = json.loads(json.dumps(payload)) + del bad["heuristics_filter"][key] + with _pytest.raises(jsonschema.ValidationError): + jsonschema.validate(bad, schema) + assert report.heuristics_filter is not None # sanity guard for the fixture + + +# --- Reviewer-summary projection ------------------------------------------- + + +def test_reviewer_summary_lens_counts_reflect_post_filter_state(tmp_path) -> None: + """ReviewerSummary's lens/audit counts must reflect the POST-filter + active finding set, not the pre-filter set. The flag works by + suppressing findings *before* build_reviewer_summary is called, so + a count of, e.g., capability_misalignments should drop or stay the + same when --no-heuristics is set. + """ + baseline_dir = tmp_path / "baseline" + filtered_dir = tmp_path / "filtered" + baseline_report, _ = run_scan( + config_path=SUPPORT_REFUND_FIXTURE, + output_dir=baseline_dir, + formats=["json"], + ci_mode="advisory", + ) + filtered_report, _ = run_scan( + config_path=SUPPORT_REFUND_FIXTURE, + output_dir=filtered_dir, + formats=["json"], + ci_mode="advisory", + no_heuristics=True, + ) + assert baseline_report.reviewer_summary is not None + assert filtered_report.reviewer_summary is not None + # Each lens count is monotone-non-increasing under filtering. (A + # filter can only suppress, never produce, findings.) This is the + # invariant the post-filter projection must satisfy. + for field in ( + "capability_misalignments", + "evidence_matrix_gaps", + ): + baseline_value = getattr(baseline_report.reviewer_summary, field) + filtered_value = getattr(filtered_report.reviewer_summary, field) + assert filtered_value <= baseline_value, ( + f"ReviewerSummary.{field} went UP after filtering: " + f"{baseline_value} → {filtered_value}. Filtering can only " + "shrink the active set." + ) diff --git a/tests/test_provenance_kind.py b/tests/test_provenance_kind.py index 05d7a91..1d9afb4 100644 --- a/tests/test_provenance_kind.py +++ b/tests/test_provenance_kind.py @@ -26,7 +26,7 @@ Path("samples/simple_crewai_agent/shipgate.yaml"), ] -CURRENT_SCHEMA = Path("docs/report-schema.v0.20.json") +CURRENT_SCHEMA = Path("docs/report-schema.v0.21.json") def test_provenance_kind_enum_values(): diff --git a/tests/test_public_surface_contract.py b/tests/test_public_surface_contract.py index 8709ddb..86dd715 100644 --- a/tests/test_public_surface_contract.py +++ b/tests/test_public_surface_contract.py @@ -408,9 +408,9 @@ def test_architecture_doc_contract_stamp_matches_runtime(): "`agents-shipgate contract --json`: runtime contract `N`, " "report schema `vX.Y`, packet schema `vX.Y`.'" ) - assert stamp.group("date") == "2026-05-22", ( + assert stamp.group("date") == "2026-05-23", ( "docs/architecture.md contract-check date must stay pinned to " - "2026-05-22 until a deliberate architecture-doc refresh moves it." + "2026-05-23 until a deliberate architecture-doc refresh moves it." ) assert stamp.group("contract") == CONTRACT_VERSION, ( f"docs/architecture.md says runtime contract " diff --git a/tests/test_reports.py b/tests/test_reports.py index aa26660..a079593 100644 --- a/tests/test_reports.py +++ b/tests/test_reports.py @@ -34,6 +34,7 @@ REPORT_SCHEMA_V18 = Path("docs/report-schema.v0.18.json") REPORT_SCHEMA_V19 = Path("docs/report-schema.v0.19.json") REPORT_SCHEMA_V20 = Path("docs/report-schema.v0.20.json") +REPORT_SCHEMA_V21 = Path("docs/report-schema.v0.21.json") CURRENT_REPORT_SCHEMA_VERSION = str( ReadinessReport.model_fields["report_schema_version"].default ) @@ -559,7 +560,7 @@ def test_json_report_validates_against_current_schema(tmp_path): formats=["json"], ci_mode="advisory", ) - schema = json.loads(REPORT_SCHEMA_V20.read_text(encoding="utf-8")) + schema = json.loads(REPORT_SCHEMA_V21.read_text(encoding="utf-8")) validate(instance=report_json_payload(report), schema=schema) @@ -1037,7 +1038,7 @@ def test_current_schema_rejects_null_release_decision_and_consequence(tmp_path): formats=["json"], ci_mode="advisory", ) - schema = json.loads(REPORT_SCHEMA_V20.read_text(encoding="utf-8")) + schema = json.loads(REPORT_SCHEMA_V21.read_text(encoding="utf-8")) payload = report_json_payload(report) # Sanity: real payload validates. diff --git a/tests/test_schema_boundaries.py b/tests/test_schema_boundaries.py index 0f07034..f4e0ef2 100644 --- a/tests/test_schema_boundaries.py +++ b/tests/test_schema_boundaries.py @@ -119,7 +119,7 @@ def test_representative_schema_payloads_keep_wire_fields() -> None: tool_surface=ToolSurfaceSummary(total_tools=0, high_risk_tools=0), ) report_payload = report_json_payload(report) - assert report_payload["report_schema_version"] == "0.20" + assert report_payload["report_schema_version"] == "0.21" assert list(report_payload) == [ "schema_version", "report_schema_version", @@ -156,6 +156,7 @@ def test_representative_schema_payloads_keep_wire_fields() -> None: "agent_summary", "policy_audit", "privacy_audit", + "heuristics_filter", "reviewer_summary", ]