Skip to content

Container Reconcile Handler — Contract & Safety Boundaries

Container Reconcile Handler — Contract & Safety Boundaries

Section titled “Container Reconcile Handler — Contract & Safety Boundaries”

cmd/gondolier/container is a standalone Go HTTP service that exposes a single reconcile endpoint (POST /internal/reconcile) for triggering a shunt merge queue reconciliation for a tenant’s repository. It runs inside Cloudflare Containers as the WASI execution boundary described in docs/architecture/shunt-container-execution.md.

Aspect Detail
Path POST /internal/reconcile
Method POST only — all other methods return 405 Method Not Allowed
Max body 1 KiB — larger bodies return 400 Bad Request
Encoding Strict JSON — DisallowUnknownFields rejects unknown keys
Content-Type Response is always application/json
type ReconcileRequest struct {
TenantID string `json:"tenant_id"` // required
RepoSlug string `json:"repo_slug"` // required, format "owner/name"
BaseBranch string `json:"base_branch"` // required
QueueName string `json:"queue_name"` // required
InstanceURL string `json:"instance_url"` // required, must be https://…
Token string `json:"token"` // required
}
type ReconcileResponse struct {
Status string `json:"status"` // "ok" or "error"
Errors []string `json:"errors,omitempty"`
}
Condition Status
Valid request, engine succeeds and result has no errors 200
Malformed / unknown fields / oversized body 400
Missing required field / invalid slug / non-https URL 400
Method not POST 405
Engine error or reconcile result with non-empty Errors 500
Unknown route 404
  • Token is request-only and ephemeral. It is never:
    • Persisted to disk or database
    • Written to logs
    • Included in any response
    • Reflected in error messages
  • The token is passed to engine.Config.Token exactly once and never re-read after that single call.
  • Engine and reconcile errors are genericised — no raw error strings leak to the response body.

All engine errors become a uniform {"status":"error","errors":["reconciliation failed"]} response at 500 Internal Server Error. The raw engine error message, stack traces, and any token data are never included in the HTTP response.

A non-nil reconcile result with non-empty Errors (but no Go error) is treated the same way — the handler returns 500 with the same generic message. The result.Errors slice is never logged, persisted, or echoed back. This is intentional: raw engine error strings may contain credential information or internal implementation details that must not leave the service boundary.

  1. Method check (POST only)
  2. Body size cap (1 KiB via http.MaxBytesReader)
  3. Strict JSON decode (DisallowUnknownFields)
  4. Required field presence (6 fields)
  5. InstanceURL must be a valid https:// URI with a non-empty host
  6. RepoSlug must be exactly owner/name (split on first /, both parts non-empty)

The handler constructor accepts a reconcileFn parameter:

func reconcileHandler(reconcile reconcileFn) http.HandlerFunc

When nil (production), it calls engine.New(cfg).Reconcile(ctx) exactly once. When non-nil (tests), it invokes the provided function, making the handler deterministic and independent of external services.

GET /healthz returns 200 OK with {"status":"ok"}. No other routes exist in this service — no index route, no dashboard, no Worker JS integration.

This handler is the entry point for the container boundary. It:

  1. Validates and sanitises the incoming request
  2. Maps the request into an engine.Config
  3. Delegates to the shunt engine via engine.New(cfg).Reconcile(ctx)
  4. Returns a safe response with no credential leakage

The existing document docs/architecture/shunt-container-execution.md covers the broader Worker → Container orchestration flow, lease coordination, and cron/webhook triggers. This doc focuses on the single HTTP contract and its safety boundaries.