Roadmap item 11: Add inactive deploy and rollback gates - #252
Conversation
Roadmap step 11 needs environment tiers, a named production gate, a rehearsed rollback, and delivery evidence. This first commit lands the paper: the three environment tiers and the shape of every document the gates will read. The gate evaluator and the fake dormant deployment adapter follow in the next commit on this branch, because the whole unit is about 660 lines of product code and the size budget is 300-400. deploy/v1/environment-tiers.json names dev, staging, and production, the gate each one requires (production takes only a named operator authorization; the lower tiers take the routine gate), and which actor roles may ask for deploy, status, or rollback. deploy/v1/deploy_contracts.jq holds the shapes: the release record binding a release id to a verified source commit and tree plus its verifier, review, CI, and optional packaging-manifest evidence; the deploy, status, and rollback requests; the operator authorization record; and the rollback rehearsal record. The packaging release manifest is referred to by shape only, so this unit does not depend on step 10's code. deploy/v1/validate-deploy-document.sh checks one canonical document of a named kind and prints nothing when it is well formed. It is fail closed: one error id on stderr, no output, for a wrong or unknown kind, a non-canonical, multi-root, oversized, too-deep, or symlinked file, or a body that does not match its contract. Nothing here deploys, rolls back, or reads any environment. There is no deployment adapter in this repository and adding one is a post-transition, operator-gated change. No credential, network, model call, authority, or qualification is involved. Proof on this commit: - /opt/homebrew/bin/shellcheck -x -S style over the new scripts: clean - bash scripts/test/deploy-rollback-gates.test.sh: deploy and rollback contracts: 24 focused checks passed - bash scripts/check-rename.sh: clean Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This is the second half of Roadmap step 11, stacked on the contracts commit. It adds the evaluator that reads a capability request together with everything that would have to be true before a deployment adapter could ever see it, and the fake dormant deployment adapter that refuses to be one. deploy/v1/evaluate-deploy.sh takes six documents - the deploy, status, or rollback request, the release record, the operator authorization, the rollback rehearsal, the risk-gates evaluation, and the kill-switch evaluation - and returns one canonical deploy_gate_evaluation whose decision is admissible or refused. Admissible means only that the request may be handed to a deployment adapter after the operating-mode transition; the output always records authority "none" and qualification "unavailable" and never carries a grant, qualification reference, or activation. A refusal names why: tier-unknown, authorization-missing, authorization-stale, authorization-wrong-tier, release-unverified, rollback-unrehearsed, kill-switch, duty-violation, or malformed. Production carries an independent final guard, so a production request is refused unless a named, authorized, same-tier operator record backs it, whatever else passes. A rollback from a non-operator actor is admissible only when the rehearsal it names covers the same environment and the same release pair and succeeded; an operator is not blocked on one, because the rehearsal requirement gates autonomous maintenance rather than the human. The risk-gates evaluator has no satisfied result by design, so this unit records its reference and refuses on its duty reasons rather than demanding a verdict it cannot produce. Time comes from the request, never a clock, so the same six inputs always produce the same bytes. Inputs must be regular non-symlink files holding exactly one canonical JSON document within fixed bounds; the tier policy is re-checked through the shipped validator inside a private runtime before the gates run, and every file is compared again afterwards, so a file moved mid-run is refused. A recognizable deploy envelope with a bad body is refused as malformed rather than crashing. adapter-tests/v1/fakes/deploy-dormant.sh follows the dormant publisher: given an admissible evaluation it returns a refusal receipt saying deployment is disabled in construction mode, and given anything else it returns nothing. It is not registered in the fake adapter contract matrix, whose inventory is a fixed producer-then-forge pipeline with pinned package digests; adding a deployment phase there is its own change. Nothing here deploys, rolls back, or reads any environment. There is no real deployment adapter in this repository and adding one is a post-transition, operator-gated change. Proof on this commit: - /opt/homebrew/bin/shellcheck -x -S style over the new scripts: clean - bash scripts/test/deploy-rollback-gates.test.sh: deploy and rollback gates: 72 focused checks passed - bash scripts/check-rename.sh: clean Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Deploying ystack with
|
| Latest commit: |
6901e3c
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://f1e8791e.fabrica-6yx.pages.dev |
| Branch Preview URL: | https://ystack-roadmap-11-deploy-rol.fabrica-6yx.pages.dev |
Codex reviewer (cross-vendor, read-only)Reviewed-head: b87e43b Posted verbatim by The deploy gate can mark a request admissible even when an input risk-gate evaluation explicitly reports a violation. That undermines the core decision the new evaluator is meant to make. Review comment:
|
The gates refused a risk-gate evaluation only when its reasons were duty violations, so a verdict of violated for a rejected or role-denied decision added no refusal and a request could still be admissible. A violated verdict for any non-duty reason now refuses with deploy.risk-gate-violated; the program digest in the decision record is re-pinned. Proof: deploy-rollback-gates 73/73 (new case: violated verdict with decision.rejected is refused), shellcheck clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codex reviewer (cross-vendor, read-only)Reviewed-head: 9fbb9f9 Posted verbatim by The gate can produce an incomplete refusal reason set for mixed duty and non-duty risk violations. The decision still refuses, but the contract's reason semantics are wrong for that scenario. Review comment:
|
A violated risk-gate verdict whose reasons mixed a duty violation with another violation reported only deploy.duty-violation, hiding the second refusal. Any violated verdict now carries deploy.risk-gate-violated as well; the program digest in the decision record is re-pinned. Proof: deploy-rollback-gates 73/73, shellcheck clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codex reviewer (cross-vendor, read-only)Reviewed-head: 405c429 Posted verbatim by The deploy gate can accept fabricated minimal control-evaluation documents and produce an admissible result without evidence that the risk/kill-switch evaluators actually ran. This undermines the new gate's core safety contract. Review comment:
|
The deploy gate accepted a risk-gate or kill-switch evaluation that carried only a policy set, a verdict and reason ids, so a fabricated kill-switch document saying "satisfied" - with no state, attempt, decision or duty references at all - passed the gate as if a real evaluator had produced it. deploy_contracts.jq now checks each of the two control evaluations against the exact document its own evaluator emits, field for field: the risk-gate body must carry exactly the fourteen fields control/v1/risk-gates.jq writes, and the kill-switch body exactly the thirteen fields control/v1/kill-switch.jq writes, with every content reference bound to its media type and every document reference to its schema version and kind. A missing field, an extra field, or a reference of the wrong sort is now refused as deploy.malformed. Nothing else was loosened or tightened. The focused test's two control fixtures become full, internally consistent documents rather than three-field stubs, and four negative cases cover a kill-switch document with no state reference, a risk document with no stage, and an unknown body key on either. deploy-decision.json re-pins the contract module digest the driver checks. Proof: /opt/homebrew/bin/shellcheck -x -S style clean; bash scripts/test/deploy-rollback-gates.test.sh -> 77 focused checks passed; bash scripts/check-rename.sh clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…loy-rollback-v1 # Conflicts: # ci/required-files.txt
Codex reviewer (cross-vendor, read-only)Reviewed-head: 64f9f6e Posted verbatim by The deploy document contract accepts invalid timestamps, which can let malformed requests or authorizations pass validation and affect gate decisions. This should be fixed before the patch is considered correct. Review comment:
|
A correctly formatted but impossible time such as 2026-99-99T99:99:99Z passed the timestamp check, so stale and expiry decisions could run on invalid values. Month, day (leap years included), hour, minute, and second are now range-checked; the contracts digest in the decision record is re-pinned. Proof: deploy-rollback-gates 79/79 (new cases: an impossible date and a non-leap-year 29 February are refused), shellcheck clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codex reviewer (cross-vendor, read-only)Reviewed-head: c8ffb70 Posted verbatim by The rollback gate can admit an autonomous rollback using after-the-fact rehearsal evidence. That violates the documented deployment transition invariant, so the patch is not correct as-is. Review comment:
|
The rehearsal gate checked outcome, tier, and release pair but not time, so a rehearsal dated after the request could make an autonomous rollback admissible. A rehearsal recorded after requested_at now refuses with deploy.rollback-unrehearsed; the program digest in the decision record is re-pinned. Proof: deploy-rollback-gates 80/80 (new case: a rehearsal dated after the request is refused), shellcheck clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…loy-rollback-v1 # Conflicts: # README.md # ci/required-files.txt # docs/components.md
Codex reviewer (cross-vendor, read-only)Reviewed-head: 18291ee Posted verbatim by The deploy gate accepts malformed control-evaluation states that the upstream evaluators would not emit, which can make otherwise valid deployment requests admissible despite unresolved risk or kill-switch signals. These are blocking correctness issues for the new gate logic. Full review comments:
|
…n tier A kill-switch evaluation could say satisfied while carrying a stop reason, and a risk-gate evaluation could say inconclusive with an unknown minimum tier or a tier-unknown reason that disagreed with its classification; the gates trusted the verdict alone. Both evaluations must now be consistent the way their evaluators emit them, an unknown minimum tier refuses with deploy.risk-gate-violated, and the contracts and program digests are re-pinned. Proof: deploy-rollback-gates 84/84 (four new cases), shellcheck clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codex reviewer (cross-vendor, read-only)Reviewed-head: b5e40f1 Posted verbatim by The new deploy gate can admit inconsistent risk-gate inputs and same-timestamp rollback rehearsals that should fail closed. These are safety gate correctness issues in the added component. Full review comments:
|
… precedence A risk-gate evaluation saying inconclusive while carrying a violation reason passed the contract and the gate admitted it; the verdict must now be exactly what the evaluator derives from its reasons (violated iff any violation, inconclusive iff only the two unknowns). The previous consistency rule named a reason id the evaluator never emits and is replaced. A rehearsal recorded in the same second as the request could not prove it came first and is now refused. Contracts and program digests are re-pinned. Proof: deploy-rollback-gates 86/86 (new cases: inconclusive with a violation reason and violated without one are malformed; a same-second rehearsal is refused), shellcheck clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…loy-rollback-v1 # Conflicts: # README.md # ci/required-files.txt # docs/components.md
Codex reviewer (cross-vendor, read-only)Reviewed-head: 89cb11e Posted verbatim by The deploy evaluator can admit a high-risk request into a routine tier, which breaks the intended deployment gate semantics. The dormant fake adapter also accepts malformed admissible-looking evaluations, so the contract proof is too weak. Full review comments:
|
…pter A request classified high could be admitted to a routine-gated tier because the gate refused only violated or unknown classifications; the minimum tier must now be no higher than the environment tier's own risk tier, and a bootstrap classification is never admissible. The dormant deployment adapter accepted an admissible-looking evaluation with extra body keys; it now checks the evaluator's complete output shape. The program digest is re-pinned. Proof: deploy-rollback-gates 89/89 (new cases: high floor admissible in production but refused in staging, bootstrap refused, forged evaluation refused by the dormant adapter), shellcheck clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codex reviewer (cross-vendor, read-only)Reviewed-head: 6901e3c Posted verbatim by No discrete, actionable correctness issues were identified in the changed deploy/rollback gate implementation, tests, or documentation updates. |
Roadmap step 11 asks for environment tiers, a named production gate, rehearsed rollback, and delivery evidence, exposing deploy, status, and rollback as environment-scoped capabilities that require the risk tier's named authorization and bind a release to verified source and evidence. This adds those contracts and their gate evaluator as an inactive unit. Nothing deploys, rolls back, or reads an environment.
deploy/v1/:environment-tiers.json(dev, staging, production; each names its gate and the actor roles allowed per request kind),deploy_contracts.jq(release record, deploy/status/rollback requests, authorization record, rollback rehearsal record, and the two control evaluations consumed),validate-deploy-document.sh,deploy-gates.jq+evaluate-deploy.sh(six inputs → one canonicaldeploy_gate_evaluationwithadmissibleorrefusedand a reason id: tier-unknown, authorization-missing/stale/wrong-tier, release-unverified, rollback-unrehearsed, kill-switch, duty-violation, malformed), anddeploy-decision.jsonpinning the driver, program, module, validator, and tiers digests. Production carries an independent final guard: refused unless a named, same-tier, authorized operator record backs it, even when everything else passes. Time comes from the request, never a clock. The rehearsal record binds a rollback request from a non-operator actor. Every output carries authority none and qualification unavailable; admissible means "may be handed to a deployment adapter after the transition", never a grant.adapter-tests/v1/fakes/deploy-dormant.shis a fake deployment adapter in the dormant-publisher pattern: it accepts an admissible evaluation and returns a refusal receipt ("deployment disabled in construction mode"). It is exercised from the focused test rather than the adapter-tests runner, whose pinned producer→forge pipeline has no deployment phase; adding one is its own unit.Notes for reviewers: the risk-gates evaluation is bound by policy-set identity and refused on any
duty.*reason rather than required to besatisfied, because that evaluator has no satisfied result by design.deploy.malformedalso covers a bundle whose control references disagree.Review-size note: two stacked commits (contracts + validator, 282 lines; evaluator + decision record + fake adapter, 383 lines). The focused suite takes about 8 minutes because each evaluator run stages the pinned jq, as the control suites do.
Proof run on b87e43b:
bash scripts/test/deploy-rollback-gates.test.sh→ 72 focused checks passed-x -S styleclean;bash scripts/check-rename.shcleanbash scripts/test/portable-core-schema.test.sh→ failures: 0Construction mode: inactive output, no human gate.
🤖 Generated with Claude Code