Skip to content

releases: gate approvals bind the release digest, the environment and the change set (#4045) - #4064

Merged
lex00 merged 1 commit into
mainfrom
feat/4045-gate-binding
Oct 17, 2026
Merged

lex00 merged 1 commit into
mainfrom
feat/4045-gate-binding

Conversation

@lex00

@lex00 lex00 commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Closes #4045

A release gate binds the release digest, the environment and the change-set document digest, as ws-104 question 2 decided. An environment whose change set is provably empty passes without an approval, recorded with empty: true.

  • Planning: when a promoted composition declares a gate, components promote --release plans the release's change set for --to and stores it with storeGateChangeSet (lifecycle: store each change set a gate binds by digest, mapped to graph node ids #4022) before deciding any gate. A component whose step can plan (terraform-apply) is planned by it. Any other component is compared with what --to runs now: its latest deploy record and the release that record names. Artifact digest, templates and target params are compared, and a difference is an update entry, or create if never deployed there. A deploy naming no release, or one whose release can't be read, becomes a hole, so that change set never counts as empty.
  • Refusals: a change set with a provisional member, or a member that failed to plan, is refused before anything deploys.
  • Binding: evaluateGate takes releaseDigest; an approval counts only when the release, the environment and the change-set digest all match. chant approve copies releaseDigest off the pending fact only when approving that fact's own plan. Pending facts, resolutions and the status gate item carry releaseDigest.
  • Empty passes: the promote writes an empty: true resolution per gate with the actor in resolvedBy. It counts only after the stored document reads back empty, quorum and signing rules don't apply, and it never counts as a person's approval. Deploy records name the release, and their admission names op release.
  • Requirement 4: the stored document names releaseDigest and environment, inside its digest. Change sets are stored under their digest, so outside it two releases with identical (for example empty) change sets would share one stored file. Documents without the fields hash as before, so no existing digest moves.
  • Gate identity: the gates each composition declares are kept, but under --release they are recorded under the op release, so components that use the same gate name share one gate per environment and one approval (chant approve release <gate> --env <env>). This keeps identity.gates signing rules, which are looked up by gate name. Gates under release are not resolved over MCP or ACP.
  • Artifact match (from lifecycle: the release is what graduates: cut from a green commit, shared artifact digests, one compile target per environment #4026): the source deploy's build manifest (manifestDigest) is compared with the release's artifactDigests at the same paths. A mismatch warns but does not refuse: image builds aren't reproducible and the source environment was built by its own run --components. Once records: join commit, release, gate and deploy through the release digest #4043 makes deploys run the release's own artifacts, it can become a refusal.
  • Promote now removes what it writes under .chant/release-templates/.

Not done here: an Op gate that binds a composeChangeSet digest still stores nothing (a follow-up in local-executor.ts).

Level-0 output changes

On the exceptions page and #2525: promote --release prints a release gate: line and writes stored change sets and empty resolutions; its gates move to op release with chant approve release <gate> --env <env>; approvals given for release promotes before this change no longer count; --json gains release.changeSet and release.empty; the artifact warning is new; new fields releaseDigest and empty on gate records, releaseDigest on the status gate item, releaseDigest/environment on change-set documents, and via: "empty" in run events. No golden moved.

Acceptance

  • Binding implemented, with a test that a different release can't use the approval (op/gate-release.test.ts "an approval for one release can't be used by another, even for the same change-set digest"; promote.test.ts).
  • A test that an approval for one environment can't be used for another (both files).
  • A test that the gate record names the release digest it admitted ("the pending fact names the release it waits on", "an approval for the release, environment and change set passes, and names the release it admitted", approve-release.test.ts).
  • Stored change sets name the release and environment they were planned for (change-set-store.test.ts, release-gate.test.ts).
  • A test that an empty change set passes and a non-empty one doesn't (promote.test.ts: non-empty stops, empty passes, empty counts only once the stored document reads back empty, provisional member refused).
  • releaseDigest and empty appear in the floor table (0.124.28 in workspace-read-contract.mdx).

Checks

  • npx tsx scripts/typecheck.ts: no regressions (26 baselined)
  • npx tsx scripts/check-lexicons.ts: no untracked tier-1 failures
  • gate, handlers, components, lifecycle, status-contract, read-contract, level-0 and wave-loop goldens, boundary roster: 2524 passed with GITHUB_SHA, GITHUB_RUN_ID and CI set, after rebasing on main
  • Full vitest: the 3 known local op/factory.e2e.test.ts failures and load timeouts that pass alone
  • lint-docs-sentences (no regressions), lint-prompts, check-docs-diataxis, ./scripts/build-docs.sh: pass
  • The real repository's chant/lifecycle (local and origin) and .git/chant/ were unchanged by the runs.

…d the change set, and an empty change set passes without approval (#4045)

components promote --release plans the release's change set for the target
environment, stores it with storeGateChangeSet, and decides every gate it
reaches under the op release against the document digest, releaseDigest and
environment. The document names the release and environment it was planned
for, inside its digest. A provisional or failed member is refused. An empty
change set gets an empty: true resolution by the actor, counted only after the
stored document reads back empty. Pending facts, resolutions and status gate
items gain releaseDigest; chant approve copies it. The promote removes the
templates it pins and warns when the source deploy's build manifest holds
other bytes than the release's (#4045)
@lex00
lex00 merged commit 8f444d4 into main Oct 17, 2026
11 checks passed
@lex00
lex00 deleted the feat/4045-gate-binding branch October 17, 2026 05:07
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.

releases: gate approvals bind the release digest, the environment and the change set

1 participant