Skip to content
Esc
navigateopen⌘Jpreview
On this page

Switchboard v0.2.0

Rules can finally look things up — allowlists, redirect maps, cohort tables, typed config — without giving up a shred of the determinism that makes them safe. And the three ways to run Switchboard (standalone, Caddy, embedded) now behave the same, down to the metrics.

A smaller release than v0.1.0, but it breaks the guest ABI again (v3v4). This time the migration is nothing but a recompile: the ABI only gained functions, so there are no source changes to make — rebuild your rules with the new SDK and you’re done.

Highlights

Rules can look things up

Ship read-only files alongside a rule and read them at request time:

my-rules/
  rule.go
  data/allowlist.txt      # picked up automatically, or --data ./dir
func Handle(req sdk.Request) sdk.Action {
	if sdk.DataSet("allowlist.txt").Contains(req.ClientIP()) {
		return sdk.Next()
	}
	return sdk.Deny(403).WithReason("not-allowlisted")
}

Data files are embedded at build, hashed into the bundle’s identity, and digest-verified on every read — so changing a lookup table ships as a new immutable bundle that passes its embedded tests before activating and rolls back like any code change. A decision is still a pure function of the request, so replay stays exact. Total size is bounded by max_data_bytes (default 4mb), enforced at build and as an activation gate.

One accessor per format, and you only pay for what you use

type Flags struct {
	Maintenance  bool     `json:"maintenance"`
	BlockedPaths []string `json:"blocked_paths"`
}
var f Flags
if err := sdk.DataJSON("flags.json", &f); err != nil { /* ... */ }

DataJSON, DataJSONL, DataTOML, DataCSV, DataSet, DataLines, DataMap, DataBytes, DataString. Each parser is linked into the guest only when its accessor is called — a DataSet-only rule stays ~50 KB while a DataTOML rule links the TOML parser and grows. A missing file is never an error. (LoadTestData injects data for host-side rule tests. YAML is intentionally absent — yaml.v3 fails at runtime under TinyGo; use JSON or TOML.)

serve reaches parity with Caddy

The standalone binary was missing two-thirds of the engine’s knobs. No longer:

switchboard serve --upstream localhost:3000 --registry file://./registry \
  --fail-mode last_good --fallback-fail-mode closed \
  --memory-limit 64mb --max-response-body 16kb \
  --pool-autoscale on --min-pool-size 8 --max-pool-size 64

Every Caddyfile directive now has a matching serve flag (kebab-case), resolved through the same validation. serve also stops silently dropping rule metadata — decisions are surfaced as structured log fields, matching Caddy.

Metrics and status, whatever the adapter

The Caddy handler used to record zero switchboard_* metrics — its request path bypassed the instrumented middleware entirely. Now the same collectors back both adapters:

switchboard {
	registry s3
	channel prod
	status_path /switchboard/status
}

Caddy registers switchboard_invocations_total, switchboard_pool_instances, switchboard_activation_total and the rest on its own /metrics, and serves the status JSON at status_path. Standalone and embedded expose the identical metric set and a shared StatusHandler.

Breaking changes

  1. ABI v4 — rebuild required, no code changes. Hosts reject bundles built with an older SDK (clear error at activation). Unlike v3, nothing in your rules changes: switchboard build with the current SDK is the whole migration.

New examples

ip-allowlist (DataSet membership from a bundled list) and feature-flags (typed struct config via DataJSON) — each with a behavioral test suite that runs at activation. New guide: Bundle data files.

Last updated on July 20, 2026

Was this page helpful?