Waves and approvals
plywood.yml lists waves in order. The first is the canary. plywood ci writes them to .chant/op-waves/pulumi-up.spec.json, which the update jobs read at base. After merge, plywood switches on chant’s gated waves:
- Each wave previews its stacks against the merged program.
- The gate compares that preview’s digest with the approved one.
- A match updates the wave, and the approval is spent.
- A mismatch refuses the wave and names both digests.
With approvals.preview in the spec, a merged wave also compares its fresh digest with the one plywood run wave --record-plans recorded for the pull request that produced the merge commit. The comparison runs under every gate policy, so a change that gate: on-destructive would let through unasked is checked too. A match goes on under the wave’s gate as before. A different digest refuses the wave with exit 4 and applies nothing, and so does a merge with no pull request or a wave whose plans were never recorded. The wave job asks the forge for the merged pull request, so it needs a token that can read pull requests. On GitHub that is pull-requests: read. Forgejo’s job token has it. GitLab needs a CHANT_FORGE_TOKEN CI/CD variable holding a project access token with read_api. A person who has read the new plans can approve their digest, and the wave then applies them.
plywood init leaves approvals.preview out unless plywood.yml says preview: true, and then it also gives each wave job its token. It is off by default because of two known limits. With it on, the second of two merges that change different resources of one stack is refused, and a pull request that creates an autonamed resource is refused at merge. Both are being fixed in chant, and the default changes back once they are.
The pull request’s approval carries over to each wave whose plans it covers. Any other wave waits at its own gate. Its job prints the plywood approve wave-<k> line for its digest. gate decides which waves ask at all. A failed stack holds only the stacks that depend on it, through chant’s failed member blocks.