diff --git a/.well-known/agents-shipgate.json b/.well-known/agents-shipgate.json index e0bb5863..2d10bdc9 100644 --- a/.well-known/agents-shipgate.json +++ b/.well-known/agents-shipgate.json @@ -228,7 +228,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.27.json", + "report": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/report-schema.v0.28.json", "agent_result": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/agent-result-schema.v1.json", "codex_boundary_result": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/codex-boundary-result-schema.v1.json", "verifier": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/verifier-schema.v0.1.json", diff --git a/AGENTS.md b/AGENTS.md index ca69c863..27638ba2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -334,13 +334,14 @@ restate version archaeology here): baseline-blind — do not gate on it) - `findings[].{id, fingerprint, check_id, severity, tool_name, evidence, recommendation, suppressed}` - `findings[].{autofix_safe, requires_human_review, suggested_patch_kind, docs_url, provenance_kind, blocks_release}` +- `findings[].policy_routing` (policy-pack owner/reviewer/approval routing metadata only; non-enforcing and not part of `evidence`) - `findings[].patches[]` (only when scan ran with `--suggest-patches`) - `baseline.{matched_count, new_count, resolved_count}` · `tool_inventory[]` · `codex_plugin_surface` - `action_surface_facts` / `action_surface_diff` - Audit envelopes: `release_decision.contribution_rules[]`, `policy_audit`, `privacy_audit`, `heuristics_filter` — explanatory, never a second gate -The full schema is at [`docs/report-schema.v0.27.json`](docs/report-schema.v0.27.json) (current; emitted reports carry `report_schema_version: "0.27"`, adding policy-pack distribution metadata — `loaded_policy_packs[].{source,sha256,sha256_status,owner}` — while preserving findings, fingerprints, policy-pack behavior, capability locks, and the release gate). v0.26 (frozen at [`docs/report-schema.v0.26.json`](docs/report-schema.v0.26.json)) added structured evidence gaps — `release_decision.evidence_coverage.evidence_gaps[]`, one actionable remediation row per low-confidence tool or source warning — plus the advisory `suggested-inventory.json` skeleton written next to `report.json`; gate behavior is unchanged. v0.25 (frozen at [`docs/report-schema.v0.25.json`](docs/report-schema.v0.25.json)) added opt-in capability-linked local trace/provenance evidence while preserving existing findings, fingerprints, policy-pack behavior, capability locks, and the release gate. v0.24 (frozen at [`docs/report-schema.v0.24.json`](docs/report-schema.v0.24.json)) added capability-native policy evidence. v0.23 (frozen at [`docs/report-schema.v0.23.json`](docs/report-schema.v0.23.json)) added semantic metadata to `capability_change` members while preserving the existing capability-change buckets and release gate. v0.22 (frozen at [`docs/report-schema.v0.22.json`](docs/report-schema.v0.22.json)) added the verifier-cycle top-level blocks `capability_change`, `protected_surface_changes`, `effective_policy`, `human_ack`, and `verifier_summary` — reviewer-facing projections that never gate independently — alongside v0.21's `heuristics_filter` audit envelope. v0.21 (frozen at [`docs/report-schema.v0.21.json`](docs/report-schema.v0.21.json)) added the `heuristics_filter` envelope on top of v0.20's `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). +The full schema is at [`docs/report-schema.v0.28.json`](docs/report-schema.v0.28.json) (current; emitted reports carry `report_schema_version: "0.28"`, moving policy-pack owner/reviewer/approval routing metadata to `findings[].policy_routing` so `Finding.evidence` remains deterministic match/gating evidence). v0.27 (frozen at [`docs/report-schema.v0.27.json`](docs/report-schema.v0.27.json)) added policy-pack distribution metadata — `loaded_policy_packs[].{source,sha256,sha256_status,owner}` — while preserving findings, fingerprints, policy-pack behavior, capability locks, and the release gate. v0.26 (frozen at [`docs/report-schema.v0.26.json`](docs/report-schema.v0.26.json)) added structured evidence gaps — `release_decision.evidence_coverage.evidence_gaps[]`, one actionable remediation row per low-confidence tool or source warning — plus the advisory `suggested-inventory.json` skeleton written next to `report.json`; gate behavior is unchanged. v0.25 (frozen at [`docs/report-schema.v0.25.json`](docs/report-schema.v0.25.json)) added opt-in capability-linked local trace/provenance evidence while preserving existing findings, fingerprints, policy-pack behavior, capability locks, and the release gate. v0.24 (frozen at [`docs/report-schema.v0.24.json`](docs/report-schema.v0.24.json)) added capability-native policy evidence. v0.23 (frozen at [`docs/report-schema.v0.23.json`](docs/report-schema.v0.23.json)) added semantic metadata to `capability_change` members while preserving the existing capability-change buckets and release gate. v0.22 (frozen at [`docs/report-schema.v0.22.json`](docs/report-schema.v0.22.json)) added the verifier-cycle top-level blocks `capability_change`, `protected_surface_changes`, `effective_policy`, `human_ack`, and `verifier_summary` — reviewer-facing projections that never gate independently — alongside v0.21's `heuristics_filter` audit envelope. v0.21 (frozen at [`docs/report-schema.v0.21.json`](docs/report-schema.v0.21.json)) added the `heuristics_filter` envelope on top of v0.20's `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. @@ -418,7 +419,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.27.json`](docs/report-schema.v0.27.json) (current). +[`docs/report-schema.v0.28.json`](docs/report-schema.v0.28.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. @@ -456,7 +457,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.27.json`](docs/report-schema.v0.27.json) | `0.27` | +| Report schema (current) | [`docs/report-schema.v0.28.json`](docs/report-schema.v0.28.json) | `0.28` | +| Report schema (v0.27 frozen reference) | [`docs/report-schema.v0.27.json`](docs/report-schema.v0.27.json) | `0.27` | | Report schema (v0.26 frozen reference) | [`docs/report-schema.v0.26.json`](docs/report-schema.v0.26.json) | `0.26` | | Report schema (v0.25 frozen reference) | [`docs/report-schema.v0.25.json`](docs/report-schema.v0.25.json) | `0.25` | | Verify-run schema | [`docs/verify-run-schema.v1.json`](docs/verify-run-schema.v1.json) | `shipgate.verify_run/v1` | diff --git a/README.md b/README.md index 5e4fb8a9..65a25148 100644 --- a/README.md +++ b/README.md @@ -518,7 +518,7 @@ Agents Shipgate is designed to be agent-friendly. If you're a coding agent (Clau - **`agents-shipgate install-hooks --target claude-code --write`** — deterministic Claude Code hooks: a PreToolUse trust-root guard, a cheap trigger check after `Edit|Write|MultiEdit`, and a full `verify` at `Stop`, so the gate runs even when instruction files lose attention on long sessions. See [`docs/agents/use-with-claude-code.md`](docs/agents/use-with-claude-code.md#hooks-the-deterministic-path-recommended). - **`agents-shipgate mcp-serve`** (`[mcp]` extra) — read-only stdio MCP server exposing `shipgate.check`, `shipgate.preflight`, `shipgate.explain`, `shipgate.capabilities`, and `shipgate.handoff` for agents without comfortable shell access. It is static-only and not a general MCP permission broker. See [`docs/mcp-server.md`](docs/mcp-server.md). - **[`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.27.json`](docs/report-schema.v0.27.json)** + **[`docs/agent-handoff-schema.v1.json`](docs/agent-handoff-schema.v1.json)** + **[`docs/preflight-schema.v0.2.json`](docs/preflight-schema.v0.2.json)** — JSON Schemas for live editor validation and agent routing (current; emitted reports carry `report_schema_version: "0.27"`, handoff emits `schema_version: "shipgate.agent_handoff/v1"`, preflight emits `preflight_schema_version: "0.2"`). v0.27 adds policy-pack distribution metadata (`loaded_policy_packs[].{source,sha256,sha256_status,owner}`) over v0.26's structured evidence gaps; gate behavior is unchanged. Read `release_decision.decision` for release gating, `agent-handoff.json.gate` / `controller` for the compact agent step, and `reviewer_summary.first_recommended_surface` for the human-review entry point. `reviewer_summary`, `verifier_summary`, runtime trace/evidence fields, Release Evidence Packet outputs, legacy `agent_result_v1` surfaces, and capability diff projections are supporting/provisional review or compatibility context, not additional gates. The per-version additive history lives in [`docs/agent-contract-current.md`](docs/agent-contract-current.md) and [`STABILITY.md`](STABILITY.md). +- **[`docs/manifest-v0.1.json`](docs/manifest-v0.1.json)** + **[`docs/report-schema.v0.28.json`](docs/report-schema.v0.28.json)** + **[`docs/agent-handoff-schema.v1.json`](docs/agent-handoff-schema.v1.json)** + **[`docs/preflight-schema.v0.2.json`](docs/preflight-schema.v0.2.json)** — JSON Schemas for live editor validation and agent routing (current; emitted reports carry `report_schema_version: "0.28"`, handoff emits `schema_version: "shipgate.agent_handoff/v1"`, preflight emits `preflight_schema_version: "0.2"`). v0.28 moves policy-pack owner/reviewer/approval routing metadata to `findings[].policy_routing` so `Finding.evidence` stays deterministic match/gating evidence; v0.27 added policy-pack distribution metadata (`loaded_policy_packs[].{source,sha256,sha256_status,owner}`) over v0.26's structured evidence gaps. Gate behavior is unchanged. Read `release_decision.decision` for release gating, `agent-handoff.json.gate` / `controller` for the compact agent step, and `reviewer_summary.first_recommended_surface` for the human-review entry point. `reviewer_summary`, `verifier_summary`, runtime trace/evidence fields, Release Evidence Packet outputs, legacy `agent_result_v1` surfaces, and capability diff projections are supporting/provisional review or compatibility context, not additional gates. The per-version additive history lives in [`docs/agent-contract-current.md`](docs/agent-contract-current.md) and [`STABILITY.md`](STABILITY.md). - **[`docs/capability-lock-schema.v0.2.json`](docs/capability-lock-schema.v0.2.json)** + **[`docs/capability-lock-diff-schema.v0.3.json`](docs/capability-lock-diff-schema.v0.3.json)** — stable schemas for the static capability envelope and semantic diff emitted by `agents-shipgate capability` and, in PR workflows, by `agents-shipgate verify`; non-gating and separate from `report.json`. - **[`docs/attestation-schema.v0.4.json`](docs/attestation-schema.v0.4.json)** + **[`docs/org-governance-schema.v0.1.json`](docs/org-governance-schema.v0.1.json)** + **[`docs/org-evidence-bundle-schema.v1.json`](docs/org-evidence-bundle-schema.v1.json)** + **[`docs/registry-schema.v0.3.json`](docs/registry-schema.v0.3.json)** + **[`docs/host-grants-inventory-schema.v0.1.json`](docs/host-grants-inventory-schema.v0.1.json)** — deterministic local attestation, organization governance, org evidence bundle, append-only registry, and host-grant inventory schemas for multi-repo governance. - **[`docs/governance-benchmark-catalog-schema.v0.2.json`](docs/governance-benchmark-catalog-schema.v0.2.json)** + **[`docs/governance-benchmark-result-schema.v0.2.json`](docs/governance-benchmark-result-schema.v0.2.json)** — stable schemas for the research benchmark catalog and deterministic result artifact. @@ -610,7 +610,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, Codex repo config, 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.27"` (additive policy-pack distribution metadata over v0.26's structured evidence gaps) 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.28"` (policy-pack routing metadata in `findings[].policy_routing`, separate from deterministic `Finding.evidence`) while preserving the stable payload contract documented in the report schema. See [ROADMAP.md](ROADMAP.md) for what is planned next. @@ -658,7 +658,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.27](docs/report-schema.v0.27.json) +- [JSON report schema v0.28](docs/report-schema.v0.28.json) - [Capability standard](docs/capability-standard.md) - [Capability lock schema v0.2](docs/capability-lock-schema.v0.2.json) - [Capability lock diff schema v0.3](docs/capability-lock-diff-schema.v0.3.json) diff --git a/STABILITY.md b/STABILITY.md index 6cb29ded..eb7c170a 100644 --- a/STABILITY.md +++ b/STABILITY.md @@ -48,9 +48,11 @@ Breaking changes from the `0.x` line: `shipgate audit --host`) without treating supporting/adoption commands as first-look guidance. `verify_local` remains in `commands{}` as a supporting compatibility command, not a promoted primary flow. - Report JSON remains `report_schema_version: "0.27"` from the current - `0.13.0` line; this alpha does not redefine that frozen report schema. - v0.27 includes policy-pack distribution metadata + Report JSON now uses `report_schema_version: "0.28"`. v0.28 moves + policy-pack owner/reviewer/approval routing metadata to + `findings[].policy_routing` so `Finding.evidence` stays limited to + deterministic rule-match evidence. v0.27 remains the frozen schema that + added policy-pack distribution metadata (`loaded_policy_packs[].{source,sha256,sha256_status,owner}`) over v0.26 evidence-gap rows and `suggested-inventory.json`. - `agents-shipgate verify` writes @@ -309,6 +311,7 @@ In `agents-shipgate-reports/report.json`, the following are guaranteed: - `findings[].provenance_kind` (v0.15+) — records *how a finding was produced*; independent of `confidence`, which records how *sure* we are. It is a reviewer triage/filter signal only: it never changes `release_decision`, severity, fingerprints, baselines, or CI exit behavior. Use `agents-shipgate findings --from agents-shipgate-reports/report.json --provenance-kind keyword_heuristic,regex_heuristic,runtime_trace --json` to filter active findings by provenance class. Enum: `static_declaration | ast_extraction | keyword_heuristic | regex_heuristic | policy_pack | runtime_trace`. `static_declaration` covers manifest, MCP, OpenAPI schema facts, and declarative framework inputs like ADK YAML agent configs or LangChain/CrewAI inventory JSON files — high-trust structural data. `ast_extraction` covers findings against Tools parsed from user Python source by a framework extractor (LangChain function/structured tools, CrewAI function/class tools, ADK Python toolsets); these are subject to extraction error and agents that distrust AST quality can filter them as a class. Framework checks that fire against both AST-extracted and declaratively loaded tools (ADK's per-tool checks) pick the label per tool from `tool.source_type`. `keyword_heuristic` covers token-list matches (broad scope, read-only prompts, free-text parameter names); `regex_heuristic` covers regex matches (secrets, prompt injection); `policy_pack` covers findings emitted by externally loaded policy packs; `runtime_trace` covers findings derived from declared local trace artifacts. Built-in checks set the value via the required kwarg on the `tool_finding`/`agent_finding` helpers; third-party plugin checks that construct `Finding(...)` directly and omit the field are coerced to `static_declaration` by `annotate_remediation` so the wire schema stays satisfied. Required + non-nullable on the wire; the field is Python-Optional only so older v0.12/v0.13 reports loaded by `explain-finding` and minimal synthetic test fixtures keep working. - `findings[].blocks_release` (v0.16+) — explicit release-policy blocking bit. Built-in and user-defined Action Surface Diff policies, plus declarative policy-pack rules with `block: true`, set it for findings that must block release when active and unbaselined; ordinary severity-based gating still works for existing checks. - `findings[].capability_refs` and optional `findings[].capability_policy_evidence` (v0.24+) — capability-native policy evidence for built-in policy checks and policy packs. `capability_refs` is required + always present (empty when no capability-policy subject matched). `capability_policy_evidence` is nullable and carries the matched capability identity, effect, authority, controls, hashes, matched predicates, and source provenance when present. These fields are explanatory only: they are not finding fingerprint inputs, do not affect baselines, and do not introduce a second gate. +- `findings[].policy_routing` (v0.28+) — optional policy-pack owner, reviewers, and approval-routing metadata. It is non-enforcing reviewer/audit metadata: it is not part of `Finding.evidence`, does not affect fingerprints, suppressions, baselines, `blocks_release`, or `release_decision`. Policy-pack `match` predicates and `block: true` remain the only policy-pack inputs that affect findings and release gating. - `findings[].capability_trace_refs` and top-level `capability_runtime_evidence` (v0.25+) — opt-in local trace/provenance evidence linked to `CapabilityFactV1`. Trace refs are required + always present on findings (empty when no local trace row matched). The top-level block carries deterministic summary counts, matched/unmatched trace rows, source provenance, and notes. It is explanatory only: it is not a finding fingerprint input, does not affect baselines or run IDs, does not change capability lock export/diff schemas, and does not introduce a second gate. - `action_surface_facts.actions[]` (v0.16+) — deterministic current action snapshot: action id, operation, effect, normalized risk tags, scopes, approval policy, safeguards, evidence, input fields, and stable hashes. - `action_surface_diff.{enabled, base, summary, added, removed, modified, notes}` (v0.16+) — reviewer-facing delta for what the agent can do vs. a prior report or v0.4 baseline. Policy findings derived from this diff can set `findings[].blocks_release=true` and affect `release_decision.decision` and strict-mode exit behavior. diff --git a/adoption-kits/claude-code-skill/.agents-shipgate-kit-metadata.json b/adoption-kits/claude-code-skill/.agents-shipgate-kit-metadata.json index 87663761..3c9e9a38 100644 --- a/adoption-kits/claude-code-skill/.agents-shipgate-kit-metadata.json +++ b/adoption-kits/claude-code-skill/.agents-shipgate-kit-metadata.json @@ -18,7 +18,8 @@ "1d80cb6eeadf064fbb613ae3bd5b5e46ed4fd729c21b314d30537cb884e4a19e", "398e88622bf73b524f91405ffc5dbccde651c6a9cb7c2df035ab01d39a964e4f", "9860b9246057289450b425daa212f248be8082327101f2a5e6a355a266f779c1", - "5062b30ee84b3871c6532ce0e6c7ad41ac0203a1bd29be51ac3342e01fcd09cb" + "5062b30ee84b3871c6532ce0e6c7ad41ac0203a1bd29be51ac3342e01fcd09cb", + "6031a900efa636677786ceda881af0aa4630500683ee1845f4dd1439fdbe2e70" ], "prompts/add-shipgate-to-repo.md": [ "ea3c37cfbbd42c40d164abfe21d468a3a5550d5384125f94a53c947dea6b4b2a", diff --git a/adoption-kits/claude-code-skill/SKILL.md b/adoption-kits/claude-code-skill/SKILL.md index 4c6af9e5..d6647e18 100644 --- a/adoption-kits/claude-code-skill/SKILL.md +++ b/adoption-kits/claude-code-skill/SKILL.md @@ -76,7 +76,7 @@ For non-GitHub CI (GitLab, CircleCI, Jenkins, Azure Pipelines, Buildkite, Bitbuc - **Installed CLI contract**: when available, run `agents-shipgate contract --json` to verify local schema versions, capability/research surfaces, `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. - **Verifier JSON**: `verifier_schema_version: "0.1"`. Read `merge_verdict`, `can_merge_without_human`, `first_next_action`, `fix_task`, `capability_review.top_changes`, `trust_root_touched`, and `policy_weakened` before summarizing an AI-generated PR. `merge_verdict` is a deterministic projection; the gate remains `report.json.release_decision.decision`. - **Verify run JSON**: `verify-run.json` uses `schema_version: "shipgate.verify_run/v1"` and records stable run identity, subject refs, input hashes, outcome, and artifact hashes. It is the reproducibility artifact for `verify`; do not treat it as a second gate. -- **Report JSON**: `report_schema_version: "0.27"`. Read `release_decision.decision` first for release gating; use `agent_summary` / `findings[].agent_action` for agent routing and `reviewer_summary` for the human-review entry point. v0.27 adds policy-pack distribution metadata (`loaded_policy_packs[].{source,sha256,sha256_status,owner}`) for organization audit while preserving gate behavior. v0.26 added structured evidence gaps (`release_decision.evidence_coverage.evidence_gaps[]` — one actionable remediation row per low-confidence tool or source warning) plus the advisory `suggested-inventory.json` skeleton written next to `report.json`; when the decision is `insufficient_evidence`, follow each gap's `next_action` instead of guessing. v0.25 added opt-in capability-linked local trace/provenance evidence (`capability_runtime_evidence`, `findings[].capability_trace_refs`, and mirrored `ReleaseDecisionItem.capability_trace_refs`) while preserving fingerprints, baselines, and gate behavior. v0.24 added capability-native policy evidence (`findings[].capability_refs`, optional `findings[].capability_policy_evidence`, and mirrored `ReleaseDecisionItem.capability_refs`). v0.23 added semantic metadata to `capability_change` members while preserving existing buckets. v0.22 added the verifier-cycle blocks `capability_change`, `protected_surface_changes`, `effective_policy`, `human_ack`, and `verifier_summary` — all reviewer-facing projections that never gate independently (`release_decision.decision` stays the only gate). `reviewer_summary`, `verifier_summary`, runtime trace/evidence fields, and non-gating capability diff projections are supporting/provisional explanatory context. To remove heuristic findings from the active gate, rerun scan with `--no-heuristics`; filtered findings remain in `findings[]` with `suppressed=true`, and `heuristics_filter` records `enabled`, `excluded_provenance_kinds`, `filtered_finding_count`, and `filtered_by_kind`; `runtime_trace` findings are not filtered as heuristics. To inspect provenance without changing gate behavior, use `agents-shipgate findings --from agents-shipgate-reports/report.json --provenance-kind keyword_heuristic,regex_heuristic,runtime_trace --json`. Do not gate on `summary.status`; it is legacy and 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), and reports validate against [`docs/report-schema.v0.27.json`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.27.json). +- **Report JSON**: `report_schema_version: "0.28"`. Read `release_decision.decision` first for release gating; use `agent_summary` / `findings[].agent_action` for agent routing and `reviewer_summary` for the human-review entry point. v0.28 moves policy-pack owner/reviewer/approval routing metadata to `findings[].policy_routing` so `Finding.evidence` stays deterministic match/gating evidence; routing metadata is non-enforcing and does not affect fingerprints, suppressions, baselines, `blocks_release`, or release decisions. v0.27 added policy-pack distribution metadata (`loaded_policy_packs[].{source,sha256,sha256_status,owner}`) for organization audit while preserving gate behavior. v0.26 added structured evidence gaps (`release_decision.evidence_coverage.evidence_gaps[]` — one actionable remediation row per low-confidence tool or source warning) plus the advisory `suggested-inventory.json` skeleton written next to `report.json`; when the decision is `insufficient_evidence`, follow each gap's `next_action` instead of guessing. v0.25 added opt-in capability-linked local trace/provenance evidence (`capability_runtime_evidence`, `findings[].capability_trace_refs`, and mirrored `ReleaseDecisionItem.capability_trace_refs`) while preserving fingerprints, baselines, and gate behavior. v0.24 added capability-native policy evidence (`findings[].capability_refs`, optional `findings[].capability_policy_evidence`, and mirrored `ReleaseDecisionItem.capability_refs`). v0.23 added semantic metadata to `capability_change` members while preserving existing buckets. v0.22 added the verifier-cycle blocks `capability_change`, `protected_surface_changes`, `effective_policy`, `human_ack`, and `verifier_summary` — all reviewer-facing projections that never gate independently (`release_decision.decision` stays the only gate). `reviewer_summary`, `verifier_summary`, runtime trace/evidence fields, and non-gating capability diff projections are supporting/provisional explanatory context. To remove heuristic findings from the active gate, rerun scan with `--no-heuristics`; filtered findings remain in `findings[]` with `suppressed=true`, and `heuristics_filter` records `enabled`, `excluded_provenance_kinds`, `filtered_finding_count`, and `filtered_by_kind`; `runtime_trace` findings are not filtered as heuristics. To inspect provenance without changing gate behavior, use `agents-shipgate findings --from agents-shipgate-reports/report.json --provenance-kind keyword_heuristic,regex_heuristic,runtime_trace --json`. Do not gate on `summary.status`; it is legacy and 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), and reports validate against [`docs/report-schema.v0.28.json`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.28.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 as a supporting/provisional reviewer artifact. The packet has fixed reviewer sections governed by [`docs/packet-schema.v0.7.json`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/packet-schema.v0.7.json) (latest; v0.7 adds capability-linked local trace evidence summary and trace refs under `human_in_the_loop`). See [STABILITY.md §Release Evidence Packet](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/STABILITY.md#release-evidence-packet-v07). Use the packet for reviewer-shaped output; use the report for finding details. - **Capability standard**: `agents-shipgate capability export` emits a stable static capability lock (`capability_lock_schema_version: "0.2"`) and `agents-shipgate capability diff` emits a stable semantic diff (`capability_lock_diff_schema_version: "0.3"`). These artifacts are supporting/provisional, non-gating, exclude runtime trace evidence, and are documented in [`docs/capability-standard.md`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/capability-standard.md). - **Governance benchmark**: `benchmark/agent-pr-governance/cases.yaml` and `scripts/run_governance_benchmark.py` are the stable research benchmark substrate (`governance_benchmark_result_schema_version: "0.2"`), not a release gate. See [`docs/governance-benchmark.md`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/governance-benchmark.md). diff --git a/docs/INDEX.md b/docs/INDEX.md index 93fbd103..219502e7 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -34,7 +34,8 @@ 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.27.json`](report-schema.v0.27.json) — JSON Schema for `report.json` (current; emitted reports carry `report_schema_version: "0.27"`, adding policy-pack distribution metadata on `loaded_policy_packs[]` while preserving fingerprints, policy-pack behavior, capability locks, and the release gate) +- [`report-schema.v0.28.json`](report-schema.v0.28.json) — JSON Schema for `report.json` (current; emitted reports carry `report_schema_version: "0.28"`, moving policy-pack routing metadata to `findings[].policy_routing` while preserving policy-pack matching, capability locks, baselines, and the release gate) +- [`report-schema.v0.27.json`](report-schema.v0.27.json) — frozen v0.27 reference schema; pre-v0.28 reports validate against this - [`report-schema.v0.26.json`](report-schema.v0.26.json) — frozen v0.26 reference schema; pre-v0.27 reports validate against this - [`report-schema.v0.25.json`](report-schema.v0.25.json) — frozen v0.25 reference schema; pre-v0.26 reports validate against this - [`verifier-schema.v0.1.json`](verifier-schema.v0.1.json) — JSON Schema for `verifier.json`, the primary coding-agent controller artifact emitted by `agents-shipgate verify` diff --git a/docs/agent-contract-current.md b/docs/agent-contract-current.md index 5270dd32..1bceecd1 100644 --- a/docs/agent-contract-current.md +++ b/docs/agent-contract-current.md @@ -32,7 +32,7 @@ Downstream repos generated with - Latest release: `v1.0.0a1` (see [pyproject.toml](../pyproject.toml) for the in-tree version) - Runtime contract: `9` -- Current report schema: `0.27` — [`docs/report-schema.v0.27.json`](report-schema.v0.27.json) +- Current report schema: `0.28` — [`docs/report-schema.v0.28.json`](report-schema.v0.28.json) - Current packet schema: `0.7` — [`docs/packet-schema.v0.7.json`](packet-schema.v0.7.json) - Current verifier schema: `0.1` — [`docs/verifier-schema.v0.1.json`](verifier-schema.v0.1.json) - Current verify-run schema: `shipgate.verify_run/v1` — [`docs/verify-run-schema.v1.json`](verify-run-schema.v1.json) @@ -48,7 +48,7 @@ Downstream repos generated with - Current host-grants inventory schema: `0.1` — [`docs/host-grants-inventory-schema.v0.1.json`](host-grants-inventory-schema.v0.1.json) - Current governance benchmark catalog schema: `0.2` — [`docs/governance-benchmark-catalog-schema.v0.2.json`](governance-benchmark-catalog-schema.v0.2.json) - Current governance benchmark result schema: `0.2` — [`docs/governance-benchmark-result-schema.v0.2.json`](governance-benchmark-result-schema.v0.2.json) -- Frozen-reference report schemas: [`v0.26`](report-schema.v0.26.json), [`v0.25`](report-schema.v0.25.json), [`v0.24`](report-schema.v0.24.json), [`v0.23`](report-schema.v0.23.json), [`v0.22`](report-schema.v0.22.json), [`v0.21`](report-schema.v0.21.json), [`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 report schemas: frozen [`v0.27`](report-schema.v0.27.json), frozen [`v0.26`](report-schema.v0.26.json), frozen [`v0.25`](report-schema.v0.25.json), frozen [`v0.24`](report-schema.v0.24.json), frozen [`v0.23`](report-schema.v0.23.json), frozen [`v0.22`](report-schema.v0.22.json), frozen [`v0.21`](report-schema.v0.21.json), frozen [`v0.20`](report-schema.v0.20.json), frozen [`v0.19`](report-schema.v0.19.json), frozen [`v0.18`](report-schema.v0.18.json), frozen [`v0.17`](report-schema.v0.17.json), frozen [`v0.16`](report-schema.v0.16.json), frozen [`v0.15`](report-schema.v0.15.json), frozen [`v0.14`](report-schema.v0.14.json), frozen [`v0.13`](report-schema.v0.13.json), frozen [`v0.12`](report-schema.v0.12.json), frozen [`v0.11`](report-schema.v0.11.json), frozen [`v0.10`](report-schema.v0.10.json), frozen [`v0.9`](report-schema.v0.9.json), frozen [`v0.8`](report-schema.v0.8.json), frozen [`v0.7`](report-schema.v0.7.json), frozen [`v0.6`](report-schema.v0.6.json), older - Frozen-reference packet schemas live in [`docs/INDEX.md`](INDEX.md#reference). - Frozen experimental capability lock and governance benchmark result schemas live in [`docs/INDEX.md`](INDEX.md#reference). @@ -115,6 +115,7 @@ In `agents-shipgate-reports/report.json`: - `release_decision.{blockers,review_items}[].capability_trace_refs` (v0.25+) — stable local trace-evidence IDs copied from the originating finding when an existing trace/evidence check used declared local trace artifacts. Empty when no local trace row is relevant. This is audit metadata only; `release_decision.decision` remains the gate. - `release_decision.evidence_coverage.evidence_gaps[]` (v0.26+) — one structured row per measurable evidence gap: `{kind, subject, source_type, source_ref, why, next_action}` with `kind` ∈ `{low_confidence_tool, source_warning}` and `next_action` carrying `{kind, command, path, why, expects}` (`kind` ∈ `{declare_tool_inventory, provide_source, review_warning}`). When `decision` is `insufficient_evidence`, work the gaps in order instead of guessing: `declare_tool_inventory` rows point at the advisory `suggested-inventory.json` skeleton scan writes next to `report.json` and name the exact `*.tool_inventories` manifest key to reference it from. Deterministic projection of the coverage counts; never gates independently. - `loaded_policy_packs[].{source,sha256,sha256_status,owner}` (v0.27+) — policy-pack distribution and ownership metadata for organization audit. `sha256_status` is `"verified"` only when the manifest pin matched; otherwise it is `"unpinned"`. This is report metadata; normal pack matching and release gating still come from deterministic rules and `release_decision.decision`. +- `findings[].policy_routing` (v0.28+) — optional policy-pack owner, reviewers, and approval-routing metadata. This is non-enforcing reviewer/audit metadata, not `Finding.evidence`; it does not affect fingerprints, suppressions, baselines, `blocks_release`, or `release_decision`. Policy-pack `match` predicates and `block: true` remain the only policy-pack inputs that affect findings and release gating. - `release_decision.fail_policy.would_fail_ci` — `true`/`false`. Matches what the CI process will exit with. - `release_decision.reason` — one-sentence explanation suitable for a PR comment. - `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). @@ -133,11 +134,18 @@ policy checks and policy packs: matched capability identity, effect, authority, controls, semantic hashes, matched predicates, and source provenance. It is explanatory only and is not included in finding fingerprint inputs. - -Existing `finding.evidence` keys remain stable for legacy readers. Policy -matching is capability-native internally, but policy-pack behavior, suppressions, -severity overrides, baselines, SARIF, Markdown, and GitHub Action outputs remain -compatible. +- `policy_routing | null` — optional policy-pack routing metadata with + `owner`, `reviewers`, and `approval.{required,teams,min_approvals,enforced}`. + `approval.enforced` is always `false`; Shipgate validates declared team names + but does not verify external approval systems or make release decisions from + these fields. + +Deterministic match and gating `finding.evidence` keys remain stable for legacy +readers. Policy-pack routing keys that used to live in `Finding.evidence` now +live in `policy_routing`; old baseline fingerprints are still matched during +baseline comparison. Policy matching is capability-native internally, but +policy-pack behavior, suppressions, severity overrides, baselines, SARIF, +Markdown, and GitHub Action outputs remain compatible. In `findings[]`, v0.25 adds opt-in trace/provenance references for existing trace/evidence checks: @@ -506,7 +514,7 @@ Companion prompt: [`prompts/explain-finding-to-user.md`](../prompts/explain-find - [STABILITY.md](../STABILITY.md) — full alpha 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.27.json`](report-schema.v0.27.json) — machine-validatable JSON Schema for the current report. +- [`docs/report-schema.v0.28.json`](report-schema.v0.28.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.7.json`](packet-schema.v0.7.json) — machine-validatable JSON Schema for the current packet. - [`docs/checks.json`](checks.json) — check catalog, including `mvp_tier` for MVP/readiness triage. diff --git a/docs/ai-search-summary.md b/docs/ai-search-summary.md index e9b406c8..583323b9 100644 --- a/docs/ai-search-summary.md +++ b/docs/ai-search-summary.md @@ -159,6 +159,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.27.json`](report-schema.v0.27.json) (v0.26 frozen at [`report-schema.v0.26.json`](report-schema.v0.26.json), v0.25 frozen at [`report-schema.v0.25.json`](report-schema.v0.25.json), v0.24 frozen at [`report-schema.v0.24.json`](report-schema.v0.24.json), v0.23 frozen at [`report-schema.v0.23.json`](report-schema.v0.23.json), v0.22 frozen at [`report-schema.v0.22.json`](report-schema.v0.22.json)) +- Report schema (current): [`report-schema.v0.28.json`](report-schema.v0.28.json) (v0.27 frozen at [`report-schema.v0.27.json`](report-schema.v0.27.json), v0.26 frozen at [`report-schema.v0.26.json`](report-schema.v0.26.json), v0.25 frozen at [`report-schema.v0.25.json`](report-schema.v0.25.json), v0.24 frozen at [`report-schema.v0.24.json`](report-schema.v0.24.json), v0.23 frozen at [`report-schema.v0.23.json`](report-schema.v0.23.json), v0.22 frozen at [`report-schema.v0.22.json`](report-schema.v0.22.json)) - Packet schema (current): [`packet-schema.v0.7.json`](packet-schema.v0.7.json) (v0.6 frozen at [`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 bbda1a6c..a9172313 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -3,7 +3,7 @@ A single-page summary of the `agents-shipgate` codebase for new contributors and AI coding agents extending the project. Current as of 2026-06-08; auto-checked against `agents-shipgate contract --json`: -runtime contract `9`, report schema `v0.27`, packet schema `v0.7`. +runtime contract `9`, report schema `v0.28`, packet schema `v0.7`. For the per-field stability contract, see [`../STABILITY.md`](../STABILITY.md). For the agent-facing field index, @@ -643,7 +643,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.27"`; older schemas frozen as + `report_schema_version: "0.28"`; 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.7"`; older schemas frozen. diff --git a/docs/autofix-policy.md b/docs/autofix-policy.md index ade1329f..a1e0b4ba 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.27.json`](report-schema.v0.27.json) — current JSON +- [`report-schema.v0.28.json`](report-schema.v0.28.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 d5f66951..e062a6b8 100644 --- a/docs/baseline.md +++ b/docs/baseline.md @@ -70,7 +70,7 @@ fail CI. Reports keep the v0.1 payload contract and add baseline fields: -- `report_schema_version: "0.27"` in current reports +- `report_schema_version: "0.28"` in current reports - `baseline.path` - `baseline.matched_count` - `baseline.new_count` diff --git a/docs/examples.md b/docs/examples.md index cdc7155f..96a053a3 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -65,7 +65,7 @@ The static scan fixtures write: - `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.27.json](report-schema.v0.27.json) (current; emitted reports -carry `report_schema_version: "0.27"`, adding policy-pack distribution metadata -over v0.26 structured evidence gaps; v0.26 is frozen at -[report-schema.v0.26.json](report-schema.v0.26.json)). +[report-schema.v0.28.json](report-schema.v0.28.json) (current; emitted reports +carry `report_schema_version: "0.28"`, moving policy-pack routing metadata to +`findings[].policy_routing`; v0.27 is frozen at +[report-schema.v0.27.json](report-schema.v0.27.json)). diff --git a/docs/faq.md b/docs/faq.md index 03d7fc29..7ac492c9 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -102,7 +102,7 @@ schema. - **Markdown** — `agents-shipgate-reports/report.md`, for human review. - **JSON** — `agents-shipgate-reports/report.json`, machine-readable - (schema v0.27, current). Always parse this for programmatic use. + (schema v0.28, 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/manifest-v0.1.md b/docs/manifest-v0.1.md index 23c677d3..4de720ce 100644 --- a/docs/manifest-v0.1.md +++ b/docs/manifest-v0.1.md @@ -772,6 +772,12 @@ Supported match fields are `risk_tags`, `source_types`, `missing_idempotency_policy`, and `parameters`. Parameter predicates support `name`, `names`, `types`, `missing_maximum`, and `required`. +Policy-pack `owner`, `reviewers`, and `approval` fields are non-enforcing +routing metadata. They appear in reports as `findings[].policy_routing`, not as +`Finding.evidence`, and do not affect fingerprints, suppressions, baselines, or +release decisions. Use deterministic `match` predicates to decide whether a +rule fires, and `block: true` to make a matched rule release-blocking. + ## Severity Overrides Teams can re-rank built-in and policy-pack checks without forking the scanner: diff --git a/docs/organization.md b/docs/organization.md index e0522611..befac564 100644 --- a/docs/organization.md +++ b/docs/organization.md @@ -68,11 +68,12 @@ approval: min_approvals: 1 ``` -These fields do not change rule matching, approval enforcement, or release -decisions. They are reviewer/audit routing metadata and are validated against -`organization.teams` when teams are declared. Shipgate does not call GitHub or -verify whether those approvals happened; use deterministic predicates and -`block: true` for release gating. +These fields do not change rule matching, approval enforcement, fingerprints, or +release decisions. They are emitted as `findings[].policy_routing` +reviewer/audit metadata and are validated against `organization.teams` when +teams are declared. Shipgate does not call GitHub or verify whether those +approvals happened; use deterministic predicates and `block: true` for release +gating. Starter packs live in [`../policies/templates/`](../policies/templates/). diff --git a/docs/overview.md b/docs/overview.md index 914d8670..487b7889 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -44,6 +44,6 @@ surface before production-like permissions are granted. - [Concepts](concepts.md) - [Manifest v0.1](manifest-v0.1.md) - [Check catalog](checks.md) -- [Report schema v0.27](report-schema.v0.27.json) (current; v0.26 frozen at [report-schema.v0.26.json](report-schema.v0.26.json), v0.25 frozen at [report-schema.v0.25.json](report-schema.v0.25.json), v0.24 frozen at [report-schema.v0.24.json](report-schema.v0.24.json), v0.23 frozen at [report-schema.v0.23.json](report-schema.v0.23.json), v0.22 frozen at [report-schema.v0.22.json](report-schema.v0.22.json), v0.21 frozen at [report-schema.v0.21.json](report-schema.v0.21.json)) +- [Report schema v0.28](report-schema.v0.28.json) (current; v0.27 frozen at [report-schema.v0.27.json](report-schema.v0.27.json), v0.26 frozen at [report-schema.v0.26.json](report-schema.v0.26.json), v0.25 frozen at [report-schema.v0.25.json](report-schema.v0.25.json), v0.24 frozen at [report-schema.v0.24.json](report-schema.v0.24.json), v0.23 frozen at [report-schema.v0.23.json](report-schema.v0.23.json), v0.22 frozen at [report-schema.v0.22.json](report-schema.v0.22.json), v0.21 frozen at [report-schema.v0.21.json](report-schema.v0.21.json)) - [Trust model](trust-model.md) - [Agent instructions](../AGENTS.md) diff --git a/docs/policy-packs.md b/docs/policy-packs.md index 81759092..d9149f2f 100644 --- a/docs/policy-packs.md +++ b/docs/policy-packs.md @@ -59,6 +59,10 @@ Supported rule fields: - `confidence`: optional `low`, `medium`, or `high`; defaults to `medium`. - `recommendation`: required remediation text. - `match`: required static predicate object. +- `owner`, `reviewers`, `approval`: optional reviewer/audit routing metadata. + They are emitted as `findings[].policy_routing`, not `Finding.evidence`, and + do not affect fingerprints, suppressions, baselines, `blocks_release`, or + `release_decision`. Supported legacy match fields: @@ -146,6 +150,11 @@ count. Policy-pack findings support suppressions, severity overrides, release-blocking `block: true`, baselines, Markdown, JSON, and SARIF like built-in findings. +Routing metadata is non-enforcing. Shipgate validates declared team names +against `organization.teams` when present, but it does not call GitHub or any +external approval system to verify approvals. Use deterministic `match` +predicates for rule firing and `block: true` for release gating. + ## Testing And Explanation Policy packs are release rules, so they need tests just like code. The diff --git a/docs/report-reading-for-agents.md b/docs/report-reading-for-agents.md index 57a91d3d..a44c447c 100644 --- a/docs/report-reading-for-agents.md +++ b/docs/report-reading-for-agents.md @@ -218,7 +218,7 @@ Surface the `next_action` to the user rather than scraping prose. The full diagn | Schema | Current | Frozen references | File | |---|---|---|---| -| Report | `0.27` | `0.26`, `0.25`, `0.24`, `0.23`, `0.22`, `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.27.json`](report-schema.v0.27.json) | +| Report | `0.28` | `0.27`, `0.26`, `0.25`, `0.24`, `0.23`, `0.22`, `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.28.json`](report-schema.v0.28.json) | | Packet | `0.7` | `0.6`, `0.5`, `0.4`, `0.3`, `0.2`, `0.1` | [`packet-schema.v0.7.json`](packet-schema.v0.7.json) | | Manifest | `0.1` | — | [`manifest-v0.1.json`](manifest-v0.1.json) | | CLI contract | `5` | — | `agents-shipgate contract --json` | diff --git a/docs/report-schema.v0.28.json b/docs/report-schema.v0.28.json new file mode 100644 index 00000000..a66e1677 --- /dev/null +++ b/docs/report-schema.v0.28.json @@ -0,0 +1,5787 @@ +{ + "$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_end_line": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source End Line" + }, + "source_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Id" + }, + "source_location": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Location" + }, + "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" + }, + "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" + }, + "CapabilityChangeBlock": { + "additionalProperties": false, + "description": "The diff-derived capability delta, grouped by direction.\n\nReviewer-facing projection over ``action_surface_diff`` /\n``tool_surface_diff`` (roadmap \u00a77.1). Four member lists \u2014\n``added`` / ``removed`` / ``broadened`` / ``narrowed`` \u2014 plus\n``enabled`` (mirrors the surface-diff enabled flag: ``False`` when no\nbase is available, so the block is a stable empty shape rather than\nabsent). Never gates on its own.", + "properties": { + "added": { + "items": { + "$ref": "#/$defs/CapabilityChangeMember" + }, + "title": "Added", + "type": "array" + }, + "broadened": { + "items": { + "$ref": "#/$defs/CapabilityChangeMember" + }, + "title": "Broadened", + "type": "array" + }, + "enabled": { + "default": false, + "title": "Enabled", + "type": "boolean" + }, + "narrowed": { + "items": { + "$ref": "#/$defs/CapabilityChangeMember" + }, + "title": "Narrowed", + "type": "array" + }, + "removed": { + "items": { + "$ref": "#/$defs/CapabilityChangeMember" + }, + "title": "Removed", + "type": "array" + } + }, + "required": [ + "added", + "broadened", + "enabled", + "narrowed", + "removed" + ], + "title": "CapabilityChangeBlock", + "type": "object" + }, + "CapabilityChangeMember": { + "additionalProperties": false, + "description": "One capability member in a base\u2192head delta.\n\nCarries enough to identify the capability \u2014 ``tool`` plus the\n``action`` and ``scope`` it concerns \u2014 and, for ``broadened`` /\n``narrowed`` directions, the ``before_scope`` / ``after_scope`` so a\nreviewer can see exactly how the scope moved. Membership changes\n(``added`` / ``removed``) leave the before/after scope fields ``None``.\n\nA deterministic ``id`` supplied by the capability-change builder keeps\nthe member stable across runs for byte-equivalent output.", + "properties": { + "action": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Action" + }, + "after_capability_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "After Capability Id" + }, + "after_scope": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "After Scope" + }, + "before_capability_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Before Capability Id" + }, + "before_scope": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Before Scope" + }, + "changed_hashes": { + "items": { + "enum": [ + "identity_hash", + "effect_hash", + "authority_hash", + "control_hash", + "schema_hash", + "risk_hash", + "evidence_hash" + ], + "type": "string" + }, + "title": "Changed Hashes", + "type": "array" + }, + "confidence": { + "default": "medium", + "enum": [ + "low", + "medium", + "high" + ], + "title": "Confidence", + "type": "string" + }, + "direction": { + "enum": [ + "added", + "removed", + "broadened", + "narrowed" + ], + "title": "Direction", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "provenance_kind": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provenance Kind" + }, + "rationale": { + "default": "", + "title": "Rationale", + "type": "string" + }, + "related_finding_ids": { + "items": { + "type": "string" + }, + "title": "Related Finding Ids", + "type": "array" + }, + "release_impact": { + "default": "informational", + "enum": [ + "none", + "informational", + "review_required", + "blocks_release", + "insufficient_evidence" + ], + "title": "Release Impact", + "type": "string" + }, + "risk_tags": { + "items": { + "type": "string" + }, + "title": "Risk Tags", + "type": "array" + }, + "scope": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Scope" + }, + "semantic_changes": { + "items": { + "$ref": "#/$defs/CapabilitySemanticChange" + }, + "title": "Semantic Changes", + "type": "array" + }, + "semantic_direction": { + "default": "unknown", + "enum": [ + "added", + "removed", + "broadened", + "narrowed", + "mixed", + "unknown", + "evidence_only" + ], + "title": "Semantic Direction", + "type": "string" + }, + "subject_kind": { + "default": "unknown", + "enum": [ + "tool", + "action", + "scope", + "policy", + "ci", + "baseline", + "agent_instruction", + "manifest", + "unknown" + ], + "title": "Subject Kind", + "type": "string" + }, + "tool": { + "title": "Tool", + "type": "string" + } + }, + "required": [ + "action", + "after_scope", + "before_scope", + "confidence", + "direction", + "id", + "provenance_kind", + "rationale", + "related_finding_ids", + "release_impact", + "risk_tags", + "scope", + "subject_kind", + "tool" + ], + "title": "CapabilityChangeMember", + "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" + }, + "CapabilityPolicyEvidence": { + "additionalProperties": false, + "description": "Capability-level audit evidence for a policy match.\n\nThis is explanatory metadata only. It lets reviewers see which durable\ncapability fact matched a policy rule without folding that metadata into\nthe legacy ``Finding.evidence`` fingerprint input.", + "properties": { + "authority": { + "additionalProperties": true, + "title": "Authority", + "type": "object" + }, + "capability_id": { + "title": "Capability Id", + "type": "string" + }, + "controls": { + "additionalProperties": true, + "title": "Controls", + "type": "object" + }, + "effect": { + "additionalProperties": true, + "title": "Effect", + "type": "object" + }, + "hashes": { + "additionalProperties": { + "type": "string" + }, + "title": "Hashes", + "type": "object" + }, + "identity": { + "additionalProperties": true, + "title": "Identity", + "type": "object" + }, + "matched_predicates": { + "additionalProperties": true, + "title": "Matched Predicates", + "type": "object" + }, + "source": { + "anyOf": [ + { + "$ref": "#/$defs/SourceReference" + }, + { + "type": "null" + } + ], + "default": null + } + }, + "required": [ + "capability_id", + "identity", + "effect", + "authority", + "controls", + "hashes" + ], + "title": "CapabilityPolicyEvidence", + "type": "object" + }, + "CapabilityRuntimeEvidence": { + "additionalProperties": false, + "description": "Top-level audit block for declared local runtime trace artifacts.", + "properties": { + "enabled": { + "default": false, + "title": "Enabled", + "type": "boolean" + }, + "matched": { + "items": { + "$ref": "#/$defs/CapabilityTraceEvidenceV1" + }, + "title": "Matched", + "type": "array" + }, + "notes": { + "items": { + "type": "string" + }, + "title": "Notes", + "type": "array" + }, + "source_provenance": { + "items": { + "$ref": "#/$defs/SourceReference" + }, + "title": "Source Provenance", + "type": "array" + }, + "summary": { + "$ref": "#/$defs/CapabilityTraceEvidenceSummary" + }, + "unmatched": { + "items": { + "$ref": "#/$defs/CapabilityTraceEvidenceV1" + }, + "title": "Unmatched", + "type": "array" + } + }, + "title": "CapabilityRuntimeEvidence", + "type": "object" + }, + "CapabilitySemanticChange": { + "additionalProperties": false, + "description": "One semantic explanation for a capability delta.", + "properties": { + "after": { + "default": null, + "title": "After" + }, + "before": { + "default": null, + "title": "Before" + }, + "field": { + "title": "Field", + "type": "string" + }, + "kind": { + "title": "Kind", + "type": "string" + }, + "rationale": { + "title": "Rationale", + "type": "string" + } + }, + "required": [ + "kind", + "field", + "rationale" + ], + "title": "CapabilitySemanticChange", + "type": "object" + }, + "CapabilityTraceEvidenceSummary": { + "additionalProperties": false, + "description": "Deterministic counts for opt-in local runtime trace evidence.", + "properties": { + "agent_trace_count": { + "default": 0, + "title": "Agent Trace Count", + "type": "integer" + }, + "api_trace_count": { + "default": 0, + "title": "Api Trace Count", + "type": "integer" + }, + "approval_trace_count": { + "default": 0, + "title": "Approval Trace Count", + "type": "integer" + }, + "matched_trace_count": { + "default": 0, + "title": "Matched Trace Count", + "type": "integer" + }, + "source_count": { + "default": 0, + "title": "Source Count", + "type": "integer" + }, + "trace_count": { + "default": 0, + "title": "Trace Count", + "type": "integer" + }, + "unmatched_trace_count": { + "default": 0, + "title": "Unmatched Trace Count", + "type": "integer" + }, + "warning_count": { + "default": 0, + "title": "Warning Count", + "type": "integer" + } + }, + "title": "CapabilityTraceEvidenceSummary", + "type": "object" + }, + "CapabilityTraceEvidenceV1": { + "additionalProperties": false, + "description": "Allowlisted local trace event linked to a durable capability fact.\n\nRaw prompts, messages, tool arguments, outputs, and arbitrary payloads\nare intentionally absent. ``observed`` contains only normalized scalar\nevidence from the allowlist enforced by ``inputs.traces``.", + "properties": { + "capability_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Capability Id" + }, + "event_hash": { + "title": "Event Hash", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "match_reason": { + "enum": [ + "capability_id", + "tool_name", + "unknown_tool", + "ambiguous_tool", + "invalid_capability_id", + "missing_tool_name" + ], + "title": "Match Reason", + "type": "string" + }, + "matched": { + "default": false, + "title": "Matched", + "type": "boolean" + }, + "matched_capability_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Matched Capability Id" + }, + "observed": { + "additionalProperties": true, + "title": "Observed", + "type": "object" + }, + "operation": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Operation" + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Provider" + }, + "source": { + "anyOf": [ + { + "$ref": "#/$defs/SourceReference" + }, + { + "type": "null" + } + ], + "default": null + }, + "source_hash": { + "title": "Source Hash", + "type": "string" + }, + "source_type": { + "title": "Source Type", + "type": "string" + }, + "tool_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Tool Name" + } + }, + "required": [ + "id", + "source_type", + "match_reason", + "event_hash", + "source_hash" + ], + "title": "CapabilityTraceEvidenceV1", + "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" + }, + "EffectivePolicy": { + "additionalProperties": false, + "description": "Normalized effective-policy snapshot block (roadmap \u00a75.3).\n\nA semantic \u2014 not text \u2014 view of the release policy surface, so the\nbase-vs-head comparator can answer \"was the gate weakened?\" without a\ntext diff. Every field is derived deterministically from the manifest\n(plus, for ``baseline_fingerprints``, the accepted-debt findings), and\nevery list/dict is emitted in sorted order so two builds of the same\nmanifest serialize byte-identically.\n\nFields are chosen so the weakening / expansion classifiers can answer\nthe \u00a75.3 questions by set/rank comparison rather than counting:\n\n- ``ci_mode`` \u2014 was the CI gate weakened (strict -> advisory)?\n- ``fail_on`` \u2014 was the fail-on severity set loosened (head \u2282 base)?\n- ``severity_overrides`` \u2014 was a check's severity lowered across a tier?\n- ``suppressed_check_ids`` \u2014 was a blocking check newly suppressed?\n- ``waiver_scopes`` \u2014 was a waiver scope expanded (head \u228b base)?\n- ``baseline_fingerprints`` \u2014 was the accepted-debt baseline expanded?\n- ``baseline_integrity_mode`` \u2014 was baseline tamper-checking weakened?\n- ``ci_gate_present`` \u2014 was Shipgate CI removed from an opted-in repo?", + "properties": { + "baseline_fingerprints": { + "items": { + "type": "string" + }, + "title": "Baseline Fingerprints", + "type": "array" + }, + "baseline_integrity_mode": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline Integrity Mode" + }, + "ci_gate_present": { + "default": true, + "title": "Ci Gate Present", + "type": "boolean" + }, + "ci_mode": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Ci Mode" + }, + "fail_on": { + "items": { + "type": "string" + }, + "title": "Fail On", + "type": "array" + }, + "severity_overrides": { + "additionalProperties": { + "type": "string" + }, + "title": "Severity Overrides", + "type": "object" + }, + "suppressed_check_ids": { + "items": { + "type": "string" + }, + "title": "Suppressed Check Ids", + "type": "array" + }, + "waiver_scopes": { + "items": { + "type": "string" + }, + "title": "Waiver Scopes", + "type": "array" + } + }, + "required": [ + "baseline_fingerprints", + "baseline_integrity_mode", + "ci_gate_present", + "ci_mode", + "fail_on", + "severity_overrides", + "suppressed_check_ids", + "waiver_scopes" + ], + "title": "EffectivePolicy", + "type": "object" + }, + "EvidenceCoverageDecision": { + "properties": { + "evidence_gaps": { + "items": { + "$ref": "#/$defs/EvidenceGap" + }, + "title": "Evidence Gaps", + "type": "array" + }, + "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" + }, + "EvidenceGap": { + "description": "v0.26: one structured row per measurable evidence gap.\n\n``insufficient_evidence`` previously diagnosed without prescribing;\neach gap names the degraded subject and the specific next action\nthat raises extraction confidence. Purely explanatory \u2014 gating\nstill uses only the counts (the gap list is a projection of them).", + "properties": { + "kind": { + "enum": [ + "low_confidence_tool", + "source_warning" + ], + "title": "Kind", + "type": "string" + }, + "next_action": { + "$ref": "#/$defs/EvidenceGapAction" + }, + "source_ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Ref" + }, + "source_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source Type" + }, + "subject": { + "title": "Subject", + "type": "string" + }, + "why": { + "title": "Why", + "type": "string" + } + }, + "required": [ + "kind", + "subject", + "why", + "next_action" + ], + "title": "EvidenceGap", + "type": "object" + }, + "EvidenceGapAction": { + "description": "One concrete, mechanically-executable step that closes a gap.\n\nMirrors the agent-mode ``next_actions[]`` error shape\n(``kind``/``command``/``path``/``why``/``expects``) so agents reuse\none routing vocabulary across error recovery and evidence repair.", + "properties": { + "command": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Command" + }, + "expects": { + "title": "Expects", + "type": "string" + }, + "kind": { + "enum": [ + "declare_tool_inventory", + "provide_source", + "review_warning" + ], + "title": "Kind", + "type": "string" + }, + "path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Path" + }, + "why": { + "title": "Why", + "type": "string" + } + }, + "required": [ + "kind", + "why", + "expects" + ], + "title": "EvidenceGapAction", + "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" + }, + "capability_policy_evidence": { + "anyOf": [ + { + "$ref": "#/$defs/CapabilityPolicyEvidence" + }, + { + "type": "null" + } + ], + "default": null + }, + "capability_refs": { + "items": { + "type": "string" + }, + "title": "Capability Refs", + "type": "array" + }, + "capability_trace_refs": { + "items": { + "type": "string" + }, + "title": "Capability Trace Refs", + "type": "array" + }, + "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 + }, + "policy_routing": { + "anyOf": [ + { + "$ref": "#/$defs/PolicyRoutingMetadata" + }, + { + "type": "null" + } + ], + "default": null + }, + "provenance_kind": { + "enum": [ + "static_declaration", + "ast_extraction", + "keyword_heuristic", + "regex_heuristic", + "policy_pack", + "runtime_trace" + ], + "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", + "capability_refs", + "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" + }, + "HumanAck": { + "additionalProperties": false, + "description": "Human-acknowledgement state block (Decision 4 shape).\n\n``required`` is set by the human-ack builder when a trust-root\nweakening finding needs declared human authority; ``satisfied`` is\n``True`` when every required acknowledgement is present. ``acks``\nlists the declared entries; ``outstanding`` lists the surfaces still\nneeding one. The default empty shape is not required and therefore\nsatisfied.", + "properties": { + "acks": { + "items": { + "$ref": "#/$defs/HumanAckEntry" + }, + "title": "Acks", + "type": "array" + }, + "outstanding": { + "items": { + "type": "string" + }, + "title": "Outstanding", + "type": "array" + }, + "required": { + "default": false, + "title": "Required", + "type": "boolean" + }, + "satisfied": { + "default": true, + "title": "Satisfied", + "type": "boolean" + } + }, + "required": [ + "acks", + "outstanding", + "required", + "satisfied" + ], + "title": "HumanAck", + "type": "object" + }, + "HumanAckEntry": { + "additionalProperties": false, + "description": "One declared human-acknowledgement record.\n\nWithin the static boundary acknowledgement can only be *declared*\nevidence (roadmap \u00a75.4) \u2014 never inferred. An entry names the human\n``owner``, the ``reason``, an optional ``expires`` date (copied\nverbatim from the source, never generated), and the\n``affected_surface`` the ack covers.", + "properties": { + "affected_surface": { + "title": "Affected Surface", + "type": "string" + }, + "expires": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Expires" + }, + "owner": { + "title": "Owner", + "type": "string" + }, + "reason": { + "title": "Reason", + "type": "string" + }, + "source": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source" + } + }, + "required": [ + "affected_surface", + "expires", + "owner", + "reason", + "source" + ], + "title": "HumanAckEntry", + "type": "object" + }, + "LoadedPolicyPack": { + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "owner": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Owner" + }, + "path": { + "title": "Path", + "type": "string" + }, + "rule_count": { + "title": "Rule Count", + "type": "integer" + }, + "sha256": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sha256" + }, + "sha256_status": { + "default": "unpinned", + "enum": [ + "unpinned", + "verified" + ], + "title": "Sha256 Status", + "type": "string" + }, + "source": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Source" + }, + "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" + }, + "PolicyApprovalRouting": { + "additionalProperties": false, + "description": "Non-enforcing approval-routing metadata from a policy-pack rule.", + "properties": { + "enforced": { + "const": false, + "default": false, + "title": "Enforced", + "type": "boolean" + }, + "min_approvals": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Min Approvals" + }, + "required": { + "default": false, + "title": "Required", + "type": "boolean" + }, + "teams": { + "items": { + "type": "string" + }, + "title": "Teams", + "type": "array" + } + }, + "title": "PolicyApprovalRouting", + "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" + }, + "PolicyRoutingMetadata": { + "additionalProperties": false, + "description": "Reviewer routing metadata carried outside finding evidence.", + "properties": { + "approval": { + "$ref": "#/$defs/PolicyApprovalRouting" + }, + "owner": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Owner" + }, + "reviewers": { + "items": { + "type": "string" + }, + "title": "Reviewers", + "type": "array" + } + }, + "title": "PolicyRoutingMetadata", + "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" + }, + "ProtectedSurfaceChange": { + "additionalProperties": false, + "description": "One touched protected path / policy surface (trust root).\n\nTier A trust-root protection (roadmap \u00a75.1/\u00a75.2) records *which*\nprotected surface a PR touched. ``path`` is the changed file; ``kind``\nis the classifier bucket it matched (e.g. ``manifest``, ``ci_gate``,\n``policy``); ``glob`` is the matched pattern. ``related_finding_ids``\nlinks to the ordinary ``SHIP-VERIFY-*`` findings that gate.", + "properties": { + "glob": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Glob" + }, + "kind": { + "default": "unknown", + "title": "Kind", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "related_finding_ids": { + "items": { + "type": "string" + }, + "title": "Related Finding Ids", + "type": "array" + } + }, + "required": [ + "glob", + "kind", + "path", + "related_finding_ids" + ], + "title": "ProtectedSurfaceChange", + "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" + }, + "capability_refs": { + "items": { + "type": "string" + }, + "title": "Capability Refs", + "type": "array" + }, + "capability_trace_refs": { + "items": { + "type": "string" + }, + "title": "Capability Trace Refs", + "type": "array" + }, + "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", + "capability_refs", + "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" + }, + "VerifierCapabilityDeltaSummary": { + "additionalProperties": false, + "description": "Counts of the capability delta, by direction.\n\nEqual by construction to the lengths of the matching\n``CapabilityChangeBlock`` member lists (roadmap \u00a78 required test:\nreviewer/verifier capability counts equal the underlying rollup).", + "properties": { + "added": { + "default": 0, + "title": "Added", + "type": "integer" + }, + "broadened": { + "default": 0, + "title": "Broadened", + "type": "integer" + }, + "narrowed": { + "default": 0, + "title": "Narrowed", + "type": "integer" + }, + "removed": { + "default": 0, + "title": "Removed", + "type": "integer" + } + }, + "required": [ + "added", + "broadened", + "narrowed", + "removed" + ], + "title": "VerifierCapabilityDeltaSummary", + "type": "object" + }, + "VerifierReasonCodeCount": { + "additionalProperties": false, + "description": "One ``(reason_code, count)`` pair for the verifier summary.\n\nA list of these (rather than a dict) keeps the ordering explicit and\nbyte-stable; the builder sorts them deterministically.", + "properties": { + "count": { + "default": 0, + "title": "Count", + "type": "integer" + }, + "reason_code": { + "title": "Reason Code", + "type": "string" + } + }, + "required": [ + "count", + "reason_code" + ], + "title": "VerifierReasonCodeCount", + "type": "object" + }, + "VerifierSummary": { + "additionalProperties": false, + "description": "Top-level verifier composition alias (Decision 5 shape).\n\nA byte-stable projection bundling the release verdict, finding counts,\nthe capability delta summary, and trust-root / acknowledgement flags\nfor one-fetch controller consumption. Composition only \u2014 it cannot\nintroduce a finding-independent blocker, and ``verdict`` MUST mirror\n``release_decision.decision`` (Principle 2; enforced by a property\ntest). Parallels ``agent_summary`` / ``reviewer_summary``.\n\n``top_reason_codes`` is capped at the top five by the builder, ranked\nseverity desc, count desc, then reason code asc.", + "properties": { + "by_reason_code": { + "additionalProperties": { + "type": "integer" + }, + "title": "By Reason Code", + "type": "object" + }, + "by_severity": { + "additionalProperties": { + "type": "integer" + }, + "title": "By Severity", + "type": "object" + }, + "capability_delta_summary": { + "$ref": "#/$defs/VerifierCapabilityDeltaSummary" + }, + "human_ack_required": { + "default": false, + "title": "Human Ack Required", + "type": "boolean" + }, + "human_ack_satisfied": { + "default": true, + "title": "Human Ack Satisfied", + "type": "boolean" + }, + "policy_weakened": { + "default": false, + "title": "Policy Weakened", + "type": "boolean" + }, + "protected_surface_touched": { + "default": false, + "title": "Protected Surface Touched", + "type": "boolean" + }, + "top_reason_codes": { + "items": { + "$ref": "#/$defs/VerifierReasonCodeCount" + }, + "title": "Top Reason Codes", + "type": "array" + }, + "verdict": { + "enum": [ + "blocked", + "review_required", + "insufficient_evidence", + "passed" + ], + "title": "Verdict", + "type": "string" + } + }, + "required": [ + "by_reason_code", + "by_severity", + "capability_delta_summary", + "human_ack_required", + "human_ack_satisfied", + "policy_weakened", + "protected_surface_touched", + "top_reason_codes", + "verdict" + ], + "title": "VerifierSummary", + "type": "object" + } + }, + "$id": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/report-schema.v0.28.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_change": { + "$ref": "#/$defs/CapabilityChangeBlock" + }, + "capability_facts": { + "items": { + "$ref": "#/$defs/CapabilityFact" + }, + "title": "Capability Facts", + "type": "array" + }, + "capability_runtime_evidence": { + "$ref": "#/$defs/CapabilityRuntimeEvidence" + }, + "codex_plugin_surface": { + "anyOf": [ + { + "$ref": "#/$defs/CodexPluginSurface" + }, + { + "type": "null" + } + ], + "default": null + }, + "declared_intentions": { + "items": { + "$ref": "#/$defs/DeclaredIntention" + }, + "title": "Declared Intentions", + "type": "array" + }, + "effective_policy": { + "$ref": "#/$defs/EffectivePolicy" + }, + "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" + }, + "human_ack": { + "$ref": "#/$defs/HumanAck" + }, + "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" + }, + "protected_surface_changes": { + "items": { + "$ref": "#/$defs/ProtectedSurfaceChange" + }, + "title": "Protected Surface Changes", + "type": "array" + }, + "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.28" + }, + "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" + }, + "verifier_summary": { + "$ref": "#/$defs/VerifierSummary" + } + }, + "required": [ + "action_surface_diff", + "action_surface_facts", + "agent", + "agent_summary", + "capability_change", + "capability_facts", + "codex_plugin_surface", + "declared_intentions", + "effective_policy", + "environment", + "findings", + "frameworks", + "generated_reports", + "heuristics_filter", + "human_ack", + "loaded_adapters", + "loaded_plugins", + "loaded_policy_packs", + "misalignments", + "policy_audit", + "privacy_audit", + "project", + "protected_surface_changes", + "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", + "verifier_summary" + ], + "title": "Agents Shipgate Readiness Report v0.28", + "type": "object" +} diff --git a/docs/report-v1-consolidation-rc.md b/docs/report-v1-consolidation-rc.md index 8a85e57d..b1ddc853 100644 --- a/docs/report-v1-consolidation-rc.md +++ b/docs/report-v1-consolidation-rc.md @@ -2,7 +2,7 @@ > Status: **proposal** (no behavior change in this document). Target: > freeze a v1.0 report schema whose top-level surface stops growing. -> Written 2026-06; current runtime is `report_schema_version: "0.27"`. +> Written 2026-06; current runtime is `report_schema_version: "0.28"`. ## Problem @@ -75,10 +75,10 @@ Rules: 1. A generated `docs/report-schema.v1.0-rc1.json` with the regrouped shape; goldens render from the same scan run in both shapes. -2. Contract test: every leaf field in v0.27 maps to exactly one v1 path +2. Contract test: every leaf field in v0.28 maps to exactly one v1 path (no drops, no renames) — a generated mapping table is committed as `docs/report-v1-field-map.json`. -3. Baseline round-trip: a v0.27 baseline matches identically against a +3. Baseline round-trip: a v0.28 baseline matches identically against a v1-shaped scan. 4. Consumer dry-run: Action outputs, PR comment, packet, SARIF, agent result, and attestation all build from the v1 shape with zero output diff --git a/llms-full.txt b/llms-full.txt index 67f1ee00..92fad87f 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -359,13 +359,14 @@ restate version archaeology here): baseline-blind — do not gate on it) - `findings[].{id, fingerprint, check_id, severity, tool_name, evidence, recommendation, suppressed}` - `findings[].{autofix_safe, requires_human_review, suggested_patch_kind, docs_url, provenance_kind, blocks_release}` +- `findings[].policy_routing` (policy-pack owner/reviewer/approval routing metadata only; non-enforcing and not part of `evidence`) - `findings[].patches[]` (only when scan ran with `--suggest-patches`) - `baseline.{matched_count, new_count, resolved_count}` · `tool_inventory[]` · `codex_plugin_surface` - `action_surface_facts` / `action_surface_diff` - Audit envelopes: `release_decision.contribution_rules[]`, `policy_audit`, `privacy_audit`, `heuristics_filter` — explanatory, never a second gate -The full schema is at [`docs/report-schema.v0.27.json`](docs/report-schema.v0.27.json) (current; emitted reports carry `report_schema_version: "0.27"`, adding policy-pack distribution metadata — `loaded_policy_packs[].{source,sha256,sha256_status,owner}` — while preserving findings, fingerprints, policy-pack behavior, capability locks, and the release gate). v0.26 (frozen at [`docs/report-schema.v0.26.json`](docs/report-schema.v0.26.json)) added structured evidence gaps — `release_decision.evidence_coverage.evidence_gaps[]`, one actionable remediation row per low-confidence tool or source warning — plus the advisory `suggested-inventory.json` skeleton written next to `report.json`; gate behavior is unchanged. v0.25 (frozen at [`docs/report-schema.v0.25.json`](docs/report-schema.v0.25.json)) added opt-in capability-linked local trace/provenance evidence while preserving existing findings, fingerprints, policy-pack behavior, capability locks, and the release gate. v0.24 (frozen at [`docs/report-schema.v0.24.json`](docs/report-schema.v0.24.json)) added capability-native policy evidence. v0.23 (frozen at [`docs/report-schema.v0.23.json`](docs/report-schema.v0.23.json)) added semantic metadata to `capability_change` members while preserving the existing capability-change buckets and release gate. v0.22 (frozen at [`docs/report-schema.v0.22.json`](docs/report-schema.v0.22.json)) added the verifier-cycle top-level blocks `capability_change`, `protected_surface_changes`, `effective_policy`, `human_ack`, and `verifier_summary` — reviewer-facing projections that never gate independently — alongside v0.21's `heuristics_filter` audit envelope. v0.21 (frozen at [`docs/report-schema.v0.21.json`](docs/report-schema.v0.21.json)) added the `heuristics_filter` envelope on top of v0.20's `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). +The full schema is at [`docs/report-schema.v0.28.json`](docs/report-schema.v0.28.json) (current; emitted reports carry `report_schema_version: "0.28"`, moving policy-pack owner/reviewer/approval routing metadata to `findings[].policy_routing` so `Finding.evidence` remains deterministic match/gating evidence). v0.27 (frozen at [`docs/report-schema.v0.27.json`](docs/report-schema.v0.27.json)) added policy-pack distribution metadata — `loaded_policy_packs[].{source,sha256,sha256_status,owner}` — while preserving findings, fingerprints, policy-pack behavior, capability locks, and the release gate. v0.26 (frozen at [`docs/report-schema.v0.26.json`](docs/report-schema.v0.26.json)) added structured evidence gaps — `release_decision.evidence_coverage.evidence_gaps[]`, one actionable remediation row per low-confidence tool or source warning — plus the advisory `suggested-inventory.json` skeleton written next to `report.json`; gate behavior is unchanged. v0.25 (frozen at [`docs/report-schema.v0.25.json`](docs/report-schema.v0.25.json)) added opt-in capability-linked local trace/provenance evidence while preserving existing findings, fingerprints, policy-pack behavior, capability locks, and the release gate. v0.24 (frozen at [`docs/report-schema.v0.24.json`](docs/report-schema.v0.24.json)) added capability-native policy evidence. v0.23 (frozen at [`docs/report-schema.v0.23.json`](docs/report-schema.v0.23.json)) added semantic metadata to `capability_change` members while preserving the existing capability-change buckets and release gate. v0.22 (frozen at [`docs/report-schema.v0.22.json`](docs/report-schema.v0.22.json)) added the verifier-cycle top-level blocks `capability_change`, `protected_surface_changes`, `effective_policy`, `human_ack`, and `verifier_summary` — reviewer-facing projections that never gate independently — alongside v0.21's `heuristics_filter` audit envelope. v0.21 (frozen at [`docs/report-schema.v0.21.json`](docs/report-schema.v0.21.json)) added the `heuristics_filter` envelope on top of v0.20's `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. @@ -443,7 +444,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.27.json`](docs/report-schema.v0.27.json) (current). +[`docs/report-schema.v0.28.json`](docs/report-schema.v0.28.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. @@ -481,7 +482,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.27.json`](docs/report-schema.v0.27.json) | `0.27` | +| Report schema (current) | [`docs/report-schema.v0.28.json`](docs/report-schema.v0.28.json) | `0.28` | +| Report schema (v0.27 frozen reference) | [`docs/report-schema.v0.27.json`](docs/report-schema.v0.27.json) | `0.27` | | Report schema (v0.26 frozen reference) | [`docs/report-schema.v0.26.json`](docs/report-schema.v0.26.json) | `0.26` | | Report schema (v0.25 frozen reference) | [`docs/report-schema.v0.25.json`](docs/report-schema.v0.25.json) | `0.25` | | Verify-run schema | [`docs/verify-run-schema.v1.json`](docs/verify-run-schema.v1.json) | `shipgate.verify_run/v1` | @@ -1009,7 +1011,7 @@ Downstream repos generated with - Latest release: `v1.0.0a1` (see [pyproject.toml](../pyproject.toml) for the in-tree version) - Runtime contract: `9` -- Current report schema: `0.27` — [`docs/report-schema.v0.27.json`](report-schema.v0.27.json) +- Current report schema: `0.28` — [`docs/report-schema.v0.28.json`](report-schema.v0.28.json) - Current packet schema: `0.7` — [`docs/packet-schema.v0.7.json`](packet-schema.v0.7.json) - Current verifier schema: `0.1` — [`docs/verifier-schema.v0.1.json`](verifier-schema.v0.1.json) - Current verify-run schema: `shipgate.verify_run/v1` — [`docs/verify-run-schema.v1.json`](verify-run-schema.v1.json) @@ -1025,7 +1027,7 @@ Downstream repos generated with - Current host-grants inventory schema: `0.1` — [`docs/host-grants-inventory-schema.v0.1.json`](host-grants-inventory-schema.v0.1.json) - Current governance benchmark catalog schema: `0.2` — [`docs/governance-benchmark-catalog-schema.v0.2.json`](governance-benchmark-catalog-schema.v0.2.json) - Current governance benchmark result schema: `0.2` — [`docs/governance-benchmark-result-schema.v0.2.json`](governance-benchmark-result-schema.v0.2.json) -- Frozen-reference report schemas: [`v0.26`](report-schema.v0.26.json), [`v0.25`](report-schema.v0.25.json), [`v0.24`](report-schema.v0.24.json), [`v0.23`](report-schema.v0.23.json), [`v0.22`](report-schema.v0.22.json), [`v0.21`](report-schema.v0.21.json), [`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 report schemas: frozen [`v0.27`](report-schema.v0.27.json), frozen [`v0.26`](report-schema.v0.26.json), frozen [`v0.25`](report-schema.v0.25.json), frozen [`v0.24`](report-schema.v0.24.json), frozen [`v0.23`](report-schema.v0.23.json), frozen [`v0.22`](report-schema.v0.22.json), frozen [`v0.21`](report-schema.v0.21.json), frozen [`v0.20`](report-schema.v0.20.json), frozen [`v0.19`](report-schema.v0.19.json), frozen [`v0.18`](report-schema.v0.18.json), frozen [`v0.17`](report-schema.v0.17.json), frozen [`v0.16`](report-schema.v0.16.json), frozen [`v0.15`](report-schema.v0.15.json), frozen [`v0.14`](report-schema.v0.14.json), frozen [`v0.13`](report-schema.v0.13.json), frozen [`v0.12`](report-schema.v0.12.json), frozen [`v0.11`](report-schema.v0.11.json), frozen [`v0.10`](report-schema.v0.10.json), frozen [`v0.9`](report-schema.v0.9.json), frozen [`v0.8`](report-schema.v0.8.json), frozen [`v0.7`](report-schema.v0.7.json), frozen [`v0.6`](report-schema.v0.6.json), older - Frozen-reference packet schemas live in [`docs/INDEX.md`](INDEX.md#reference). - Frozen experimental capability lock and governance benchmark result schemas live in [`docs/INDEX.md`](INDEX.md#reference). @@ -1092,6 +1094,7 @@ In `agents-shipgate-reports/report.json`: - `release_decision.{blockers,review_items}[].capability_trace_refs` (v0.25+) — stable local trace-evidence IDs copied from the originating finding when an existing trace/evidence check used declared local trace artifacts. Empty when no local trace row is relevant. This is audit metadata only; `release_decision.decision` remains the gate. - `release_decision.evidence_coverage.evidence_gaps[]` (v0.26+) — one structured row per measurable evidence gap: `{kind, subject, source_type, source_ref, why, next_action}` with `kind` ∈ `{low_confidence_tool, source_warning}` and `next_action` carrying `{kind, command, path, why, expects}` (`kind` ∈ `{declare_tool_inventory, provide_source, review_warning}`). When `decision` is `insufficient_evidence`, work the gaps in order instead of guessing: `declare_tool_inventory` rows point at the advisory `suggested-inventory.json` skeleton scan writes next to `report.json` and name the exact `*.tool_inventories` manifest key to reference it from. Deterministic projection of the coverage counts; never gates independently. - `loaded_policy_packs[].{source,sha256,sha256_status,owner}` (v0.27+) — policy-pack distribution and ownership metadata for organization audit. `sha256_status` is `"verified"` only when the manifest pin matched; otherwise it is `"unpinned"`. This is report metadata; normal pack matching and release gating still come from deterministic rules and `release_decision.decision`. +- `findings[].policy_routing` (v0.28+) — optional policy-pack owner, reviewers, and approval-routing metadata. This is non-enforcing reviewer/audit metadata, not `Finding.evidence`; it does not affect fingerprints, suppressions, baselines, `blocks_release`, or `release_decision`. Policy-pack `match` predicates and `block: true` remain the only policy-pack inputs that affect findings and release gating. - `release_decision.fail_policy.would_fail_ci` — `true`/`false`. Matches what the CI process will exit with. - `release_decision.reason` — one-sentence explanation suitable for a PR comment. - `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). @@ -1110,11 +1113,18 @@ policy checks and policy packs: matched capability identity, effect, authority, controls, semantic hashes, matched predicates, and source provenance. It is explanatory only and is not included in finding fingerprint inputs. - -Existing `finding.evidence` keys remain stable for legacy readers. Policy -matching is capability-native internally, but policy-pack behavior, suppressions, -severity overrides, baselines, SARIF, Markdown, and GitHub Action outputs remain -compatible. +- `policy_routing | null` — optional policy-pack routing metadata with + `owner`, `reviewers`, and `approval.{required,teams,min_approvals,enforced}`. + `approval.enforced` is always `false`; Shipgate validates declared team names + but does not verify external approval systems or make release decisions from + these fields. + +Deterministic match and gating `finding.evidence` keys remain stable for legacy +readers. Policy-pack routing keys that used to live in `Finding.evidence` now +live in `policy_routing`; old baseline fingerprints are still matched during +baseline comparison. Policy matching is capability-native internally, but +policy-pack behavior, suppressions, severity overrides, baselines, SARIF, +Markdown, and GitHub Action outputs remain compatible. In `findings[]`, v0.25 adds opt-in trace/provenance references for existing trace/evidence checks: @@ -1483,7 +1493,7 @@ Companion prompt: [`prompts/explain-finding-to-user.md`](../prompts/explain-find - [STABILITY.md](../STABILITY.md) — full alpha 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.27.json`](report-schema.v0.27.json) — machine-validatable JSON Schema for the current report. +- [`docs/report-schema.v0.28.json`](report-schema.v0.28.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.7.json`](packet-schema.v0.7.json) — machine-validatable JSON Schema for the current packet. - [`docs/checks.json`](checks.json) — check catalog, including `mvp_tier` for MVP/readiness triage. @@ -2926,7 +2936,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.27.json`](report-schema.v0.27.json) — current JSON +- [`report-schema.v0.28.json`](report-schema.v0.28.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 3377b859..fc102756 100644 --- a/llms.txt +++ b/llms.txt @@ -59,7 +59,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.27.json +- JSON report schema (current): https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/report-schema.v0.28.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.7.json - SARIF report: `agents-shipgate-reports/report.sarif`. @@ -148,7 +148,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.27.json +- Report schema (current): https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/report-schema.v0.28.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.7.json - Preflight schema (current): https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/preflight-schema.v0.2.json diff --git a/samples/simple_crewai_agent/expected/report.json b/samples/simple_crewai_agent/expected/report.json index 64e8934c..450dac29 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.27", + "report_schema_version": "0.28", "run_id": "agents_shipgate_def9be4e0968c3e8", "manifest_dir": "/samples/simple_crewai_agent", "project": { @@ -497,8 +497,7 @@ "findings": [], "recommended_actions": [], "generated_reports": { - "markdown": "expected/report.md", - "json": "expected/report.json" + "json": "/private/var/folders/_n/xw32gm0n7f957mtdq1mwfl3c0000gn/T/tmp.klkty3Baqf/simple_crewai_agent/report.json" }, "loaded_policy_packs": [], "loaded_plugins": [], @@ -574,8 +573,7 @@ "redacted_occurrence_count": 0, "redacted_paths": [], "output_surfaces": [ - "json", - "markdown" + "json" ], "notes": [ "Default-on best-effort pattern/key redaction ran before public artifacts were written.", diff --git a/samples/simple_langchain_agent/expected/report.json b/samples/simple_langchain_agent/expected/report.json index 9015e222..5bf07126 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.27", + "report_schema_version": "0.28", "run_id": "agents_shipgate_785cc45e038e4687", "manifest_dir": "/samples/simple_langchain_agent", "project": { @@ -401,8 +401,7 @@ "findings": [], "recommended_actions": [], "generated_reports": { - "markdown": "expected/report.md", - "json": "expected/report.json" + "json": "/private/var/folders/_n/xw32gm0n7f957mtdq1mwfl3c0000gn/T/tmp.klkty3Baqf/simple_langchain_agent/report.json" }, "loaded_policy_packs": [], "loaded_plugins": [], @@ -463,8 +462,7 @@ "redacted_occurrence_count": 0, "redacted_paths": [], "output_surfaces": [ - "json", - "markdown" + "json" ], "notes": [ "Default-on best-effort pattern/key redaction ran before public artifacts were written.", diff --git a/samples/simple_openai_api_agent/expected/report.json b/samples/simple_openai_api_agent/expected/report.json index 31ded252..40831658 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.27", + "report_schema_version": "0.28", "run_id": "agents_shipgate_587541de1c2da61f", "manifest_dir": "/samples/simple_openai_api_agent", "project": { @@ -1747,6 +1747,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Add a maximum bound to create_refund.amount or document an equivalent limit in the tool policy.", "blocks_release": false, @@ -1785,6 +1786,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Constrain send_customer_email.message with an enum, structured schema, or narrower field-specific parameters.", "blocks_release": false, @@ -1825,6 +1827,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Declare auth scopes for create_refund in OpenAPI, MCP metadata, or the manifest before release review.", "blocks_release": false, @@ -1866,6 +1869,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Declare auth scopes for send_customer_email in OpenAPI, MCP metadata, or the manifest before release review.", "blocks_release": false, @@ -1916,6 +1920,7 @@ }, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Remove create_refund, narrow its policy, or revise prohibited_actions so the manifest and tool surface do not contradict each other.", "blocks_release": false, @@ -1967,6 +1972,7 @@ }, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Remove send_customer_email, narrow its policy, or revise prohibited_actions so the manifest and tool surface do not contradict each other.", "blocks_release": false, @@ -2017,6 +2023,7 @@ }, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Add an idempotency key, idempotent annotation, or declared idempotency policy for create_refund.", "blocks_release": false, @@ -2068,6 +2075,7 @@ }, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Add an idempotency key, idempotent annotation, or declared idempotency policy for send_customer_email.", "blocks_release": false, @@ -2114,6 +2122,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Make create_refund a strict function schema: object parameters, additionalProperties=false, complete required list, and bounded risky fields.", "blocks_release": false, @@ -2158,6 +2167,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Make send_customer_email a strict function schema: object parameters, additionalProperties=false, complete required list, and bounded risky fields.", "blocks_release": false, @@ -2202,6 +2212,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Tighten the structured output schema with enums, needs_review/refusal/error modeling, and declared critical fields.", "blocks_release": false, @@ -2240,6 +2251,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Align prompt scope with enabled tools or remove write/high-risk tools.", "blocks_release": false, @@ -2278,6 +2290,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Add prompt instructions requiring human approval and explicit confirmation before financial, destructive, or external customer actions.", "blocks_release": false, @@ -2316,6 +2329,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Declare tool-call timeout metadata for high-risk OpenAI API flows.", "blocks_release": false, @@ -2355,6 +2369,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Declare success_fields and failure_fields for create_refund in openai_api policy rules.", "blocks_release": false, @@ -2398,6 +2413,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Add idempotency evidence for create_refund or avoid retrying this side effect.", "blocks_release": false, @@ -2442,6 +2458,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Add idempotency evidence for send_customer_email or avoid retrying this side effect.", "blocks_release": false, @@ -2480,6 +2497,7 @@ "cap_b3b20fa59428b8c1" ], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [ "ctrace_8669c1b40747c28a" ], @@ -2523,6 +2541,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Declare an owner for each high-risk production tool in risk_overrides.tools.", "blocks_release": false, @@ -2565,6 +2584,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Declare an owner for each high-risk production tool in risk_overrides.tools.", "blocks_release": false, @@ -2589,8 +2609,7 @@ "Declare an owner for each high-risk production tool in risk_overrides.tools." ], "generated_reports": { - "markdown": "expected/report.md", - "json": "expected/report.json" + "json": "/private/var/folders/_n/xw32gm0n7f957mtdq1mwfl3c0000gn/T/tmp.klkty3Baqf/simple_openai_api_agent/report.json" }, "loaded_policy_packs": [], "loaded_plugins": [], @@ -2661,8 +2680,7 @@ "redacted_occurrence_count": 0, "redacted_paths": [], "output_surfaces": [ - "json", - "markdown" + "json" ], "notes": [ "Default-on best-effort pattern/key redaction ran before public artifacts were written.", diff --git a/samples/support_refund_agent/expected/report.json b/samples/support_refund_agent/expected/report.json index 9858d716..a3056064 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.27", + "report_schema_version": "0.28", "run_id": "agents_shipgate_33cced54c422d1e8", "manifest_dir": "/samples/support_refund_agent", "project": { @@ -2270,6 +2270,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Replace wildcard tool exposure with an explicit tool allowlist before release review.", "blocks_release": false, @@ -2310,6 +2311,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Add a maximum bound to stripe.create_refund.amount or document an equivalent limit in the tool policy.", "blocks_release": false, @@ -2350,6 +2352,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Constrain zendesk.update_ticket.updates with an enum, structured schema, or narrower field-specific parameters.", "blocks_release": false, @@ -2388,6 +2391,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Constrain gmail.send_customer_email.body with an enum, structured schema, or narrower field-specific parameters.", "blocks_release": false, @@ -2425,6 +2429,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Prefer a structured output schema for send_email_preview, especially when output is later passed back into model context.", "blocks_release": false, @@ -2465,6 +2470,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Replace broad manifest permission scopes with the narrowest scopes needed for this release.", "blocks_release": false, @@ -2514,6 +2520,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Add the required scopes for shopify.cancel_order to permissions.scopes or narrow the tool's declared auth requirements.", "blocks_release": false, @@ -2561,6 +2568,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Add the required scopes for support.search_kb to permissions.scopes or narrow the tool's declared auth requirements.", "blocks_release": false, @@ -2608,6 +2616,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Add the required scopes for gmail.send_customer_email to permissions.scopes or narrow the tool's declared auth requirements.", "blocks_release": false, @@ -2661,6 +2670,7 @@ }, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Remove stripe.create_refund, narrow its policy, or revise prohibited_actions so the manifest and tool surface do not contradict each other.", "blocks_release": false, @@ -2711,6 +2721,7 @@ }, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Remove gmail.send_customer_email, narrow its policy, or revise prohibited_actions so the manifest and tool surface do not contradict each other.", "blocks_release": false, @@ -2843,6 +2854,7 @@ "pointer": "/paths/~1refunds/post" } }, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Declare an approval policy for stripe.create_refund or remove this tool from the release.", "blocks_release": false, @@ -2975,6 +2987,7 @@ "pointer": "/paths/~1refunds/post" } }, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Declare a user confirmation policy for stripe.create_refund or remove this action from the release.", "blocks_release": false, @@ -3103,6 +3116,7 @@ "pointer": "/tools/1" } }, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Declare a user confirmation policy for gmail.send_customer_email or remove this action from the release.", "blocks_release": false, @@ -3156,6 +3170,7 @@ }, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Add an idempotency key, idempotent annotation, or declared idempotency policy for stripe.create_refund.", "blocks_release": false, @@ -3206,6 +3221,7 @@ }, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Add an idempotency key, idempotent annotation, or declared idempotency policy for gmail.send_customer_email.", "blocks_release": false, @@ -3249,6 +3265,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Declare an owner for each high-risk production tool in risk_overrides.tools.", "blocks_release": false, @@ -3291,6 +3308,7 @@ "policy_evidence_source": null, "capability_refs": [], "capability_policy_evidence": null, + "policy_routing": null, "capability_trace_refs": [], "recommendation": "Remove unused manifest scopes or add tool metadata showing why they are required.", "blocks_release": false, @@ -3315,8 +3333,7 @@ "Declare an owner for each high-risk production tool in risk_overrides.tools." ], "generated_reports": { - "markdown": "expected/report.md", - "json": "expected/report.json" + "json": "/private/var/folders/_n/xw32gm0n7f957mtdq1mwfl3c0000gn/T/tmp.klkty3Baqf/support_refund_agent/report.json" }, "loaded_policy_packs": [], "loaded_plugins": [], @@ -3499,8 +3516,7 @@ "redacted_occurrence_count": 0, "redacted_paths": [], "output_surfaces": [ - "json", - "markdown" + "json" ], "notes": [ "Default-on best-effort pattern/key redaction ran before public artifacts were written.", diff --git a/skills/agents-shipgate/SKILL.md b/skills/agents-shipgate/SKILL.md index 4c6af9e5..d6647e18 100644 --- a/skills/agents-shipgate/SKILL.md +++ b/skills/agents-shipgate/SKILL.md @@ -76,7 +76,7 @@ For non-GitHub CI (GitLab, CircleCI, Jenkins, Azure Pipelines, Buildkite, Bitbuc - **Installed CLI contract**: when available, run `agents-shipgate contract --json` to verify local schema versions, capability/research surfaces, `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. - **Verifier JSON**: `verifier_schema_version: "0.1"`. Read `merge_verdict`, `can_merge_without_human`, `first_next_action`, `fix_task`, `capability_review.top_changes`, `trust_root_touched`, and `policy_weakened` before summarizing an AI-generated PR. `merge_verdict` is a deterministic projection; the gate remains `report.json.release_decision.decision`. - **Verify run JSON**: `verify-run.json` uses `schema_version: "shipgate.verify_run/v1"` and records stable run identity, subject refs, input hashes, outcome, and artifact hashes. It is the reproducibility artifact for `verify`; do not treat it as a second gate. -- **Report JSON**: `report_schema_version: "0.27"`. Read `release_decision.decision` first for release gating; use `agent_summary` / `findings[].agent_action` for agent routing and `reviewer_summary` for the human-review entry point. v0.27 adds policy-pack distribution metadata (`loaded_policy_packs[].{source,sha256,sha256_status,owner}`) for organization audit while preserving gate behavior. v0.26 added structured evidence gaps (`release_decision.evidence_coverage.evidence_gaps[]` — one actionable remediation row per low-confidence tool or source warning) plus the advisory `suggested-inventory.json` skeleton written next to `report.json`; when the decision is `insufficient_evidence`, follow each gap's `next_action` instead of guessing. v0.25 added opt-in capability-linked local trace/provenance evidence (`capability_runtime_evidence`, `findings[].capability_trace_refs`, and mirrored `ReleaseDecisionItem.capability_trace_refs`) while preserving fingerprints, baselines, and gate behavior. v0.24 added capability-native policy evidence (`findings[].capability_refs`, optional `findings[].capability_policy_evidence`, and mirrored `ReleaseDecisionItem.capability_refs`). v0.23 added semantic metadata to `capability_change` members while preserving existing buckets. v0.22 added the verifier-cycle blocks `capability_change`, `protected_surface_changes`, `effective_policy`, `human_ack`, and `verifier_summary` — all reviewer-facing projections that never gate independently (`release_decision.decision` stays the only gate). `reviewer_summary`, `verifier_summary`, runtime trace/evidence fields, and non-gating capability diff projections are supporting/provisional explanatory context. To remove heuristic findings from the active gate, rerun scan with `--no-heuristics`; filtered findings remain in `findings[]` with `suppressed=true`, and `heuristics_filter` records `enabled`, `excluded_provenance_kinds`, `filtered_finding_count`, and `filtered_by_kind`; `runtime_trace` findings are not filtered as heuristics. To inspect provenance without changing gate behavior, use `agents-shipgate findings --from agents-shipgate-reports/report.json --provenance-kind keyword_heuristic,regex_heuristic,runtime_trace --json`. Do not gate on `summary.status`; it is legacy and 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), and reports validate against [`docs/report-schema.v0.27.json`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.27.json). +- **Report JSON**: `report_schema_version: "0.28"`. Read `release_decision.decision` first for release gating; use `agent_summary` / `findings[].agent_action` for agent routing and `reviewer_summary` for the human-review entry point. v0.28 moves policy-pack owner/reviewer/approval routing metadata to `findings[].policy_routing` so `Finding.evidence` stays deterministic match/gating evidence; routing metadata is non-enforcing and does not affect fingerprints, suppressions, baselines, `blocks_release`, or release decisions. v0.27 added policy-pack distribution metadata (`loaded_policy_packs[].{source,sha256,sha256_status,owner}`) for organization audit while preserving gate behavior. v0.26 added structured evidence gaps (`release_decision.evidence_coverage.evidence_gaps[]` — one actionable remediation row per low-confidence tool or source warning) plus the advisory `suggested-inventory.json` skeleton written next to `report.json`; when the decision is `insufficient_evidence`, follow each gap's `next_action` instead of guessing. v0.25 added opt-in capability-linked local trace/provenance evidence (`capability_runtime_evidence`, `findings[].capability_trace_refs`, and mirrored `ReleaseDecisionItem.capability_trace_refs`) while preserving fingerprints, baselines, and gate behavior. v0.24 added capability-native policy evidence (`findings[].capability_refs`, optional `findings[].capability_policy_evidence`, and mirrored `ReleaseDecisionItem.capability_refs`). v0.23 added semantic metadata to `capability_change` members while preserving existing buckets. v0.22 added the verifier-cycle blocks `capability_change`, `protected_surface_changes`, `effective_policy`, `human_ack`, and `verifier_summary` — all reviewer-facing projections that never gate independently (`release_decision.decision` stays the only gate). `reviewer_summary`, `verifier_summary`, runtime trace/evidence fields, and non-gating capability diff projections are supporting/provisional explanatory context. To remove heuristic findings from the active gate, rerun scan with `--no-heuristics`; filtered findings remain in `findings[]` with `suppressed=true`, and `heuristics_filter` records `enabled`, `excluded_provenance_kinds`, `filtered_finding_count`, and `filtered_by_kind`; `runtime_trace` findings are not filtered as heuristics. To inspect provenance without changing gate behavior, use `agents-shipgate findings --from agents-shipgate-reports/report.json --provenance-kind keyword_heuristic,regex_heuristic,runtime_trace --json`. Do not gate on `summary.status`; it is legacy and 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), and reports validate against [`docs/report-schema.v0.28.json`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/report-schema.v0.28.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 as a supporting/provisional reviewer artifact. The packet has fixed reviewer sections governed by [`docs/packet-schema.v0.7.json`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/packet-schema.v0.7.json) (latest; v0.7 adds capability-linked local trace evidence summary and trace refs under `human_in_the_loop`). See [STABILITY.md §Release Evidence Packet](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/STABILITY.md#release-evidence-packet-v07). Use the packet for reviewer-shaped output; use the report for finding details. - **Capability standard**: `agents-shipgate capability export` emits a stable static capability lock (`capability_lock_schema_version: "0.2"`) and `agents-shipgate capability diff` emits a stable semantic diff (`capability_lock_diff_schema_version: "0.3"`). These artifacts are supporting/provisional, non-gating, exclude runtime trace evidence, and are documented in [`docs/capability-standard.md`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/capability-standard.md). - **Governance benchmark**: `benchmark/agent-pr-governance/cases.yaml` and `scripts/run_governance_benchmark.py` are the stable research benchmark substrate (`governance_benchmark_result_schema_version: "0.2"`), not a release gate. See [`docs/governance-benchmark.md`](https://github.com/ThreeMoonsLab/agents-shipgate/blob/main/docs/governance-benchmark.md). diff --git a/src/agents_shipgate/cli/scan/run_identity.py b/src/agents_shipgate/cli/scan/run_identity.py index fedcec5a..e93324ee 100644 --- a/src/agents_shipgate/cli/scan/run_identity.py +++ b/src/agents_shipgate/cli/scan/run_identity.py @@ -91,6 +91,10 @@ def _run_id( # otherwise-identical policy findings. "capability_refs": True, "capability_policy_evidence": True, + # v0.28 policy-pack routing is non-enforcing + # reviewer/audit metadata. Owner/reviewer/approval + # routing changes must not churn run_id. + "policy_routing": True, # v0.25 trace refs are explanatory runtime-audit # metadata and must not churn run_id. "capability_trace_refs": True, diff --git a/src/agents_shipgate/core/baseline.py b/src/agents_shipgate/core/baseline.py index e058c44e..67896602 100644 --- a/src/agents_shipgate/core/baseline.py +++ b/src/agents_shipgate/core/baseline.py @@ -24,6 +24,7 @@ expands_to_check_id, ) from agents_shipgate.core.errors import InputParseError +from agents_shipgate.core.findings.identity import legacy_policy_routing_fingerprint from agents_shipgate.schemas.baseline import ( BaselineFile, BaselineFinding, @@ -32,6 +33,8 @@ from agents_shipgate.schemas.common import Severity from agents_shipgate.schemas.report import BaselineSummary, Finding, ReadinessReport +LegacyFingerprintEntry = str | Sequence[str] | None + def _filled_text(existing: str | None, replacement: str | None) -> str | None: """Fill-only merge for reviewer-set text metadata. @@ -354,7 +357,7 @@ def apply_baseline( baseline: BaselineFile, *, display_path: str, - legacy_fingerprints: Sequence[str | None] | None = None, + legacy_fingerprints: Sequence[LegacyFingerprintEntry] | None = None, ) -> BaselineSummary: baseline_fingerprints = { finding.fingerprint for finding in baseline.findings if finding.fingerprint @@ -367,21 +370,22 @@ def apply_baseline( if finding.suppressed: continue fingerprint = finding.fingerprint or finding.id - legacy_fingerprint = ( - legacy_fingerprints[index] - if legacy_fingerprints is not None and index < len(legacy_fingerprints) - else None + legacy_candidates = _legacy_fingerprint_candidates( + legacy_fingerprints, + index, ) + if policy_routing_fingerprint := legacy_policy_routing_fingerprint(finding): + legacy_candidates.add(policy_routing_fingerprint) if not fingerprint: continue current_active_fingerprints.add(fingerprint) if fingerprint in baseline_fingerprints: finding.baseline_status = "matched" matched += 1 - elif legacy_fingerprint and legacy_fingerprint in baseline_fingerprints: + elif legacy_matches := baseline_fingerprints & legacy_candidates: finding.baseline_status = "matched" matched += 1 - matched_legacy_fingerprints.add(legacy_fingerprint) + matched_legacy_fingerprints.update(legacy_matches) elif legacy_match := _legacy_baseline_match(finding, baseline.findings): finding.baseline_status = "matched" matched += 1 @@ -405,6 +409,20 @@ def _active_findings(findings: list[Finding]) -> list[Finding]: return [finding for finding in findings if not finding.suppressed] +def _legacy_fingerprint_candidates( + legacy_fingerprints: Sequence[LegacyFingerprintEntry] | None, + index: int, +) -> set[str]: + if legacy_fingerprints is None or index >= len(legacy_fingerprints): + return set() + legacy = legacy_fingerprints[index] + if legacy is None: + return set() + if isinstance(legacy, str): + return {legacy} if legacy else set() + return {fingerprint for fingerprint in legacy if fingerprint} + + def _legacy_baseline_match( finding: Finding, baseline_findings: list[BaselineFinding] ) -> BaselineFinding | None: @@ -538,7 +556,7 @@ def baseline_resolved_fingerprints( findings: list[Finding], baseline: BaselineFile, *, - legacy_fingerprints: Sequence[str | None] | None = None, + legacy_fingerprints: Sequence[LegacyFingerprintEntry] | None = None, ) -> list[BaselineIntegrityIssue]: """Scan-aware companion to ``verify_baseline``. @@ -561,18 +579,19 @@ def baseline_resolved_fingerprints( if finding.suppressed: continue fingerprint = finding.fingerprint or finding.id - legacy_fingerprint = ( - legacy_fingerprints[index] - if legacy_fingerprints is not None and index < len(legacy_fingerprints) - else None + legacy_candidates = _legacy_fingerprint_candidates( + legacy_fingerprints, + index, ) + if policy_routing_fingerprint := legacy_policy_routing_fingerprint(finding): + legacy_candidates.add(policy_routing_fingerprint) if not fingerprint: continue active.add(fingerprint) if fingerprint in baseline_fingerprints: continue - if legacy_fingerprint and legacy_fingerprint in baseline_fingerprints: - legacy_matched.add(legacy_fingerprint) + if legacy_matches := baseline_fingerprints & legacy_candidates: + legacy_matched.update(legacy_matches) continue match = _legacy_baseline_match(finding, baseline.findings) if match is not None: diff --git a/src/agents_shipgate/core/findings/__init__.py b/src/agents_shipgate/core/findings/__init__.py index 10847fa6..31fe3d36 100644 --- a/src/agents_shipgate/core/findings/__init__.py +++ b/src/agents_shipgate/core/findings/__init__.py @@ -12,6 +12,7 @@ assign_finding_ids, dedupe_findings, finding_fingerprint, + legacy_policy_routing_fingerprint, ) from .mutations import ( NO_HEURISTICS_SUPPRESSION_REASON, @@ -95,6 +96,7 @@ "dedupe_findings", "derive_agent_action", "finding_fingerprint", + "legacy_policy_routing_fingerprint", "provenance_kind_counts", "recommended_actions", "summarize_findings", diff --git a/src/agents_shipgate/core/findings/identity.py b/src/agents_shipgate/core/findings/identity.py index ae6fb75b..bbf92ecd 100644 --- a/src/agents_shipgate/core/findings/identity.py +++ b/src/agents_shipgate/core/findings/identity.py @@ -75,6 +75,25 @@ def finding_fingerprint(finding: Finding) -> str: return f"fp_{digest}" +def legacy_policy_routing_fingerprint(finding: Finding) -> str | None: + """Return the v0.27 policy-pack fingerprint for baseline compatibility.""" + routing = finding.policy_routing + if finding.provenance_kind != "policy_pack" or routing is None: + return None + evidence = dict(finding.evidence) + evidence.update( + { + "policy_owner": routing.owner, + "policy_reviewers": list(routing.reviewers), + "policy_approval_required": routing.approval.required, + "policy_approval_teams": list(routing.approval.teams), + "policy_approval_min_approvals": routing.approval.min_approvals, + "policy_approval_enforced": routing.approval.enforced, + } + ) + return finding_fingerprint(finding.model_copy(update={"evidence": evidence})) + + def _canonicalize_for_fingerprint(value): if isinstance(value, dict): return { diff --git a/src/agents_shipgate/inputs/policy_packs.py b/src/agents_shipgate/inputs/policy_packs.py index 8940321e..e33ab5ea 100644 --- a/src/agents_shipgate/inputs/policy_packs.py +++ b/src/agents_shipgate/inputs/policy_packs.py @@ -19,6 +19,8 @@ from agents_shipgate.schemas.report import ( Finding, LoadedPolicyPack, + PolicyApprovalRouting, + PolicyRoutingMetadata, ) @@ -103,24 +105,6 @@ def run_policy_pack_rules( "policy_pack_source": resolved.pack.source, "policy_pack_sha256": resolved.pack.sha256, "policy_pack_sha256_status": resolved.pack.sha256_status, - "policy_owner": resolved.rule.owner or resolved.pack.owner, - "policy_reviewers": resolved.rule.reviewers, - "policy_approval_required": ( - resolved.rule.approval.required - if resolved.rule.approval is not None - else False - ), - "policy_approval_teams": ( - resolved.rule.approval.teams - if resolved.rule.approval is not None - else [] - ), - "policy_approval_min_approvals": ( - resolved.rule.approval.min_approvals - if resolved.rule.approval is not None - else None - ), - "policy_approval_enforced": False, }, ) if match is None: @@ -142,6 +126,7 @@ def run_policy_pack_rules( source=SourceReference(type="policy_pack", ref=resolved.pack.path), capability_refs=[subject.fact.id], capability_policy_evidence=match.capability_policy_evidence, + policy_routing=_policy_routing_metadata(resolved), recommendation=rule.recommendation, blocks_release=rule.block, ) @@ -149,6 +134,22 @@ def run_policy_pack_rules( return findings +def _policy_routing_metadata( + resolved: ResolvedPolicyPackRule, +) -> PolicyRoutingMetadata: + approval = resolved.rule.approval + return PolicyRoutingMetadata( + owner=resolved.rule.owner or resolved.pack.owner, + reviewers=list(resolved.rule.reviewers), + approval=PolicyApprovalRouting( + required=approval.required if approval is not None else False, + teams=list(approval.teams) if approval is not None else [], + min_approvals=approval.min_approvals if approval is not None else None, + enforced=False, + ), + ) + + def _verify_pack_pin( path: Path, config: PolicyPackConfig ) -> tuple[str | None, str]: diff --git a/src/agents_shipgate/schemas/report.py b/src/agents_shipgate/schemas/report.py index dc63a6d1..19021d24 100644 --- a/src/agents_shipgate/schemas/report.py +++ b/src/agents_shipgate/schemas/report.py @@ -115,6 +115,27 @@ class CapabilityRuntimeEvidence(BaseModel): notes: list[str] = Field(default_factory=list) +class PolicyApprovalRouting(BaseModel): + """Non-enforcing approval-routing metadata from a policy-pack rule.""" + + model_config = ConfigDict(extra="forbid") + + required: bool = False + teams: list[str] = Field(default_factory=list) + min_approvals: int | None = None + enforced: Literal[False] = False + + +class PolicyRoutingMetadata(BaseModel): + """Reviewer routing metadata carried outside finding evidence.""" + + model_config = ConfigDict(extra="forbid") + + owner: str | None = None + reviewers: list[str] = Field(default_factory=list) + approval: PolicyApprovalRouting = Field(default_factory=PolicyApprovalRouting) + + class Finding(BaseModel): model_config = ConfigDict(extra="allow") @@ -163,6 +184,10 @@ class Finding(BaseModel): # finding fingerprints do not churn. capability_refs: list[str] = Field(default_factory=list) capability_policy_evidence: CapabilityPolicyEvidence | None = None + # v0.28: non-enforcing policy-pack routing metadata. Kept outside + # ``evidence`` so owner/reviewer/approval routing changes do not + # affect fingerprints, suppressions, baselines, or release gating. + policy_routing: PolicyRoutingMetadata | None = None # v0.25: opt-in local runtime trace/provenance evidence linked to # capability facts. Kept outside ``evidence`` so fingerprints, # baselines, run IDs, and de-dupe identity do not churn. @@ -793,8 +818,10 @@ class ReadinessReport(BaseModel): # Pure projection of existing counts; gate behavior unchanged. # v0.27: additive policy-pack distribution metadata on # ``loaded_policy_packs[]`` (source, sha256, sha256_status, owner). - # The release gate is unchanged; this is org-governance audit metadata. - report_schema_version: str = "0.27" + # v0.28: policy-pack rule owner/reviewer/approval routing metadata + # moved out of ``Finding.evidence`` into ``Finding.policy_routing``. + # The release gate is unchanged; these are org-governance audit fields. + report_schema_version: str = "0.28" run_id: str # v0.6 (per C13): absolute path to the directory containing # shipgate.yaml. apply-patches uses this to enforce a containment diff --git a/tests/test_agent_instructions_renderers.py b/tests/test_agent_instructions_renderers.py index b68d7b15..0bfaea17 100644 --- a/tests/test_agent_instructions_renderers.py +++ b/tests/test_agent_instructions_renderers.py @@ -45,7 +45,7 @@ REPO_ROOT = Path(__file__).resolve().parent.parent EXPECTED_CLAUDE_CODE_SKILL_RENDER_SHA256 = { ".claude/skills/agents-shipgate/SKILL.md": ( - "6031a900efa636677786ceda881af0aa4630500683ee1845f4dd1439fdbe2e70" + "354bb39884f622ed34c5616a9f616cb5a5ea5e60f0d705e971ad109830058b81" ), ".claude/skills/agents-shipgate/ci-recipes/advisory-pr-comment.yml": ( "99b2acfbd9dfc6653a6bbee268b83f1e2d4297829636eba662d9f4ad6fa35423" diff --git a/tests/test_baseline_integrity.py b/tests/test_baseline_integrity.py index 4e1fa4f2..b301fbbc 100644 --- a/tests/test_baseline_integrity.py +++ b/tests/test_baseline_integrity.py @@ -520,6 +520,48 @@ def test_resolved_fingerprints_flags_entries_with_no_current_match(): assert issues[0].fingerprint == "fp_resolved" +def test_resolved_fingerprints_accepts_multiple_legacy_fingerprints(): + """A baseline entry matching any legacy candidate is not stale.""" + baseline = BaselineFile( + created_at="2026-01-01T00:00:00Z", + source_report_run_id="run_x", + findings=[ + BaselineFinding( + fingerprint="fp_legacy_policy_routing", + check_id="ORG-X", + tool_name="tool_a", + severity="high", + title="legacy", + provenance=BaselineProvenance( + scanner_version="1.0.0", + run_id="run_x", + recorded_at="2026-01-01T00:00:00Z", + ), + ) + ], + ) + current = Finding( + check_id="ORG-X", + title="legacy", + severity="high", + category="org_policy", + tool_name="tool_a", + evidence={"risk_tags": ["financial_action"]}, + confidence="high", + provenance_kind="policy_pack", + recommendation="stub", + fingerprint="fp_current", + ) + + issues = baseline_resolved_fingerprints( + [current], + baseline, + legacy_fingerprints=[["fp_raw_pre_privacy", "fp_legacy_policy_routing"]], + ) + + assert issues == [] + + # --- build_findings (check module) --------------------------------------- diff --git a/tests/test_capability_domain.py b/tests/test_capability_domain.py index 106b087e..d186c8ab 100644 --- a/tests/test_capability_domain.py +++ b/tests/test_capability_domain.py @@ -388,4 +388,4 @@ def test_building_capability_facts_does_not_change_action_fact_output() -> None: def test_capability_substrate_uses_current_report_schema_version() -> None: - assert ReadinessReport.model_fields["report_schema_version"].default == "0.27" + assert ReadinessReport.model_fields["report_schema_version"].default == "0.28" diff --git a/tests/test_governance_benchmark.py b/tests/test_governance_benchmark.py index e3e6cd3b..dac90ac2 100644 --- a/tests/test_governance_benchmark.py +++ b/tests/test_governance_benchmark.py @@ -227,7 +227,7 @@ def test_governance_benchmark_schemas_validate_catalog_and_result(tmp_path: Path def test_governance_benchmark_preserves_public_schema_boundaries() -> None: - assert ReadinessReport.model_fields["report_schema_version"].default == "0.27" + assert ReadinessReport.model_fields["report_schema_version"].default == "0.28" assert CAPABILITY_LOCK_SCHEMA_VERSION == "0.2" assert CAPABILITY_LOCK_DIFF_SCHEMA_VERSION == "0.3" assert GOVERNANCE_BENCHMARK_RESULT_SCHEMA_VERSION == "0.2" diff --git a/tests/test_policy_packs.py b/tests/test_policy_packs.py index fc6bf90e..d279ae55 100644 --- a/tests/test_policy_packs.py +++ b/tests/test_policy_packs.py @@ -8,9 +8,20 @@ from agents_shipgate.cli.main import app from agents_shipgate.cli.scan import run_scan from agents_shipgate.core.errors import ConfigError +from agents_shipgate.core.findings.identity import legacy_policy_routing_fingerprint +from agents_shipgate.schemas.baseline import BaselineFile, BaselineFinding runner = CliRunner() +ROUTING_EVIDENCE_KEYS = { + "policy_owner", + "policy_reviewers", + "policy_approval_required", + "policy_approval_teams", + "policy_approval_min_approvals", + "policy_approval_enforced", +} + def test_manifest_policy_pack_emits_suppressible_overridable_findings(tmp_path): _write_openapi(tmp_path) @@ -171,12 +182,248 @@ def test_policy_pack_org_metadata_and_pin_are_reported(tmp_path): assert finding.evidence["policy_pack_source"] == "github.com/acme/shipgate-policies@v3" assert finding.evidence["policy_pack_sha256"] == digest assert finding.evidence["policy_pack_sha256_status"] == "verified" - assert finding.evidence["policy_owner"] == "security" - assert finding.evidence["policy_reviewers"] == ["agent-platform"] - assert finding.evidence["policy_approval_required"] is True - assert finding.evidence["policy_approval_teams"] == ["security"] - assert finding.evidence["policy_approval_min_approvals"] == 1 - assert finding.evidence["policy_approval_enforced"] is False + assert ROUTING_EVIDENCE_KEYS.isdisjoint(finding.evidence) + assert finding.policy_routing is not None + assert finding.policy_routing.model_dump(mode="json") == { + "owner": "security", + "reviewers": ["agent-platform"], + "approval": { + "required": True, + "teams": ["security"], + "min_approvals": 1, + "enforced": False, + }, + } + + +def test_policy_pack_routing_metadata_does_not_change_fingerprint_or_gate(tmp_path): + _write_openapi(tmp_path) + pack_path = tmp_path / "org-pack.yaml" + pack_template = """ +name: Org Release Policy +version: "3.0" +rules: + - id: ORG-ROUTING-ONLY-CHANGE + title: Routing-only metadata change + category: org_policy + severity: medium + recommendation: Route before release. + owner: {owner} + reviewers: [{reviewer}] + approval: + required: true + teams: [{team}] + min_approvals: {min_approvals} + match: + risk_tags: [financial_action] + source_types: [openapi] +""" + (tmp_path / "shipgate.yaml").write_text( + _manifest_without_policy_pack() + + """ +organization: + id: acme + teams: + security: + reviewers: ["@acme/security"] + agent-platform: + reviewers: ["@acme/agent-platform"] +checks: + policy_packs: + - path: org-pack.yaml +""", + encoding="utf-8", + ) + pack_path.write_text( + pack_template.format( + owner="security", + reviewer="agent-platform", + team="security", + min_approvals=1, + ), + encoding="utf-8", + ) + report_a, _ = run_scan( + config_path=tmp_path / "shipgate.yaml", + output_dir=tmp_path / "reports-a", + formats=["json"], + ci_mode="advisory", + ) + pack_path.write_text( + pack_template.format( + owner="agent-platform", + reviewer="security", + team="agent-platform", + min_approvals=2, + ), + encoding="utf-8", + ) + report_b, _ = run_scan( + config_path=tmp_path / "shipgate.yaml", + output_dir=tmp_path / "reports-b", + formats=["json"], + ci_mode="advisory", + ) + + finding_a = next( + item for item in report_a.findings if item.check_id == "ORG-ROUTING-ONLY-CHANGE" + ) + finding_b = next( + item for item in report_b.findings if item.check_id == "ORG-ROUTING-ONLY-CHANGE" + ) + assert finding_a.fingerprint == finding_b.fingerprint + assert finding_a.evidence == finding_b.evidence + assert finding_a.policy_routing != finding_b.policy_routing + assert report_a.release_decision is not None + assert report_b.release_decision is not None + assert report_a.release_decision.decision == report_b.release_decision.decision + + +def test_policy_pack_approval_routing_alone_does_not_block_release(tmp_path): + _write_openapi(tmp_path) + (tmp_path / "approval-pack.yaml").write_text( + """ +name: Approval Routing Policy +rules: + - id: ORG-APPROVAL-ROUTING-ONLY + title: Route approval metadata only + category: org_policy + severity: low + approval: + required: true + teams: [security] + min_approvals: 1 + recommendation: Route to security. + match: + source_types: [openapi] +""", + encoding="utf-8", + ) + (tmp_path / "shipgate.yaml").write_text( + _manifest_without_policy_pack() + + """ +organization: + id: acme + teams: + security: + reviewers: ["@acme/security"] +checks: + policy_packs: + - path: approval-pack.yaml +""", + encoding="utf-8", + ) + + report, _ = run_scan( + config_path=tmp_path / "shipgate.yaml", + output_dir=tmp_path / "reports", + formats=["json"], + ci_mode="advisory", + ) + + finding = next( + item for item in report.findings if item.check_id == "ORG-APPROVAL-ROUTING-ONLY" + ) + assert finding.blocks_release is False + assert finding.policy_routing is not None + assert finding.policy_routing.approval.required is True + assert report.release_decision is not None + assert all( + item.check_id != "ORG-APPROVAL-ROUTING-ONLY" + for item in report.release_decision.blockers + ) + + +def test_policy_pack_legacy_v027_routing_fingerprint_baseline_still_matches( + tmp_path, +): + _write_openapi(tmp_path) + (tmp_path / "org-pack.yaml").write_text( + """ +name: Org Release Policy +rules: + - id: ORG-LEGACY-ROUTING-FP + title: Legacy routing fingerprint + category: org_policy + severity: high + recommendation: Route before release. + owner: security + reviewers: [agent-platform] + approval: + required: true + teams: [security] + min_approvals: 1 + match: + risk_tags: [financial_action] + source_types: [openapi] +""", + encoding="utf-8", + ) + (tmp_path / "shipgate.yaml").write_text( + _manifest_without_policy_pack() + + """ +organization: + id: acme + teams: + security: + reviewers: ["@acme/security"] + agent-platform: + reviewers: ["@acme/agent-platform"] +checks: + policy_packs: + - path: org-pack.yaml +""", + encoding="utf-8", + ) + report, _ = run_scan( + config_path=tmp_path / "shipgate.yaml", + output_dir=tmp_path / "reports-a", + formats=["json"], + ci_mode="advisory", + ) + finding = next( + item for item in report.findings if item.check_id == "ORG-LEGACY-ROUTING-FP" + ) + legacy_fingerprint = legacy_policy_routing_fingerprint(finding) + assert legacy_fingerprint is not None + assert legacy_fingerprint != finding.fingerprint + baseline_path = tmp_path / "baseline.json" + baseline_path.write_text( + BaselineFile( + created_at="2026-01-01T00:00:00Z", + source_report_run_id="v027", + findings=[ + BaselineFinding( + fingerprint=legacy_fingerprint, + check_id=finding.check_id, + tool_name=finding.tool_name, + severity=finding.severity, + title=finding.title, + ) + ], + ).model_dump_json(indent=2) + + "\n", + encoding="utf-8", + ) + + report_with_baseline, _ = run_scan( + config_path=tmp_path / "shipgate.yaml", + output_dir=tmp_path / "reports-b", + formats=["json"], + ci_mode="advisory", + baseline_path=baseline_path, + ) + + matched = next( + item + for item in report_with_baseline.findings + if item.check_id == "ORG-LEGACY-ROUTING-FP" + ) + assert matched.fingerprint == finding.fingerprint + assert matched.baseline_status == "matched" + assert report_with_baseline.baseline is not None + assert report_with_baseline.baseline.matched_count == 1 + assert report_with_baseline.baseline.resolved_count == 0 def test_cli_policy_pack_override_and_parameter_predicate(tmp_path): diff --git a/tests/test_schema_boundaries.py b/tests/test_schema_boundaries.py index dffd0dd4..8763754b 100644 --- a/tests/test_schema_boundaries.py +++ b/tests/test_schema_boundaries.py @@ -87,19 +87,31 @@ def test_frozen_v025_report_schema_does_not_backport_v026_action_fact_sources() v25 = _report_schema_action_fact_properties("0.25") v26 = _report_schema_action_fact_properties("0.26") v27 = _report_schema_action_fact_properties("0.27") + v28 = _report_schema_action_fact_properties("0.28") assert ACTION_FACT_SOURCE_FIELDS.isdisjoint(v25) assert ACTION_FACT_SOURCE_FIELDS.issubset(v26) assert ACTION_FACT_SOURCE_FIELDS.issubset(v27) + assert ACTION_FACT_SOURCE_FIELDS.issubset(v28) def test_frozen_v026_report_schema_does_not_backport_v027_policy_pack_metadata() -> None: v26 = _loaded_policy_pack_properties("0.26") v27 = _loaded_policy_pack_properties("0.27") + v28 = _loaded_policy_pack_properties("0.28") v27_fields = {"source", "sha256", "sha256_status", "owner"} assert v27_fields.isdisjoint(v26) assert v27_fields.issubset(v27) + assert v27_fields.issubset(v28) + + +def test_frozen_v027_report_schema_does_not_backport_v028_policy_routing() -> None: + v27 = _finding_properties("0.27") + v28 = _finding_properties("0.28") + + assert "policy_routing" not in v27 + assert "policy_routing" in v28 def _report_schema_action_fact_properties(version: str) -> set[str]: @@ -120,6 +132,15 @@ def _loaded_policy_pack_properties(version: str) -> set[str]: return set(schema["$defs"]["LoadedPolicyPack"]["properties"]) +def _finding_properties(version: str) -> set[str]: + schema = json.loads( + (REPO_ROOT / "docs" / f"report-schema.v{version}.json").read_text( + encoding="utf-8" + ) + ) + return set(schema["$defs"]["Finding"]["properties"]) + + def _collect_removed_schema_imports(path: Path, offenders: list[str]) -> None: tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) for node in ast.walk(tree): @@ -158,7 +179,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.27" + assert report_payload["report_schema_version"] == "0.28" assert list(report_payload) == [ "schema_version", "report_schema_version", @@ -277,7 +298,7 @@ def test_representative_schema_payloads_keep_wire_fields() -> None: assert ContractPayload( contract_version="9", cli_version="0.0.0", - report_schema_version="0.27", + report_schema_version="0.28", packet_schema_version="0.7", verifier_schema_version="0.1", verify_run_schema_version="shipgate.verify_run/v1", @@ -326,7 +347,7 @@ def test_representative_schema_payloads_keep_wire_fields() -> None: ).model_dump(mode="json") == { "contract_version": "9", "cli_version": "0.0.0", - "report_schema_version": "0.27", + "report_schema_version": "0.28", "packet_schema_version": "0.7", "verifier_schema_version": "0.1", "verify_run_schema_version": "shipgate.verify_run/v1",