Skip to content

Grade the local boundary stop and make the Stop hook follow control.state (contract v19)#291

Open
pengfei-threemoonslab wants to merge 5 commits into
mainfrom
claude/shipgate-agent-adoption-792f14
Open

Grade the local boundary stop and make the Stop hook follow control.state (contract v19)#291
pengfei-threemoonslab wants to merge 5 commits into
mainfrom
claude/shipgate-agent-adoption-792f14

Conversation

@pengfei-threemoonslab

Copy link
Copy Markdown
Contributor

Summary

  • Grade the local boundary stop (contract v19). shipgate check previously projected every require_review violation onto the same human_review_required + must_stop: true state as a block — a CLAUDE.md comment, an unknown .claude/settings.json key, and a critical grant expansion were operationally identical, which routinely hard-stopped coding agents on user-requested benign edits (2026-07-26 governed-loop UX review). A require_review set that is entirely low/medium risk now projects control.state: "agent_action_required" with the exact verify command; the review obligation is carried in the new additive pending_review[] field on shipgate.agent_boundary_result/v1 and re-asserted by PR-time verify, whose release_decision branching is byte-identical.
  • Fail-closed band exclusions. These keep the human stop at any scored risk: any block action or critical risk in the set; BOUNDARY-INPUT-INCOMPLETE; parse-failure evidence (allow-list polarity — only the parseable unknown_host_config_key case is band-eligible, so new evidence kinds default to the stop); CODEX-AGENTS-SHIPGATE-REQUIREMENT-REMOVED (gate weakening); experimental adapter surfaces; and every gate-governing trust-root class (manifest, policy, ci_gate, shipgate_state) — preserving the Allow coding agents to author safe tool-source proposals #282 composite-diff guarantee that a safe manifest append bundled with an unsafe manifest edit still routes to a human (its both-orderings regression test caught an earlier draft of this change).
  • Stop hook follows control.state, not the release label. A Claude Code Stop-hook block forces the agent to KEEP working, so blocking on any non-passed decision was inverted twice: must_stop: true means "end the turn and hand off". The hook now ends the turn silently on complete, blocks once with the one exact remaining command on agent_action_required, and prints a hand-off notice and lets the turn end on human_review_required. Unparseable/unrecognized verifier output warns loudly, is never cached by the verified-signature short-circuit, and is never treated as passing; the cold-start no-manifest case advises verify --preview instead of forcing continuation.
  • Mechanics. Trust-root table moved to core/trust_roots.py (checks.verify re-exports; fixes the checks.verify → core.context → core.agent_boundary cycle the boundary evaluator would otherwise create). pending_review lives on AgentBoundaryResultV1 only, so the deprecated codex-boundary-json v2 schema stays byte-frozen; agent-boundary-result-schema.v1.json regenerates additively under /v1 per existing convention (P0: prove actual agent-to-tool bindings #268, P0: Make the agent control contract unambiguous #270P0: make verification identity and portable execution reproducible #273). Contract 18→19; minimum_control_contract_version stays 14 (the AgentControl union and its fixed must_stop/completion_allowed literals are untouched). Version 0.16.0b6 → 0.16.0b7 swept across pyproject, plugin manifests, marketplace, .well-known, llms.txt, doc stamps, wheel-guard tests, and the codex boundary goldens; STABILITY gains the 0.16.0b7 migration note.
  • Scoped out after expert review (recorded in the migration note): in-session approval receipts are deliberately NOT in this change — unsigned receipts consumable by control routing would be a self-approval hole, the local ledger never reaches CI, and report-visible annotations would break content-addressed reproducibility. A future host-authenticated receipt (bound to the exact PreToolUse request/tool_use_id) is the sanctioned follow-up.

Type

  • Check or risk-model change
  • Input adapter change
  • CLI or GitHub Action behavior
  • Report, schema, or SARIF output
  • Documentation only

Verification

CI is authoritative for python -m ruff check ., python -m compileall -q src tests, and python -m pytest.

Additional local checks run:

  • Full pytest suite green (exit 0) after each phase; ruff check . and compileall clean.
  • Behavior acceptance matrix on a live fixture repo (manifest + hooks installed): CLAUDE.md comment and a plain pytest workflow → agent_action_required + pending_review[] (previously hard stops); MCP-server add, shipgate.yaml edit, and AGENTS.md verifier-requirement removal → still human_review_required; fixture run ai_generated_refund_pr → still blocked.
  • Stop-hook simulation against stubbed verifier outputs complete | agent_action_required | human_review_required | garbage | unknown-state → silent / one-shot block with exact command / hand-off systemMessage / uncached warning / uncached warning; stop_hook_active re-entry guard intact (5 new tests in tests/test_install_hooks.py).
  • finding_fingerprints/audit_id stability: no rule action/risk values changed, so boundary fingerprints do not churn; only the unknown_permission_key golden's control block flips, all 8 goldens re-stamped for 0.16.0b7.
  • scripts/generate_schemas.py and scripts/build-llms-full.py re-run to zero diff; schema-roundtrip and public-surface-contract suites green.

Release-readiness notes

  • No user-code import added to default scan paths
  • No network access added to default scan paths
  • New or changed check IDs are documented in docs/checks.md (no check IDs added or removed; local control mapping only)
  • Report/schema changes are additive or documented in STABILITY.md (additive pending_review[] on agent-boundary v1; migration note 0.16.0b7; report/packet/verifier/handoff versions unchanged)

🤖 Generated with Claude Code

pengfei-threemoonslab and others added 5 commits July 26, 2026 21:46
…rify

The local check previously collapsed every require_review violation into
the same human_review_required hard stop as block — a CLAUDE.md comment,
an unknown settings key, and a critical grant expansion were operationally
identical. The graded mapping routes require_review sets that are entirely
low/medium risk to control.state=agent_action_required with the exact
verify command; the review obligation is preserved in a new additive
pending_review[] field on the agent-boundary result and re-asserted by
PR-time verify, whose release-decision semantics are unchanged.

Fail-closed exclusions keep the human stop for: block actions and critical
risk anywhere in the set; BOUNDARY-INPUT-INCOMPLETE and unparseable
parse-failure evidence (unknown content is not reviewable content);
CODEX-AGENTS-SHIPGATE-REQUIREMENT-REMOVED (gate weakening); every
gate-governing trust-root class (manifest, policy, ci_gate,
shipgate_state) so a composite diff bundling a safe append with an unsafe
manifest edit still routes to a human; and experimental adapter surfaces.

The trust-root table moves to core/trust_roots.py (checks.verify
re-exports it) so the boundary evaluator classifies paths from the same
data without an import cycle. pending_review lives on AgentBoundaryResultV1
only — the deprecated codex-boundary v2 format stays byte-frozen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A Claude Code Stop-hook block forces the agent to keep working, so
blocking on any non-passed release decision was backwards twice over:
human_review_required (must_stop=true) means the turn must END so a human
can take over, and review_required with a pending agent action deserves a
route, not a lecture. The hook now mirrors the operational contract:
complete ends the turn silently; agent_action_required blocks once and
names the one exact remaining command; human_review_required prints a
hand-off notice and lets the turn end. Unparseable or unrecognized
verifier output warns loudly, is never cached, and is never treated as
passing. The cold-start no-manifest case advises verify --preview instead
of forcing continuation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Runtime contract 18 -> 19 documents the graded local boundary stop and the
control-state-driven Stop hook. minimum_control_contract_version stays 14:
the AgentControl union, its fixed must_stop/completion_allowed literals,
and the release gating signal are untouched, so every >=14 consumer keeps
working. pending_review[] ships additively on the regenerated
agent-boundary-result-schema.v1.json; the deprecated codex-boundary v2
schema stays byte-frozen. Version 0.16.0b6 -> 0.16.0b7 swept across
pyproject, __init__, plugin manifests, marketplace, .well-known, llms.txt,
doc stamps, wheel-guard tests, and the codex boundary goldens; STABILITY
gains the 0.16.0b7 migration note and CHANGELOG the three graded-stop
bullets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ci-recipes/advisory-pr-comment.yml templates {{ shipgate_version }}, so the
version bump changes its rendered bytes: EXPECTED_CLAUDE_CODE_SKILL_RENDER_SHA256
advances to the b7 render and the outgoing b6 hash joins prior_render_sha256 so
kit updates recognize previously installed copies as managed. Hashes verified
byte-exact against the template for both versions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR #274 expanded fail_on_merge_verdicts to include human_review_required
and insufficient_evidence, but no verifier mechanism can clear a
human_review_required merge verdict (human_ack is decision-inert,
checks.ignore is category-blocked for SHIP-VERIFY-*, and a baseline never
changes the review branch) - so every trust-root-touching PR, including
routine release pin sweeps that bump the plugin manifests, fails CI
permanently. The verdict's own rationale says release is allowed and the
human reviewer should weigh in; the PR review is where that human weighs
in. Both advisory workflows now fail only on blocked and unknown
(fail-closed against parse/contract breakage). Review routing stays fully
visible in the uploaded verifier artifact, and the GitHub Action's own
defaults are unchanged.

Reviewed and approved by Pengfei in-session before commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant