Documentation Index
Fetch the complete documentation index at: https://docs.cowboy.inc/llms.txt
Use this file to discover all available pages before exploring further.
Companion to: CIP-15: Public Asset Hosting. This is not a normative CIP; it is an implementation handbook.
Audience: Gateway implementers
Status: Living document
0. Mental model
The Gateway is a stateless HTTP server with two caches:- Resolves the actor from the host header (CIP-14 Route Registry — already implemented).
- Looks up the cached
Routesfor that actor. - Picks a
(verb, path)winner (CIP-15 §6.6). - Dispatches: serve from a CBFS volume (static), or invoke a named handler via existing CIP-14 RPC (dynamic). Optionally gates on payment per CIP-18.
1. Build order
Implement in this order. Each phase is independently shippable.| Phase | Scope | Depends on |
|---|---|---|
| P1 | Routes fetch + method-target dispatch | CIP-14 (existing) |
| P2 | Static volume serving (Volume targets) | CIP-9 GET_MANIFEST/GET_SHARD |
| P3 | Runtime mutability (state_root re-poll) | P1 |
| P4 | Payment gating (pays = caller) | CIP-18 |
| P5 | CORS, conditional requests, compression | P2 |
| P6 | Hedging, parallel fetch, optimization | P2 |
http.request” CIP-14 behavior with verb-aware routing. Worth shipping that first and proving the core loop end-to-end before tackling static.
2. Phase 1 — Routes fetch + method dispatch
2.1 New types
2.2 New RPC: GET_STATE
CIP-15 §8.12 describes the shape. Before writing the Gateway side, confirm with the runtime team:
- Does Cowboy’s state machine already expose a verifiable KV-with-proof read RPC against a Runner?
- If yes: wire to it. If no: this CIP needs to spec it; coordinate with runtime team to add it. (It is cheap — a single Merkle path proof against the actor’s state root.)
2.3 Routes fetch loop
- The poll interval is shared with the volume
manifest_rootpoll — one polling task can refresh both anchors per actor. - Empty value (actor never wrote
__cowboy/routes) → not an error. Cache an empty Routes; dispatch falls back to CIP-14 unchanged. Existing actors continue to work. - Failures are sticky in the worst sense: if validation fails, the previously-cached table stays in use. That’s the safe behavior.
2.4 The resolver
This is the function that gets called on every request:{name} (single segment, captured), and *name / * (zero+ segments, captured if named).
2.5 Dispatch — Method target, pays = actor
This is the “easy” path for P1: it reuses CIP-14 dispatch end-to-end. The only change is the handler name comes from the matched route, not always "http.request":
http.request. Now the Gateway sends to route.target.name. Confirm with the runtime team that named-handler dispatch on the CIP-14 query and command paths is supported. (It already should be — that’s how non-HTTP message handlers work.)
2.6 Tests for P1
Unit:- Resolver: every branch in CIP-15 §6.6 — priority ties, longest-prefix ties, ANY vs concrete verb, Method-vs-Volume tie, empty routes table, reserved paths.
- Validation: malformed CBOR, oversized table, unknown volume, missing price for
caller, invalid status codes, reserved path prefix. - Path matching: literals,
{id},*rest, edge cases (trailing slashes, empty segments, encoded chars).
- Deploy a sample actor with two routes:
GET /a → method_a,POST /b → method_b. curl GET /a→ handlermethod_aruns.curl POST /b→ handlermethod_bruns.curl GET /unknown→ 404.- Mutate the routes table at runtime, wait one poll interval, verify dispatch follows the new table.
- Have the actor return zero routes; verify CIP-14 fallback (everything hits
http.request).
- One real actor, one Gateway, one Runner. Run the resolver under load (e.g.,
wrk -t4 -c100 -d30s http://actor.cowboy.network/api/foo). Watch for cache miss thrashing if the poll interval is wrong.
3. Phase 2 — Static volume serving (Volume targets)
The static-asset serving path is independent of the routes-resolver — once aVolume target wins resolution, the Gateway’s job is just to fetch and serve the object.
3.1 Components
- Per-volume metadata cache (CIP-15 §8.2 Layer 1a): manifest, content_types, cache_config, cors_config.
- Object cache (Layer 2): bounded LRU, keyed by
(volume_id, object_path). manifest_rootpolling for invalidation (CIP-15 §8.3).GET_MANIFEST+GET_SHARDRPCs (CIP-15 §8.4).- Reed-Solomon reconstruction + BLAKE3 integrity chain (CIP-15 §8.5–§8.6).
- Hedged parallel fetch (CIP-15 §8.7).
3.2 Volume-target dispatch
get_object is the existing P2 plumbing — cache check, manifest lookup, parallel shard fetch, RS reconstruct, integrity verify. No changes needed there.
3.3 Tests for P2
- Volume mount serves from the right
volume_nameand appliesstrip_prefix/volume_path_prefixcorrectly. - Fallback path (SPA case):
GET /aboutwith noaboutobject → servesindex.htmlwith status 200. - 404 fallback:
GET /missing.pngwith no fallback configured → 404. - 405 on
POST /assets/foo.png(volume route, non-GET). - Integrity chain: corrupted shard → fetch from another node and verify; if all corrupt → 502.
4. Phase 3 — Runtime mutability
This is mostly free once P1 is in. The poll loop already detects state_root changes and reloads. Two extra concerns:4.1 Rate-limit enforcement
The runtime — not the Gateway — enforcesMIN_ROUTES_UPDATE_INTERVAL_BLOCKS. The Gateway only sees the result (the next valid state_root). But the Gateway should:
- Log routes-table churn (rate of state_root changes per actor) for ops visibility.
- Treat rapid back-to-back updates as expected noise; don’t add Gateway-side rate limits.
4.2 Validation-rejected updates
If an actor writes a malformed routes value:- Validation fails (§6.8).
- The Gateway logs a warning, keeps the previously-cached table, and continues serving from it.
- Next poll: if the actor fixes it, replace. If not, keep serving stale.
4.3 SDK helpers vs wholesale rewrites
The Gateway doesn’t distinguish: any write that advances the state_root and changes__cowboy/routes is just “a new table.” The narrow mutators (§6.9) are an SDK convenience. From the Gateway’s perspective they’re all the same.
5. Phase 4 — pays = caller (CIP-18)
CIP-18 may not be ready when this CIP ships. That’s fine: the schema lands now, enforcement waits. Two options:
Option A: Accept the schema, reject the requests. If a route has pays = "caller" and CIP-18 isn’t implemented, the Gateway returns 501 Not Implemented with X-Cowboy-Reason: cip18-required. Document this clearly so actor authors don’t ship paid endpoints expecting them to work.
Option B: Validate-only. The Gateway accepts schemas with pays = "caller" but treats them as if they were pays = "actor" until CIP-18 lands. This is more forgiving but lets actors ship endpoints that quietly behave differently than declared. Don’t do this.
Once CIP-18 lands, the Method-target dispatch path adds:
6. Phase 5 — CORS, conditional requests, compression
Per CIP-15 §9 / §8.9 / §8.10. Note the precedence rule in §9.4: forMethod-target routes, actor-set Access-Control-* headers in the response envelope are authoritative — the Gateway only fills in CORS from cors_config when the actor sets none. For Volume-target routes, the Gateway applies cors_config rules; the default permissive CORS policy applies when no _meta/cors.json exists.
7. Phase 6 — Optimization
After correctness:- Hedged parallel shard fetch (CIP-15 §8.7).
- Compressed variants in the cache.
- Cache warming on actor registration.
- Coalescing concurrent requests for the same object.
8. Operational surface
What ops needs to see:- Per-Gateway: cache hit ratio (object, metadata, routes), cache size, evictions.
- Per-actor: requests/s, dispatch breakdown (volume vs method, by verb), routes-table state_root churn rate.
- Per-route: hit count, p50/p95/p99 latency.
- Errors: validation failures (per actor), proof failures, 502s from integrity failures.
X-Cowboy-Source header ("static" or "dynamic") on responses already gives clients a debugging signal.
9. Open questions (confirm before merging the PR)
GET_STATERPC. Does it exist? If not, who specs it — this CIP, or a sibling CIP?- Path-param delivery. This CIP specifies the Gateway extracts
path_paramsand passes them in theHttpRequestEnvelope. Confirm with the runtime team that the envelope schema can carry them, or extend it. - Named-handler dispatch. Confirm the runner-side dispatcher accepts arbitrary handler names on the CIP-14 query and command paths (it should — that’s how non-HTTP messages work — but verify).
- CIP-18 readiness. When does it land? Ship Phase 4 with it or as a follow-up?
- Rollout. Is there an existing actor that’s a good first canary?
17-hn-feed? A toy actor in a devnet?
10. What success looks like
- Existing actors keep working with zero changes.
- Actors that write a
__cowboy/routesvalue get verb-aware dispatch + named handlers + per-route payment policy without redeploys. - Static asset serving meets CDN-class latency for cache hits (sub-10ms p99 from a warm Gateway).
- Routes updates land in Gateway caches within 6 seconds of commit.
- A FastAPI/axum/Hono developer can read a CIP-15 actor and immediately understand what it does.

