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.
Verify the installed CLI contract locally before relying on hard-coded docs:
agents-shipgate contract --jsonRuntime 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.
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.
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 --hostandaudit --host --jsonask 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
--workspaceit 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-patchescommand that writes those answers, plusquestions[]— open declaration questions, each taggedauthorable_by: "coding_agent" | "human"— and the two countsagent_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. Readrelease_decision.evidence_coverage.semantic_coverage.declaration_questions.open_questions[]for the unabridged list. -
What it authorizes. Exactly the named command, plus whatever
permissionssays — publish-only on this route:edit,commit,push,update_prtrue;mergeandreport_completefalse. Writing declarations intoshipgate.yamltouches 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 itsdeclaration_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: adeclaration_driftrow 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.patchis exactlydeclaration_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. Itstarget_pathis relative tomanifest_dir, so the row means the same thing in the packet, the SARIF file, and a cached base scan.apply-patcheswrites 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 controlrefuses withworkspace_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 whyexpectspromises 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.jsonpublishes every open question withauthorable_byresolved 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 thereforecontrol.reasonandcontrol.next_action.why— now carries one sentence naming the cause, for example3 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 adecisionfrom the closed setup vocabularysetup_complete | setup_incomplete | setup_not_applicable. The published schema enforces the pairing both ways: a setup source can only come fromdetect/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
permissionsisfalseon every setup envelope, no setup envelope binds an artifact or acurrent_control_id, andcontrol_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.
- Latest release:
v1.2.0 - In-tree runtime:
1.2.0— see pyproject.toml - Runtime contract:
42(minimum control contract:21) - Current report schema:
1.0, frozen, superseding0.43—docs/report-schema.v1.0.json; the1.xrules are indocs/report-1-0-contract.md - Current packet schema:
0.18—docs/packet-schema.v0.18.json - Current shared agent result schema:
agent_result_v3—docs/agent-result-schema.v3.json - Current verifier schema:
0.21—docs/verifier-schema.v0.21.json(0.20and earlier stay frozen;0.20says what each host comparison established, source by source, and0.21also names the changed inputs it does not read) - Current verify-run schema:
shipgate.verify_run/v5—docs/verify-run-schema.v5.json - Current verification identity schemas:
plan v1,unit result v1,artifact manifest v1, andterminal receipt v1 - Current control pointer schema:
shipgate.current_control/v1—docs/current-control-schema.v1.json - Current agent control envelope schema:
shipgate.agent_control/v1—docs/agent-control-schema.v1.json - Current human-authorization schemas: request, signed grant, verifier evaluation, and external trust policy v1 —
docs/human-authorization-schema.v1.json - Current agent handoff schema:
shipgate.agent_handoff/v9—docs/agent-handoff-schema.v9.json - Current agent boundary result schema:
shipgate.agent_boundary_result/v3—docs/agent-boundary-result-schema.v3.json - Frozen deprecated Codex projection:
shipgate.codex_boundary_result/v2—docs/codex-boundary-result-schema.v2.json - Current preflight schema:
0.6—docs/preflight-schema.v0.6.json(0.5and earlier stay frozen;0.6addsplanning_onlyand denies every permission on every route) - Current downstream local agent contract schema:
10 - Current capability standard:
0.5—docs/capability-standard.md - Current capability lock schema:
0.8—docs/capability-lock-schema.v0.8.json - Current capability lock diff schema:
0.9—docs/capability-lock-diff-schema.v0.9.json - Current capability payload schema:
shipgate.capability_payload/v1—docs/capability-payload-schema.v1.json, specified indocs/capability-payload.md - Current capability delta attestation:
shipgate.capability_delta_attestation/v1, predicate typehttps://threemoonslab.com/agents-shipgate/capability-delta/v1—docs/capability-delta-attestation-schema.v1.json, specified indocs/capability-delta-attestation.md - Current attestation schema:
0.5—docs/attestation-schema.v0.5.json - Current registry schema:
0.4—docs/registry-schema.v0.4.json - Current org evidence bundle schema:
shipgate.org_evidence_bundle/v2—docs/org-evidence-bundle-schema.v2.json - Current host-grants inventory, baseline, and drift schemas:
0.9—inventory,baseline,drift. Version 0.9 adds explicit local OpenShell composition, provider profile provenance and effective-snapshot metadata, and, on a reusable workflow call whose permission ceiling holds a write scope and whose target is in the same repository,callee_permissionsandcallee_write_scopes(#921); historical host schemas remain frozen. See OpenShell support and workflow token scopes. - Optional OpenShell native trust/evidence schemas:
1—trust,evidence. Only explicit external trust inputs enable local execution; the default remains static. Native containment never grants merge authority. See OpenShell support. - Current trigger catalog schema:
0.4—docs/triggers.json - Current governance benchmark catalog schema:
0.2—docs/governance-benchmark-catalog-schema.v0.2.json - Current governance benchmark result schema:
0.2—docs/governance-benchmark-result-schema.v0.2.json - Frozen-reference report schemas: frozen
v0.33, frozenv0.32, frozenv0.31, frozenv0.30, and older versions listed indocs/INDEX.md - Frozen-reference packet schemas live in
docs/INDEX.md. - Boundary v1, verifier v0.1–v0.5, verify-run v1/v2, handoff v1–v5, and preflight v0.1/v0.2 remain frozen references for legacy readers.
- Frozen experimental capability lock and governance benchmark result schemas live in
docs/INDEX.md.
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/processin the repository's own Git config, afilter=attribute on a tracked path (any repository with Git LFS-tracked files has one), repository-localdiff.*configuration (git-crypt setsdiff.git-crypt.textconv), or a non-empty.git/info/attributes. The action iskind: "review"for a human. Re-runningverify, 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 noverifyoragent controlanswer in such a repository reads as current while the configuration is in place. Shipgate never advises removing it.agents-shipgate diffstill 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
verifycommand, after committing or shrinking the change. - Anything else, including a workspace that is not a Git checkout: the
producing
verifycommand, 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_pathsis the union ofbase...HEADand 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 stalecompletemust not authorize work the decision never covered, and a stalehuman_review_requiredmust not keep enforcing a pre-change stop. Re-running the same archived--headverification 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 noverification_plan— also in a repository with a manifest, where the preview still records the plan averifywould run andverify-run.jsonembeds 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 asworkspace_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 asworkspace_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 readagents-shipgate-reports/agent-handoff.jsonfor the compactshipgate.agent_handoff/v9view: lead withcontrol.state, then readcontrol.next_action,gate.merge_verdict, andreproducibility.run_idfor the content-addressed verify identity.verifier.jsonremains the authoritative controller substrate andverify-run.jsonremains the detailed run projection; finally confirmreport.json.release_decision.decisionfor the release gate..well-known/agents-shipgate.json→agent_read_orderis the machine-readable cross-artifact order.verifier_read_orderremains the intra-verifier.jsonfield order. - Gate / CI flow — deciding pass/fail, or any raw
report.jsonconsumer. Readagents-shipgate-reports/report.json→release_decision.decision(the next section)..well-known→gating_signalnames this signal.
merge_verdict is a deterministic projection of release_decision.decision, so
the two can never disagree.
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.decisionis authoritative.scanwrites report, advisory scaffold, and configured packet formats; it does not produce a verifier handoff or terminal receipt. - After
verify, validateverification-receipt.json, then readagent-handoff.jsonand 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.
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 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.
In agents-shipgate-reports/report.json:
-
release_decision.decision—"blocked"/"review_required"/"insufficient_evidence"/"passed". Baseline-aware. This is the gating signal. Precedence isblocked→review_required(active high/critical named concern) →insufficient_evidence→review_required(known review concern) →passed. Starting in v0.29,passedmeans 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 preventspassed, even when every other action is healthy. Existing extraction thresholds remain: low-confidence tools at leastmax(1, ceil(tool_count × 0.5))or more than three source-loader warnings also degrade evidence.insufficient_evidencemeans the scan cannot confidently gate release from the available static evidence; it does not prove the agent is unsafe. Switch on the enum with areview_requiredfallback 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 aretrue,false, and the canonical static-verdict disclaimer respectively. Packet §1 mirrors them exactly. Preserve these fields in agent summaries;passedmust 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 aCapabilityFactV1. Empty for findings that are not capability-policy matches. This is audit metadata only;release_decision.decisionremains 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.decisionremains 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 semanticgap_countpreventspassed; a non-zeroreview_concern_countprevents 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. Readreason_countsfor 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 byhuman_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 reviewedaction_surface.actionsrow has to answer, and onlyeffectandauthorityare counted — an action whose effect the scan established by itself (an OpenAPI method, an MCP annotation) was never asked and is not intotal, and an inventory oragent_bindingsdeclaration has no per-action counterfactual to score against.answeredis exact rather than optimistic: it counts dimensions that gap when the same action is re-resolved without its declaration.total == answered + open, andopen_by_dimensionsums toopen.open_questions[]is the answer order and joins toevidence_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, witheffectbeforeauthoritywithin 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_countand top-levelpolicy_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 preventspassed. -
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 reviewedtool_identity.bindings[]entry joins them. Any ambiguous selector, invalid binding, or conflicting identity preventspassed. -
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_idis 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 tolow_confidence_toolandsource_warning, semantic rows includeincomplete_surfacefor enumeration failure andunattested_surfacewhen lower-confidence extraction lacks reviewed inventory attestation without an enumeration defect. Only an exact adaptersurface: enumeratedfact earns positive enumeration wording. Other semantic kinds includemissing_effect_evidence,inferred_effect_only,conflicting_effect_evidence,missing_authority_evidence,partial_authority_evidence,conflicting_authority_evidence, andinvalid_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 addsaction_surface.actions[].overridewith theevidencethey checked and thereasonit does not apply. An acknowledged override keeps the action pass-eligible and is reported as one semantic review concern, so the run can never readpassed. Each acknowledgement is also emitted inrelease_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, orresolve_semantic_conflict, include accepted values and exact source/manifest pointers, and are human-routed. v0.37 addsnext_action.observed_readings[]on effect rows —{effect, sources[], observed}— so the row can be answered without openingaction_surface_facts. Where those readings support one conservative answer,next_action.declaration_templatecarries it pre-filled instead of a<REVIEW_REQUIRED>blank. The value is a proposal, never an assertion: it comes from the closedActionEffectvocabulary, is never weaker than any reading, and is offered only where something was observed. A protocol default standing in for absent evidence, or a heuristicread, keeps the blank. The placeholders carryauto_apply=falseandrequires_human_review=true; only a reviewed manifest edit makes one operative.v0.41 adds
next_action.authorable_by(coding_agent|human, defaulthuman) — who may write the first draft. It iscoding_agentonly where the scan filled every blank indeclaration_templateand the gap is not one that asks a person to look again.declaration_drifttherefore stays human-owned. An agent-authorable row carriessuggested_patch_kind: "declare_action"and anext_action.patchexactly matching the template, split into the action selector and fields written; the schema rejects any other pairing.target_pathis relative tomanifest_dir.auto_applystays false andrequires_human_reviewstays true for every row, and the patch remains outsideapply-patches --kindsby default; only theconfirm_declarationsroute 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 isprovide_sourcewith no declaration template, not a declaration that could not close the row, and it is excluded fromdeclaration_questionsfor 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_statusis"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 andrelease_decision.decision. -
findings[].support(v0.33+) — typed predicate support with status, effective confidence, policy/block eligibility, claim IDs, evidence bases, predicate rows, andsupport_hash. Rule confidence andblock: trueare 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, notFinding.evidence; it does not affect fingerprints, suppressions, baselines,blocks_release, orrelease_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 tupledecision="insufficient_evidence",would_fail_ci=true,exit_code=20; advisory mode keeps exit0while 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 eachreport.findingsentry was classified. Exactly one row per finding (including suppressed). In v0.33,unsupported_evidencerecords 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. Readenabled,rules_version,sensitive_field_inventory_version,redacted_occurrence_count,redacted_paths[], andoutput_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 toagent_summary. Read this block first when triaging a scan for a human reviewer. Carriesverdict(mirrorsrelease_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), andfirst_recommended_surface: ReviewerSurfacePointer | None— a deterministic pointer naming which lens/audit to open first ({kind, name, path, why}wherekind∈{release_decision, lens, audit, evidence_matrix}andname∈{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-heuristicsCLI filter pass. Always present, even when the flag is unset (enabled: Falsewith zero counts), so the report shape is stable. Carriesenabled: bool,excluded_provenance_kinds: list[str](["keyword_heuristic", "regex_heuristic"]),filtered_finding_count: int, andfiltered_by_kind: dict[str, int](per-kind breakdown). Whenenabled: True, findings whoseprovenance_kindis in the excluded list have been markedsuppressed=Truewithsuppression_reason="filtered by --no-heuristics"BEFORE the release decision was built — they remain infindings[]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:verdictmirrorsrelease_decision.decisionexactly (Principle: one decision engine). Carriesby_severity: dict[str,int]andby_reason_code: dict[str,int](active-finding histograms — the complete per-code map),capability_delta_summary: {added, removed, broadened, narrowed}(equal by construction to thecapability_changemember-list lengths),protected_surface_touched: bool,policy_weakened: bool,human_ack_required: bool,human_ack_satisfied: bool, andtop_reason_codes: list[{reason_code, count}]— the ranked top-five highlight (severity desc → count desc → code asc; the full set stays inby_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]— stableCapabilityFactV1.idvalues 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 withowner,reviewers, andapproval.{required,teams,min_approvals,enforced}.approval.enforcedis alwaysfalse; 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-levelcapability_runtime_evidenceblock. 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 overaction_surface_diff/tool_surface_diff. EachCapabilityChangeMembercarries{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_directionexplains the proven capability-level movement (added | removed | broadened | narrowed | mixed | unknown | evidence_only), andsemantic_changes[]gives field-level reasons when a base action snapshot is available.enabled: falsewhen no base diff is available. A member with an emptytoolandsubject_kind: "scope"whosescopereads"<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).toolis empty because no remote leaf was enumerated and none is invented;before_scope/after_scopecarry the two sides. Only the tool-filter axis claims a direction; for the othersbroadenedis the block's documented opaque-direction bucket and therationalesays 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 activeSHIP-VERIFY-*findings, so every row'srelated_finding_idsresolves to a realfindings[]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 infindings[]and surface viaverifier_summaryflags.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-onmoveci_mode/fail_onat 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 surfacerequired; it issatisfiedonly by a matchinghuman_ackentry inshipgate.yaml(owner + reason + affected surface, optional expiry).required == (acks-covering-required) + outstanding. The acknowledgement section lives inshipgate.yaml— itself a trust root — so a coding agent cannot add its own ack without trippingSHIP-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 jsonverify 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 --jsonIn 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 discriminatedcomplete | agent_action_required | review_publishable | human_review_requiredoperational projection. Its variant fixescompletion_allowed,must_stop,permissions,human_review, and the actor/action shape; generated schemas enforce the variants withoneOf. 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.completenessis"complete"/"partial"/"unavailable";reasonisnullonly when complete, and otherwisenot_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, orgit_failed.remediationnames the repair andfetch_repairablesays whether fetching can perform it. Only"complete"licenses reading a negativetriggerresult; anything else means the diff was not read, which is never evidence that a PR is unrelated to agent capabilities.nullmeans a pre-v0.7 artifact — unknown, not complete. -
trigger— the run/skip evaluation. Readevaluation_statusfirst; two of its three values withhold the verdict, and in bothshould_run/run_shipgate/skip/skip_reasonarenull. Never readnullasfalse. The embeddednext_actionis diagnostic only: it preserves the evaluated kind but carries no command, setsauthoritative: false, and points atcontrol.next_action, which is the route to follow. Embeddedmatched_rules[]carry no commands either. The standaloneagents-shipgate trigger --jsoncommand retains its own actionablenext_action."not_evaluated"— the diff could not be read. Repair the input throughcontrol.next_action;skip_reasonis 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 andcontrol.next_actionroutes forward to the scan.surface_exclusions.entries[]lists the unclassified files.
stop_conditions_firedis the raw block result;stop_conditions_terminalsays whether it decided. A matchedrun_shipgate/force_runrule overrides a fired stop, because a capability match in the diff is evidence the whole-workspace negative did not account for."evaluated"on an incompletediff_statusis not a contradiction: only skip verdicts are withheld, so ashould_run: truereached from evidence that did not depend on the missing bytes is authoritative and must not be overridden. Readmatched_rulesto see what carried it — aforce_runmatch rests on the manifest, not on the diff. -
merge_verdict—"mergeable"/"human_review_required"/"insufficient_evidence"/"blocked"/"unknown". Deterministic projection ofrelease_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 anunknown/human_review_requiredfallback for future values. -
static_analysis_only,runtime_behavior_verified, andstatic_verdict_disclaimer— locked totrue,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 amergeableverdict:"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 tomerge_verdict; additive and locked to"verified"whenever arelease_decisionis present. -
can_merge_without_human—bool. -
decision— mirror ofrelease_decision.decision(ornullwhen no scan ran). -
headline— single-sentence, PR-comment-friendly summary (ornull). Every completed blocked run names the deterministically selected worst blocker asMost severe: <title>., ordered by severity, check id, then title. A run with no blocker adds no cause clause. -
authorization— theshipgate.human_authorization_evaluation/v1result. Onlyacceptedcan expose a command, and that command must exactly match bothcontrol.next_action.commandand the sole entry incontrol.allowed_next_commands.rejected,not_requested, andnot_applicablecarry no command authority. -
control.human_reviewandcontrol.next_actionare 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, andVerifierHumanReviewremain importable only as deprecated v0.1/v0.2 reader models. Verifier v0.6 does not emit or invoke the retiredbuild_agent_controllerprojector. -
fix_task—{actor, safe_to_attempt, instructions[], allowed_repairs[], forbidden_repairs[], forbidden_shortcuts[], verification_command, patches[]}ornull. This is the deterministic repair boundary:actor: coding_agentwithsafe_to_attempt: truemeans the agent may attempt only the listed mechanicalallowed_repairs[]and rerunverification_command;actor: humanmeans 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-patchesand the task routes to the coding agent; repair aids, never gate inputs. -
trust_root_touched—bool;truewhen 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 theSHIP-VERIFY-TRUST-ROOT-TOUCHEDcheck. -
capability_review— reviewer-facing projection ofcapability_changewith{trust_root_touched, policy_weakened, policy_weakening_proven, capability_changes_added, capability_changes_removed, capability_changes_modified, top_changes[]}. Gate onpolicy_weakened(the fail-closed flag, raised even when no base policy existed to compare against); say the policy was weakened only whenpolicy_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}.impactmirrors the gate (blocks_release,review_required,insufficient_evidence, or informational values) and never introduces a finding-independent blocker. -
mode—"advisory"/"strict"/"skipped"/"preview".
Authorization changes operational routing, never the static release verdict. The flow is deliberately two-pass:
- Run
agents-shipgate verify --no-pluginsand 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 unsignedshipgate.human_authorization_request/v1from 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. - 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;HOMEandXDG_CONFIG_HOMEdo not redirect that lookup. - 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.
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.
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 carriessemantic_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 samesemantic_assessmentprojected 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 setfindings[].blocks_release=trueand appear inrelease_decision.blockers.findings[].blocks_releaseandrelease_decision.{blockers,review_items}[].blocks_release— explicit release-policy blockers from Action Surface Diff policies and policy-pack rules withblock: 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 asenabled: falsewith anotesreason.
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 carrypathandpointerbut 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 highwill 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 moreManualPatchsiblings (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--applyand surface any manual instructions verbatim.escalate_to_human— no machine-applicable patch. Either every patch isManualPatch, orpatchesis 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— mirrorsrelease_decision.decision. -
headline— single-sentence verdict + counts; suitable for a PR comment lead. The headline usesneeds_human_review(action-driven) for "require human review" wording, so areview_requiredverdict with only auto-applicable findings reads honestly as "auto-applicable; none require human input" rather than falsely claiming N findings need review. -
blocker_count— mirrorslen(release_decision.blockers). -
review_item_count— mirrorslen(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 withagent_action == "auto_apply". -
needs_human_review— action-driven: number of active findings withagent_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--applyafter the user confirms. Use this when reasoning about what work an agent must do. -
review_item_countandneeds_human_reviewtrack different populations and can diverge. A medium-severity stale-suppression finding lands inrelease_decision.review_items(severity rule) but itsagent_actionisauto_apply(high-confidence patch attached), so it's counted inreview_item_countandauto_appliable_patchesbut not inneeds_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 aninsufficient_evidenceverdict) always renders the selected gap's action.A row is addressable when it names a visible target or carries a publishable command.
next_action.pathandnext_action.commandare 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 -rfwould author a different program. Only leading/trailing U+0020 is trimmed, which cannot changeargv[0].
Two guarantees, scoped to exactly what holds:
- Alignment, on
insufficient_evidencewith an addressable gap. When the verdict isinsufficient_evidenceand at least one gap is addressable,release_decision.reason,Improve evidence:, andfirst_recommended_action.whyname 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 asContext:. - No false dead end, on every verdict. The phrase "no machine-applicable fix is available" is never emitted in
first_recommended_action.whywhile 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_evidencewith no addressable gap:reasonkeeps theEvidence coverage below threshold (…)wording andfirst_recommended_actionroutes to gathering deeper sources, whileImprove evidence:still renders the first gap'sexpects. That line is a remediation hint, not a restatement of the reason.review_required:reasonis severity/findings-driven and never names a gap. Whetherfirst_recommended_actionnames 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 theinsufficient_evidencethreshold, the action is theapply-patchescommand even if an addressable gap exists (the gap is still called out in thewhy).first_recommended_actionnames the selected gap on the evidence-first branches only — evidence below the threshold, or evidence-driven review with no findings to walk. Readevidence_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'spathembeds 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, soagents/👩💻.yamland identifiers carrying ZWNJ are named as they actually are. Paths and commands are additionally never whitespace-folded —configs/foo␣␣bar.yamlkeeps both spaces, andpython -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 noRun:line and anullrepair command, and accepted values with nothing visible in them are dropped.Authority comes from the action's own fields, never from its
kindor its path. Every evidence-gap row published today isrequires_human_review: trueandauto_apply: false— including theprovide_sourcerow that regenerates a stale--diff-fromcomparison base, for whichverifyemitsfix_task.actor: "human"andsafe_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"withsuggested_patch_kind: "declare_action". That is still not a licence to apply it unasked — it is applied by theconfirm_declarationsroute that proposes it, and the human merge it needs is unchanged. Acommandon such a row tells a human exactly what to run; it does not make the row agent-owned. A coding agent acts mechanically only wherefix_task.actor == "coding_agent"andsafe_to_attemptis true, and then only withinallowed_repairs[]. Separately,first_recommended_action.kindstays"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 everyreview_requiredaction (the auto-apply branch returnskind: "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_inventoriesappear intool_inventory[]; apps, hooks, skills, and MCP server declarations stay incodex_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 --jsonThe 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 publicreport.jsonfields. The matrix never contributes torelease_decision, CI exit behavior, severity, suppression, baseline matching, oragent_summary; its blocker and review-item cells are copied fromrelease_decision.ReleaseDecisionItem.sourceandReleaseDecisionItem.policy_evidence_source(PR #103) — packet §1 / §2 re-renders carry the same dual-source provenance thatFinding.source/Finding.policy_evidence_sourceexpose 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.
summary.status— preserved for v0.7 callers, baseline-blind. A baseline-matched critical flips this torelease_blockers_detectedeven thoughrelease_decision.decisioncorrectly classifies it asreview_required. New consumers should not gate onsummary.status. See STABILITY.md §release_decision.decisionvssummary.status.
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 --jsonThe 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— fullCheckMetadatafor 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_tieris 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 toagent_action. Same inputs always produce the same output.source_report— absolute path (always; relative--fromvalues 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.
- STABILITY.md — full alpha stability contract. Source of truth for everything above.
- AGENTS.md — agent-facing instructions: install, run, single-turn flow, error semantics.
docs/report-schema.v1.0.json— machine-validatable JSON Schema for the current report.docs/privacy.mdanddocs/report-sensitive-fields.json— default redaction behavior and sensitive-field inventory.docs/packet-schema.v0.18.json— machine-validatable JSON Schema for the current packet.docs/checks.json— check catalog, includingmvp_tierfor MVP/readiness triage.
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 fromreport.json.
