diff --git a/.agents/skills/agents-shipgate/references/recipes.md b/.agents/skills/agents-shipgate/references/recipes.md index edb8f164..198a7a1c 100644 --- a/.agents/skills/agents-shipgate/references/recipes.md +++ b/.agents/skills/agents-shipgate/references/recipes.md @@ -16,7 +16,7 @@ agents-shipgate --version agents-shipgate contract --json ``` -Require `agents-shipgate contract --json` to report `contract_version: "9"` or +Require `agents-shipgate contract --json` to report `contract_version: "10"` or newer. If it is missing or stale, ask the user to install or upgrade: ```bash diff --git a/.cursor/rules/agents-shipgate.mdc b/.cursor/rules/agents-shipgate.mdc index 40734b4a..459c58fc 100644 --- a/.cursor/rules/agents-shipgate.mdc +++ b/.cursor/rules/agents-shipgate.mdc @@ -42,7 +42,7 @@ For local agent control, run: Read the check stdout JSON only. It is `shipgate.codex_boundary_result/v1`; switch on `decision`, `completion_allowed`, and `must_stop`, then follow `first_next_action`, -`human_review`, `repair`, and `policy`. Do not infer a decision from prose. +`human_review`, `repair`, `policy`, and `verify_required`. Do not infer a decision from prose. If `decision=allow` or `warn`, continue and summarize. If `first_next_action.kind` is `repair` and `repair.safe_to_attempt=true`, make diff --git a/.well-known/agents-shipgate.json b/.well-known/agents-shipgate.json index b7fc6aaa..00c0a753 100644 --- a/.well-known/agents-shipgate.json +++ b/.well-known/agents-shipgate.json @@ -16,7 +16,12 @@ "short": "Merge verdicts for AI-generated agent PRs", "answer": "Agents Shipgate is described by the canonical tagline: The deterministic merge gate for AI-generated agent capability changes. It is an open-source, local-first CLI and GitHub Action from Three Moons Lab that verifies PRs from Codex, Claude Code, Cursor, or humans and returns a deterministic merge verdict before agent code lands.", "primary_use_case": "Run on PRs from Codex, Claude Code, Cursor, or humans when agent tools, prompts, permissions, MCP/OpenAPI surfaces, policies, or release gates change.", - "not_for": ["llm_evals", "runtime_guardrails", "runtime_observability", "general_linting"] + "not_for": [ + "llm_evals", + "runtime_guardrails", + "runtime_observability", + "general_linting" + ] }, "primary_keywords": [ "agent release readiness", @@ -44,7 +49,12 @@ "sarif", "agent-governance" ], - "audiences": ["agent_builders", "platform_engineers", "security_reviewers", "coding_agents"], + "audiences": [ + "agent_builders", + "platform_engineers", + "security_reviewers", + "coding_agents" + ], "when_to_use": [ "AI-generated agent PRs from Codex, Claude Code, Cursor, or similar tools", "AI agent tool-surface changes", @@ -70,7 +80,10 @@ "pip": "python -m pip install agents-shipgate", "uv": "uv tool install agents-shipgate" }, - "binaries": ["agents-shipgate", "shipgate"], + "binaries": [ + "agents-shipgate", + "shipgate" + ], "quickstart": "shipgate check --agent codex --workspace . --format codex-boundary-json", "primary_commands": { "check_codex": "shipgate check --agent codex --workspace . --format codex-boundary-json", @@ -106,19 +119,43 @@ "fixture_run": "agents-shipgate fixture run ai_generated_refund_pr", "static_scan_fixture_run": "agents-shipgate fixture run support_refund_agent", "feedback_loop": { - "when": ["false_positive", "missed_capability", "unsafe_pass", "unclear_next_action", "adapter_gap"], + "when": [ + "false_positive", + "missed_capability", + "unsafe_pass", + "unclear_next_action", + "adapter_gap" + ], "export_command": "agents-shipgate feedback export --from agents-shipgate-reports/verifier.json --redact --out shipgate-feedback.json", "capture_command": "agents-shipgate feedback capture --before verifier-before.json --after verifier-after.json --redact --out scenario.json", "issue_template": "https://github.com/ThreeMoonsLab/agents-shipgate/issues/new?template=agent_feedback.yml", - "attach": ["shipgate-feedback.json", "minimal safe manifest/tool-source snippet"], - "do_not_attach": ["unredacted reports", "raw tool outputs", "secrets", "customer data", "chain-of-thought"] + "attach": [ + "shipgate-feedback.json", + "minimal safe manifest/tool-source snippet" + ], + "do_not_attach": [ + "unredacted reports", + "raw tool outputs", + "secrets", + "customer data", + "chain-of-thought" + ] }, "self_check": "agents-shipgate self-check --json", "contract": "agents-shipgate contract --json", "agent_protocol": "docs/agents/protocol.md", "agent_result_schema_version": "agent_result_v1", "agent_result_schema_path": "docs/agent-result-schema.v1.json", - "agent_result_control_fields": ["decision", "completion_allowed", "must_stop", "first_next_action", "human_review", "repair", "policy"], + "agent_result_control_fields": [ + "decision", + "completion_allowed", + "must_stop", + "first_next_action", + "human_review", + "repair", + "policy", + "verify_required" + ], "codex_boundary_result_schema_version": "shipgate.codex_boundary_result/v1", "codex_boundary_result_schema_path": "docs/codex-boundary-result-schema.v1.json", "verifier_schema_version": "0.1", @@ -126,9 +163,44 @@ "agent_handoff_schema_version": "shipgate.agent_handoff/v1", "agent_handoff_schema_path": "docs/agent-handoff-schema.v1.json", "agent_handoff_artifact": "agents-shipgate-reports/agent-handoff.json", - "contract_version": "9", - "inputs": ["mcp", "openapi", "openai_agents_sdk", "anthropic_api", "google_adk", "langchain", "crewai", "openai_api", "codex_config", "codex_plugin", "n8n"], - "outputs": ["markdown", "json", "sarif", "packet_md", "packet_json", "packet_html", "verifier_json", "verify_run_json", "agent_handoff_json", "pr_comment_md", "check_annotations_json", "capability_lock_json", "base_capability_lock_json", "capability_lock_diff_json", "capability_lock_diff_md", "feedback_json", "attestation_json", "org_evidence_bundle_json", "host_grants_json", "org_status_json", "scenario_json", "governance_benchmark_result_json"], + "contract_version": "10", + "inputs": [ + "mcp", + "openapi", + "openai_agents_sdk", + "anthropic_api", + "google_adk", + "langchain", + "crewai", + "openai_api", + "codex_config", + "codex_plugin", + "n8n" + ], + "outputs": [ + "markdown", + "json", + "sarif", + "packet_md", + "packet_json", + "packet_html", + "verifier_json", + "verify_run_json", + "agent_handoff_json", + "pr_comment_md", + "check_annotations_json", + "capability_lock_json", + "base_capability_lock_json", + "capability_lock_diff_json", + "capability_lock_diff_md", + "feedback_json", + "attestation_json", + "org_evidence_bundle_json", + "host_grants_json", + "org_status_json", + "scenario_json", + "governance_benchmark_result_json" + ], "artifacts": { "verifier": "agents-shipgate-reports/verifier.json", "verify_run": "agents-shipgate-reports/verify-run.json", @@ -161,9 +233,36 @@ "capability_standard_version": "0.1", "governance_benchmark_catalog_schema_version": "0.2", "governance_benchmark_result_schema_version": "0.2", - "supporting_provisional_surfaces": ["agent_result", "agent_decision", "release_evidence_packet", "reviewer_summary", "verifier_summary", "capability_review", "runtime_trace_evidence", "capability_diff_projections", "skill_review"], - "external_integration_surfaces": ["agent_handoff", "preflight", "capability_lock", "capability_lock_diff", "capability_standard", "attestation", "registry", "org_governance", "org_evidence_bundle", "host_grants_inventory", "governance_benchmark_catalog", "governance_benchmark_result"], - "agent_interface_operations": ["verify_pr", "verify_local", "verify_preview"], + "supporting_provisional_surfaces": [ + "agent_result", + "agent_decision", + "release_evidence_packet", + "reviewer_summary", + "verifier_summary", + "capability_review", + "runtime_trace_evidence", + "capability_diff_projections", + "skill_review" + ], + "external_integration_surfaces": [ + "agent_handoff", + "preflight", + "capability_lock", + "capability_lock_diff", + "capability_standard", + "attestation", + "registry", + "org_governance", + "org_evidence_bundle", + "host_grants_inventory", + "governance_benchmark_catalog", + "governance_benchmark_result" + ], + "agent_interface_operations": [ + "verify_pr", + "verify_local", + "verify_preview" + ], "exit_code_policy": { "0": "command completed; inspect JSON verdict fields for release state", "2": "configuration or CLI flag error", @@ -172,11 +271,31 @@ "6": "baseline integrity failure", "20": "strict-mode gate failure or opt-in governance failure" }, - "mcp_tools": ["shipgate.check", "shipgate.preflight", "shipgate.explain", "shipgate.capabilities", "shipgate.handoff"], + "mcp_tools": [ + "shipgate.check", + "shipgate.preflight", + "shipgate.explain", + "shipgate.capabilities", + "shipgate.handoff" + ], "gating_signal": "release_decision.decision", - "agent_controller_signals": ["merge_verdict", "applicability", "agent_controller"], - "merge_verdicts": ["mergeable", "human_review_required", "insufficient_evidence", "blocked", "unknown"], - "check_run_policies": ["advisory", "blocked-fails", "require-mergeable"], + "agent_controller_signals": [ + "merge_verdict", + "applicability", + "agent_controller" + ], + "merge_verdicts": [ + "mergeable", + "human_review_required", + "insufficient_evidence", + "blocked", + "unknown" + ], + "check_run_policies": [ + "advisory", + "blocked-fails", + "require-mergeable" + ], "github_action_pr_workflow": { "recommended_inputs": { "ci_mode": "advisory", @@ -187,8 +306,17 @@ }, "branch_protection_check_run_policy": "require-mergeable" }, - "applicability_values": ["verified", "not_applicable", "unknown"], - "release_decisions": ["passed", "review_required", "insufficient_evidence", "blocked"], + "applicability_values": [ + "verified", + "not_applicable", + "unknown" + ], + "release_decisions": [ + "passed", + "review_required", + "insufficient_evidence", + "blocked" + ], "merge_verdict_labels": { "passed": "mergeable", "review_required": "human_review_required", diff --git a/AGENTS.md b/AGENTS.md index 4abcfbb5..b26108ad 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -84,7 +84,7 @@ shipgate check --agent cursor --workspace . --format codex-boundary-json Read the single stdout object as `shipgate.codex_boundary_result/v1`. Switch on `decision`, `completion_allowed`, `must_stop`, `first_next_action`, -`human_review`, `repair`, and `policy`; never infer a local-control decision +`human_review`, `repair`, `policy`, and `verify_required`; never infer a local-control decision from Markdown, PR comments, or prose. If `decision=allow` or `warn`, continue and summarize the result. If `first_next_action.kind=repair` and `repair.safe_to_attempt` is `true`, apply only that repair and rerun the diff --git a/CHANGELOG.md b/CHANGELOG.md index f3df8a27..803d5e07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ ## Unreleased +- **Contract v10 (additive): machine-readable `verify_required` on the Codex + boundary result.** `shipgate check` already escalated to `warn` and routed + to `verify` when a diff touched a tool surface it cannot gate; that deferral + now also sets a top-level boolean `verify_required` on + `shipgate.codex_boundary_result/v1`, and `verify_required` joins + `agent_result_control_fields` in the runtime contract. Agents switch on the + field instead of parsing warning prose; the observable pair is + `decision="warn"` with `verify_required=true` — "no boundary rule fired, + but capability is not yet gated: run verify before completion" (the + escalation means a plain `allow` always has `verify_required=false`). The + field lives on the shared `AgentResultV1` base, so the legacy + `agent-result-schema.v1.json` carries it too and + `agent_result_control_fields` validates against both schemas. Additive + over v9: consumers pinned to `contract_version >= 9` keep working. + ## 0.14.0 - 2026-06-30 - **Versioning: the `1.0.0-alpha` line is withdrawn; this work ships as diff --git a/README.md b/README.md index 4ce602ab..72c48dfc 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ shipgate check --agent cursor --workspace . --format codex-boundary-json ``` Switch on `decision`, `completion_allowed`, `must_stop`, -`first_next_action`, `human_review`, `repair`, and `policy`; never infer a +`first_next_action`, `human_review`, `repair`, `policy`, and `verify_required`; never infer a decision from prose. `shipgate check` is necessary but not sufficient for capability-expanding diffs: if a change adds dynamic, undeclared, or otherwise ambiguous tool capability, do not treat `decision="allow"` as merge readiness; @@ -276,7 +276,7 @@ shipgate audit --host --json --out agents-shipgate-reports/host-grants.json For local control, parse the `shipgate check` stdout JSON (`shipgate.codex_boundary_result/v1`): switch on `decision`, `completion_allowed`, `must_stop`, `first_next_action`, `human_review`, -`repair`, and `policy`. For local uncommitted verify work, +`repair`, `policy`, and `verify_required`. For local uncommitted verify work, omit `--base`/`--head`. For committed PR/CI refs, make the base ref available first because `verify` never fetches. Read `agents-shipgate-reports/agent-handoff.json` first and lead with diff --git a/adoption-kits/codex-skill/.agents-shipgate-kit-metadata.json b/adoption-kits/codex-skill/.agents-shipgate-kit-metadata.json index 1f0524ef..9d26eb15 100644 --- a/adoption-kits/codex-skill/.agents-shipgate-kit-metadata.json +++ b/adoption-kits/codex-skill/.agents-shipgate-kit-metadata.json @@ -26,7 +26,8 @@ "6bf8b3a409df3cd6f94e070555d62eedf8ba3690b4cfdceae2d7a7482b90e91b", "8576dc41812871e97a5d5c213a2c9c44f9766f746d99e9bc9909ee69a4002575", "d7779f4f2365c84727d02da770696a40490428a396e10d7a031b316f15ef849d", - "64cfd980d399f24995008eeca4d196a6efd224edd01e108543ec11aeb291d085" + "64cfd980d399f24995008eeca4d196a6efd224edd01e108543ec11aeb291d085", + "d8b393e61aef853105a47630b9cbfd404378b6c9e1bbed6028b357b4e38fc72c" ], "references/report-reading.md": [ "75a655059f3d45db365c744b0ff82d1c9d69c3638acacf640fd667ae87260d05", diff --git a/adoption-kits/codex-skill/references/recipes.md b/adoption-kits/codex-skill/references/recipes.md index edb8f164..198a7a1c 100644 --- a/adoption-kits/codex-skill/references/recipes.md +++ b/adoption-kits/codex-skill/references/recipes.md @@ -16,7 +16,7 @@ agents-shipgate --version agents-shipgate contract --json ``` -Require `agents-shipgate contract --json` to report `contract_version: "9"` or +Require `agents-shipgate contract --json` to report `contract_version: "10"` or newer. If it is missing or stale, ask the user to install or upgrade: ```bash diff --git a/docs/agent-contract-current.md b/docs/agent-contract-current.md index 904faabd..9394351e 100644 --- a/docs/agent-contract-current.md +++ b/docs/agent-contract-current.md @@ -10,7 +10,9 @@ Verify the installed CLI contract locally before relying on hard-coded docs: agents-shipgate contract --json ``` -Runtime contract v9 also exposes the local agent command spec: +Runtime contract v10 (v10 adds `verify_required` to +`agent_result_control_fields` and to the boundary result; additive over v9) +also exposes the local agent command spec: `primary_commands{}`, `commands{}`, `default_paths{}`, `artifacts{}`, `agent_read_order[]`, `verifier_read_order[]`, `merge_verdicts[]`, `release_decisions[]`, `do_not_auto_assert[]`, `verifier_schema_version`, @@ -31,7 +33,7 @@ Downstream repos generated with `.shipgate/agent-contract.json`. - Latest release: `v0.14.0` (see [pyproject.toml](../pyproject.toml) for the in-tree version) -- Runtime contract: `9` +- Runtime contract: `10` - 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) @@ -365,8 +367,21 @@ The removed `--format agent-json` alias and `agent_result_v1` schema string are breaking 0.14.0 changes; see [STABILITY.md](../STABILITY.md#migration-note-0-14-0). Coding agents should switch on `decision`, `completion_allowed`, `must_stop`, -`first_next_action`, `human_review`, `repair`, and `policy`. Do not derive an agent -decision from Markdown, PR comments, or natural language. Do not confuse this +`first_next_action`, `human_review`, `repair`, `policy`, and `verify_required`. Do not derive an agent +decision from Markdown, PR comments, or natural language. + +`verify_required` (contract v10, additive) is the machine-readable +check→verify deferral: `true` whenever the diff touches a tool surface — +declared or undeclared — that the boundary check does not gate. The evaluator +simultaneously escalates what would otherwise be a clean `allow` to +`decision="warn"`, so the observable pair is `decision="warn"` with +`verify_required=true`: "no boundary rule fired, but capability is not yet +gated" — run `agents-shipgate verify` and read `release_decision.decision` +before reporting completion. A plain `decision="allow"` always has +`verify_required=false`. It is a deterministic +projection of the same deferral that emits the +`capability_change_requires_verify` / `undeclared_capability_surface` +diagnostics — not a second verdict. Do not confuse this local boundary result with `agents-shipgate verify`: verify writes `agent-handoff.json`, `verifier.json`, and `verify-run.json`, and `report.json` remains the full CI/reviewer substrate. diff --git a/docs/agent-result-schema.v1.json b/docs/agent-result-schema.v1.json index 9125d478..7d5c767e 100644 --- a/docs/agent-result-schema.v1.json +++ b/docs/agent-result-schema.v1.json @@ -666,6 +666,11 @@ "default": null, "title": "Trigger" }, + "verify_required": { + "default": false, + "title": "Verify Required", + "type": "boolean" + }, "violated_rules": { "items": { "$ref": "#/$defs/AgentResultViolatedRule" diff --git a/docs/agents/README.md b/docs/agents/README.md index 074909d3..a502a347 100644 --- a/docs/agents/README.md +++ b/docs/agents/README.md @@ -28,7 +28,7 @@ shipgate check --agent codex --workspace . --format codex-boundary-json Use `--agent claude-code` for Claude Code and `--agent cursor` for Cursor. Parse stdout as `shipgate.codex_boundary_result/v1`; switch on `decision`, `completion_allowed`, `must_stop`, `first_next_action`, `human_review`, -`repair`, and `policy`. Do not infer a control decision from prose. +`repair`, `policy`, and `verify_required`. Do not infer a control decision from prose. For committed PR verification, run `agents-shipgate verify`, then read `agents-shipgate-reports/agent-handoff.json` first and diff --git a/docs/agents/claude-code.md b/docs/agents/claude-code.md index 87c45aa9..7e598188 100644 --- a/docs/agents/claude-code.md +++ b/docs/agents/claude-code.md @@ -8,7 +8,7 @@ shipgate check --agent claude-code --workspace . --format codex-boundary-json Parse stdout as `shipgate.codex_boundary_result/v1`. Switch only on `decision`, `completion_allowed`, `must_stop`, `first_next_action`, `human_review`, -`repair`, and `policy`. +`repair`, `policy`, and `verify_required`. If the binary is missing, surface the schema-valid install fixture with `first_next_action.kind="install"` and command `pipx install agents-shipgate`. diff --git a/docs/agents/codex.md b/docs/agents/codex.md index 15ecee72..a6d319ed 100644 --- a/docs/agents/codex.md +++ b/docs/agents/codex.md @@ -8,7 +8,7 @@ shipgate check --agent codex --workspace . --format codex-boundary-json Parse stdout as `shipgate.codex_boundary_result/v1`. Switch only on `decision`, `completion_allowed`, `must_stop`, `first_next_action`, `human_review`, -`repair`, and `policy`. +`repair`, `policy`, and `verify_required`. If the binary is missing, surface the schema-valid install fixture with `first_next_action.kind="install"` and command `pipx install agents-shipgate`. diff --git a/docs/agents/cursor.md b/docs/agents/cursor.md index 12264776..c679d558 100644 --- a/docs/agents/cursor.md +++ b/docs/agents/cursor.md @@ -8,7 +8,7 @@ shipgate check --agent cursor --workspace . --format codex-boundary-json Parse stdout as `shipgate.codex_boundary_result/v1`. Switch only on `decision`, `completion_allowed`, `must_stop`, `first_next_action`, `human_review`, -`repair`, and `policy`. +`repair`, `policy`, and `verify_required`. If the binary is missing, surface the schema-valid install fixture with `first_next_action.kind="install"` and command `pipx install agents-shipgate`. diff --git a/docs/agents/use-with-claude-code.md b/docs/agents/use-with-claude-code.md index 032991b5..e7e6cce8 100644 --- a/docs/agents/use-with-claude-code.md +++ b/docs/agents/use-with-claude-code.md @@ -10,7 +10,7 @@ shipgate check --agent claude-code --workspace . --format codex-boundary-json Parse stdout as `shipgate.codex_boundary_result/v1` and switch on `decision`, `completion_allowed`, `must_stop`, `first_next_action`, `human_review`, -`repair`, and `policy`. Do not infer a local control decision from prose. +`repair`, `policy`, and `verify_required`. Do not infer a local control decision from prose. Two pieces of agent-facing surface ship with this repo. Drop them into your own agent project so Claude Code can install, run, and explain Shipgate without you typing the steps. diff --git a/docs/agents/use-with-codex.md b/docs/agents/use-with-codex.md index b3328fb0..7d86ba49 100644 --- a/docs/agents/use-with-codex.md +++ b/docs/agents/use-with-codex.md @@ -10,7 +10,7 @@ shipgate check --agent codex --workspace . --format codex-boundary-json Parse stdout as `shipgate.codex_boundary_result/v1` and switch on `decision`, `completion_allowed`, `must_stop`, `first_next_action`, `human_review`, -`repair`, and `policy`. Do not infer a local control decision from prose. +`repair`, `policy`, and `verify_required`. Do not infer a local control decision from prose. Agents Shipgate ships a skill-only Codex plugin so users can install it from the Codex plugin experience, start a new thread, invoke `$agents-shipgate`, and diff --git a/docs/agents/use-with-cursor.md b/docs/agents/use-with-cursor.md index 2d842f2c..ac4fd1c0 100644 --- a/docs/agents/use-with-cursor.md +++ b/docs/agents/use-with-cursor.md @@ -10,7 +10,7 @@ shipgate check --agent cursor --workspace . --format codex-boundary-json Parse stdout as `shipgate.codex_boundary_result/v1` and switch on `decision`, `completion_allowed`, `must_stop`, `first_next_action`, `human_review`, -`repair`, and `policy`. Do not infer a local control decision from prose. +`repair`, `policy`, and `verify_required`. Do not infer a local control decision from prose. Cursor's discoverability surface is the auto-attach project rule: a Markdown file under `.cursor/rules/*.mdc` with frontmatter that lists which globs cause it to attach to a chat. The canonical Shipgate rule already exists as a copy-paste snippet — drop it in and Cursor will load it whenever a chat touches `shipgate.yaml`, an OpenAPI/MCP spec, a tools JSON, or any `.py` file. diff --git a/docs/architecture.md b/docs/architecture.md index a9172313..d9113cef 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.28`, packet schema `v0.7`. +runtime contract `10`, 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, diff --git a/docs/codex-boundary-result-schema.v1.json b/docs/codex-boundary-result-schema.v1.json index 68847182..82aaa570 100644 --- a/docs/codex-boundary-result-schema.v1.json +++ b/docs/codex-boundary-result-schema.v1.json @@ -666,6 +666,11 @@ "default": null, "title": "Trigger" }, + "verify_required": { + "default": false, + "title": "Verify Required", + "type": "boolean" + }, "violated_rules": { "items": { "$ref": "#/$defs/AgentResultViolatedRule" diff --git a/docs/quickstart.md b/docs/quickstart.md index da33ce94..c442fc15 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -19,7 +19,7 @@ shipgate check --agent cursor --workspace . --format codex-boundary-json ``` Switch on `decision`, `completion_allowed`, `must_stop`, `first_next_action`, -`human_review`, `repair`, and `policy`; do not infer a decision from prose. +`human_review`, `repair`, `policy`, and `verify_required`; do not infer a decision from prose. ### PR And Local Verification diff --git a/docs/target-repo-agent-snippets.md b/docs/target-repo-agent-snippets.md index 89fd850e..0d8e3ff1 100644 --- a/docs/target-repo-agent-snippets.md +++ b/docs/target-repo-agent-snippets.md @@ -243,7 +243,7 @@ For local agent control, run: Read the check stdout JSON only. It is `shipgate.codex_boundary_result/v1`; switch on `decision`, `completion_allowed`, and `must_stop`, then follow `first_next_action`, -`human_review`, `repair`, and `policy`. Do not infer a decision from prose. +`human_review`, `repair`, `policy`, and `verify_required`. Do not infer a decision from prose. If `decision=allow` or `warn`, continue and summarize. If `first_next_action.kind` is `repair` and `repair.safe_to_attempt=true`, make diff --git a/llms-full.txt b/llms-full.txt index 8781470d..31b63d60 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -109,7 +109,7 @@ shipgate check --agent cursor --workspace . --format codex-boundary-json Read the single stdout object as `shipgate.codex_boundary_result/v1`. Switch on `decision`, `completion_allowed`, `must_stop`, `first_next_action`, -`human_review`, `repair`, and `policy`; never infer a local-control decision +`human_review`, `repair`, `policy`, and `verify_required`; never infer a local-control decision from Markdown, PR comments, or prose. If `decision=allow` or `warn`, continue and summarize the result. If `first_next_action.kind=repair` and `repair.safe_to_attempt` is `true`, apply only that repair and rerun the @@ -992,7 +992,9 @@ Verify the installed CLI contract locally before relying on hard-coded docs: agents-shipgate contract --json ``` -Runtime contract v9 also exposes the local agent command spec: +Runtime contract v10 (v10 adds `verify_required` to +`agent_result_control_fields` and to the boundary result; additive over v9) +also exposes the local agent command spec: `primary_commands{}`, `commands{}`, `default_paths{}`, `artifacts{}`, `agent_read_order[]`, `verifier_read_order[]`, `merge_verdicts[]`, `release_decisions[]`, `do_not_auto_assert[]`, `verifier_schema_version`, @@ -1013,7 +1015,7 @@ Downstream repos generated with `.shipgate/agent-contract.json`. - Latest release: `v0.14.0` (see [pyproject.toml](../pyproject.toml) for the in-tree version) -- Runtime contract: `9` +- Runtime contract: `10` - 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) @@ -1347,8 +1349,21 @@ The removed `--format agent-json` alias and `agent_result_v1` schema string are breaking 0.14.0 changes; see [STABILITY.md](../STABILITY.md#migration-note-0-14-0). Coding agents should switch on `decision`, `completion_allowed`, `must_stop`, -`first_next_action`, `human_review`, `repair`, and `policy`. Do not derive an agent -decision from Markdown, PR comments, or natural language. Do not confuse this +`first_next_action`, `human_review`, `repair`, `policy`, and `verify_required`. Do not derive an agent +decision from Markdown, PR comments, or natural language. + +`verify_required` (contract v10, additive) is the machine-readable +check→verify deferral: `true` whenever the diff touches a tool surface — +declared or undeclared — that the boundary check does not gate. The evaluator +simultaneously escalates what would otherwise be a clean `allow` to +`decision="warn"`, so the observable pair is `decision="warn"` with +`verify_required=true`: "no boundary rule fired, but capability is not yet +gated" — run `agents-shipgate verify` and read `release_decision.decision` +before reporting completion. A plain `decision="allow"` always has +`verify_required=false`. It is a deterministic +projection of the same deferral that emits the +`capability_change_requires_verify` / `undeclared_capability_surface` +diagnostics — not a second verdict. Do not confuse this local boundary result with `agents-shipgate verify`: verify writes `agent-handoff.json`, `verifier.json`, and `verify-run.json`, and `report.json` remains the full CI/reviewer substrate. diff --git a/llms.txt b/llms.txt index 2001ddfb..979fde76 100644 --- a/llms.txt +++ b/llms.txt @@ -89,7 +89,7 @@ - Install with pipx: `pipx install agents-shipgate`. - Install with pip: `python -m pip install agents-shipgate`. - Install with uv: `uv tool install agents-shipgate`. -- Local Codex boundary control: `shipgate check --agent codex --workspace . --format codex-boundary-json` (or `--agent claude-code` / `--agent cursor`); parse stdout `shipgate.codex_boundary_result/v1` and switch on `decision`, `completion_allowed`, `must_stop`, `first_next_action`, `human_review`, `repair`, and `policy`. +- Local Codex boundary control: `shipgate check --agent codex --workspace . --format codex-boundary-json` (or `--agent claude-code` / `--agent cursor`); parse stdout `shipgate.codex_boundary_result/v1` and switch on `decision`, `completion_allowed`, `must_stop`, `first_next_action`, `human_review`, `repair`, `policy`, and `verify_required`. - Preview whether Shipgate is relevant: `agents-shipgate verify --preview --json`. - Before editing protected surfaces, run `agents-shipgate preflight --workspace . --plan - --json` with a `PreflightPlanV1` object; stop when `requires_human_review` is true. - Install the AI coding workflow: `agents-shipgate init --workspace . --write --ci --agent-instructions=default --json`. diff --git a/plugins/agents-shipgate/skills/agents-shipgate/references/recipes.md b/plugins/agents-shipgate/skills/agents-shipgate/references/recipes.md index edb8f164..198a7a1c 100644 --- a/plugins/agents-shipgate/skills/agents-shipgate/references/recipes.md +++ b/plugins/agents-shipgate/skills/agents-shipgate/references/recipes.md @@ -16,7 +16,7 @@ agents-shipgate --version agents-shipgate contract --json ``` -Require `agents-shipgate contract --json` to report `contract_version: "9"` or +Require `agents-shipgate contract --json` to report `contract_version: "10"` or newer. If it is missing or stale, ask the user to install or upgrade: ```bash diff --git a/src/agents_shipgate/cli/discovery/agent_instructions/renderers/agents_md.py b/src/agents_shipgate/cli/discovery/agent_instructions/renderers/agents_md.py index 85a356f9..180285bc 100644 --- a/src/agents_shipgate/cli/discovery/agent_instructions/renderers/agents_md.py +++ b/src/agents_shipgate/cli/discovery/agent_instructions/renderers/agents_md.py @@ -41,7 +41,7 @@ def render_block() -> str: For local agent control, read the `shipgate check` stdout JSON only. It is `shipgate.codex_boundary_result/v1`; switch on `decision`, `completion_allowed`, and `must_stop`, then follow `first_next_action`, -`human_review`, `repair`, and `policy`. Do not infer a decision from prose. +`human_review`, `repair`, `policy`, and `verify_required`. Do not infer a decision from prose. Before finishing an agent-related diff, run `shipgate check`. If `decision=allow` or `warn`, continue and summarize. If `first_next_action.kind` diff --git a/src/agents_shipgate/cli/discovery/agent_instructions/renderers/claude_md.py b/src/agents_shipgate/cli/discovery/agent_instructions/renderers/claude_md.py index 6fdbc578..53837a9e 100644 --- a/src/agents_shipgate/cli/discovery/agent_instructions/renderers/claude_md.py +++ b/src/agents_shipgate/cli/discovery/agent_instructions/renderers/claude_md.py @@ -33,7 +33,7 @@ def render_block() -> str: For local agent control, read the `shipgate check` stdout JSON only. It is `shipgate.codex_boundary_result/v1`; switch on `decision`, `completion_allowed`, and `must_stop`, then follow `first_next_action`, -`human_review`, `repair`, and `policy`. +`human_review`, `repair`, `policy`, and `verify_required`. Before finishing an agent-related diff, run `shipgate check`. If `decision=allow` or `warn`, continue and summarize. If `first_next_action.kind` diff --git a/src/agents_shipgate/cli/discovery/agent_instructions/renderers/cursor.py b/src/agents_shipgate/cli/discovery/agent_instructions/renderers/cursor.py index 759ef409..aea8c4c0 100644 --- a/src/agents_shipgate/cli/discovery/agent_instructions/renderers/cursor.py +++ b/src/agents_shipgate/cli/discovery/agent_instructions/renderers/cursor.py @@ -58,7 +58,7 @@ def render_file() -> str: Read the check stdout JSON only. It is `shipgate.codex_boundary_result/v1`; switch on `decision`, `completion_allowed`, and `must_stop`, then follow `first_next_action`, -`human_review`, `repair`, and `policy`. Do not infer a decision from prose. +`human_review`, `repair`, `policy`, and `verify_required`. Do not infer a decision from prose. If `decision=allow` or `warn`, continue and summarize. If `first_next_action.kind` is `repair` and `repair.safe_to_attempt=true`, make diff --git a/src/agents_shipgate/core/codex_boundary.py b/src/agents_shipgate/core/codex_boundary.py index ba24f336..6a60eef7 100644 --- a/src/agents_shipgate/core/codex_boundary.py +++ b/src/agents_shipgate/core/codex_boundary.py @@ -537,6 +537,9 @@ def add(rule_id: str, *, path: str | None, evidence: dict[str, Any]) -> None: policy_version=policy.version, summary=summary, changed_files=changed_files, + # Machine-readable form of the check→verify deferral: the boundary + # verdict does not cover a changed tool surface, declared or not. + verify_required=bool(undeclared_gap or coverage_gap), completion_allowed=decision in {"allow", "warn"}, must_stop=decision in {"require_review", "block"} and not repair.safe_to_attempt, first_next_action=first_next_action, diff --git a/src/agents_shipgate/schemas/agent_result_v1.py b/src/agents_shipgate/schemas/agent_result_v1.py index 8ae32957..22f512b1 100644 --- a/src/agents_shipgate/schemas/agent_result_v1.py +++ b/src/agents_shipgate/schemas/agent_result_v1.py @@ -146,6 +146,14 @@ class AgentResultV1(BaseModel): summary: str changed_files: list[str] = Field(default_factory=list) completion_allowed: bool = False + # Contract v10 (additive): the check→verify deferral, machine-readable. + # True when the diff touches a tool surface — declared or undeclared — + # that the boundary check does not gate; the evaluator simultaneously + # escalates what would have been a clean ``allow`` to ``warn``, so this + # is always observed alongside ``decision="warn"``. Lives on the shared + # base so ``agent_result_control_fields`` validates against both the + # boundary schema and this legacy compatibility schema. + verify_required: bool = False must_stop: bool = True first_next_action: AgentResultNextAction human_review: AgentResultHumanReview = Field(default_factory=AgentResultHumanReview) diff --git a/src/agents_shipgate/schemas/codex_boundary_result.py b/src/agents_shipgate/schemas/codex_boundary_result.py index a522f36a..ee1f40ce 100644 --- a/src/agents_shipgate/schemas/codex_boundary_result.py +++ b/src/agents_shipgate/schemas/codex_boundary_result.py @@ -56,6 +56,15 @@ class CodexBoundaryResultV1(AgentResultV1): CODEX_BOUNDARY_RESULT_SCHEMA_VERSION ) + # ``verify_required`` (contract v10) is inherited from the shared + # ``AgentResultV1`` base: ``check`` is boundary-only and never computes + # the capability delta, so when the diff touches a tool surface the + # evaluator escalates to ``decision="warn"`` and sets the flag — the + # machine-readable form of "run ``agents-shipgate verify`` before + # completion". Deterministic projection of the same deferral that emits + # the ``capability_change_requires_verify`` / + # ``undeclared_capability_surface`` diagnostics; no second verdict. + CodexBoundaryResult = CodexBoundaryResultV1 diff --git a/src/agents_shipgate/schemas/contract.py b/src/agents_shipgate/schemas/contract.py index 3914b9f5..b08564bb 100644 --- a/src/agents_shipgate/schemas/contract.py +++ b/src/agents_shipgate/schemas/contract.py @@ -33,7 +33,7 @@ from agents_shipgate.schemas.verifier import VerifierArtifact from agents_shipgate.schemas.verify_run import VERIFY_RUN_SCHEMA_VERSION -CONTRACT_VERSION: Literal["9"] = "9" +CONTRACT_VERSION: Literal["10"] = "10" GATING_SIGNAL: Literal["release_decision.decision"] = "release_decision.decision" AGENT_RESULT_SCHEMA_VERSION: Literal["agent_result_v1"] = "agent_result_v1" AGENT_RESULT_SCHEMA_PATH: Literal["docs/agent-result-schema.v1.json"] = ( @@ -47,6 +47,12 @@ "human_review", "repair", "policy", + # v10: the check→verify deferral, machine-readable. True when the diff + # touches a tool surface the boundary check cannot gate; the evaluator + # escalates to decision="warn" at the same time, and agents must run + # verify before completion. Lives on AgentResultV1 so this list is valid + # against both the boundary and the legacy agent-result schema. + "verify_required", ) EXTERNAL_INTEGRATION_SURFACES: tuple[str, ...] = ( "agent_handoff", diff --git a/tests/golden/codex_boundary_result/agents_requirement_removed.json b/tests/golden/codex_boundary_result/agents_requirement_removed.json index 8a280f77..880e26f7 100644 --- a/tests/golden/codex_boundary_result/agents_requirement_removed.json +++ b/tests/golden/codex_boundary_result/agents_requirement_removed.json @@ -132,5 +132,6 @@ "fp_e3eff6c19a8ab16d" ], "policy_snapshot_sha256": "83ea4e4afeee1b003310f4f3ae88dbf986e0b24f64c339aaf3045dcb2b7746e8", - "exit_code_hint": 0 + "exit_code_hint": 0, + "verify_required": false } diff --git a/tests/golden/codex_boundary_result/docs_only.json b/tests/golden/codex_boundary_result/docs_only.json index 0cd75ddf..5ee237d8 100644 --- a/tests/golden/codex_boundary_result/docs_only.json +++ b/tests/golden/codex_boundary_result/docs_only.json @@ -101,5 +101,6 @@ }, "finding_fingerprints": [], "policy_snapshot_sha256": "83ea4e4afeee1b003310f4f3ae88dbf986e0b24f64c339aaf3045dcb2b7746e8", - "exit_code_hint": 0 + "exit_code_hint": 0, + "verify_required": false } diff --git a/tests/golden/codex_boundary_result/github_action_removed.json b/tests/golden/codex_boundary_result/github_action_removed.json index 3a30a0b3..2e5358d3 100644 --- a/tests/golden/codex_boundary_result/github_action_removed.json +++ b/tests/golden/codex_boundary_result/github_action_removed.json @@ -139,5 +139,6 @@ "fp_fe55c27fa455ddfd" ], "policy_snapshot_sha256": "83ea4e4afeee1b003310f4f3ae88dbf986e0b24f64c339aaf3045dcb2b7746e8", - "exit_code_hint": 20 + "exit_code_hint": 20, + "verify_required": false } diff --git a/tests/golden/codex_boundary_result/malformed_toml.json b/tests/golden/codex_boundary_result/malformed_toml.json index f3d3b52b..b570c141 100644 --- a/tests/golden/codex_boundary_result/malformed_toml.json +++ b/tests/golden/codex_boundary_result/malformed_toml.json @@ -135,5 +135,6 @@ "fp_86fb9b0faf6ca539" ], "policy_snapshot_sha256": "83ea4e4afeee1b003310f4f3ae88dbf986e0b24f64c339aaf3045dcb2b7746e8", - "exit_code_hint": 0 + "exit_code_hint": 0, + "verify_required": false } diff --git a/tests/golden/codex_boundary_result/mcp_auto_approve_write.json b/tests/golden/codex_boundary_result/mcp_auto_approve_write.json index 585c298d..be17a18a 100644 --- a/tests/golden/codex_boundary_result/mcp_auto_approve_write.json +++ b/tests/golden/codex_boundary_result/mcp_auto_approve_write.json @@ -139,5 +139,6 @@ "fp_71c94cd92ba68747" ], "policy_snapshot_sha256": "83ea4e4afeee1b003310f4f3ae88dbf986e0b24f64c339aaf3045dcb2b7746e8", - "exit_code_hint": 20 + "exit_code_hint": 20, + "verify_required": false } diff --git a/tests/golden/codex_boundary_result/network_wildcard.json b/tests/golden/codex_boundary_result/network_wildcard.json index beba654e..2d4ccf98 100644 --- a/tests/golden/codex_boundary_result/network_wildcard.json +++ b/tests/golden/codex_boundary_result/network_wildcard.json @@ -137,5 +137,6 @@ "fp_97b6fa25c1f62073" ], "policy_snapshot_sha256": "83ea4e4afeee1b003310f4f3ae88dbf986e0b24f64c339aaf3045dcb2b7746e8", - "exit_code_hint": 0 + "exit_code_hint": 0, + "verify_required": false } diff --git a/tests/golden/codex_boundary_result/python_refactor.json b/tests/golden/codex_boundary_result/python_refactor.json index fc24679e..77c7f8f9 100644 --- a/tests/golden/codex_boundary_result/python_refactor.json +++ b/tests/golden/codex_boundary_result/python_refactor.json @@ -94,5 +94,6 @@ }, "finding_fingerprints": [], "policy_snapshot_sha256": "83ea4e4afeee1b003310f4f3ae88dbf986e0b24f64c339aaf3045dcb2b7746e8", - "exit_code_hint": 0 + "exit_code_hint": 0, + "verify_required": false } diff --git a/tests/golden/codex_boundary_result/unknown_permission_key.json b/tests/golden/codex_boundary_result/unknown_permission_key.json index 12a07288..87c40c54 100644 --- a/tests/golden/codex_boundary_result/unknown_permission_key.json +++ b/tests/golden/codex_boundary_result/unknown_permission_key.json @@ -136,5 +136,6 @@ "fp_b75e128442b5e376" ], "policy_snapshot_sha256": "83ea4e4afeee1b003310f4f3ae88dbf986e0b24f64c339aaf3045dcb2b7746e8", - "exit_code_hint": 0 + "exit_code_hint": 0, + "verify_required": false } diff --git a/tests/test_agent_instructions_apply.py b/tests/test_agent_instructions_apply.py index c5a3e096..09f00118 100644 --- a/tests/test_agent_instructions_apply.py +++ b/tests/test_agent_instructions_apply.py @@ -190,7 +190,7 @@ def test_claude_command_current_file_matches_renderer() -> None: def test_local_contract_renderer_has_required_fields() -> None: payload = json.loads(render_local_contract_file()) assert payload["schema_version"] == "2" - assert payload["contract_version"] == "9" + assert payload["contract_version"] == "10" assert "verify_local" not in payload["primary_commands"] assert payload["primary_commands"]["verify_pr"].startswith("agents-shipgate verify") assert payload["commands"]["verify_local"].startswith("agents-shipgate verify") diff --git a/tests/test_agent_instructions_renderers.py b/tests/test_agent_instructions_renderers.py index 4f397d78..86487562 100644 --- a/tests/test_agent_instructions_renderers.py +++ b/tests/test_agent_instructions_renderers.py @@ -89,7 +89,7 @@ "7ef7ccb331a0171f0fb5580df4dad32003b230b150886a40d317a954ace0fb55" ), ".agents/skills/agents-shipgate/references/recipes.md": ( - "d8b393e61aef853105a47630b9cbfd404378b6c9e1bbed6028b357b4e38fc72c" + "f32d0046473377705e0ba487e19cfcf918edfe33a96a0666147cbbd1ea3f0de7" ), ".agents/skills/agents-shipgate/references/report-reading.md": ( "6d2848f3436f6e246bf553e6cf061c990888d6ff39eb82fec9a41f291b2e94fe" @@ -144,6 +144,7 @@ def test_agent_instruction_surfaces_name_phase1_control_fields() -> None: "human_review", "repair", "policy", + "verify_required", ): assert token in text, f"{name} missing {token!r}" @@ -164,7 +165,7 @@ def test_local_contract_renderer_exposes_agent_operational_fields() -> None: payload = json.loads(render_local_contract_file()) assert payload["schema_version"] == "2" assert payload["agents_shipgate_version"] - assert payload["contract_version"] == "9" + assert payload["contract_version"] == "10" assert payload["primary_commands"]["verify_pr"].startswith("agents-shipgate verify") assert payload["primary_commands"]["host_audit"].startswith("shipgate audit --host") assert "verify_local" not in payload["primary_commands"] @@ -194,6 +195,7 @@ def test_local_contract_renderer_exposes_agent_operational_fields() -> None: "human_review", "repair", "policy", + "verify_required", ] assert payload["commands"]["agent_check_codex"].startswith("shipgate check") assert payload["commands"]["agent_check_claude_code"].startswith("shipgate check") @@ -347,7 +349,7 @@ def test_codex_skill_has_required_surfaces() -> None: assert "agents-shipgate contract --json" in skill assert "install or upgrade `agents-shipgate`" in skill recipes = files[".agents/skills/agents-shipgate/references/recipes.md"] - assert 'contract_version: "9"' in recipes + assert 'contract_version: "10"' in recipes assert "shipgate.codex_boundary_result/v1" in recipes diff --git a/tests/test_codex_boundary_check.py b/tests/test_codex_boundary_check.py index 3f104117..e3fb7148 100644 --- a/tests/test_codex_boundary_check.py +++ b/tests/test_codex_boundary_check.py @@ -108,6 +108,9 @@ def test_declared_tool_surface_change_warns_and_routes_to_verify(tmp_path: Path) assert payload["first_next_action"]["command"].startswith("agents-shipgate verify") assert any(d["code"] == "capability_change_requires_verify" for d in payload["diagnostics"]) assert any(t["step"] == "coverage" for t in payload["trace"]) + # v10: the deferral is machine-readable, not just prose/diagnostics — + # agents switch on this instead of parsing the warning text. + assert payload["verify_required"] is True def test_no_coverage_signal_keeps_clean_allow(tmp_path: Path) -> None: @@ -120,6 +123,7 @@ def test_no_coverage_signal_keeps_clean_allow(tmp_path: Path) -> None: ) assert result.decision == "allow" assert result.first_next_action.kind == "continue" + assert result.verify_required is False def test_coverage_gap_only_escalates_from_allow_never_downgrades_a_block(tmp_path: Path) -> None: @@ -289,6 +293,7 @@ def test_undeclared_surface_warns_and_routes_to_detect_when_manifest_present( assert any(t["step"] == "coverage" for t in payload["trace"]) assert payload["suggested_fixes"][0] == "shipgate detect --workspace . --json" assert any(fix.startswith("agents-shipgate verify") for fix in payload["suggested_fixes"]) + assert payload["verify_required"] is True def test_mixed_declared_and_undeclared_routes_to_detect_when_manifest_present( diff --git a/tests/test_local_contract.py b/tests/test_local_contract.py index a3d8aca6..6e3a5bc3 100644 --- a/tests/test_local_contract.py +++ b/tests/test_local_contract.py @@ -109,6 +109,7 @@ def test_local_agent_contract_is_minimal_agent_operational_payload() -> None: "human_review", "repair", "policy", + "verify_required", ] assert payload["agent_interface_operations"] == [ "verify_pr", diff --git a/tests/test_schema_boundaries.py b/tests/test_schema_boundaries.py index 8763754b..1400e246 100644 --- a/tests/test_schema_boundaries.py +++ b/tests/test_schema_boundaries.py @@ -296,7 +296,7 @@ def test_representative_schema_payloads_keep_wire_fields() -> None: } assert ContractPayload( - contract_version="9", + contract_version="10", cli_version="0.0.0", report_schema_version="0.28", packet_schema_version="0.7", @@ -345,7 +345,7 @@ def test_representative_schema_payloads_keep_wire_fields() -> None: release_decisions=["passed", "blocked"], do_not_auto_assert=["approval"], ).model_dump(mode="json") == { - "contract_version": "9", + "contract_version": "10", "cli_version": "0.0.0", "report_schema_version": "0.28", "packet_schema_version": "0.7",