Skip to content

feat(frontdoor): instant rollback — re-point to a prior release's immutable image - #39

Merged
mdheller merged 1 commit into
mainfrom
feat/instant-rollback
Aug 4, 2026
Merged

feat(frontdoor): instant rollback — re-point to a prior release's immutable image#39
mdheller merged 1 commit into
mainfrom
feat/instant-rollback

Conversation

@mdheller

@mdheller mdheller commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What

Completes the Vercel/Heroku deploy ergonomic trio: push → preview → promote → **rollback**. Rollback was only implied by the promotion gate; this makes it real — and better than a plain PaaS's, because of a property the stack already guarantees:

every deploy is a content-addressed, immutable image (a data sphere).

So a release is a sealed record binding (tenant, app, branch, image_digest, workload), and rolling back is re-pointing to a prior release's already-built digest.

rollback(app)  ──▶ find a PRIOR release that ran          ← target never ran here ⇒ BLOCKED, nothing served
               ──▶ re-point to its immutable image_digest  ← NO REBUILD (instant), reproducible bits
               ──▶ record a new head release (kind=rollback, from→to)   ← itself auditable
               ──▶ a SEALED decision

Why it's better than a plain PaaS rollback

  • Reproducible, not "rebuild-and-hope." The digest is the guarantee — you get back exactly the bytes that ran, not a fresh build of an old ref.
  • Fail-closed. You can only roll back to a release that actually ran here; an image that was never built+deployed is never served. Rolling forward to a new version still goes through the promotion gate — rollback to a known-prior-good release is the fast, sealed, audited escape hatch.
  • Every rollback is itself an auditable head release (links from→to), so you can roll again either way.

Design

  • tools/release_ledger.py — pure, file-backed core (make_release / history / current / rollback) + a real CLI (history|current|rollback <tenant> <app> [--to-digest D | --steps N]).
  • History filenames carry a monotonic sequence so ordering is deterministic regardless of clock resolution (two releases in the same microsecond must not sort by their hash-based id).
  • push_webhook.handle_push now records a release on every successful deploy (optional ledger_dir; the HTTP receiver passes artifacts/releases) — real pushes accrue the history rollback operates on. Failed builds are not releases.

Wired in

  • capd/instant-rollback.mesh.capd.json (caps.dev.instant-rollback, composes_with git-push-deploy + git-push-webhook)
  • validate.py, Makefile rollback, docs/BUILD_DEPLOY.md (documents the full trio)

Tests

+12 (10 ledger + 2 webhook-integration) = 209 tools tests green. 1-step rollback re-points with no rebuild; rollback to a specific prior digest; rollback to a never-built image blocked (fail-closed, current unchanged); no-history blocked; rollback-to-current no-op; rollback is a new linked head; only successful deploys become releases; records sealed.

🤖 Generated with Claude Code

…utable image

Completes the Vercel/Heroku deploy ergonomic trio (push -> preview -> promote -> ROLLBACK). Rollback
was only implied by the promotion gate; now it is real, and better than a plain PaaS's, because every
deploy is a content-addressed immutable image (a data sphere):

tools/release_ledger.py — a sealed release ledger + instant rollback:
  - a release binds (tenant, app, branch, image_digest, workload); a successful deploy records one.
  - rollback re-points to a PRIOR release's already-built digest: NO rebuild (instant) and reproducible
    (the digest guarantees you get back exactly the bits that ran — not "rebuild an old ref and hope").
  - FAIL-CLOSED: you can only roll back to a release that actually ran here; a target image that was
    never built+deployed is blocked and never served. Rolling forward still goes through the promotion
    gate; rollback to a known-prior-good release is the fast, sealed, audited escape hatch.
  - a rollback is itself a new auditable head release (links from->to), so you can roll again either way.
  - history filenames carry a monotonic sequence so ordering is deterministic regardless of clock
    resolution (two releases in the same microsecond must not sort by their hash-based id).
  - real CLI: `release_ledger.py history|current|rollback <tenant> <app> [--to-digest|--steps]`.

push_webhook.handle_push now records a release on every successful deploy (optional ledger_dir; the
HTTP receiver passes artifacts/releases) — so real pushes accrue the history rollback operates on.

Wired: capd/instant-rollback.mesh.capd.json (caps.dev.instant-rollback, composes_with git-push-deploy
+ git-push-webhook); validate.py; Makefile `rollback`; docs/BUILD_DEPLOY.md documents the full trio.

Tests: +12 (10 ledger + 2 webhook-integration) = 209 tools tests green.
@mdheller
mdheller merged commit faa7076 into main Aug 4, 2026
1 check passed
@mdheller
mdheller deleted the feat/instant-rollback branch August 4, 2026 07: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