Skip to content

fix(actor): an agent cannot switch chock off (CH-1) - #307

Merged
jothimani-rajendran merged 5 commits into
mainfrom
ch-1-agent-cannot-switch-off
Oct 19, 2026
Merged

jothimani-rajendran merged 5 commits into
mainfrom
ch-1-agent-cannot-switch-off

Conversation

@jothimani-rajendran

@jothimani-rajendran jothimani-rajendran commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

What

CH-1 from org-plan plan/prerelease-fixes.md (chock review B1, B2, M2): the agent cannot switch chock off.

  1. Person-only commands. sync, recompile, install-hooks, enable, disable, add, remove, init and compile (every command that changes .chock/config.yaml, chock.lock or the compiled tree) refuse under an agent marker with the rule chock resume already applies: "a person runs chock <command> from their own shell". Data-driven: person_only: true in data/commands.json, enforced once in cli._dispatch, logged as policy_id: chock-cli. A --check run writes nothing and stays open, as do check and status. The review's chain (wrapper script edits the config, chock sync applies it) is now refused at sync.
  2. One actor rule. agent_signal() is stop_agent_signal()'s rule (the latter is gone, its two callers use agent_signal): CHOCK_AGENT_COMMIT=0 unsets that one variable and clears no vendor marker. CHOCK_ALLOW=x CHOCK_AGENT_COMMIT=0 CLAUDECODE=1 git commit on an ask gate exits 1; CHOCK_ROLLOUT=observe CHOCK_AGENT_COMMIT=0 with CLAUDECODE=1 still blocks; a person's shell (no marker) keeps both.
  3. Ask refusal. A person is told the CHOCK_ALLOW=<id> line; an agent is told to ask the person and is given no recipe.
  4. Committed disable. policy_status(..., committed=) disables a policy for an agent only when the working tree and HEAD list it (the stricter-of-both reading of "read from the HEAD-committed file", the way committed_rollout caps rollout). Applied in chock_guidance (advise._enabled), the one runtime reader of policies.disabled at an agent event; sync/recompile/autocompile, the other readers, are now person-only.
  5. docs/assurance-case.md restates "tamper-evident" to what the mechanism gives ("pinned, and only a person changes it") and lists the bytecode and runtime-pin limits as residual risks (CH-4 removes them). docs/enforcement-surfaces.md, spec/gate-dsl.md, docs/agentic-risk-coverage.md and docs/decision-log.md stated the old hatch rule and now state the new one.
  6. SessionStart advice (orchestrator review, b80f7fd): the unarmed-clone text now tells an agent to ask a person to run chock sync, since that command refuses an agent's shell; tests/test_sessionstart_arm.py pins it and the Claude Code runtime golden is regenerated.

