Repository navigation
Make deploy enforce approval: manual (S-008-1, 3, 5..10, 15, 16) - #178
Conversation
A manual application's deploy --yes now deploys a release only when a release approve record counts for it: the same archive SHA-256, exactly the component's declared servers, and not expired. Without one it exits PRECONDITION deployment_approval_missing before any playbook runs, naming the command a person runs and each record that doesn't count with its reason. The plan and the result carry approval, a broken approvals file stops a manual deploy with RECORD_INVALID, an automatic application never reads the folder, and mcp serve binds the approvals folder so a deploy call can't name its own (spec S-008). Fixes #163 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@romamo Decided without waiting: Why: both are additions every existing reader keeps working with; S-008-9 already puts Reply with a comment starting with |
|
@romamo Decision needed: may this PR (and #182 stacked on it) land with its Spec S-008 (S-008-16) asks for this entry:
Review: no way around the approval was found; CI is green on both. Reply here with a number or your own answer; shipmill takes this up on the tick after your reply. Nothing on #178 or #182 merges until then. |
|
Decision by @romamo, relayed by Claude Code
|
Plan
approval; MCP bindsapprovalsand says a person approves; then the changelog fragment and the CI deploy guideSummary
cloudfall deploynow enforces an application'sapproval: manualwith the recordcloudfall release approvewrites (#172), as spec S-008 says:release_approval.check_approvalreads every*.jsonat the top of--approvals(defaultdecisions/releases). A record about the same application, component and release counts when it names the SHA-256verify_artifactcomputes now, exactly the component's declared servers (compared as a set), and anexpiresAtlater than now; the latestapprovedAtwins. Every other record for that release is listed withdigest_mismatch,servers_changedorexpired. A file that isn't JSON, isn't an object, or doesn't matchrelease-approval.schema.jsonstops amanualdeploy (plan or--yes) withRECORD_INVALIDnaming it. Anautomaticapplication never reads the folderdeploy --yeswithout one exits 4PRECONDITION,context.code: deployment_approval_missing, withapplication,component,release,command(cloudfall release approve <component> --release <R>) andnotCounting. It refuses after the artifact check and before the inventory render, so no playbook runs and no receipt is writtenapproval(policy,path,approver,approved_at). The approved digest is passed tolifecycle.deploy(DeployOptions.approved_sha256), which refuses an archive swapped between the check and the deploy. A failed health gate leaves the record alone; a retry uses it againapproval:{"policy": "automatic"}, or formanualthestatus(approved,missing), the counting record'spath,approverandexpires_at,not_counting, andcommandwhen none countscloudfall mcp servegains--approvalsand binds it, so adeploycall can't name its own folder (ARG_ERROR);release approvestays off the tool list, and the project instructions say a person runs itdocs/ci-deploy-guide.md"Approval: who presses the button" now saysdeployenforcesmanual, and shows the build job, the person'srelease approve, and the deploy job that downloads the same artifact;docs/operator-guide.mdlistsrelease approvewith the commands no tool stands in forchangelog.d/106-enforce-manual-approval.mdhas the### Breakingentry with both ways out; the Add cloudfall release approve and its approval record (S-008-2, S-008-4, S-008-11, S-008-12) #160 fragment no longer saysdeploydoesn't read the recordOut of scope here, in #165:
migrate'sdeploy:step (it callslifecycle.deploydirectly and is not gated yet) androllback's plan.Test notes
sdk/tests/test_deploy_approval.py(21 tests) runs the real CLI against a stand-in engine whose deploy playbook only leaves a marker and a receipt on the controller: S-008-1, 3, 6 (digest, servers, expiry, servers as a set), 7, 8 (before/after, latest of several), 9 (plan and--yes, with a junk file), 10 (not JSON, not the schema, not an object; plan and--yes), 15 (a failing playbook then a retry), 16 (fragment and guide), the swapped-archive guard, and the output schemasdk/tests/test_agent_server.py: S-008-5 over the real stdio transport (plan showsmissing, a call namingapprovalsisARG_ERROR,yes: trueisPRECONDITION/deployment_approval_missing, nothing rendered), and the project instructionsuv run ruff check .,uv run mypy, the touched and neighbouring SDK test files (364 passed), anduv run pytest engine/tests(232 passed, 1 skipped) sincecommands.pyis imported by the engine. The full suite is left to CIFixes #163
🤖 Generated with Claude Code