Repository navigation
Require a person at a terminal for operator approve - #42
Merged
shipmill-romamo[bot] merged 1 commit intoOct 9, 2026
Merged
Conversation
operator approve ran a proposal's playbook and its verify step on any shell call, so an agent with a shell could approve its own proposal. It now follows decisions approve (#37): requires_person=True, and ctx.attest asks the person to type the proposal id before anything runs. No flag answers it, --yes and a raw payload's "yes" included; off a terminal it exits 4 with PERSON_REQUIRED, a mistyped id with ATTESTATION_MISMATCH, and the proposal stays proposed. operator show is the preview. A person's approval on the receipt records approval.via (terminal; a human approval written before reads as unknown; a policy's has none), reusing ApprovalChannel from the decision record. The field is optional in operator-proposal.schema.json. requires_person implies mcp=False, so on its own this breaks `mcp serve --project`: its bound gateway_url no longer names a served tool (MCP_BIND_UNKNOWN). It lands after #38, which takes the gateway flags off mcp serve. Part of #35 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
shipmill-romamo
Bot
force-pushed
the
fix/35-operator-approve-off-mcp
branch
from
October 9, 2026 08:17
a9022a7 to
f668590
Compare
shipmill-romamo
Bot
force-pushed
the
feat/35-operator-approve-requires-person
branch
from
October 9, 2026 08:17
22b7de2 to
d213d13
Compare
shipmill-romamo
Bot
deleted the
feat/35-operator-approve-requires-person
branch
October 9, 2026 08:21
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
cloudfall operator approve <id>ran a proposal's playbook and its verify step on any shell call, so an agent with a shell could approve its own proposal. It now followsdecisions approve(#37): the command isrequires_person=Trueand callsctx.attest, so the person types the proposal id at a terminal before the playbook or the verify step runs. No flag answers it,--yesand a raw payload's"yes": trueincluded. Off a terminal it exits 4 withPERSON_REQUIRED; a mistyped id exits 4 withATTESTATION_MISMATCH; either way the proposal staysproposed.operator showis the preview.A person's approval on the receipt records
approval.via: terminal, reusing #37'sApprovalChannel; the field is optional inoperator-proposal.schema.json. Docs:docs/operator-guide.md(Reviewing and approving),sdk/README.md, and a CHANGELOG Security entry beside #25's.Stacked on #38. #38's branch predates #37, so this branch also carries a replay of #37's commit (05a7348, already on main as 327ef40); this PR's own change is 22b7de2. On main alone the change breaks
cloudfall mcp serve --project(MCP_BIND_UNKNOWN: "McpServe bind fixed 'gateway_url', which no served command tool has."), becauserequires_personimpliesmcp=False; #38 removes those gateway flags. The decorator keeps #38's explicitmcp=Falsenext torequires_person=True.Test notes
uv sync,uv run ruff check .,uv run mypy,uv run cloudfall config validate --project config/examples,uv lock --check: passuv run pytest: 416 passedsdk/tests/test_operator.py: off a terminal with and without--yes,--raw-payloadwith"yes": true, stdin-only and stdout-only terminals all exitPERSON_REQUIRED; a mistyped id givesATTESTATION_MISMATCH; each refusal leaves the proposalproposedwith no approval or outcome. The typed id runs the playbook and recordsvia: terminal(the example hosts do not resolve, so the engine step fails fast withENGINE_STEP_FAILED). The gateway flags still parse and still name missing material. Older receipts read asvia: unknown; a new approval cannot beunknownDecisions for you
operator approverefuses any approval made off a terminal, so scripted or CI approvals stop working. Autonomous runs licensed by anOperatorPolicyare unaffectedcloudfall.operator.approverequires a keyword-onlyvia: ApprovalChanneland rejectsUNKNOWN(operator_approval_channel_unknown).ApprovalRecordgainsvia; a human approval without one, or a policy approval with one, is rejectedapproval.via(terminalorunknown); only a person's approval carries it--gateway-*flag reportsPRECONDITION, notPERSON_REQUIRED--approver, unlikedecisions approve); the guide's "Who approved" line and "Through an agent" section are Take operator approve off the MCP server #38'sPart of #35
🤖 Generated with Claude Code