Skip to content

Add a converge action: re-apply the merged manifest when only its shape changed - #2

Merged
SnowballSH merged 4 commits into
mainfrom
converge-action
Aug 28, 2026
Merged

SnowballSH merged 4 commits into
mainfrom
converge-action

Conversation

@SnowballSH

Copy link
Copy Markdown
Owner

A manifest edit that changes env, volumes, or the template without moving the image digest had no path to the host: Deploy refuses the pinned digest (ErrAlreadyAtDigest), so the rendered unit kept the old environment until the next digest change — observed live 2026-08-27 during the snowsys Phase 12 budget red test (a merged BUDGET_MONTHLY_USD never reached the unit).

What this adds

  • Engine: a third action, converge, that applies the manifest as main holds it at the front of the service's queue — sync, re-render, write the unit, restart, probe. It opens no pull request (the change it applies already merged through review) and never enters the merge queue. It journals a run like any other, joins duplicate converge clicks, and never joins a pending deploy at the same digest, whose frozen manifest may predate the edit.
  • No automatic rollback: the previously rendered unit is recorded nowhere, so a failed probe finishes failed with explicit guidance instead of pretending it can restore anything.
  • Journal: SetDigests rewrites a still-unfinished entry when the pin moved while the converge waited, so a healthy converge receipt names the pin it actually verified (PreviousHealthyDigest stays honest). Finished receipts are refused.
  • API: POST /api/v1/services/{name}/converge (authed, no body) → 202 {journalId, joined}.
  • CLI: snowdeploy converge <service>, following the event stream to a terminal state like deploy/rollback.

Design doc with the considered alternatives: docs/superpowers/specs/2026-08-27-converge-action-design.md.

Review

An adversarial review pass caught two real bugs in the first cut, both fixed test-first in 5f3a1c2: converge applied the mirror without syncing at its turn (stale or post-revert mirrors would be applied and reported healthy), and a moved pin left click-time digests on the receipt. The test fake could not see either, so fakeRepo now models main and the fetched mirror separately with an onSync hook.

go vet clean, full go test -race ./... green. Shipping to the SnowSys host is a separate, operator-scheduled release cycle — this PR only lands the code.

A manifest edit that changes env, volumes, or the template without moving
the image digest had no path to the host: Deploy refuses the pinned digest
(ErrAlreadyAtDigest), so the rendered unit kept the old environment until
the next digest change — observed live 2026-08-27 during the snowsys
Phase 12 budget red test.

Converge is a third engine action that applies the manifest as main holds
it at the front of the service's queue: sync, re-render, write the unit,
restart, probe. It opens no pull request (the change it applies already
merged through review) and never enters the merge queue. It journals a
run like any other, joins duplicate converge clicks, and deliberately
never joins a pending deploy at the same digest, whose frozen manifest
may predate the edit. There is no automatic rollback: the previously
rendered unit is recorded nowhere, so a failed probe finishes failed and
says exactly that.

Exposed as POST /api/v1/services/{name}/converge (no body) and
'snowdeploy converge <service>', which follows the event stream like
deploy and rollback.
… truth

Review caught two holes in the first cut. Converge read the mirror without
syncing at the front of the queue, so it applied whatever the daemon last
happened to fetch — an edit merged while it waited went unapplied yet the
run finished healthy, and after a failed deploy's revert the mirror still
pinned the digest that had just failed its probe. Converge now syncs before
its re-read, wording failures through syncFailure so the credential-bearing
transport error stays out of the journal.

And when the pin moved while the converge waited, its receipt kept the
click-time digests: a healthy row whose new_digest this run never verified,
feeding PreviousHealthyDigest the wrong rollback target. Journal.SetDigests
rewrites the still-unfinished entry to the pin actually applied; it refuses
finished receipts, so receipts stay immutable.

The single-view fake repo could not see either bug — merge landed straight
in the manifests every read consulted. It now keeps main and the fetched
mirror apart, the way the real gitops repo does, with an onSync hook for
moving main between two syncs.
@SnowballSH
SnowballSH merged commit a1f7de2 into main Aug 28, 2026
3 checks passed
@SnowballSH
SnowballSH deleted the converge-action branch August 28, 2026 04:09
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.

1 participant