Tests changed because the fix is right (the plan's rule): test_an_explicit_off_wins_over_every_marker, test_the_escape_hatch_lets_a_person_commit_in_an_agents_terminal, test_a_person_in_an_agent_terminal_answers_with_the_escape_hatch, test_the_escape_hatch_answers_from_an_agent_terminal and the CHOCK_AGENT_COMMIT=0 + CLAUDECODE=1 -> person row of test_a_git_hook_line_names_the_actor_by_the_actor_rule asserted the escape hatch the review's B2 names. Each now asserts the opposite, with a bare-shell case beside it. test_a_disabled_policy_gives_no_guidance wrote an uncommitted disable; it is now a committed one, plus a test that the uncommitted one changes nothing. New: tests/test_person_only_commands.py (46 cases).

Needs a person: .chock/bin. gate.py is assembled from gate/runner/*.py and claude_code.py carries the SessionStart text, so this PR makes both stale, and chock sync --repo . (the regen) is exactly what this PR refuses to an agent. CI's "Compiled artifacts match their manifests", chock check --only validate (compiled_drift), chock check --only verify, tests/test_compiled_freshness.py and tests/test_runner_selfcontained.py::test_vendored_gate_py_matches_source are red until someone runs, from their own shell on this branch:

chock sync --repo .
git add .chock/bin
git commit -s -m "regen: .chock/bin after CH-1"
git push

The diff should be exactly .chock/bin/gate.py and .chock/bin/claude_code.py.

Definition of done

  • chock check → red on validate (compiled_drift) and verify (differs: bin/gate.py, bin/claude_code.py) until the regen above; evals, matrix, mechanisms, index and conflicts pass locally
  • chock check --only matrix passes; no matrix row changed
  • chock sync --repo . --check clean: pending the regen above
  • chock check --only verify clean: pending the regen above
  • Registry rescanned; no stale entries (chock check --only index passes)
  • pytest -q locally: 4729 passed, 98 skipped, 2 failed (test_runner_selfcontained::test_vendored_gate_py_matches_source, the regen above; test_repo_standards::test_no_file_exceeds_line_budget, fixed in the second commit) with test_compiled_freshness::test_this_repo_has_compiled_what_it_declares deselected (the regen above) and test_wheel_install.py deselected; new checks have attack (agent marker) + ordinary-data (person's shell) tests
  • pytest acceptance/ -c acceptance/pytest.ini --rootdir=acceptance: 21 passed
  • No check was added or extended
  • No manifest touched; changelog.d/agent-cannot-switch-chock-off.md added
  • ruff check . and ruff format --check . clean

Claims

  • No surface is described as enforcing more than it installs. Nothing emitted or installed changes; docs/enforcement-surfaces.md states the new actor rule.

🤖 Generated with Claude Code

The commands that change .chock/config.yaml, chock.lock or the compiled
tree (sync, recompile, install-hooks, enable, disable, add, remove, init,
compile) refuse under an agent marker with the rule `chock resume`
applies; a person runs them from their own shell. The refusal is logged
as policy_id chock-cli. `sync --check`, `check` and `status` stay open.

One actor rule serves every check: CHOCK_AGENT_COMMIT=0 unsets that one
variable and clears no vendor marker (CLAUDECODE=1, AI_AGENT, a
configured agent_commit_env name), since an agent can set it too. The
ask refusal tells a person the CHOCK_ALLOW line and tells an agent to
ask the person, naming no override.

For the agent asking chock_guidance, a policies.disabled entry counts
only once committed at HEAD, the way the committed rollout caps the
working tree's.

docs/assurance-case.md restates "tamper-evident" to what the mechanism
gives and lists the bytecode and runtime-pin limits; the spec and the
surfaces doc state the new rule.

Tests that asserted the old escape hatch now assert the opposite: the
hatch was the review's B2 (an agent sets it too).

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com>

Copy link
Copy Markdown
Collaborator Author

CI status on 69a4b85: validate (ubuntu-latest, 3.12) fails at chock check --only validate with compiled_drift: Compiled artifact does not match its source (differs: bin/gate.py). The other compiled-artifact steps (chock sync --repo . --check, chock check --only verify, tests/test_compiled_freshness.py) will fail the same way.

This is this PR's failure and it is expected: .chock/bin/gate.py is assembled from src/chock/gate/runner/*.py, which this PR changes, and the regen command (chock sync --repo .) is one this PR makes person-only, so the worker session that opened the PR is refused it under CLAUDECODE=1 and does not script around that refusal. No other fix exists; it is not a flake and is not re-run.

What unblocks it, from a person's own shell on ch-1-agent-cannot-switch-off:

chock sync --repo .
git add .chock/bin/gate.py
git commit -s -m "regen: .chock/bin after CH-1"
git push

Nothing else in the PR waits on this. Reported to the orchestrator in the org-plan handoff.


Generated by Claude Code

jothimani-rajendran and others added 2 commits October 19, 2026 11:32
…get (CH-1)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com>
…erson to run chock sync (CH-1)

`chock sync` now refuses an agent's shell, so a sentence inviting the agent to run it was
false. The Claude Code runtime golden carries the new text; the vendored
.chock/bin/claude_code.py follows at the person-run regen.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com>

Copy link
Copy Markdown
Collaborator Author

Update to the regen step after b80f7fd (the SessionStart advice now tells an agent to ask a person to run chock sync): that text is also vendored into .chock/bin/claude_code.py, so the person-run regen now touches two vendored files, not one. From a person's own shell on ch-1-agent-cannot-switch-off:

chock sync --repo .
git add .chock/bin
git commit -s -m "regen: .chock/bin after CH-1"
git push

git status after chock sync should show only .chock/bin/gate.py and .chock/bin/claude_code.py; anything else is worth a look before committing. The runtime golden under tests/fixtures/runtime_goldens/ is already regenerated in b80f7fd.


Generated by Claude Code

Signed-off-by: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com>
@jothimani-rajendran
jothimani-rajendran marked this pull request as ready for review October 19, 2026 12:46
@jothimani-rajendran
jothimani-rajendran merged commit 9274866 into main Oct 19, 2026
26 checks passed
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