Shunt Scheduling
Shunt Scheduling
Section titled “Shunt Scheduling”Architecture
Section titled “Architecture”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 LeaseTwo sources of truth
Section titled “Two sources of truth”| 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. |
Cron (correctness source)
Section titled “Cron (correctness source)”The scheduled() handler in index.mjs is the correctness source:
- Enumerates all managed repos from Supabase (
managed_repostable) - For each repo, fetches its forge connection to get
instance_urlandencrypted_token - Calls the LeaseDO’s
POST /internal/reconcileendpoint with:X-Container-Authheader (fromCONTAINER_AUTH_SECRETCloudflare Secret)- Encrypted token (never decrypted by the Worker)
- Repo config (tenant ID, repo name, instance URL, base branch)
- The LeaseDO acquires a per-tenant/repo lease, decrypts the token in-memory, dispatches to the Go Container via
ShuntContainer, and releases the lease - If the lease is already held (409 Conflict), the cron job skips that repo and retries on the next tick
- Errors are counted but never crash the handler — the next cron tick will retry
Webhook (optional low-latency wake)
Section titled “Webhook (optional low-latency wake)”The POST /api/v1/webhooks/forgejo endpoint is an optional latency optimization:
- Receives a Forgejo webhook payload (push, pull_request, check_run, status)
- Verifies HMAC signature using the tenant’s
webhook_secret - Filters out bot events (e.g., mq-bot events)
- Routes the event to the LeaseDO’s
POST /eventendpoint - The LeaseDO processes the event (queue join/leave/re-evaluate) and triggers an engine tick
- Unlike cron, the webhook does not call
/internal/reconcile— it calls/eventwhich processes the specific event
Why webhook is optional
Section titled “Why webhook is optional”- 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
Lease semantics
Section titled “Lease semantics”Exactly-once execution is guaranteed by the LeaseDO lease mechanism:
- Acquire — atomic compare-and-set with TTL (30 seconds). Succeeds only if no lease is held or lease has expired.
- Dispatch — execute the container call (cron) or process the event (webhook)
- Release — clear the lease (runs in a
finallyblock, 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.
Fail-safe guarantees
Section titled “Fail-safe guarantees”| 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 |
Credential safety
Section titled “Credential safety”- 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-Authsecret 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 |
Related docs
Section titled “Related docs”docs/architecture/container-dispatch.md— Container dispatch auth and contractdocs/architecture/container-reconcile.md— Go container endpointdocs/architecture/shunt-container-execution.md— Worker → Container orchestration