agents-shipgate/docs/agent-contract-current.md at main · ThreeMoonsLab/agents-shipgate · GitHub
Skip to content

Latest commit

 

History

History
1835 lines (1647 loc) · 140 KB

agent-contract-current.md

File metadata and controls

1835 lines (1647 loc) · 140 KB

Current Agent Contract

Runtime contract v42, unreleased, stops an empty preflight plan from minting authority (#610). Through preflight 0.5 a plan that named nothing to route returned the shared complete state, whose permissions grant merge and report_complete, with no verifier identity behind it. Preflight 0.6 has its own control union: planning_only, agent_action_required and human_review_required. planning_only is new and means only that planning finished, because the plan named no changed file, capability request or host permission request and no drift signal fired; it has no next_action. Preflight never returns complete or review_publishable, and every permission is false on every route, in the model and in docs/preflight-schema.v0.6.json alike, so leaving files out of a plan can never stand in for an evaluation of the change: preflight never authorizes merge or completion. 0.5 stays frozen and readable as a --base-preflight; minimum_control_contract_version stays 21, because the shared AgentControl union is unchanged and a reader that does not know planning_only cannot mistake it for complete. See the migration note.

Runtime contract v41, new in 1.2.0, names the changed inputs a host comparison does not read (#821). A zero-row comparison used to print "No static host-grant changes detected" for a pull request that added a Cursor plugin's mcp.json or moved a marketplace plugin's pinned sha, exactly as for a docs-only one. Verifier 0.21 and shipgate diff --json (capability diff 0.4) now add, inside the existing coverage block, a changed_not_read item for each path in the comparison's own changed-file set that a bounded, documented candidate rule names and no reader of this entry read, with its candidate rule (plugin_mcp_config, plugin_manifest_mcp_servers, plugin_manifest_hooks, plugin_hook_file, unparsed_plugin_manifest, cursor_project_hooks, nested_host_settings, external_plugin_source) and, for an external plugin source, the source it names as detail, never fetched. read_sources_only is false while such an item is named, and unread_candidates (examined, not_examined, or null for not recorded) with unread_candidates_not_examined say whether the change set was examined and how many candidates were not, past the bound or because a file their rule needed was not read or did not parse. An item is never a row, a widening, a loading claim or a check violation. The one route it moves: a manifest-free verify whose only host-relevant change is such an input, or a changed candidate it counts as not examined, publishes that comparison instead of the setup route, and on verify --preview the next action for that change is discover (audit --host) with the comparison published, where it was initialize (init --write) with none, in an agent-related workspace too, as it already was when the change edited a host file this entry reads. minimum_control_contract_version stays 21, and a 0.20 verifier reads with the search not recorded. See the migration note.

Still contract v41, new in 1.2.0: a plugin directory a host comparison cannot compare no longer hides the changes outside it (#808). Where every blocking limit that refused the comparison is a plugin-reference limit bounded by its plugin directory, and no compared source depends on that directory, verifier 0.21 and shipgate diff --json (capability diff 0.4) publish comparison_status: partial: the same incomparable_reasons, the rows, review and unchanged_limits established outside the directory, and the directory as scope on each such blocking_limit coverage item, which 0.20 reserved as always null. Treat partial as incomparable for any decision: every control state, permission, next action and check decision is the refusal's, the control envelope's capability_rows projects it as incomparable with no rows, and check, whose boundary result cannot name a directory, still refuses its comparison. A 0.20 verifier claiming a partial comparison or a scope is refused. See the migration note.

Unreleased, still contract v42: a plain instruction document (AGENTS.md, AGENTS.override.md, CLAUDE.md) that both sides read directly and that is refused only because it is past the 256 KiB instruction bound is withheld the same way (#973): the comparison is partial, and the document's own path is the scope of its blocking_limit item, with limit: unsupported (a hook script's own-path scope is unreadable). A blocking limit both sides carry ends its detail with whether Git shows the file changed in this change, is byte-identical, or neither. Treat partial as before. See the migration note.

Runtime contract v41, extended in place, also reads how a coding agent is launched inside a workflow job (#823). Host-grants 0.6 shipped in 1.1.0, so host-grants inventory, baseline and drift schemas move to 0.7, and a workflow grant adds agent_launches[], unread_agent_runs[] and checkout_refs[], each omitted when empty. An agent launch is a step whose uses: is a documented agent action (anthropics/claude-code-action, anthropics/claude-code-base-action, openai/codex-action) with the permission inputs it declares, or a run: that is one line of plain words running claude -p / codex exec under bash or sh, with its documented permission flags; its job, step, agent, form (read, or unresolved with inputs_not_a_mapping), settings[] (name, value, unresolved_reason, holds_expression), widening_rules[] (rule, setting) and job_secrets[]. Shell is not parsed: any other run: that mentions claude or codex is an unread_agent_runs[] entry (job, step, agent), a named non-blocking limit that publishes none of its text, is never compared and gives no row; and claude_args / codex-args are read only as a plain list of words, any other value being unread_arguments, compared by a digest and read for no rule. A checkout ref is each actions/checkout step's with.ref, null for the default. Values are compared as text and never executed. A JSON object in a settings or mcp_config input publishes its shape and none of its free text — key names, with each string a <withheld:…> digest except those a host reader publishes (a permission rule, a documented setting's value, an MCP server's command name and URL host) — so an MCP server's arguments and a hook's command are compared but never published; a URL publishes its scheme and host. Only a documented rule a job's launches gain — bypassed permission checks (a flag, or JSON settings whose defaultMode is bypassPermissions), a bypassed or danger-full-access sandbox (permission-profile: :danger-full-access included, and in a codex exec step without --sandbox a --config override of sandbox_mode or default_permissions that selects it), safety-strategy: unsafe, or a user gate opened to * — raises workflow_agent_widened_<added|changed> and makes the row widened. A rule is read only from text this audit reads exactly, and one a launch already met in a job it left, one where an unread step of the job became a read launch, or one where the job's launch before held an expression or an unread argument input the rule is read from, is named and not claimed. Every other edit is changed, and a workflow row that runs an agent ends its why with the job facts beside each agent step. An action whose with: is not a mapping is unresolved and a named non-blocking limit; a setting holding credential-shaped text, prose included, is published redacted, compared as published and a named non-blocking limit, and a checkout ref holding it is a blocking limit, as a redacted step reference is. A 0.4–0.6 baseline holding a workflow grant is incomparable (baseline_workflow_agent_launches_unavailable); one without a workflow stays comparable. It moves neither #821's verifier 0.21 nor its capability diff 0.4, and minimum_control_contract_version stays 21. See the migration note.

The same runtime contract v41, new in 1.2.0, also names what changed in a hook and in an MCP server's launch arguments (#819). Host-grants inventory, baseline and drift schemas move to 0.7: a hook grant adds handlers[] (each handler's group matcher, its command as {executable, sha256} and its timeout) and omitted_handlers, and an MCP server grant adds package and args_sha256. No command or argument text is published: a command is its executable's name, when that is a plain token, and a digest; the arguments are one package specification of a strict shape and a digest of the rest. A hook row names the changed field, PostToolUse: matcher Edit → Edit|Write|Bash or command changed with both digests, and a version pin moving to @latest is a package difference, in the text and in review.changes[].change. The members display what config_sha256 already binds, so grant equality and the inventory digests leave them out: they move no row value, row count, verifier or capability-diff schema, a 0.6 baseline without workflow grants stays comparable with no new row or reason, and minimum_control_contract_version stays 21. A saved baseline holds none of the members. See the migration note.

Previous runtime contract v40 reads the action reference each workflow step declares (#771). Host-grants inventory, baseline and drift schemas move to 0.6, and a workflow grant adds step_actions[]: the job, the step (id, else name, else steps[N]), the declared uses, and its form — remote, docker, or unresolved with a reason. Moving a step from a pinned SHA to @main is a changed row naming the job and step, with expands: false: a reference names different code, not new token scopes. Local ./ actions stay unread (#701). A 0.4 or 0.5 baseline holding a workflow grant is incomparable (baseline_workflow_step_actions_unavailable); one without a workflow stays comparable, and minimum_control_contract_version stays 21. See the migration note. The same contract and 0.6 schemas, new in 1.1.0, also read the named secrets a job passes to a reusable workflow (#693): a reusable call adds secret_mappings[] (the destination, the source name from ${{ secrets.NAME }}, form and unresolved_reason) and uses_redacted. Pointing a destination at a different source is a changed row with expands: false; a reusable target or secret name that redacts is a blocking limit rather than a comparison, while a literal or another expression is a named non-blocking limit that leaves coverage complete. See the migration note. The same contract and 0.6 schemas, new in 1.1.0, publish every workflow label redacted (#802): a job id, a step's id or name, a trigger and a permission scope name shaped like a credential (ghp_…, AKIA…) or holding userinfo after scheme:// are redacted in every field, row and check evidence that names them, and config_sha256 is computed over the redacted labels. Ordinary names are unchanged. One redacted label still compares. Two distinct job ids or triggers in a workflow, or scope names in one permissions mapping, that publish alike are a blocking limit: while they exist, check, audit --host --save-baseline and drift refuse on every run. See the migration note. New in 1.1.0, still contract v40: every host comparison says what it established (#812). Verifier 0.20 adds host_comparison.coverage, and shipgate diff --json (capability diff 0.3) the same block: a capped list of sources with their side (base, head, both), a status (compared with its rows, zero on both sides only for a file proven byte-identical, changed_without_grant_change, changed_without_rows, unchanged_not_proven, or on an incomparable result blocking_limit with its limit kind) and omitted_items. It is evidence beside the rows; it moves no row, reason, control state, permission or next action, and null means not recorded. A 0.19 verifier reads with coverage: null. See the migration note. New in 1.1.0, still contract v40: the same two versions publish what the text says about those rows, so a machine consumer and a reader cannot disagree (#795). A row adds disposition (allow, ask, deny, or null for another grant kind) on every route that publishes rows, and host_comparison.review, the same block diff --json carries, adds the changes the text prints — each with the row_indexes it stands for, a direction that may be widened, narrowed or moved, its cells, why and one expands — plus summary ({rows, changes, widenings}), question and reproduce_command. A joined change whose two sides read alike is refused, so check and a provided diff publish rows alone. Every row value and the row count are unchanged, and null means not recorded. See the migration note.

Previous runtime contract v39 reads through an in-tree link at a boundary path (#700). A link such as CLAUDE.md -> AGENTS.md or .claude/skills -> ../.agents/skills that resolves inside the repository is read at its target and published under its own path. Host-grants inventory, baseline and drift schemas move to 0.5, which adds artifacts[].resolved_through, the in-tree paths the read followed, present only on such an artifact. An external, escaping or dangling target, a link inside a linked directory, a chain past eight hops, and a directory link that could only hide a **/ match still refuse. A 0.4 baseline stays comparable, and minimum_control_contract_version stays 21. See the migration note.

Previous runtime contract v38 lets the control envelope name what host capability changed (#662). shipgate.agent_control/v1 gains an optional capability_rows block on check --format agent-control-json, verify --format control and agent control. It holds up to five rows, the rows the engine called an expansion first, plus omitted_rows, comparison_status, incomparable_reasons and unchanged_limit_count. It is evidence beside the control and moves no state, permission or route. It is omitted when no host comparison ran, so those envelopes are byte-identical. The envelope object is closed, so a reader that validates against the v37 schema rejects an envelope carrying the block. That break is deliberate. minimum_control_contract_version stays 21. See the migration note.

Previous runtime contract v37 names the limits a host comparison compared past (#721). Verifier 0.19 adds host_comparison.unchanged_limits, and shipgate diff (capability diff 0.2) carries the same list. A surface that is partial (unsupported, parse_failed) or experimental on both sides, and byte-identical between them, no longer refuses the whole comparison: the rest is compared and each limit is named. A limit that changed, appears on one side only, or is unreadable still refuses. check's boundary result cannot name limits yet and keeps refusing. See the migration note.

Previous runtime contract v36 freezes the report contract at 1.0 (#569). No field is added, renamed, retyped or removed: the emitted shape is exactly the one v35 advertised as report 0.43, so a consumer written against 0.43 reads a 1.0 report unchanged. What moved is the promise. 1.x is additive-only; a change that cannot be expressed additively needs 2.0; a deprecation cycle is counted in shipped releases, never in time on unreleased main; and every published schema URL keeps its bytes forever. The production qualification policy's required_report_schema_version moves with the engine to 1.0, and issuance of the pre_1_0 qualification tier is retired — existing pre_1_0 artifacts stay readable, nameable and scoreable, they are simply not produced any more.

Pre-freeze 0.x reports are no longer accepted as input to this engine. Every external report boundary — --diff-from, explain-finding, findings, scenario suggest, and packet from a report — refuses one by name and names the regeneration route, instead of validating it against a model whose defaults would stand in for blocks it never recorded. Every superseded schema stays published for reading archived artifacts. Operational control shapes are byte-identical, so minimum_control_contract_version stays at 21. See docs/report-1-0-contract.md.

Previous runtime contract v35 adds manifest-free host comparison evidence in verifier 0.18 and named boundary rows in shipgate.agent_boundary_result/v3. Comparison health and captured input identity remain separate from release and control authority. See the migration note.

Previous runtime contract v34 advertises host inventory, baseline and drift schema 0.4: workflow permission projections and reusable secret recipients. Operational control is unchanged; older baselines cannot prove that unrecorded recipients were absent. See the migration note.

Runtime contract v33 adds host_boundary_candidates[] and host_discovery_incomplete_paths[] to discovery. They describe recognized configuration paths and traversal the bounded census could not see through, never parsed grants. An incomplete census withholds the product-wide negative and publishes no candidates; it does not fail the classification. A host-only workspace follows audit --host without creating a manifest. init and bootstrap hand off without setup writes; incomplete input cannot publish a product-wide negative. Existing operational control and persisted evidence schemas are unchanged; all setup permissions remain false.

Runtime contract v32 separates instruction prose from supported parsed permission structure across verification, preflight, host drift and generated edit hooks. It publishes verifier v0.17, handoff v9, preflight v0.5 and host evidence v0.3; preflight v0.6 (#610) keeps that structure and changes only its control. Raw identity still changes on prose edits; legacy evidence is never upgraded to a new permission claim. conditional_file_edits is a standing routing rule with grants_authority: false, separate from unconditional forbidden_file_edits. See the comparison and migration contract.

Runtime contract v31 adds a read-only external review decision evaluator for the bounded human-review-request.json class. It verifies current scope, host key trust, reviewer eligibility and expiry and returns separate evaluation evidence. It writes no artifact and grants no merge or completion authority. The shared control union and all existing persisted schema identifiers are unchanged; the minimum control contract stays at 21. GitHub acquisition and persistence remain an integration obligation.

The single, current statement of what AI coding agents and CI integrations should read from Agents Shipgate output. When the contract changes, update STABILITY.md first, then this file. Other agent-facing surfaces (AGENTS.md, llms.txt, .well-known/agents-shipgate.json, the slash command, the skill, the FAQ) link here instead of restating field lists.

For contributors changing the contract: regenerate schemas with python scripts/generate_schemas.py, then run python scripts/regenerate_goldens.py and its --check mode using the committed sample recipe. Review actual artifact changes and preserve behavioral assertions; sample regeneration does not freeze a release contract or qualify a candidate wheel.

Current versions

Verify the installed CLI contract locally before relying on hard-coded docs:

agents-shipgate contract --json

1.0.0 declaration-review migration

Runtime contract v29 coordinates the public artifacts that carry changed action declarations: report v0.43 (now frozen at 1.0), packet v0.18, and verifier v0.16. minimum_control_contract_version remains 21 because the operational control envelope is unchanged.

Read release_decision.evidence_coverage.semantic_coverage.declaration_review for the base-vs-head reviewer projection. It includes added, removed, and semantically modified declaration rows. A requested comparison that could not run is distinct from an available comparison with no changes; missing or conflicting identity and semantic evidence gaps cannot earn an evidence_consistent status. Report Markdown, packets, PR comments, annotations, and the GitHub summary consume the same bounded projection.

See the declaration-review migration note.

Earlier projection-only migration

That change moved no schema or runtime-contract version; the v0.42 report schema gained the typed unattested_surface gap and optional EvidenceGap.policy_id. Completed blocked verifier runs now append the deterministically selected worst blocker to the plain headline as well as the adoption/self-approval branches; that same headline feeds control.reason and control.next_action.why. Runs with no blocker do not gain the clause. A genuinely incomplete enumeration remains incomplete_surface; a lower-confidence extraction without an enumeration defect is the distinct unattested_surface gap, and only an explicit surface: enumerated adapter fact earns positive enumeration wording. Its remedy asks for reviewed attestation. Policy gaps retain their exact identity in structured policy_id, while policy-evidence gap prose no longer prefixes why with an engine-owned builtin-* policy id. Public and organization-defined check ids remain stable labels. Branch on the typed gap kind and policy_id, never on the explanatory sentences. See the verifier-explanation migration note.

Standalone trigger evaluation is unchanged. Inside a verifier artifact, however, its generic route has already been consumed. Embedded trigger.next_action therefore preserves the evaluated kind while clearing its command and adding authoritative: false and authoritative_path: "control.next_action"; the self-referential preview command is not repeated. Commands on embedded matched_rules[] are cleared as well. In verifier, verify-run, and preview output, follow only control.next_action and control.allowed_next_commands. See the embedded-trigger migration note.

Emitted next actions terminate

Following control.next_action.command — or the human Next: line, which is the same routing rendered for a person — reaches a terminal. Three properties hold, and tests/test_next_action_chains_terminate.py walks every entry command on a host-only and a manifest repository to keep them holding:

  • No cycle. No chain returns to a question it already asked. Output format is not part of a step's identity: audit --host and audit --host --json ask the same thing, and treating them as different steps is what once hid a three-command loop (verify --preview → init --write → audit --host → verify --preview).
  • No workspace loss. Every emitted command names the same --workspace it was produced for. A step that drops it does not continue the walk; it starts a different one against the caller's current directory, which is the silent retargeting the invocation policy exists to prevent.
  • Every step is runnable. An emitted command names a Shipgate command that accepts it. Prose that names no command is a terminal: it hands the reader a decision, not another lap.

A run that evaluated no gate says so. verify --preview on a repository with nothing to gate reports execution: "not_run" and applicability: "not_evaluated", and the human line reads Agents Shipgate verify: not evaluated rather than failed.

Runtime contract v28 publishes the capability delta as a standalone attestation. verify writes agents-shipgate-reports/capability-delta-attestation.json: an in-toto Statement whose predicateType is https://threemoonslab.com/agents-shipgate/capability-delta/v1 and whose predicate carries the frozen shipgate.capability_payload/v1 delta view unchanged. The contract now names the predicate type, both schema versions, both schema paths, and the artifact, so a consumer can discover the format without reading our source. Full specification: docs/capability-delta-attestation.md.

Read it when you want what the agent can do after this change as a portable fact — a runtime gateway, a policy engine, a dashboard, another CI system. Nothing about the gate moves: the attestation carries no verdict, no severity and no release impact, and release_decision.decision remains the only release decision signal.

Two things a consumer must branch on. The attestation is written only for a committed-tree subject — a worktree run publishes a note on verifier.base_notes[] and no file, because the bytes it evaluated are not in any tree object. And predicate.verification.status is bound or unbound: only bound chains to verification-receipt.json through input_set_id, and a consumer that needs that chain checks the status rather than probing for absent fields. Everything else is unchanged from v27.

Runtime contract v27 finishes the v24 rollout on the stream v24 left out. Every agent-mode error line from detect, init, and doctor now carries the same shipgate.agent_control/v1 object those commands publish on --json. Before, doctor's failure routes carried one and detect's and five of init's did not, so whether a caller that routes on control could route at all depended on which setup command had failed and on which of its failures — a caller cannot branch on that, and the run that most needs a route is the one that printed no payload to carry it.

No field is removed. next_action (single string) and next_actions[] (ranked array) are unchanged on those lines, and are derived from the same selected route as control.next_action, so the three cannot send you to different work. Three command values do move, and they are listed at the end of this section.

What every setup error line guarantees, in the published schema and not only in the producer: decision_source: "setup", a decision from setup_complete | setup_incomplete | setup_not_applicable, every field of permissions false, and control_state never complete. Setup authorizes nothing, on either stream.

Do not read execution as "this is an error line". The error field says that. execution answers whether the command reached an answer about the workspace, and both values appear on error lines: "failed" when it could not (an unparseable flag value, discovery that could not be bounded, a manifest it could not open), and "succeeded" with a non-zero exit_code when it did and the answer is a refusal it can route past — config_already_exists from init --write declining to overwrite, and the unresolved-scope config_error. The second kind still carries permissions all false, because it is still a setup envelope.

One stated exception. The shared --workspace refusal (config_error, exit 2, emitted by every command that takes a --workspace) carries next_action/next_actions[] and no envelope. It fires because the workspace does not exist, so there is no setup subject for input_id to address and no setup facts for a state to be derived from. Treat an error line with no control as "this is not a setup answer", not as "route on something else".

scan, verify, and check are unchanged and are deliberately not in this. scan and verify publish a control pointer, so their envelope is the promoted read agents-shipgate agent control — or, for verify, --format control directly. check publishes no pointer at all and binds its authority to input_id instead; its envelope is --format agent-control-json.

init --write over a manifest that already exists also publishes a different route. It reported next_action.kind: "edit" on shipgate.yaml with expects: "The manifest reflects the desired tool sources, agent declared_purpose, and policies" — a postcondition the file already satisfied, because a manifest that does not load is claimed by the repair route above it. On this contract next_action is the step, so that route could not change the answer: an envelope-only caller opened the file, found nothing to change, re-ran, and got the identical action back. It is now the doctor invocation for the manifest on disk, which reports what that manifest still owes. The exit code (2) and the "already exists — edit it directly or remove it" sentence are unchanged.

Unless the request was not applied. init --write --control-pack <id> over a manifest that selects a different pack keeps a reconciliation route naming policies.control_pack and the exact value. Both onward routes would otherwise advance under the pack that is there, and the request would be lost with nothing saying so — a recovery that completes with less than the caller asked for reports success for work it did not do. The same reconciliation is published for a scoped candidate whose manifest selects a different pack, where a bare doctor had the same effect one directory down.

"Asked for" is read from the parser, not inferred: an explicit --control-pack default over a financial-strict manifest is a request, and it is the one that can only weaken.

Who owns that edit is decided by the direction, not by who typed the flag. A governed coding agent composes its own argv, so process arguments are not authenticated human provenance. A transition that keeps at least every obligation the manifest has today is agent_action_required with a typed edit; one that drops any obligation — or names a pack this build cannot resolve, where the direction is unprovable — is human_review_required with no command, naming the effects and controls it would remove. That is the same direction verify_policy raises control_pack_weakened for, computed by the same function.

Two command values also move. Every recovery init publishes now repeats the whole invocation with only the invalid value corrected — --minimal, --allow-unresolved-scope, --agent-instructions-kit, and a non-default --max-python-files ride along, where before they were dropped; and the internal_error fallback names this invocation with --minimal added instead of a bare agents-shipgate init --minimal that ran in the process directory. These are command value changes on next_action, next_actions[0], and control.next_action alike, which are one route.

Both the AgentControl union and shipgate.agent_control/v1 are byte-identical to v26 — the same envelope reaches one more place — so minimum_control_contract_version stays at 21.

Runtime contract v26 adds the declaration continuation: a receipt apply-patches writes beside the report it applied from (declaration-continuation.json, shipgate.declaration_continuation/v1), and the one situation in which a blocked release decision may authorize publication. The receipt pins the manifest by byte digest on both sides of the write; the run additionally compares the two manifests and requires the delta to be added action_surface.actions rows and nothing else. On that proof the control is review_publishable — edit/commit/push/update_pr so the drafted proposal reaches a person, merge and report_complete still denied. Without it a blocked decision authorizes nothing, exactly as before. verifier.json, agent-handoff.json and verify-run.json each carry the resulting declaration_continuation boolean, which is why their schema versions move to 0.15, v8 and v5.

Runtime contract v25 added one route to the control envelope: next_action.kind: "confirm_declarations", published by verify on a working-tree run whose verdict is insufficient_evidence, with no blockers and no policy weakening, when at least one open declaration question is one the scan can answer from its own evidence. It is not published on a ref-bound (--base/--head) run: apply-patches mutates the checkout, so the exact rerun would re-scan the commit the edit is not in yet — the same precondition every mechanical repair route carries.

Weakening here is capability_review.policy_weakening_proven, plus the fail-closed capability_review.policy_weakened unless this diff introduces the gate it is judged by. That exemption is a separate, narrow, proven fact — the configured manifest is in the diff, absent at the comparison ref, the diff removes or renames away no YAML file, and every policy surface it touches is that manifest — so there is no prior version of this gate the change could have loosened. It exists because policy_weakened stays raised whenever the direction could not be established, and establishing it means proving no file in the tree parses as a manifest under any name: one blob past the probe's read bound ends that proof, which is the normal case on a real repository. Gating the route on the flag alone made a first adoption — the run with every question still open — the one run that could never be offered it. Nothing else moves: the flag, the verdict, the adoption wording, and the human route are all unchanged, and a proven weakening still refuses.

  • What it carries. The exact apply-patches command that writes those answers, plus questions[] — open declaration questions, each tagged authorable_by: "coding_agent" | "human" — and the two counts agent_authorable / human_authorable, which are the real totals. The list is a prefix capped at six rows, ordered human-owned first (a drafted row is answered by the command whether or not it is printed; a human-owned row is what you have to hand a person), each half keeping the report's ranking. Read release_decision.evidence_coverage.semantic_coverage.declaration_questions.open_questions[] for the unabridged list.

  • What it authorizes. Exactly the named command, plus whatever permissions says — publish-only on this route: edit, commit, push, update_pr true; merge and report_complete false. Writing declarations into shipgate.yaml touches the trust root, so the change still reaches the gate only through a human merge. The route is a proposal step, never a completion.

  • The content rule. A row is authorable_by: "coding_agent" only when the scan filled every blank in its declaration_template — from the closed effect vocabulary, never weaker than any reading it observed — and the question is not one that asks a person to look again: a declaration_drift row restates a confirmed answer beside a moved pin and stays "human", because an agent re-stamping the pin would close the very request the row is for. A template still carrying a <REVIEW_REQUIRED> blank (every authority block, every override) stays "human" too. Authorship is decided by content, never by who is running: an agent may propose what the evidence supports, and only a human may assert against it.

  • What the patch may write. next_action.patch is exactly declaration_template, split into the keys that name the action and the fields that are written — the schema rejects any other pairing, so a row cannot advertise an evidence-derived tag beside a patch that writes something else. Its target_path is relative to manifest_dir, so the row means the same thing in the packet, the SARIF file, and a cached base scan. apply-patches writes only into fields the manifest leaves silent, and refuses — exit 5, nothing written — when a row already answers one differently, when two equally compatible rows name the same tool, or when the manifest has changed since the scan.

  • After it, in this order: rerun, then publish. The command edits shipgate.yaml, so the moment it succeeds this control is stale — agents-shipgate agent control refuses with workspace_changed ("the working tree carries 1 uncommitted change this decision never saw"), and the v20 refresh rule requires that read before any commit, push, or PR update. The permissions printed beside the route were computed against a manifest that no longer exists and cannot authorize publishing what the command just wrote, which is why expects promises the write and the supersession rather than a commit. Re-run verification, then act on what that run authorizes. It is a fresh decision, and a declaration whose whole purpose is to make a risk judgeable is exactly what can move the verdict to one only a person may clear. If questions remain, they are the ones tagged "human", and the human route names them rather than asking for generic review.

  • When it is withheld. report.json publishes every open question with authorable_by resolved whether or not the route is published, so a control that offered nothing and explained nothing read as an invitation to write the manifest without the route. The headline — and therefore control.reason and control.next_action.why — now carries one sentence naming the cause, for example 3 declaration(s) this scan could draft are withheld: a blocker is open, and that decision is a person's. It is produced by the same pass that withholds the route, so the cause published is the cause that acted, and only a comparison that actually ran may say the gate was weakened. It appears only where a drafting route existed to publish and something refused it; a run with no agent-authorable question withheld nothing and says nothing.

    It is an explanation, not a machine field. Like every headline context clause it shares the 400-byte prose budget with the verdict, the worst blocker, the gap-provenance clause and the reserved human-review requirement, and it is dropped whole — after gap provenance, which names a subject that left the analysed surface and outranks it — rather than truncated. A run whose verdict and blocker have already spent the budget publishes no context at all. Do not branch on its presence.

The AgentControl union is unchanged again, so minimum_control_contract_version stays at 21: the control holds the step as the repair command it truthfully is, and the envelope publishes the richer form — the same split SetupEditAction uses.

Runtime contract v24 rolls the control envelope across the setup commands. detect --json, init --json, and every doctor --json payload now carry a control field holding the same shipgate.agent_control/v1 object that verify, check, and agent control emit, so one vocabulary answers "what may I do next" for the whole adoption walk. Two things keep the setup family distinguishable from the gate:

  • Setup names its own source. These commands run before a release decision exists, so they report decision_source: "setup" and a decision from the closed setup vocabulary setup_complete | setup_incomplete | setup_not_applicable. The published schema enforces the pairing both ways: a setup source can only come from detect/init/doctor, and those operations can report no other source. decision_source: "release_decision" still means, and only means, release_decision.decision.
  • Setup authorizes nothing. No setup command reads a diff, so every field of permissions is false on every setup envelope, no setup envelope binds an artifact or a current_control_id, and control_state: "complete" is unreachable for these operations in the schema itself. Setup routes; it never finishes a task.

agent control on a scan generation publishes no release verdict. scan reaches one, but its pointer records no HEAD, no worktree overlay, and no input set, so nothing about that verdict can be reconfirmed against the workspace as it stands: editing the manifest, a tools.json it references, a policy pack, or a baseline leaves the pointer reading cleanly. What the envelope carries instead is reason, stating why there is no verdict — which is what keeps it distinguishable from an envelope produced before any engine ran, and is the ambiguity #323 set out to remove. Run verify for a verdict a reader can check.

The AgentControl union is unchanged, and minimum_control_contract_version stays at 21. That union is embedded by the verifier, the handoff, preflight, the agent result, the boundary result, and verify-run, so widening it would widen six durable published schemas under unchanged identifiers — and five of those artifacts record no contract_version, so a consumer holding a stored payload could not use the floor to tell which shape it has.

A setup step that needs a file changed is still typed, though: the envelope publishes next_action.kind: "edit" with path and expects, as SetupEditAction — declared on the envelope, which is stdout-only, and rejected in both layers on any non-setup operation. Routing such a step as the command that merely checks the edit was tried and is wrong: an envelope-only consumer executing it re-ran doctor against an unchanged file forever.

What v24 widens is shipgate.agent_control/v1 itself, which is emitted on stdout and never written as an artifact: there are no stored envelopes to disambiguate, and its new operations cannot appear in anything a v21 consumer holds.

A human-owned manifest declaration is never published as a coding-agent edit. When shipgate.yaml still holds an unresolved declared_purpose, policy, or permission placeholder, the setup control state is human_review_required and the action names the exact file, line, and field a person must fill in. The command-specific next_action / next_actions[] fields are unchanged, and remain supported.

Runtime contract v23 spells every emitted command for the invocation that produced it. A run started with python -m agents_shipgate now proposes <sys.executable> -P -m agents_shipgate ... instead of a console script its environment may not have (-P keeps the repository it is run from off sys.path); a console-script run is unchanged. Actions with kind="command" in next_actions[] also carry executable[] and args[] — the authoritative runnable form on every platform; run them as [*executable, *args] with no shell. The pair is computed from command and cannot be supplied, so the two forms cannot disagree, and it is omitted (not null) whenever the command has no faithful argv form. command itself is a POSIX rendering for display and POSIX shells. When the rank-1 action is a command, the legacy next_action string is that command verbatim. Set AGENTS_SHIPGATE_CLI to name the entry point explicitly. Durable evidence artifacts (report.json, packet.*) stay canonical: "same inputs, same report" outranks runnability there, and process-entry spelling is not an input.

Runtime contract v22 publishes shipgate.agent_control/v1, the compact control envelope. It is a projection of the control state, not a second decision: every field is copied from a producer that already published it, and the schema's validators only assert that the copies cannot contradict each other. One object answers the whole routing question — tool execution status, the release or boundary decision and which engine made it, the control state, the six-way permissions vector, who acts next, the exact next action, and the content-addressed path and hash of every artifact current-control.json binds — not every file a run writes, and none at all from check, which publishes no pointer. It is emitted by agents-shipgate verify --format control, agents-shipgate check --format agent-control-json, and agents-shipgate agent control (now its default output; --format pointer returns the raw pointer). It publishes a size budget, agent_control_budget_bytes (6144), that representative output meets and that is pinned by tests; it is not a hard maximum, because a long required-reviewer list or an unusually long exact command must never be truncated to hit a size target. Free-text fields are capped at 400 UTF-8 bytes; commands, paths, hashes, and reviewer names never are. It is never written to disk — the artifacts it names stay where they are.

The envelope is a discriminated union on control_state, like the AgentControl union it projects, so the published JSON Schema — not only Pydantic — rejects a contradictory payload: execution: "failed" beside control_state: "complete", a coding-agent route on a stopping state, a review_publishable that denies publication, or merge authority outside complete.

Three separations are structural, not documentary. execution says whether the tool ran; decision says what the gate decided; permissions says what the agent may do. A failed run can never authorize completion, but a succeeded one carries no implication at all.

Both entry points apply one currency test. verify --format control reads the pointer it just published through the generation-safe protocol, validated against the live workspace, and withholds authority when the workspace has moved past what the run evaluated; the route comes from the verifier bytes captured inside that read, so a pointer is never reported beside another generation's decision. artifacts[].path is relative to the directory the command was invoked from. exit_code reports the CI gate signal, which is mode-dependent — in advisory mode a blocked decision still exits 0 — so permissions.merge is the only field that answers "may I merge". The full verifier.json remains the authoritative substrate and is unchanged; verify --json still emits it.

Runtime contract v20 adds agents-shipgate-reports/current-control.json, the one atomic entry point that says which control identity is current. It is a pointer, not a second decision: it binds identities and hashes the receipt, handoff, verifier, and report already published. Every run replaces it with a non-terminal unavailable marker before touching any other artifact, and publishes the terminal pointer atomically, last. Consumers must re-read it at every boundary in agent_refresh_triggers — after any human or external-tool action, after any worktree change, after any command returns, before enforcing a cached must_stop, before commit/push/PR update, before merge, and before declaring the task complete. A control state remembered from earlier in a conversation never outranks the pointer, in either direction: it can neither keep blocking after a newer complete run exists, nor authorize action after the workspace moved.

Runtime contract v21 separates publish authority from merge authority. control.permissions is a required object on every state with the exact booleans edit, commit, push, update_pr, merge, report_complete, fixed by the state and never set independently, and the new control.state: "review_publishable" means "a human must approve the merge, and the agent may still commit, push, and update the pull request to obtain that review". human_review_required keeps its exact old meaning — nothing is authorized — and is now reserved for results Shipgate cannot vouch for: a block decision, a failed run, unreadable or unbindable diff input, an undeclared surface with no discovery route, and preflight protected-surface touches. merge and report_complete always equal completion_allowed, so human review never becomes self-approvable.

Runtime contract v19 grades the LOCAL boundary stop: a require_review violation set that is entirely low/medium risk projects control.state: "agent_action_required" with the exact verify command, and the review obligation is carried in the additive pending_review[] field on the agent-boundary result instead of ending the turn. Block actions, critical risk, gate-weakening rules (CODEX-AGENTS-SHIPGATE-REQUIREMENT-REMOVED), unparseable content, incomplete input, experimental surfaces, and every gate-governing trust-root class (manifest, policy, ci_gate, shipgate_state) keep the human route; as of v20 an evaluated require_review route is review_publishable and a block route is human_review_required. PR-time release_decision semantics are unchanged. It retains the v18 human-authorization overlay, the v17 content-addressed verification identity, v16 typed policy-evidence, v15 host-neutral boundary, v14 unambiguous AgentControl, and v13 root-reachable binding contracts. v18 added a signed, externally rooted human-authorization overlay for one exact post-review coding-agent action. Agents switch on control.state; decision remains diagnostic and release_decision.decision remains the release gate. Contract v14 requires completion_allowed == (state == "complete") and must_stop == (state == "human_review_required"). The report, packet, verifier, verify-run, and handoff projections all bind to the same request and decision IDs; their current versions are listed once under Current schema versions above rather than restated here, where the list went stale. The terminal receipt hashes the complete artifact set; see Verification Identity and Reproduction. The runtime contract 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, verify_run_schema_version, verification_plan_schema_version, verification_unit_result_schema_version, verification_artifact_manifest_schema_version, verification_receipt_schema_version, human_authorization_request_schema_version, human_authorization_schema_version, human_authorization_evaluation_schema_version, human_authorization_trust_policy_schema_version, human_authorization_trust_policy_default_path, human_authorization_schema_path, agent_handoff_schema_version, agent_handoff_schema_path, agent_handoff_artifact, agent_boundary_result_schema_version, the deprecated codex_boundary_result_schema_version, attestation_schema_version, registry_schema_version, org_evidence_bundle_schema_version, host_grants_inventory_schema_version, host_grants_baseline_schema_version, host_grants_drift_schema_version, trigger_catalog_schema_version, agent_interface_operations[], exit_code_policy, mcp_tools[], minimum_control_contract_version, agent_control_fields[], and agent_control_states[]. The legacy agent_result_* fields are retained only for older protocol readers. primary_commands{} is the prominent entry surface and contains only shipgate check, agents-shipgate verify, and shipgate audit --host flows; commands{} is compatibility/supporting metadata and retains local verify commands for older consumers. The short shipgate verify alias remains invokable for compatibility, but it is not the promoted PR-gate spelling in primary_commands{}. Contract v11 adds action_effect and action_authority to do_not_auto_assert[]. They are reviewed human claims that can close semantic evidence gaps; an agent may route the structured next action but must never invent or auto-fill either declaration. Downstream repos generated with init --agent-instructions=default get the minimal local copy at .shipgate/agent-contract.json.

Two read entry points

Both start at agents-shipgate-reports/current-control.json (agents-shipgate agent control --workspace .), which names the run that is current. Everything below it describes a run; only the pointer says which run. A non-zero exit from the reader means no control identity is current here and the caller holds no authority — not that the previous answer still stands.

Without --reports-dir, the reader looks where a default verify --workspace publishes: agents-shipgate-reports under --workspace, so agent control --workspace <repo> finds that run from any directory. An explicit --reports-dir is read as given — a relative path resolves against the current directory — and no other directory is searched in its place. An explicit verify --out follows the same rule, as do scan --out and audit --host --out, so verify --out <dir> and agent control --reports-dir <dir> typed in one shell name one directory. Where a relative --out names a different directory than the 1.0.0 rule did (the Git root for verify, the manifest's directory for scan), a note: line on stderr names both. A refusal names the absolute directory it searched, and its recovery command writes back to it, naming it as an absolute --out even where the producing run's recorded command carried a relative one; a verify command a run emits names --out absolutely whenever it carries one.

The reports directory is left out of the change set the reader checks, just as verify leaves its output directory out of the change set it decides on, so it must hold nothing else Git would report. A directory inside the repository that holds a committed path (at HEAD, or at the merge base a worktree pointer records and since removed), a staged or untracked unignored path that is not a Shipgate artifact, or any trust-root path whatever its name, or that lies inside a trust root Git does not ignore, is refused as workspace_unverifiable whatever the pointer's state, and verify refuses to write into one. The recovery for that refusal is the producing run's own verification command with only --out removed, publishing into the default directory with the same --config, base and policy options, or a review step when the default directory is the one refused (recognized by physical identity). A gitignored directory, one outside the repository, and one holding only uncommitted Shipgate artifacts outside any trust root are read as before.

The promoted read for a coding-agent control loop is one command. agents-shipgate agent control --workspace . runs the currency protocol and returns the shipgate.agent_control/v1 envelope, which already carries the route the pointer deliberately omits. An agent that routes on permissions and next_action from that one object never needs the artifact walk below; the walk remains the contract for consumers that want the forensic detail, for CI, and for anything reading the artifacts directly. A run can skip the second command entirely with agents-shipgate verify --format control, which emits the same envelope for the run it just performed.

The compact reader validates every entry in current-control.json.artifacts. It does not follow the terminal receipt into optional artifacts absent from that map. For example, a successful read does not validate human-review-request.json merely because the bound receipt references it. Changed, missing, oversized, or symlinked receipt-only files leave the compact read unchanged; those same conditions on pointer-bound files make it refuse.

For API consumers, read_current_control(..., capture=...) returns selected bytes from the same pass that validated every pointer entry. capture neither reduces validation nor adds optional files: a requested unbound key is absent from the returned artifacts. Consume these captured bytes instead of reopening their paths. A consumer that needs a receipt-only artifact must also call load_validated_receipt_artifacts, compare its returned receipt with the pointer-captured verification_receipt, and consume the returned closure bytes. The full loader rejects invalid optional files. It proves a coherent artifact snapshot, not current workspace state or permission to act; retain the currency checks below and revalidate before consequential actions. See the consumer boundary and resource bounds for the exact distinction. Standalone renderers such as agent handoff do not implicitly perform this current-control protocol.

Byte consistency is not generation consistency. A pointer whose artifacts all still hash correctly can describe a workspace that one commit has moved past, so the reader compares the bound workspace_identity against the live repository — repository, HEAD commit, and HEAD tree — and refuses on any drift. Completion authority is never returned without that comparison: a reader that cannot resolve the workspace reports it as unverified rather than passing. Nor is any other state of a pointer that binds a Git identity (a HEAD, a base, a merge base, an overlay or a snapshot kind): when agent control, verify --format control or the human-review decision reader cannot observe the workspace at all — .git is missing or unreadable, or Git refuses the checkout — that pointer is refused with exit 4, workspace_unverifiable (workspace_unverified for a complete pointer), whatever its state. A pointer that binds no Git identity, such as verify --preview run outside a Git checkout, has nothing to compare and still reads.

Every refusal caused by a workspace that could not be read leads with why, in one bounded, redacted sentence that names the Git configuration key or path and never a configuration value or file content, and agent control's next_actions[0] follows that cause:

  • Git configuration the worktree readers refuse: an executable filter.<driver>.clean/smudge/process in the repository's own Git config, a filter= attribute on a tracked path (any repository with Git LFS-tracked files has one), repository-local diff.* configuration (git-crypt sets diff.git-crypt.textconv), or a non-empty .git/info/attributes. The action is kind: "review" for a human. Re-running verify, with or without --head, does not change the answer: the refresh reads the working tree under that configuration for every pointer, a committed-tree one included, so no verify or agent control answer in such a repository reads as current while the configuration is in place. Shipgate never advises removing it. agents-shipgate diff still reports the host-grant changes, read-only and without authority.
  • A reports directory that holds repository content: the recovery above, away from that directory.
  • An uncommitted change beyond a static read bound, or a Git timeout: the producing verify command, after committing or shrinking the change.
  • Anything else, including a workspace that is not a Git checkout: the producing verify command, as before.

Global and system Git configuration are not read, so an LFS filter configured only globally (git lfs install) does not trigger the first cause; a tracked file carrying the filter=lfs attribute does.

When the decision named a base, that base is compared too. A decision about base...HEAD is a decision about that range, and advancing the base — a merge, or a fetch moving origin/main — can empty the range without touching HEAD or the working tree, leaving every HEAD-based check satisfied while the evidence underneath has gone. The pointer therefore carries base_ref, base_commit_sha, and merge_base_sha, and the reader resolves the ref live.

Uncommitted work is checked according to what the decision actually covered:

  • A worktree decision (snapshot_kind: "worktree_overlay") is re-checked two ways. Every path it covered must still hash to the overlay it committed to, and no path outside that set may differ from HEAD now — anything outside it was identical to HEAD when the decision was made, so a live change the plan never recorded is evidence the decision never saw. That second test is a subset test, not equality: plan.inputs.changed_paths is the union of base...HEAD and the worktree, not the uncommitted set, so requiring equality would refuse a clean workspace the moment the run that produced it finished.
  • A committed-tree decision (snapshot_kind: "committed_tree") stops at HEAD, so any uncommitted change appearing afterwards invalidates it — in both directions. A stale complete must not authorize work the decision never covered, and a stale human_review_required must not keep enforcing a pre-change stop. Re-running the same archived --head verification cannot clear that, so the refusal routes to a worktree verification instead.
  • A preview (operation: "preview") decided nothing about the inputs a plan names, so its pointer binds no verification_plan — also in a repository with a manifest, where the preview still records the plan a verify would run and verify-run.json embeds it. Its path set is therefore not recorded: the overlay of every path that differs from HEAD is recomputed live and compared with the one it was read from, so any tracked edit, new untracked file or removal refuses it as workspace_changed, and restoring the tree it read makes it current again. A configured and a manifest-free preview are read the same way, whether their reports are in the default directory, another directory inside the repository or beside it (#807). Inside a repository such a pointer always declares the worktree snapshot, even when its overlay could not be read, so under Git configuration the worktree readers refuse it is refused with the cause like every other pointer; one published over an overlay it could not read is current, once the tree can be read, only over a tree identical to HEAD. A pointer that does bind a plan is read against that plan's recorded inputs, and one whose plan lacks its input-directory census is refused as workspace_unverifiable, as before; see the census.

An overlay row carries content and the two metadata axes Git itself tracks: entry kind and the executable bit. Content alone is not the capability — flipping a tool script from 100755 to 100644 changes no bytes, and swapping a regular file for a symlink to an identical in-repo file changes no bytes either. Full mode is deliberately not recorded: it varies with umask and would make the identity depend on noise Git does not track.

Given a current pointer, there are two correct "read first" paths; which one applies depends on who is reading. They are not two decisions — they are two entry points into the same one decision engine.

  • PR / controller flow — an autonomous coding agent deciding continue, repair, or stop. Prefer validate agents-shipgate-reports/verification-receipt.json, then read agents-shipgate-reports/agent-handoff.json for the compact shipgate.agent_handoff/v9 view: lead with control.state, then read control.next_action, gate.merge_verdict, and reproducibility.run_id for the content-addressed verify identity. verifier.json remains the authoritative controller substrate and verify-run.json remains the detailed run projection; finally confirm report.json.release_decision.decision for the release gate. .well-known/agents-shipgate.json → agent_read_order is the machine-readable cross-artifact order. verifier_read_order remains the intra-verifier.json field order.
  • Gate / CI flow — deciding pass/fail, or any raw report.json consumer. Read agents-shipgate-reports/report.json → release_decision.decision (the next section). .well-known → gating_signal names this signal.

merge_verdict is a deterministic projection of release_decision.decision, so the two can never disagree.

Command-scoped artifact lifecycle

Choose the read path from the command that just completed, not merely from filenames already present in the output directory:

  • After standalone scan, report.json.release_decision.decision is authoritative. scan writes report, advisory scaffold, and configured packet formats; it does not produce a verifier handoff or terminal receipt.
  • After verify, validate verification-receipt.json, then read agent-handoff.json and the supporting verifier artifacts in the order above. The receipt and handoff retain the content-addressed identity of that exact verify run.

current-control.json records which of those two just happened in its operation field, so the choice does not have to be inferred from filenames at all. Only an operation: "verify" pointer can carry control.state: "complete", and only when it also binds a verification_receipt whose request_id and decision_id are the ones the pointer records — the assembler accepts any --out name under its artifacts root, so an older canonical receipt must not be mistaken for the one a run just closed. A scan or preview pointer is structurally incapable of authorizing completion or merge, and each binds only the artifacts it actually wrote: a scan --format markdown after a verify does not claim that verifier's report.json. A preview binds its verifier route and never the verification-plan.json a manifest lets it record, because it read none of that plan's inputs. While a run is in flight the pointer reads lifecycle_state: "in_progress" with control.state: "unavailable", must_stop: true, so an interrupted or crashed run leaves a directory that denies cached control rather than one that still authorizes it. Consumers built before the pointer existed fall back through current_control_fallback_read_order; the pointer's absence is evidence of an older producer, never permission.

When standalone scan replaces a report set in the same output directory, it removes the complete prior verifier route and its identity support: verifier.json, agent-handoff.json, pr-comment.md, verify-run.json, verification-plan.json, verification-input.diff, verification-base-report.json, verification-unit-result.json, verification-artifacts.json, verification-receipt.json, and human-authorization.json. Their absence is intentional: an older controller substrate, route, or receipt must never appear to authorize or describe the newer scan. verify calls the same scan pipeline internally and writes a fresh, mutually consistent verifier artifact set afterward. Supporting commands that only need an in-memory report, including baseline save, scan into an isolated temporary directory so they preserve the current report and verifier evidence.

Reader strings are in the reader's language

headline, summary, control.reason and every why are read by a person. They carry no field paths and no internal nouns — tests/test_reader_vocabulary.py runs every shipped fixture and fails on control.state, input_set_id, binding graph, Route H, exclusion ledger, or an enum identifier written into a sentence.

The enum values are untouched: release_decision.decision is still insufficient_evidence, because a machine consumer gates on it. What the guard forbids is that identifier appearing in prose written for a person. "The agent's tool binding graph is incomplete" became "some tools cannot be traced to an agent that can call them" — the thing to fix, rather than the model it is fixed in.

Root --help lists the six commands that carry a reader from a fresh checkout to an answer: diff, check, verify, audit, init, doctor. --help-all lists every command. Prominence is a reading aid, never a claim about what exists.

Primary vs supporting surfaces

Primary gates are intentionally narrow. CI gates on report.json.release_decision.decision. Coding agents handling committed PRs read agent-handoff.json.control.state first, with verifier.json.control, execution, applicability, and merge_verdict as the authoritative detailed substrate. Everything else in the verifier/report/packet family is supporting review evidence or a convenience projection.

Treat legacy agent_result_v1 / agent-result.json compatibility surfaces, runtime trace/evidence fields, the Release Evidence Packet, reviewer_summary, verifier_summary, capability_review, non-gating capability diff projections, and agents-shipgate skill ... review output as supporting/provisional surfaces. They may be useful for routing and review, but they do not replace the gate above and must not introduce a second verdict.

agents-shipgate preflight --workspace . --plan - --json remains a supporting proactive routing surface for coding agents before edits. It accepts a single PreflightPlanV1 object with changed_files[], optional diff_text, capability_requests[], host_permission_requests[], and context.{agent,task}. The emitted PreflightResultV6 reports protected surfaces, forbidden shortcut actions, required evidence for proposed high-risk capabilities, host-grant drift when a host baseline is present, deterministic signals[], control, requires_verify, verification_command, allowed_next_commands[], and plan_summary. A concrete, resolvable diff that only appends valid built-in tool_sources rows may mark that manifest touch requires_human_review=false and route the coding agent to verification. This authorizes proposal authorship only: existing rows and all other manifest values must be unchanged, authority-bearing fields and custom adapters are excluded, and the resulting trust-root diff still requires human review. It is not a second gate; it must never be read as passed or mergeable. Its control.state is planning_only, agent_action_required or human_review_required, never complete, and every control.permissions value is false on all three (#610). The release gate remains release_decision.decision.

Read these first for release gating

In agents-shipgate-reports/report.json:

  • release_decision.decision — "blocked" / "review_required" / "insufficient_evidence" / "passed". Baseline-aware. This is the gating signal. Precedence is blocked → review_required (active high/critical named concern) → insufficient_evidence → review_required (known review concern) → passed. Starting in v0.29, passed means every in-scope action has complete, conflict-free static surface, effect, and authority evidence, all applicable controls were evaluated, and no policy condition requires review. It does not prove runtime behavior or enforcement. Any required semantic dimension that is unknown, inferred-only, protocol-defaulted, partial, conflicting, invalid, or incomplete prevents passed, even when every other action is healthy. Existing extraction thresholds remain: low-confidence tools at least max(1, ceil(tool_count × 0.5)) or more than three source-loader warnings also degrade evidence. insufficient_evidence means the scan cannot confidently gate release from the available static evidence; it does not prove the agent is unsafe. Switch on the enum with a review_required fallback for unknown future values.

  • release_decision.blockers[] — items that block release on this run.

  • release_decision.review_items[] — items the human reviewer should look at; includes baseline-matched accepted debt.

  • release_decision.{static_analysis_only,runtime_behavior_verified,static_verdict_disclaimer} (v0.29+) — the machine-readable verdict boundary. Emitted values are true, false, and the canonical static-verdict disclaimer respectively. Packet §1 mirrors them exactly. Preserve these fields in agent summaries; passed must never be rewritten as runtime verification or safety proof.

  • release_decision.{blockers,review_items}[].capability_refs (v0.24+) — stable capability IDs copied from the originating finding when a policy or policy-pack rule matched a CapabilityFactV1. Empty for findings that are not capability-policy matches. This is audit metadata only; release_decision.decision remains the gate.

  • 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.semantic_coverage (v0.29+) — {total_actions, pass_eligible_actions, gap_count, review_concern_count, reason_counts}. A non-zero semantic gap_count prevents passed; a non-zero review_concern_count prevents an automatic pass and routes known review concerns to human review — unscoped/ambient authority, and (v0.36+) acknowledged_effect_override, a declared effect a reviewer acknowledged as weaker than the evidence inferred for it. Read reason_counts for which; the count is of concerns, so one action can contribute more than one. Semantic gaps are not Findings and cannot be suppressed, baselined, severity-overridden, waived by --no-heuristics, or satisfied by human_ack.

  • release_decision.evidence_coverage.semantic_coverage.declaration_questions (v0.37+) — the same action surface counted as a questionnaire: {total, answered, open, open_by_dimension, open_questions[]}. A question is one (action, dimension) a reviewed action_surface.actions row has to answer, and only effect and authority are counted — an action whose effect the scan established by itself (an OpenAPI method, an MCP annotation) was never asked and is not in total, and an inventory or agent_bindings declaration has no per-action counterfactual to score against. answered is exact rather than optimistic: it counts dimensions that gap when the same action is re-resolved without its declaration. total == answered + open, and open_by_dimension sums to open. open_questions[] is the answer order and joins to evidence_gaps[].subject_id. v0.38 ranks it by the ceiling of what an answer can establish: the actions nothing has bounded first — no effect evidence, a protocol default standing in for its absence, or only a heuristic reading the scan may not act on — then the actions a reviewed declaration or policy-eligible source evidence established, strongest-acting first, with effect before authority within one action. Position is not severity: the action at the top is the one least is known about. Nothing here gates; it is a projection of counts the decision already made, published so a coding agent (and the generated questionnaire) can report progress instead of a gap tally.

  • release_decision.evidence_coverage.policy_gap_count and top-level policy_evidence_gaps[] (v0.33+) — policy applicability that is heuristic-only, mixed, unknown, or conflicting. These rows are outside Findings and cannot be suppressed, baselined, severity-overridden, acknowledged, or removed by --no-heuristics; any row prevents passed.

  • release_decision.evidence_coverage.identity_coverage (v0.30+) — {total_observations, canonical_tools, bound_tools, pass_eligible_tools, ambiguous_name_count, gap_count, reason_counts}. Provider-scoped observations remain separate unless an exact reviewed tool_identity.bindings[] entry joins them. Any ambiguous selector, invalid binding, or conflicting identity prevents passed.

  • release_decision.evidence_coverage.evidence_gaps[] (v0.26+; semantic kinds added v0.29) — one structured row per measurable gap: {kind, subject, source_type, source_ref, policy_id, why, next_action}. policy_id is optional and present on policy-applicability rows; it retains exact machine identity even when an engine-owned id is intentionally omitted from adopter prose. In addition to low_confidence_tool and source_warning, semantic rows include incomplete_surface for enumeration failure and unattested_surface when lower-confidence extraction lacks reviewed inventory attestation without an enumeration defect. Only an exact adapter surface: enumerated fact earns positive enumeration wording. Other semantic kinds include missing_effect_evidence, inferred_effect_only, conflicting_effect_evidence, missing_authority_evidence, partial_authority_evidence, conflicting_authority_evidence, and invalid_semantic_annotation.

    v0.36 adds declaration_below_inferred_evidence: the declared effect is weaker than evidence this scan inferred for the same action. The declaration still stands as the operative effect — heuristics never drive a verdict — but the action is not evidence-backed-pass until a reviewer raises the declared effect or adds action_surface.actions[].override with the evidence they checked and the reason it does not apply. An acknowledged override keeps the action pass-eligible and is reported as one semantic review concern, so the run can never read passed. Each acknowledgement is also emitted in release_decision.evidence_coverage.semantic_coverage.acknowledged_overrides[] naming the action, both readings, the hint source, any source evidence that agrees, and the reviewer's evidence and reason. The packet's §1 and the PR comment render it, and policy applicability consumes it, so applying an override reaches review rather than trading one gap for another.

    Semantic next actions use declare_action_effect, declare_action_authority, declare_tool_inventory, provide_complete_inventory, or resolve_semantic_conflict, include accepted values and exact source/manifest pointers, and are human-routed. v0.37 adds next_action.observed_readings[] on effect rows — {effect, sources[], observed} — so the row can be answered without opening action_surface_facts. Where those readings support one conservative answer, next_action.declaration_template carries it pre-filled instead of a <REVIEW_REQUIRED> blank. The value is a proposal, never an assertion: it comes from the closed ActionEffect vocabulary, is never weaker than any reading, and is offered only where something was observed. A protocol default standing in for absent evidence, or a heuristic read, keeps the blank. The placeholders carry auto_apply=false and requires_human_review=true; only a reviewed manifest edit makes one operative.

    v0.41 adds next_action.authorable_by (coding_agent | human, default human) — who may write the first draft. It is coding_agent only where the scan filled every blank in declaration_template and the gap is not one that asks a person to look again. declaration_drift therefore stays human-owned. An agent-authorable row carries suggested_patch_kind: "declare_action" and a next_action.patch exactly matching the template, split into the action selector and fields written; the schema rejects any other pairing. target_path is relative to manifest_dir. auto_apply stays false and requires_human_review stays true for every row, and the patch remains outside apply-patches --kinds by default; only the confirm_declarations route proposes it.

    v0.37 also re-routes partial_authority_evidence: it is raised when source authority evidence is ambiguous or incomplete, and persists whatever the manifest declares. Its action is provide_source with no declaration template, not a declaration that could not close the row, and it is excluded from declaration_questions for the same reason. Work the rows in order instead of guessing; Agents Shipgate never auto-asserts effect or authority.

  • 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[].support (v0.33+) — typed predicate support with status, effective confidence, policy/block eligibility, claim IDs, evidence bases, predicate rows, and support_hash. Rule confidence and block: true are ceilings/requests; they cannot upgrade the support. Baseline matching for supported findings requires the same support hash.

  • 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.

  • release_decision.fail_policy.would_fail_ci — true/false. Matches what the CI process will exit with. For a semantic evidence gap, strict mode emits the consistent tuple decision="insufficient_evidence", would_fail_ci=true, exit_code=20; advisory mode keeps exit 0 while preserving the same non-pass decision.

  • 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). In v0.33, unsupported_evidence records a finding that cannot contribute because its typed support is not policy-eligible. Reading the contribution rule is sufficient to predict the gate outcome without re-deriving the decision logic.

  • privacy_audit (v0.18+) — confirms the default redaction pass ran before public artifacts were written. Read enabled, rules_version, sensitive_field_inventory_version, redacted_occurrence_count, redacted_paths[], and output_surfaces[]. redacted_paths[] contains structural paths and counts only, never raw values or raw hashes.

  • reviewer_summary (v0.20+) — deterministic projection of the reviewer lens surfaces and audit envelopes; the reviewer-side parallel to agent_summary. Read this block first when triaging a scan for a human reviewer. Carries verdict (mirrors release_decision.decision), headline (≤200 chars, PR-comment-friendly), per-lens activity counts (tool_surface_changes, capability_misalignments, action_surface_changes, evidence_matrix_gaps), per-audit-envelope counts (severity_overrides_applied, severity_overrides_tier_crossed, privacy_redactions, baseline_integrity_issues), and first_recommended_surface: ReviewerSurfacePointer | None — a deterministic pointer naming which lens/audit to open first ({kind, name, path, why} where kind ∈ {release_decision, lens, audit, evidence_matrix} and name ∈ {tool_surface_diff, capability_intent_diff, action_surface_diff, evidence_matrix, policy_audit, privacy_audit, baseline_integrity, release_decision}). Same inputs always produce the same output; this block cannot disagree with the underlying lens/audit data.

  • heuristics_filter (v0.21+) — top-level audit envelope describing the --no-heuristics CLI filter pass. Always present, even when the flag is unset (enabled: False with zero counts), so the report shape is stable. Carries enabled: bool, excluded_provenance_kinds: list[str] (["keyword_heuristic", "regex_heuristic"]), filtered_finding_count: int, and filtered_by_kind: dict[str, int] (per-kind breakdown). When enabled: True, findings whose provenance_kind is in the excluded list have been marked suppressed=True with suppression_reason="filtered by --no-heuristics" BEFORE the release decision was built — they remain in findings[] for transparency but no longer gate release. The filter never un-suppresses a finding; manifest-driven suppression reasons are preserved when they overlap with the filter. Useful for security/GRC reviewers who want declared-only findings.

  • verifier_summary (v0.22+) — top-level composition for one-fetch controller consumption (the AI-coding-workflow verifier surface). It derives no independent verdict: verdict mirrors release_decision.decision exactly (Principle: one decision engine). Carries by_severity: dict[str,int] and by_reason_code: dict[str,int] (active-finding histograms — the complete per-code map), capability_delta_summary: {added, removed, broadened, narrowed} (equal by construction to the capability_change member-list lengths), protected_surface_touched: bool, policy_weakened: bool, human_ack_required: bool, human_ack_satisfied: bool, and top_reason_codes: list[{reason_code, count}] — the ranked top-five highlight (severity desc → count desc → code asc; the full set stays in by_reason_code). This block cannot introduce a finding-independent blocker.

In findings[], v0.24 adds capability-native policy evidence for built-in policy checks and policy packs:

  • capability_refs: list[str] — stable CapabilityFactV1.id values that matched the rule. It is emitted as an empty list for findings that are not capability-policy matches.
  • capability_policy_evidence | null — optional typed audit metadata with the 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.
  • 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:

  • capability_trace_refs: list[str] — stable IDs from the top-level capability_runtime_evidence block. It is emitted as an empty list for findings that are not linked to a local trace row.
  • provenance_kind: "runtime_trace" — used only for findings derived from declared local trace artifacts. It is not filtered by --no-heuristics.

The top-level capability_runtime_evidence block is a deterministic audit projection over local trace artifacts declared in openai_api.trace_samples, google_adk.trace_samples, validation.evidence.approval_traces, and validation.evidence.agent_traces. It carries summary counts, matched and unmatched CapabilityTraceEvidenceV1 rows, source provenance, and notes. Trace normalization keeps only allowlisted scalar fields and discards prompts, messages, tool arguments, tool outputs, and arbitrary payload bodies. The block is empty when no trace inputs are declared. It is not part of capability locks, fingerprints, baselines, run IDs, or release gating.

The remaining v0.22 verifier blocks are reviewer-facing projections / declared inputs — none gates independently (release_decision.decision stays the only gate). They populate with real values only under verify mode (a VerificationContext from agents-shipgate verify or an equivalent scan context); a plain scan emits their stable empty shape:

  • capability_change (v0.22+, semantic metadata v0.23+) — the diff-derived capability delta, grouped into {enabled, added, removed, broadened, narrowed} member lists over action_surface_diff / tool_surface_diff. Each CapabilityChangeMember carries {id, direction, subject_kind, tool, action, scope, before_scope, after_scope, before_capability_id, after_capability_id, changed_hashes, semantic_direction, semantic_changes, risk_tags, release_impact, provenance_kind, confidence, rationale, related_finding_ids}. broadened = more effective capability (wider scope, escalated effect, removed control); narrowed = less (removed scope, added control). semantic_direction explains the proven capability-level movement (added | removed | broadened | narrowed | mixed | unknown | evidence_only), and semantic_changes[] gives field-level reasons when a base action snapshot is available. enabled: false when no base diff is available. A member with an empty tool and subject_kind: "scope" whose scope reads "<agent> -> MCP binding <slot> <axis>" is an agent's remote binding — the endpoint, credential reference, transport or tool filter of a remote MCP server it mounts (#538). tool is empty because no remote leaf was enumerated and none is invented; before_scope / after_scope carry the two sides. Only the tool-filter axis claims a direction; for the others broadened is the block's documented opaque-direction bucket and the rationale says so, because a host name or an environment variable name establishes no privilege level.
  • protected_surface_changes (v0.22+) — list of touched release trust roots, each {path, kind, glob, related_finding_ids}. Derived from the active SHIP-VERIFY-* findings, so every row's related_finding_ids resolves to a real findings[] entry and the rollup can never disagree with the gate. A row means "a protected file was touched"; purely-semantic weakenings with no file path stay in findings[] and surface via verifier_summary flags.
  • effective_policy (v0.22+) — normalized (not text-diff) snapshot of the release-policy surface for base-vs-head weakening comparison: {ci_mode, fail_on[], suppressed_check_ids[], waiver_scopes[], severity_overrides{}, baseline_integrity_mode, baseline_fingerprints[], ci_gate_present}. Every list/dict is sorted for byte-stable output; derived purely from the manifest (plus accepted-debt fingerprints). It describes the policy the repository declares, not the policy this invocation runs under: --ci-mode / --fail-on move ci_mode / fail_on at the top level of the report but never here, so two runs of the same tree produce the same snapshot and a base-vs-head comparison stays repository-vs-repository.
  • human_ack (v0.22+) — declared human-acknowledgement state, {required, satisfied, acks[], outstanding[]}. Within the static boundary, acknowledgement is declared evidence only — never inferred (human authority cannot be synthesized). A trust-root weakening (SHIP-VERIFY-POLICY-WEAKENED, -POLICY-BASE-ABSENT, -CI-GATE-REMOVED, -BASELINE-OR-WAIVER-EXPANDED) makes a surface required; it is satisfied only by a matching human_ack entry in shipgate.yaml (owner + reason + affected surface, optional expiry). required == (acks-covering-required) + outstanding. The acknowledgement section lives in shipgate.yaml — itself a trust root — so a coding agent cannot add its own ack without tripping SHIP-VERIFY-TRUST-ROOT-TOUCHED.

New SHIP-VERIFY-* reason codes (v0.22+, category verify — suppression-immune and floor-protected; emit only under verify mode): SHIP-VERIFY-POLICY-WEAKENED (base-vs-head policy weakened), SHIP-VERIFY-POLICY-BASE-ABSENT (0.16+; a policy trust root changed with no base snapshot to compare against — split out of -POLICY-WEAKENED so a first adoption no longer reports a weakening that could not have happened; evidence kind is manifest_introduced or base_snapshot_unavailable, and only the former reports policy_weakened: false), SHIP-VERIFY-BASELINE-OR-WAIVER-EXPANDED (suppression/waiver/baseline broadened), SHIP-VERIFY-CI-GATE-REMOVED (Shipgate CI workflow deleted), SHIP-VERIFY-AGENT-INSTRUCTIONS-WEAKENED (deprecated compatibility ID; no findings; supported structural changes remain covered by the shared trust-root comparison), SHIP-VERIFY-TRIGGER-CATALOG-DRIFT (trigger catalog changed). They are ordinary Findings routed through release_decision — never a second verdict.

The action exposes these as outputs decision, blocker_count, review_item_count, ci_would_fail (v0.8+). For verifier-cycle PR workflows it also exposes additive outputs should_run, trigger_action, trigger_rule_ids, verifier_verdict, verifier_json, verify_run_json, run_id, merge_verdict, can_merge_without_human, agent_control_state, agent_control_reason, agent_controller_must_stop, agent_controller_stop_reason, agent_controller_completion_allowed, trust_root_touched, policy_weakened, capability_changes_added, capability_changes_modified, and capability_changes_removed. These are review and routing aids only. trust_root_touched and policy_weakened mirror verifier_summary; the capability counts mirror capability_change (modified is broadened + narrowed). Keep using decision as the release-gating output and agent_control_state as the coding-agent operational output.

When the action is asked to emit organization-governance artifacts, it also exposes attestation_json, org_evidence_bundle_json, host_grants_json, and org_status_json as artifact paths. These are ingestion and audit surfaces for platform teams; they never create a second verdict.

For ongoing PR workflows, prefer:

agents-shipgate verify --workspace . --config shipgate.yaml \
  --base origin/main --head HEAD --ci-mode advisory --format json

verify writes verifier.json, verify-run.json, agent-handoff.json, and pr-comment.md alongside the head scan artifacts. agent-handoff.json is the compact coding-agent projection over the verifier, verify-run, and report artifacts; it does not gate independently. After a successful head scan it also writes the head static capability lock to agents-shipgate-reports/capabilities.lock.json. When --base is provided and the base scan can be materialized, verify writes agents-shipgate-reports/base.capabilities.lock.json, agents-shipgate-reports/capability-lock-diff.json and agents-shipgate-reports/capability-lock-diff.md. The packet artifact is intentionally packet.json only; use scan for manifest-driven packet Markdown/HTML/PDF rendering. Read verifier.json.base_status to understand whether base diff enrichment ran; do not use it as a release verdict. The release gate is still report.json.release_decision.decision. verify never fetches, so CI callers must make the base ref available before invocation. Supplying --head makes verify scan an isolated archive of that ref; omitting it scans the checked-out workspace. If an explicit --base ref or PR diff cannot be inspected, verify skips a head-only scan; verifier.json.merge_verdict is unknown and the command exits 2.

agents-shipgate verify --preview --json is a lightweight relevance check — no scan, no manifest required, exits 0 for every workspace it evaluates. A --workspace that does not exist is not a workspace it evaluates: it is refused as an invocation error (config_error, exit 2) before any directory is created, on preview and on every other command that takes the option. It emits a verifier.json with mode: "preview", execution: "not_run", applicability: "not_evaluated", and control.state: "agent_action_required". control.next_action carries the next recommended action: an exact init --workspace <workspace> --write --json command for unconfigured repos, or an exact verify command for configured repos using the supplied workspace/config/base/head/out arguments. verify --preview --format control prints that same state and command, and an agent control refresh of the pointer it published returns them until the working tree moves. Where the working tree cannot be read, both withhold them and lead with the cause instead (how a preview is read). Use it as the first touch before a full scan. To evaluate just the run/skip trigger, run agents-shipgate trigger --base origin/main --head HEAD --json.

agents-shipgate verify and verify --preview also write agents-shipgate-reports/verify-run.json whenever the output directory can be created. It carries schema_version: "shipgate.verify_run/v5", the exact verification plan, executor, unit-result IDs, decision ID, outcome projection, and artifact references. request_id is the content-addressed run identity; the deprecated run_id remains for one compatibility cycle as its exact alias, never as a separately derived identity. It has no wall-clock timestamp and is not a second gate.

agents-shipgate-reports/agent-handoff.json carries schema_version: "shipgate.agent_handoff/v9" and top-level sections gate, control, fix_task, blocked_by[], remediation_plan[], capability_review, authorization, reproducibility, and artifacts. gate.decision mirrors release_decision.decision; gate.merge_verdict mirrors verifier.json.merge_verdict; and gate.{static_analysis_only,runtime_behavior_verified,static_verdict_disclaimer} mirrors the report/verifier static-only boundary. The values are locked to true, false, and the canonical disclaimer. control is byte-identical to the verifier/verify-run control object, and can_merge_without_human is true only for a verified passed result or a completed deterministic not_applicable skip. authorization is the byte-equivalent verifier evaluation; the handoff cannot grant a command independently. Re-render it from existing artifacts with:

agents-shipgate agent handoff --from agents-shipgate-reports/verifier.json --json

In agents-shipgate-reports/verifier.json, read the fields below (full schema docs/verifier-schema.v0.21.json). Lead with control.state. Every release and merge field below is a mirror or deterministic projection of report.json; the authorization evaluation is an operational overlay and cannot change those fields. release_decision.decision remains the gate.

  • control — the discriminated complete | agent_action_required | review_publishable | human_review_required operational projection. Its variant fixes completion_allowed, must_stop, permissions, human_review, and the actor/action shape; generated schemas enforce the variants with oneOf. Only a new verifier artifact can clear a pending control obligation.

  • execution — "not_run" | "succeeded" | "skipped" | "failed".

  • diff_status — whether the compared change set was read at all. completeness is "complete" / "partial" / "unavailable"; reason is null only when complete, and otherwise not_attempted, refs_missing, merge_base_missing (shallow checkout — deepening restores the merge base), unrelated_histories (no common ancestor exists; no fetch can create one), objects_missing, metadata_limit_exceeded, body_limit_exceeded, git_timeout, or git_failed. remediation names the repair and fetch_repairable says whether fetching can perform it. Only "complete" licenses reading a negative trigger result; anything else means the diff was not read, which is never evidence that a PR is unrelated to agent capabilities. null means a pre-v0.7 artifact — unknown, not complete.

  • trigger — the run/skip evaluation. Read evaluation_status first; two of its three values withhold the verdict, and in both should_run / run_shipgate / skip / skip_reason are null. Never read null as false. The embedded next_action is diagnostic only: it preserves the evaluated kind but carries no command, sets authoritative: false, and points at control.next_action, which is the route to follow. Embedded matched_rules[] carry no commands either. The standalone agents-shipgate trigger --json command retains its own actionable next_action.

    • "not_evaluated" — the diff could not be read. Repair the input through control.next_action; skip_reason is never "no_match" for inputs that were not fully read.
    • "unclassified" — the diff was read in full and no rule classified some or all of the changed files. That is a fact about the catalog, not about the PR, so the skip is withheld and control.next_action routes forward to the scan. surface_exclusions.entries[] lists the unclassified files.

    stop_conditions_fired is the raw block result; stop_conditions_terminal says whether it decided. A matched run_shipgate/force_run rule overrides a fired stop, because a capability match in the diff is evidence the whole-workspace negative did not account for. "evaluated" on an incomplete diff_status is not a contradiction: only skip verdicts are withheld, so a should_run: true reached from evidence that did not depend on the missing bytes is authoritative and must not be overridden. Read matched_rules to see what carried it — a force_run match rests on the manifest, not on the diff.

  • merge_verdict — "mergeable" / "human_review_required" / "insufficient_evidence" / "blocked" / "unknown". Deterministic projection of release_decision.decision (passed→mergeable, review_required→human_review_required, insufficient_evidence→insufficient_evidence, blocked→blocked, missing decision→unknown). It cannot disagree with the gate; switch on the enum with an unknown/human_review_required fallback for future values.

  • static_analysis_only, runtime_behavior_verified, and static_verdict_disclaimer — locked to true, false, and the canonical non-runtime disclaimer. When a release decision is embedded, construction rejects any disagreement between these top-level values and the decision.

  • applicability — "not_evaluated" / "verified" / "not_applicable" / "failed". Disambiguates a mergeable verdict: "verified" means Shipgate evaluated the change and produced a release decision; "not_applicable" means the head scan was skipped (nothing to gate — do not read this as "verified safe"); "failed" means the scan could not complete. Orthogonal to merge_verdict; additive and locked to "verified" whenever a release_decision is present.

  • can_merge_without_human — bool.

  • decision — mirror of release_decision.decision (or null when no scan ran).

  • headline — single-sentence, PR-comment-friendly summary (or null). Every completed blocked run names the deterministically selected worst blocker as Most severe: <title>., ordered by severity, check id, then title. A run with no blocker adds no cause clause.

  • authorization — the shipgate.human_authorization_evaluation/v1 result. Only accepted can expose a command, and that command must exactly match both control.next_action.command and the sole entry in control.allowed_next_commands. rejected, not_requested, and not_applicable carry no command authority.

  • control.human_review and control.next_action are the serialized route for the current verifier state; when authorization is accepted, the signed evaluation is the provenance for the exact coding-agent next action.

  • AgentController, VerifierNextAction, and VerifierHumanReview remain importable only as deprecated v0.1/v0.2 reader models. Verifier v0.6 does not emit or invoke the retired build_agent_controller projector.

  • fix_task — {actor, safe_to_attempt, instructions[], allowed_repairs[], forbidden_repairs[], forbidden_shortcuts[], verification_command, patches[]} or null. This is the deterministic repair boundary: actor: coding_agent with safe_to_attempt: true means the agent may attempt only the listed mechanical allowed_repairs[] and rerun verification_command; actor: human means the agent must not invent action effect, action authority, approval, idempotency, policy, waiver, baseline, or trust-root evidence to make the gate pass. forbidden_repairs[] explicitly lists reward-hacking moves such as suppressing findings, lowering severity, expanding baselines/waivers, weakening CI or policy, adding human ack, or inventing action-effect/action-authority/approval/idempotency evidence. patches[] (v0.13+) carries {finding_id, check_id, patch} rows with the machine-applicable suggested patches for the gating findings — populated only when verify ran with --suggest-patches and the task routes to the coding agent; repair aids, never gate inputs.

  • trust_root_touched — bool; true when the PR changed a release-gate trust root (shipgate.yaml, the Shipgate CI workflow, AGENTS.md/CLAUDE.md, policy packs, prompts, baselines, waivers, etc.). Backed by the SHIP-VERIFY-TRUST-ROOT-TOUCHED check.

  • capability_review — reviewer-facing projection of capability_change with {trust_root_touched, policy_weakened, policy_weakening_proven, capability_changes_added, capability_changes_removed, capability_changes_modified, top_changes[]}. Gate on policy_weakened (the fail-closed flag, raised even when no base policy existed to compare against); say the policy was weakened only when policy_weakening_proven (0.16+) is also true — that one means a base-vs-head comparison actually ran. top_changes[] carries the highest-signal capability deltas with {id, change_type, change_bucket, subject_kind, subject, impact, rationale, source_path, source_start_line, related_finding_ids}. impact mirrors the gate (blocks_release, review_required, insufficient_evidence, or informational values) and never introduces a finding-independent blocker.

  • mode — "advisory" / "strict" / "skipped" / "preview".

Trusted human authorization for one exact command

Authorization changes operational routing, never the static release verdict. The flow is deliberately two-pass:

  1. Run agents-shipgate verify --no-plugins and validate the resulting terminal receipt. Authorization requires the plan's exact effective plugin mode to be false; the protected executor never loads third-party plugin or adapter entry points. agents-shipgate authorization request --receipt <receipt> --artifacts-root <root> --destination-ref <full-ref> --expected-lease-oid <oid> --out <request> constructs the unsigned shipgate.human_authorization_request/v1 from that receipt's current request, subject, decision, source receipt/artifact-set/engine/executor and tree identities, the complete ordered review set, and one typed Git-push operation. This command creates a challenge, not authority.
  2. The host authenticates the human and signs the canonical request with an Ed25519 key kept outside coding-agent reach. Agents Shipgate supplies no private key and no command that signs or approves a request. The v1 trust policy must be stored outside the evaluated workspace and protected from writes by the agent. On POSIX, Agents Shipgate reads it only from the OS account home's fixed path ~/.config/agents-shipgate/human-authorization-trust-policy.json; HOME and XDG_CONFIG_HOME do not redirect that lookup.
  3. Rerun agents-shipgate verify --no-plugins --authorization <external-grant>. The verifier recomputes the current identities and validates the signature, principal, repository scope, TTL, request, subject, trees, decision, full review set, and operation before publishing any command authority.

The only v1 operation is an exact force-with-lease Git push. It binds the exact evaluated commit, a canonical credential-free HTTPS destination whose repository identity equals the verified repository, a full destination refs/heads/... ref, and the expected remote OID. A synthetic PR merge receipt cannot authorize pushing a different parent commit. Authorization is eligible only when execution succeeded and the release decision is review_required. An accepted grant changes control.state from human_review_required to agent_action_required for that exact command, while all release facts remain unchanged: release_decision.decision="review_required", merge_verdict="human_review_required", can_merge_without_human=false, and completion_allowed=false. The coding agent may perform only the serialized guarded agents-shipgate authorization execute command. That consumer revalidates the current receipt, trust root, clock, repository, and commit and isolates Git configuration and hooks before issuing the internal typed push; the raw Git command is never operational authority. The agent must rerun verification afterward.

The signer must authenticate the source closure: content addressing is integrity, not provenance. It must rerun verification in a trusted worker or verify trusted-CI attestation over the bound source receipt/artifact-set IDs. The request exposes the evaluated base commit and merge base, and the source commit transitively binds its full parent graph. The signer must review that complete ancestry and reachable history rather than relying only on the final tree diff. Execution enforces a 512 MiB graph-pack ceiling and a 120-second process timeout; the host broker should impose tighter deployment quotas. The compressed pack ceiling does not bound expanded-object indexing memory or CPU, so production brokers need cgroup, container, or equivalent host resource limits. Execution also requires a host-protected broker with a sanitized environment, external trust policy, interpreter, entire virtual environment and site-packages tree (including startup .pth files), dependencies, credentials, and separately installed Agents Shipgate distribution. Same-UID file permissions alone are insufficient, and an editable install rooted in the authorized workspace is ineligible. If the host cannot enforce those boundaries, authorization remains disabled. The guarded executor is POSIX-only in v1 and authorization remains disabled on Windows. V1 is push-only and does not authorize the coding agent to apply reviewed protected patches.

Malformed, untrusted, expired, not-yet-valid, incomplete-review-set, wrong-tree, wrong-request, wrong-ref, or wrong-lease grants fail closed with zero allowed commands. Plain JSON in the repository, a PR comment, or conversation-level approval is not equivalent to a signed grant. This release defines the protocol and verifier consumer; it does not claim a current Codex, Claude Code, or other coding-host UI signing integration. Such a host adapter must be implemented separately. A grant replayed after the remote ref advances cannot overwrite that ref: Git enforces the signed command's explicit expected lease OID.

verifier.json also carries trigger, base_status, head_status, base_ref, head_ref, changed_files, base_notes, the embedded release_decision, and an artifacts map. In verifier.json each artifact path (and head_report_json, base_report_json) inside the repository is relative to workspace, the Git root, and one outside it is absolute. verify --format json prints the same object with those relative paths spelled for the invoking shell: relative to its current directory when beneath it, absolute otherwise, so they open as printed; from the Git root the two are identical. When present, artifacts.capability_lock_json, artifacts.base_capability_lock_json, artifacts.capability_lock_diff_json, and artifacts.capability_lock_diff_markdown are review artifacts only; they do not change the gate. The matching GitHub Action outputs are agent_control_state, agent_control_reason, merge_verdict, can_merge_without_human, and the compatibility mirrors agent_controller_must_stop, agent_controller_stop_reason, agent_controller_completion_allowed, trust_root_touched, and capability_changes_{added,modified,removed} (the original decision, blocker_count, review_item_count, ci_would_fail outputs are preserved). See STABILITY.md §Verify Orchestrator for the authoritative contract.

The default Action PR comment style for the verifier-cycle minor is capability-review: exactly two reviewer sections, a human summary and a fenced JSON agent instruction block. The human summary leads with merge_verdict, can_merge_without_human, capability delta, next actor, and artifact links, including the semantic capability-lock diff summary when a base lock is available. The agent block carries control and fix_task for coding-agent routing. Existing adopters that need the v1 findings-oriented comment during migration can set pr_comment_style: findings for one minor release cycle.

The GitHub Action emits source-backed GitHub Actions job annotations by default for active blockers and review items. check_annotations: "false" disables the projection; check_annotation_limit caps the number emitted. The helper also writes agents-shipgate-reports/check-annotations.json for audit/debug.

verify writes non-gating capability artifacts when static extraction succeeds: agents-shipgate-reports/capabilities.lock.json for head, and when a base ref is available, base.capabilities.lock.json plus capability-lock-diff.json. These artifacts are review/integration surfaces only and cannot introduce a second verdict.

Read this for local boundary control

shipgate check --agent <codex|claude-code|cursor> --workspace . --format agent-boundary-json is the local static multi-host boundary command. The --agent value is caller identity, never a coverage selector. The command emits exactly one stdout JSON object using schema_version: "shipgate.agent_boundary_result/v3" and the schema in agent-boundary-result-schema.v3.json. The old codex-boundary-json spelling remains a deprecated compatibility projection of the same assessment, kept through 1.x.

Read input_coverage, host_coverage[], affected_hosts[], policies[], issues[], pending_review[], and excluded_scopes[] before relying on the result. complete means complete only within the declared static input scope; it is not proof of session grants, runtime enforcement, or tool behavior. pending_review[] is non-empty only alongside agent_action_required: those are review obligations the graded mapping carried forward instead of stopping the turn, and an agent must name them when summarizing the change. The detailed matrix is host-boundary-support.md.

Coding agents switch on control.state, then follow control.next_action and control.allowed_next_commands. decision is diagnostic only. A pending verification obligation produces agent_action_required; it can never coexist with completion_allowed=true. An evaluated human route produces review_publishable, must_stop=false, a human next action, and permissions that authorize publishing but not merging. An unsafe or unbindable one produces human_review_required, must_stop=true, and permissions that authorize nothing. Do not derive control from Markdown, PR comments, natural language, or a conversation-level acknowledgement. Only a new verifier artifact can clear the obligation. 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.

Read these for release review

agents-shipgate contract --json exposes manual_review_signals[] as the installed CLI's stable list of report/packet fields to inspect for human review work. findings[].provenance_kind is included there as a filter/review signal only; it never changes the release decision, severity, fingerprints, baselines, or CI exit behavior.

The runtime contract also exposes stable non-gating integration fields: agent_handoff_schema_version, agent_handoff_schema_path, agent_handoff_artifact, agent_interface_operations[], exit_code_policy, mcp_tools[], capability_lock_schema_version, capability_lock_diff_schema_version, capability_payload_schema_version, capability_payload_schema_path, capability_delta_attestation_schema_version, capability_delta_attestation_schema_path, capability_delta_predicate_type, capability_delta_attestation_artifact, capability_standard_version, governance_benchmark_catalog_schema_version, governance_benchmark_result_schema_version, and external_integration_surfaces[]. These advertise capability lock/diff and benchmark artifacts for integrations and research. They do not change the gate: release_decision.decision remains the only release decision signal.

The capability/intent diff fields (v0.9+), used by reviewers to spot misalignment between declared agent intent and actual tool surface:

  • capability_facts[] — every capability surfaced from the tool inventory. In v0.29 each newly emitted fact carries semantic_assessment, the normalized effect/authority claims, issues, conservative effect, and pass-eligibility state consumed by the gate.
  • declared_intentions[] — what the manifest says the agent is supposed to do.
  • misalignments[] — where capabilities exceed (or fall short of) declared intent.
  • release_consequence — capability-aware roll-up of the release decision.
  • suggested_scenarios[] — dynamic-validation scenarios derived from misalignments and findings.

The Action Surface Diff fields (v0.16+), reviewer-facing PR/release delta:

  • action_surface_facts.actions[] — deterministic snapshot of the current agent action surface: action id, operation, effect, normalized risk tags, scopes, approval policy, safeguards, evidence, hashes, and (v0.29+) the same semantic_assessment projected onto the corresponding capability fact. semantic_assessment.conservative_effect, action.effect, and the capability effect must agree.
  • action_surface_diff.{enabled, base, summary, added, removed, modified, notes} — what changed vs. a base report or v0.4 baseline. Policy findings generated from this diff can set findings[].blocks_release=true and appear in release_decision.blockers.
  • findings[].blocks_release and release_decision.{blockers,review_items}[].blocks_release — explicit release-policy blockers from Action Surface Diff policies and policy-pack rules with block: true. Advisory CI may still exit 0; strict CI exits nonzero when an active unbaselined release blocker is present.

The tool-surface diff fields (v0.10+), lower-level explanatory data:

  • tool_surface_facts.{tools, scopes, controls, policies} — current static facts about the tool surface.
  • tool_surface_diff.{enabled, base, summary, tools, high_risk_effects, scopes, controls, metadata_changes, policy_drift, finding_deltas, notes} — what changed vs. a base ref. Disabled diffs render as enabled: false with a notes reason.

Source provenance fields on findings[].source (v0.11+), additive and optional:

  • path, start_line, end_line, start_column, pointer — manifest-relative file path, 1-based line/column, and RFC 6901 JSON pointer for the offending tool. Populated for OpenAPI, MCP, OpenAI tool artifacts, and Anthropic tool artifacts when the source is YAML. JSON inputs carry path and pointer but no line in v0.11.

Per-finding agent_action enum (v0.12+), deterministic projection — read this first when deciding what to do with a finding so you don't have to synthesize an action from patches/autofix_safe/requires_human_review/suggested_patch_kind:

  • auto_apply — apply-patches --confidence high will resolve cleanly. Every patch is non-manual and high-confidence.
  • propose_patch_for_review — at least one non-manual patch is attached and machine-applicable, but the full patch set is not auto-safe. Two shapes land here: (a) every non-manual patch is medium- or low-confidence, and (b) a high-confidence non-manual patch sits alongside one or more ManualPatch siblings (the non-manual is safe to apply, but the manual instructions still need a human). In both cases the agent should ask the user before --apply and surface any manual instructions verbatim.
  • escalate_to_human — no machine-applicable patch. Either every patch is ManualPatch, or patches is empty/absent and the check requires human review.
  • suppress_with_reason — reserved for future check classes that explicitly mark themselves as suppressible. Not emitted by the v0.12 deterministic projection; the schema accepts it so callers can extend.
  • informational — no action required (suppressed finding or non-actionable advisory).

Top-level agent_summary block (v0.12+), one-fetch summary shaped for direct agent consumption — read this when you want the headline numbers without traversing arrays:

  • verdict — mirrors release_decision.decision.

  • headline — single-sentence verdict + counts; suitable for a PR comment lead. The headline uses needs_human_review (action-driven) for "require human review" wording, so a review_required verdict with only auto-applicable findings reads honestly as "auto-applicable; none require human input" rather than falsely claiming N findings need review.

  • blocker_count — mirrors len(release_decision.blockers).

  • review_item_count — mirrors len(release_decision.review_items); severity-driven (medium-and-up severity findings that aren't blockers, plus baseline-matched accepted debt). Use this when reporting release-review debt to the human reviewer.

  • auto_appliable_patches — number of active findings with agent_action == "auto_apply".

  • needs_human_review — action-driven: number of active findings with agent_action ∈ {"escalate_to_human", "propose_patch_for_review"}. Both kinds need explicit human attention before any change applies — full escalations have no machine path, and proposed patches ship at medium/low confidence and require an explicit --apply after the user confirms. Use this when reasoning about what work an agent must do.

  • review_item_count and needs_human_review track different populations and can diverge. A medium-severity stale-suppression finding lands in release_decision.review_items (severity rule) but its agent_action is auto_apply (high-confidence patch attached), so it's counted in review_item_count and auto_appliable_patches but not in needs_human_review.

  • first_recommended_action — {kind, command|null, why}; deterministic next step. kind: "command" carries an actual CLI invocation; kind: "info" is a "surface this to the user" hint with no command. The agent_summary block is a deterministic projection — same inputs, same output, no agent-side aggregation needed.

  • Evidence-gap actionability (v0.16+). One selected gap feeds every short-form surface that names one: the first release_decision.evidence_coverage.evidence_gaps[] row that is addressable, falling back to the first row when none is. Improve evidence: (CLI and step summary, printed only on an insufficient_evidence verdict) always renders the selected gap's action.

    A row is addressable when it names a visible target or carries a publishable command. next_action.path and next_action.command are independently nullable, and either one alone is enough:

    • Visible target — the path contains at least one character that actually renders. Whitespace, control characters, and Unicode Default_Ignorable code points (U+200B ZWSP, U+200E/U+202E bidi marks, U+FE0F VS16, U+034F CGJ, …) render as nothing, so a path made only of those names no surface and is not addressable.
    • Publishable command — the command is safe to run exactly as authored. A command containing any control, bidi, or invisible code point, or any whitespace other than U+0020, is suppressed entirely rather than cleaned up: deleting a zero-width character from r␣m -rf would author a different program. Only leading/trailing U+0020 is trimmed, which cannot change argv[0].

    Two guarantees, scoped to exactly what holds:

    • Alignment, on insufficient_evidence with an addressable gap. When the verdict is insufficient_evidence and at least one gap is addressable, release_decision.reason, Improve evidence:, and first_recommended_action.why name the same gap and the same target (or, for a command-only row, the same command). The reason leads with that gap and reports the source-warning / low-confidence counts as Context:.
    • No false dead end, on every verdict. The phrase "no machine-applicable fix is available" is never emitted in first_recommended_action.why while any gap is addressable. When you do see it, no gap names a surface to open or a command to run, and the next step really is a human gathering evidence.

    Outside that first case the three surfaces answer different questions, by design — do not read alignment into them:

    • insufficient_evidence with no addressable gap: reason keeps the Evidence coverage below threshold (…) wording and first_recommended_action routes to gathering deeper sources, while Improve evidence: still renders the first gap's expects. That line is a remediation hint, not a restatement of the reason.
    • review_required: reason is severity/findings-driven and never names a gap. Whether first_recommended_action names one depends on which branch of the action picker fires, and auto-apply wins on sub-threshold evidence: when the scan carries auto-applicable patches and evidence is not below the insufficient_evidence threshold, the action is the apply-patches command even if an addressable gap exists (the gap is still called out in the why). first_recommended_action names the selected gap on the evidence-first branches only — evidence below the threshold, or evidence-driven review with no findings to walk. Read evidence_coverage.evidence_gaps[] directly if you need the gap on this verdict; do not infer it from the action.

    Rendering never rewrites what it renders. Values reaching these one-line surfaces are repository-derived — a gap subject is a tool name, a policy pack authors expects, a semantic gap's path embeds a tool name — and they are made line-safe without being altered otherwise. Characters that could break a line or reorder it (controls, U+2028/U+2029, bidi marks) become a visible <U+XXXX> escape; nothing is deleted. Identity-bearing invisibles survive, so agents/👩‍💻.yaml and identifiers carrying ZWNJ are named as they actually are. Paths and commands are additionally never whitespace-folded — configs/foo␣␣bar.yaml keeps both spaces, and python -c 'print("a␣␣b")' stays the program that was written — while prose (subjects, why/expects, loader warnings) does fold whitespace, because there a stray newline is better read as a space. An affordance is published only when it exists: a suppressed or absent command produces no Run: line and a null repair command, and accepted values with nothing visible in them are dropped.

    Authority comes from the action's own fields, never from its kind or its path. Every evidence-gap row published today is requires_human_review: true and auto_apply: false — including the provide_source row that regenerates a stale --diff-from comparison base, for which verify emits fix_task.actor: "human" and safe_to_attempt: false. Since v0.41 the one row that carries a machine-applicable patch says so in its own fields too: authorable_by: "coding_agent" with suggested_patch_kind: "declare_action". That is still not a licence to apply it unasked — it is applied by the confirm_declarations route that proposes it, and the human merge it needs is unchanged. A command on such a row tells a human exactly what to run; it does not make the row agent-owned. A coding agent acts mechanically only where fix_task.actor == "coding_agent" and safe_to_attempt is true, and then only within allowed_repairs[]. Separately, first_recommended_action.kind stays "info" on the evidence-first actions — a statement about the summary projection, not a claim that no gap row ever carries a command, and not a promise about every review_required action (the auto-apply branch returns kind: "command").

Codex plugin surface block (v0.13+), explanatory only — never a release-gate input by itself:

  • codex_plugin_surface.{plugins, marketplaces, skills, apps, mcp_server_stubs, hook_stubs, mcp_inventory_files, component_path_issues, warnings} — local static plugin package and marketplace facts.
  • Only explicit MCP inventory tools from codex_plugins.mcp_tool_inventories appear in tool_inventory[]; apps, hooks, skills, and MCP server declarations stay in codex_plugin_surface.

Per-finding provenance_kind enum (v0.15+), additive classification — read this when you want to filter findings by the kind of rule that fired, independent of confidence (sureness):

  • static_declaration — declared metadata: manifest, MCP export, OpenAPI schema, ADK YAML agent config, LangChain/CrewAI inventory JSON. High-trust structural facts.
  • ast_extraction — Tool parsed from user Python source by a framework extractor (LangChain function/structured tools, CrewAI function/class tools, ADK Python toolsets). Subject to extraction errors; agents that distrust AST quality may filter these as a class.
  • keyword_heuristic — matched a keyword list (broad-scope tokens, read-only/approval prompt terms, free-text parameter names). Higher false-positive risk than declarative facts.
  • regex_heuristic — matched a regex (secret-like values in descriptions, prompt-injection patterns). Highest false-positive risk; pair with the recommendation before acting.
  • policy_pack — emitted by an external policy pack rule after its predicates have authoritative typed support. Rule confidence can lower, but never raise, evidence confidence.
  • runtime_trace — derived from declared local trace artifacts. Audit evidence only; never filtered by --no-heuristics.

Provenance generally follows the rule's own trigger (e.g., a rule that checks for a declared manifest field is static_declaration even when the underlying Tool was AST-extracted). For framework checks that fire across both AST and declarative tool sources (ADK's per-tool checks against google_adk_function AND google_adk_config tools), the label tracks the underlying tool's source. Third-party plugin checks that don't yet set the field land at static_declaration by default — pre-v0.15 plugins continue to validate against the v0.15 wire schema. Use findings[].source.type for the precise underlying tool source.

To filter operationally, use:

agents-shipgate findings --from agents-shipgate-reports/report.json \
  --provenance-kind keyword_heuristic,regex_heuristic --json

The command reads active findings by default; add --include-suppressed when a reviewer needs suppressed entries in the same provenance summary.

For reviewer-shaped output, also read the Release Evidence Packet at agents-shipgate-reports/packet.{md,json,html} (and packet.pdf when the [pdf] extras are installed). The packet is a supporting/provisional reviewer projection, not a second gate. Packet outputs are redacted by the same default privacy layer as the report. The packet has fixed reviewer sections governed by docs/packet-schema.v0.18.json — see STABILITY.md §Release Evidence Packet. Packet schema 0.9 carries the report's evidence-backed semantic coverage and gap remediation contract. Packet §1 also mirrors static_analysis_only=true, runtime_behavior_verified=false, and static_verdict_disclaimer from the report release decision. Frozen packet schema 0.7 added capability-linked trace summary and trace refs under human_in_the_loop; frozen schema 0.6 preserved the v0.5 action_surface_diff section and added two independent additive extensions:

  • evidence_matrix (PR #104) — a compact packet-only review aid derived from public report.json fields. The matrix never contributes to release_decision, CI exit behavior, severity, suppression, baseline matching, or agent_summary; its blocker and review-item cells are copied from release_decision.
  • ReleaseDecisionItem.source and ReleaseDecisionItem.policy_evidence_source (PR #103) — packet §1 / §2 re-renders carry the same dual-source provenance that Finding.source / Finding.policy_evidence_source expose in the report.

It preserves every v0.5 field (human_in_the_loop.runtime_control_disclaimer, human_in_the_loop.source_provenance[], action_surface_diff). The release_decision.verdict label includes INSUFFICIENT EVIDENCE when the report decision is insufficient evidence.

Don't use for new gating

  • summary.status — preserved for v0.7 callers, baseline-blind. A baseline-matched critical flips this to release_blockers_detected even though release_decision.decision correctly classifies it as review_required. New consumers should not gate on summary.status. See STABILITY.md §release_decision.decision vs summary.status.

Per-finding contextual explanation (v0.12+)

For prose summaries of a single finding (PR comments, chat replies, commit messages), use:

agents-shipgate explain-finding <FINGERPRINT> \
    --from agents-shipgate-reports/report.json --json

The payload is the full Finding shape (every field on findings[] in report.json, including source, patches, confidence, agent_id, etc.) overlaid with three derived fields:

  • metadata — full CheckMetadata for the check_id (rationale, fires_when, evidence_fields, docs_url, mvp_tier) when the check is in the catalog; null for unknown ids (third-party plugins, future checks). mvp_tier is display/triage metadata only and never affects gating.
  • explanation — a deterministic 3–5 sentence prose summary suitable for direct quotation. Names the affected tool, the severity, the recommended fix, and an action-aware closing sentence keyed to agent_action. Same inputs always produce the same output.
  • source_report — absolute path (always; relative --from values are resolved before serialization) to the report file the explanation was sourced from; round-trippable for caching and audit.

explain-finding requires report_schema_version >= 0.12 because the action-aware explanation depends on per-finding agent_action. Pre-v0.12 reports are rejected with input_parse_error and a next_action pointing at the canonical scan command. The Pydantic ReadinessReport model is intentionally looser than this command's contract (so test fixtures can construct minimal findings); the version gate is what enforces v0.12 semantics on emitted reports.

Companion prompt: prompts/explain-finding-to-user.md. Use it when you need to translate a finding for a human who has never read the Shipgate docs. Keep agents-shipgate explain <CHECK_ID> for static catalog metadata (no specific finding); use explain-finding whenever you have a fingerprint and want the evidence-tied prose.

Authoritative references

See also

  • report-reading-for-agents.md — reader's primer that walks the JSON in the order a new consumer should read it; complements this field index.
  • agent-autofix-boundary.md — what an agent may assert mechanically vs. what must defer to a human reviewer when surfacing findings from report.json.