Skip to content

Shunt Container Execution Boundary

Standard Go Shunt (from shunt) runs inside Cloudflare Containers — the WASI-based isolated runtime on Cloudflare’s edge. This document describes the execution boundary between the gondolier orchestration layer and the containerised Shunt engine.

gondolier Worker (public API)
├── auth & credential custody (tenant forge tokens)
├── schedule & trigger decision
├── LeaseDO (per-tenant/repo lease state)
└── dispatch → Cloudflare Container (WASI)
└── Shunt: reconcile PR, compute status, return summary
Container ⟵→ Worker (reconcile response)

The Worker is the only public-facing component. It owns:

  • HTTP API (tenant CRUD, queue status, auth)
  • Encrypted credential storage (tenant forge tokens, envelope-encrypted at rest)
  • Lease coordination via LeaseDO (Durable Object, per-tenant/repo)
  • Container dispatch and result consumption

The container owns only the Shunt execution: reading the PR queue state supplied in the request, computing merge-ready status, and returning a safe reconciliation summary.

  1. Worker receives a scheduled tick (cron) or optional webhook event.
  2. Worker checks LeaseDO for an active lease on the target tenant/repo.
  3. If no lease is held (or the lease is expired), Worker acquires the lease atomically.
  4. Worker dispatches a Container request containing:
    • The tenant identifier (for routing; no credentials are sent).
    • A ephemeral credential handle — an encrypted blob signed by the Worker that grants the container read-only access to the queue state for this single invocation.
    • The list of queued PRs (shallow refs: sha, url, labels) derived from the Worker’s knowledge.
    • The current shunt state (staged gates, bisect status).
  5. Container runs the standard Shunt engine: evaluates merge readiness, updates internal queue state, computes any required actions (status update, merge trigger, bisect decision).
  6. Container returns a safe reconcile summary — a serialisable struct containing only:
    • Computed status (ready / waiting / failing)
    • Actions to perform (e.g., “update merge queue status to ready”)
    • No decrypted tokens, no raw API responses, no secrets.
  7. Worker applies the summary to the real Forgejo (status comments, merge triggers) and releases the lease.

The container receives no long-lived credentials. Instead:

  • The Worker encrypts a short-lived (≤5 minute) capability token using the tenant-specific KMS envelope key.
  • The token is scoped to a single Container invocation — one request, one response.
  • The container presents no credentials to external services; it operates purely on the state passed in the request.
  • For any Forgejo API calls, the container returns the payload to the Worker, which signs and sends the real request using the Worker’s stored tenant token.

This means:

  • Compromised containers leak nothing beyond the invocation window.
  • Tenant tokens never leave the Worker.
  • The container is stateless and deterministic given its input.

A LeaseDO is a Durable Object instance whose identity is the tuple (tenantID, repoFullName). It provides:

  • Lease acquisition — atomic compare-and-set on a lease token (UUID) with TTL (default 120s).
  • Lease hold check — returns current lease token + expiry.
  • Lease release — clears the lease on completion (success or failure).
  • Holdover detection — if a lease exceeds its TTL without release, the next tick treats it as expired and retries.

LeaseDO prevents concurrent Shunt executions on the same repo, even across Worker instances or container invocations. The lease is per-tenant/repo, not global, so unrelated repos reconcile in parallel.

Source Role
Cron Primary reconciliation trigger. Guarantees correctness by running on a fixed interval regardless of external state.
Webhook Optional latency optimiser. Wakes a reconcile early when a PR event arrives (push, status change, merge request). Does not replace cron.

Cron runs every N seconds (configurable per tenant, typically 10–60s). Webhooks are optional — they shorten the window between a PR event and the next reconcile, but the cron tick is the source of truth for correctness.

  1. Deploy the Worker with the container dispatch logic and LeaseDO integration. This is the only deployable unit.
  2. Verify the cron job dispatches containers and returns valid summaries for one non-critical tenant.
  3. Monitor reconcile latency, lease contention, and error rates.
  4. Gradual enablement — enable cron for remaining tenants incrementally (per-tenant feature flag in config).
  5. Full rollout — all tenants reconciling via containers.

Rollback is a single Worker redeployment to the prior version. LeaseDO state is forward-compatible (new code reads old lease format; old code ignores new fields).

  1. Re-deploy the prior Worker version from the same main branch commit.
  2. LeaseDO state is compatible — old Worker code will read existing lease entries without error.
  3. Pending container invocations complete; new invocations revert to pre-container Shunt execution (if the prior version ran Shunt in-process).
  4. Verify cron tick completes end-to-end for one tenant.
  5. Confirm no leases are stale (LeaseDO holdover detection handles this automatically).
  • Direct Go Workers deployment. Containers are WASI-based; Shunt runs in the container runtime, not as a Go Worker module. Go Workers (Go compiled to WASI via TinyGo/cc) is not used for Shunt execution.
  • Container-external credential management. The container never holds or manages tenant credentials. All credential custody remains in the Worker.
  • Webhook-only reconciliation. Webhooks are optional latency aids; cron is the correctness guarantee.
  • Multi-tenant container sharing. Each container invocation is scoped to a single tenant/repo via the lease. Containers do not share in-memory state across tenants.
  • Container-to-Container communication. Containers are stateless and do not communicate. The Worker is the sole coordination point.
  • Persistent container storage. Containers do not write to D1, KV, or R2. All state is returned to the Worker and applied there.