Skip to content

Shunt Scheduling

sequenceDiagram
participant Cron as Cloudflare Cron Trigger
participant Webhook as Forgejo Webhook
participant Worker as Worker (index.mjs)
participant Lease as LeaseDO
participant Container as Go Container (WASI)
Cron->>Worker: scheduled() every 5 minutes
Cron->>Worker: enumerate managed_repos
loop for each repo
Worker->>Lease: POST /internal/reconcile (auth+encrypted_token)
activate Lease
Lease->>Lease: acquire lease (atomic CAS)
alt lease held (409)
Lease-->>Worker: 409 Conflict — skip tick
else lease free
Lease->>Lease: decrypt token in-memory
Lease->>Container: POST /internal/reconcile
Container-->>Lease: {status, errors}
Lease-->>Worker: {status, errors}
end
deactivate Lease
end
Webhook->>Worker: POST /api/v1/webhooks/forgejo (HMAC verified)
Worker->>Worker: find tenant by repo
Worker->>Lease: POST /event (tenant+repo+payload)
activate Lease
Lease->>Lease: processEvent → engine tick
Lease-->>Worker: result
deactivate Lease
Source Role Latency Guarantees
Cron (primary) Reconciliation safety net — runs on a fixed schedule (*/5 * * * *) regardless of external state Every 5 minutes Correctness. Converges the queue to a consistent state after any disruption.
Webhook (optional) Low-latency wake — triggers an engine tick when a PR event arrives Sub-second Latency optimization only. Does not replace cron.

The scheduled() handler in index.mjs is the correctness source:

  1. Enumerates all managed repos from Supabase (managed_repos table)
  2. For each repo, fetches its forge connection to get instance_url and encrypted_token
  3. Calls the LeaseDO’s POST /internal/reconcile endpoint with:
    • X-Container-Auth header (from CONTAINER_AUTH_SECRET Cloudflare Secret)
    • Encrypted token (never decrypted by the Worker)
    • Repo config (tenant ID, repo name, instance URL, base branch)
  4. The LeaseDO acquires a per-tenant/repo lease, decrypts the token in-memory, dispatches to the Go Container via ShuntContainer, and releases the lease
  5. If the lease is already held (409 Conflict), the cron job skips that repo and retries on the next tick
  6. Errors are counted but never crash the handler — the next cron tick will retry

The POST /api/v1/webhooks/forgejo endpoint is an optional latency optimization:

  1. Receives a Forgejo webhook payload (push, pull_request, check_run, status)
  2. Verifies HMAC signature using the tenant’s webhook_secret
  3. Filters out bot events (e.g., mq-bot events)
  4. Routes the event to the LeaseDO’s POST /event endpoint
  5. The LeaseDO processes the event (queue join/leave/re-evaluate) and triggers an engine tick
  6. Unlike cron, the webhook does not call /internal/reconcile — it calls /event which processes the specific event
  • Cron runs regardless of whether webhooks are received (missed webhooks are harmless)
  • Duplicate webhooks are harmless (cron converges the state)
  • If the webhook system is disabled, cron alone guarantees correctness
  • Webhooks reduce the window between a PR event and the next engine tick

Exactly-once execution is guaranteed by the LeaseDO lease mechanism:

  1. Acquire — atomic compare-and-set with TTL (30 seconds). Succeeds only if no lease is held or lease has expired.
  2. Dispatch — execute the container call (cron) or process the event (webhook)
  3. Release — clear the lease (runs in a finally block, ensuring release even on failure)

If acquire fails (409 Conflict), the cron job skips that repo and retries on the next tick. The webhook handler returns a 409 response, and the next cron tick will process the event.

Failure mode Action
Supabase fetch failure Log error, skip tick, retry on next cron run
Missing connection for repo continue to next repo
Container unreachable Log error, increment error counter, retry on next tick
Token decryption failure Handled by LeaseDO, generic error logged
Lease already held Skip tick for this repo, retry on next cron run
Go Container error Generic error logged, retry on next tick
Cron handler crashes Outer try/catch prevents Worker crash, logs error
  • Tenant forge tokens are never decrypted by the Worker. Only the encrypted blob is passed to the LeaseDO.
  • The LeaseDO decrypts tokens in-memory via encrypt.mjs (AES-256-GCM envelope decryption) for exactly one tick.
  • Decrypted tokens are never logged, persisted, or returned in responses.
  • The X-Container-Auth secret is injected via Cloudflare Secrets and never stored in code.
File Role
wrangler.toml Cron trigger config (crons = ["*/5 * * * *"])
index.mjs scheduled() handler, webhook handler, export default with fetch + scheduled
lease-do.mjs LeaseDO with /internal/reconcile (cron path) and /event (webhook path)
shunt-container.mjs ShuntContainer.dispatch() — container invocation
encrypt.mjs Envelope encryption/decryption
cron.test.mjs JS tests for scheduling (moved to tests/unit/cron.test.mjs)
tests/unit/cron.test.mjs JS tests for scheduling
tests/unit/cron/cron_test.go Go tests for cron handler