-
Notifications
You must be signed in to change notification settings - Fork 26
feat(gastown): Hosted Wasteland service — managed commons with Kilo auth/billing proxy #1810
Description
Summary
Offer a hosted Wasteland service on the Kilo platform. Users can create, manage, and participate in Wastelands with a Kilo-native UI and per-wasteland infrastructure that sleeps when not in use.
Revised 2026-03-31 (v4): Simplified architecture. We build our own UI and container image rather than deploying the Wasteland's hosted Go server. No
@nangohq/frontenddependency, no cookie bridging, no SPA routing issues. Per-wasteland containers (like towns), not multi-tenant.
Architecture
We build a Kilo-native Wasteland service — our own UI, our own Worker, our own Container image. The Container includes the wl CLI binary for Wasteland protocol operations (browse, claim, done, sync, post). The wl CLI handles the DoltHub fork-push-PR cycle. We do NOT run wl serve --hosted.
Browser (Kilo Next.js app)
↓ standard Kilo JWT auth
Cloudflare Worker (wasteland-proxy)
├── tRPC or REST API for wasteland management
├── Kilo auth (JWT validation, org membership)
├── Billing metering
└── Forwards protocol operations to Container
↓
WastelandDO (Durable Object — one per wasteland)
├── Wasteland metadata (name, owner, org, visibility)
├── Member list + credential storage (encrypted DoltHub tokens)
├── Cached wanted board state (polled from DoltHub)
└── Alarm loop for periodic sync
↓
WastelandContainerDO (Container — one per wasteland, sleeps when idle)
├── `wl` CLI binary (~50MB)
├── Pre-configured: `wl join` during onboarding
├── Protocol operations: `wl browse`, `wl claim`, `wl done`, `wl post`, `wl sync`
├── Env: DOLTHUB_TOKEN (from WastelandDO credential store)
└── sleepAfter: 30m (sleeps when wasteland is not in use)
Why NOT wl serve --hosted
The previous versions of this issue proposed running the Wasteland's hosted Go server. We're not doing that because:
- We build better UI — our Next.js app with Kilo's design system is a better experience than the embedded React SPA
- No Nango dependency — the hosted mode uses
@nangohq/frontendin the browser which we'd need to fork. Instead, we store DoltHub tokens ourselves. - Per-wasteland containers — the hosted mode is multi-tenant (one process for all wastelands, never sleeps). We want per-wasteland containers that sleep when idle, matching the Gastown town pattern.
- Simpler credential model — users provide DoltHub tokens in our settings UI. We store them encrypted in the WastelandDO. No Nango, no OAuth delegation, no session cookies.
The wl CLI is the only Wasteland binary we need. It handles the DoltHub protocol (fork, push, PR, sync) without running a server.
Container Image
Build our own container image with the wl CLI:
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y curl git ca-certificates jq && rm -rf /var/lib/apt/lists/*
# Install Wasteland CLI (remote mode — no Dolt needed)
RUN curl -fsSL https://github.com/gastownhall/wasteland/releases/download/v0.3.0/wasteland_0.3.0_linux_amd64.tar.gz | tar xz -C /usr/local/bin/
# Lightweight control server for receiving commands from the WastelandDO
COPY control-server /app/control-server
CMD ["/app/control-server"]The control server is a small HTTP server (~200 lines) that receives commands from the WastelandDO and executes wl CLI commands:
POST /wl/browse → wl browse --json
POST /wl/claim → wl claim <id>
POST /wl/done → wl done <id> --evidence <url>
POST /wl/post → wl post --title ... --description ...
POST /wl/sync → wl sync
POST /wl/join → wl join <upstream>
POST /wl/status → wl status <id>
GET /health → check wl + dolt availability
Container Lifecycle
- Per-wasteland: One
WastelandContainerDOper wasteland (same pattern asTownContainerDO) sleepAfter: 30m: Container sleeps when not in use. Wakes on next API call.- Cold start:
wlCLI needs a configured local clone. On first start (or after eviction): runwl join <upstream>to set up. Cache in the Container's ephemeral disk. Re-join on eviction (~10-30s). - State on disk:
~/.config/wasteland/(wl config) +~/.local/share/wasteland/<upstream>/(local Dolt clone). Ephemeral — reconstructed from DoltHub on cold start.
Coding Conventions
Copy cloudflare-gastown/AGENTS.md to cloudflare-wasteland/AGENTS.md as the starting point for coding conventions. The gastown AGENTS.md covers file naming, Durable Object patterns (sub-modules, import * as X), IO boundary validation (Zod), column naming (entity_id), SQL query patterns (type-safe query() helper, table interpolators), and HTTP route conventions (Hono, handler modules). All of these apply equally to the wasteland service.
Credential Storage
No Nango. DoltHub tokens stored encrypted in the WastelandDO:
// WastelandDO SQLite
type WastelandCredential = {
user_id: string; // Kilo user ID
dolthub_token: string; // Encrypted DoltHub API token
rig_handle: string; // User's rig handle on this wasteland
dolthub_org: string; // User's DoltHub org (for fork)
connected_at: string;
};When executing wl commands in the Container, the WastelandDO:
- Resolves the user's encrypted token from its SQLite
- Decrypts it
- Passes it to the Container as a request header or env var for the specific operation
The Container's control server injects DOLTHUB_TOKEN for each wl CLI invocation.
UI (Kilo-native)
Built in the Kilo Next.js app, not the Wasteland's embedded SPA.
Pages
/gastown/wastelands— list user's wastelands (personal)/organizations/:orgId/gastown/wastelands— org wastelands/gastown/wastelands/new— create a new wasteland/gastown/wastelands/:wastelandId— wasteland dashboard:- Wanted board — browse, filter, claim items
- My claims — items claimed by this user/org's rigs
- Completions — published completions and their validation status
- Members — rig registry, invite, manage trust levels
- Settings — name, visibility, DoltHub connection, danger zone
Design
Follow the Gastown town UI design language: dark surfaces, information-dense cards, same typography and color system. The wanted board should feel like the bead list in a town — compact cards with status pills, priority indicators, and type badges.
Authorization Model
Ownership
Same model as Gastown towns:
type WastelandOwnership =
| { type: 'user'; userId: string } // Personal wasteland
| { type: 'org'; orgId: string } // Org-owned wastelandAccess Resolution
Follow the resolveTownOwnership pattern (router.ts:141-188):
async function resolveWastelandOwnership(env, ctx, wastelandId): Promise<WastelandOwnershipResult> {
const config = await getWastelandDO(env, wastelandId).getConfig();
if (config.owner_type === 'user') {
if (config.owner_user_id !== ctx.userId) {
if (ctx.isAdmin) return { type: 'admin' };
throw new TRPCError({ code: 'FORBIDDEN' });
}
return { type: 'user', userId: ctx.userId };
}
if (config.owner_type === 'org') {
const membership = getOrgMembership(ctx.orgMemberships, config.organization_id);
if (!membership || membership.role === 'billing_manager') {
if (ctx.isAdmin) return { type: 'admin' };
throw new TRPCError({ code: 'FORBIDDEN' });
}
return { type: 'org', orgId: config.organization_id };
}
if (ctx.isAdmin) return { type: 'admin' };
throw new TRPCError({ code: 'NOT_FOUND' });
}Per-Operation Authorization
| Operation | Who | Notes |
|---|---|---|
| Create wasteland | Any Kilo user or org admin | Personal or org-owned |
| Delete wasteland | Owner or org admin | Irreversible |
| Manage settings | Owner or org admin | Visibility, etc. |
| Invite members | Owner or org admin | |
| Browse wanted board | Any member | Public wastelands: any Kilo user |
| Post wanted items | Any member | |
| Claim items | Any member | |
| Submit completions | The claiming member | Enforced by Wasteland protocol |
| Accept/reject completions | Owner or org admin (registered as maintainer) | Issues reputation stamps |
| Connect a Kilo town | Any member who owns the town |
Organization Support
- Org-owned wastelands bill to the org
- Org members (except
billing_manager) auto-granted access - Org admins registered as trust_level 3 maintainers on the commons
- Cross-org: Org A's town can participate in Org B's wasteland if the user is a member of both
Wasteland Creation Flow
Creating a new wasteland requires initializing the Dolt database with the v1.2 schema. Two options:
Option A: Template fork (recommended)
Pre-create a template wasteland on DoltHub (kilo-wastelands/wl-template) with the v1.2 schema. Creating a new wasteland = fork this template via the DoltHub REST API. Instant (pointer-based fork).
Option B: CLI initialization
Run wl create in the Container, which uses dolt to init the schema locally and push to DoltHub. Takes 10-30 seconds.
Recommendation: Option A. The fork is instant and doesn't require the Container to be running during creation.
DoltHub Relationship
DoltHub API rate limits are not publicly documented. At scale, we should cultivate a partnership with the DoltHub team for:
- Higher rate limits for Kilo's service account
- Potentially a dedicated DoltHub instance or API tier for hosted wastelands
- Schema migration coordination when the Wasteland protocol evolves
- Template repo management (
kilo-wastelands/wl-template)
Implementation Plan
Phase 1: Infrastructure (Week 1)
- Create
cloudflare-wasteland/directory in the cloud monorepo - Copy
cloudflare-gastown/AGENTS.mdtocloudflare-wasteland/AGENTS.md(coding conventions) -
WastelandDO— metadata storage, credential storage, alarm loop -
WastelandContainerDO— per-wasteland Container class - Container Dockerfile with
wlCLI + Dolt + lightweight control server - Control server: HTTP endpoints that execute
wlCLI commands - Credential storage: encrypted DoltHub tokens in WastelandDO SQLite
-
wrangler.jsoncconfiguration (Worker + DO + Container bindings)
Phase 2: Core API + Onboarding (Week 2)
- tRPC router:
createWasteland,listWastelands,deleteWasteland,getWasteland -
resolveWastelandOwnershipwith user, org, and admin support - Template fork provisioning on DoltHub
- DoltHub token input + validation in wasteland settings
- Rig registration:
wl joinin Container during onboarding - Wanted board polling: WastelandDO alarm reads DoltHub REST API, caches results
Phase 3: UI + Operations (Week 3)
- Wasteland list page (personal + org)
- "New Wasteland" creation wizard
- Wasteland dashboard: wanted board, claims, completions, members, settings
- Claim, post, and done operations via control server →
wlCLI - Invite flow for members
- Billing metering
Phase 4: Town Integration + Polish (Week 4)
- Fast-path: Kilo town connecting to Kilo-hosted wasteland (no manual token entry)
- Mayor tools from Integrating with the Wasteland — Dolt sync + DoltHub onboarding #1040 (
gt_wasteland_browse, etc.) work against hosted wastelands - End-to-end test: create wasteland → connect town → claim → complete → publish
- Container eviction handling (re-join on cold start)
- Health monitoring
- Documentation
Key Architectural Decision: Remote Mode + Hybrid Reads
No Dolt in the Container
The wl CLI has a remote mode that uses the DoltHub REST API for all operations. No local Dolt clone, no Dolt binary needed. Cold start = boot Container (~3s) + wl join if needed (~2s) = ~5 seconds. No disk state beyond a tiny config file.
Hybrid: Reads from DO, Writes from Container
- Read operations (browse, status, poll): Direct DoltHub SQL API calls from the WastelandDO. No Container needed. Instant, no cold start.
- Write operations (claim, done, post): Via Container
wlCLI. The CLI handles branch naming, PR creation, idempotency, and protocol validation.
Users can browse the wanted board instantly without waking the Container. The Container only starts when they act.
Operational Details
Control Server (~450 lines)
8 endpoints x ~40 lines + health check + token injection + serial queue + logging. TypeScript/Bun or Go.
Mutation Verification
wl claim/wl done may silently no-op (SQL WHERE matches 0 rows). The control server must re-query DoltHub after mutations to verify state change.
Container Config
instance_type: standard-2 (lighter than Gastown). max_instances: 200. sleepAfter: 30m.
Heartbeat
60s interval. Reports wl version, last operation timestamp. WastelandDO uses for crash detection.
Version Pinning
Pin wl to specific release in Dockerfile. Health check reports version. Monitor upstream releases. Rebuild on new release.
Error Handling
DoltHub down: retry with backoff, 503 to UI. Claim race: re-query, return "already claimed." Fork drift: wl sync, conflicts at PR merge time. Container eviction mid-op: retry on next tick.
Wasteland Deletion
Soft-delete in DO. Stop Container. DoltHub fork deletion not automatable (manual/orphaned). Member data retained 30 days.
Rate Limiting
Per-user: claim 10/min, done 10/min, post 5/min, browse 60/min.
Observability
wasteland_events Analytics Engine dataset. 100% Workers Observability sampling. Structured JSON logs. Health endpoint in control server.
Acceptance Criteria
Phase 1-2 (MVP)
-
WastelandDOwith metadata + credential storage -
WastelandContainerDOwith per-wasteland Container lifecycle - Container image with
wlCLI + Dolt + control server -
sleepAfter: 30m— Container sleeps when wasteland is idle - Encrypted DoltHub token storage in WastelandDO
- Template fork provisioning for new wastelands
- Rig registration via
wl joinin Container - Wanted board polling via DoltHub REST API
-
resolveWastelandOwnershipsupporting user, org, and admin - Org members auto-granted access
- Org admins registered as trust_level 3 maintainers
Phase 3-4 (Production)
- Kilo-native UI: wasteland list, dashboard, wanted board, settings
- Claim, post, done operations end-to-end
- Town→hosted-wasteland fast-path integration
- Billing metering
- Container eviction recovery (re-join on cold start)
- Health monitoring
- End-to-end: create → connect town → claim → complete → publish
References
- Integrating with the Wasteland — Dolt sync + DoltHub onboarding #1040 — Wasteland integration (town-side: connecting to and participating in wastelands)
- Wasteland CLI:
wasteland/cmd/wl/— browse, claim, done, post, sync, join, status commands - Wasteland schema:
wasteland/schema/commons.sql(v1.2, 9 tables) - DoltHub REST API:
wasteland/internal/backend/remote.go— the API our WastelandDO alarm uses for polling - Gastown TownContainerDO pattern:
cloudflare-gastown/wrangler.jsonc— per-resource Container lifecycle - Gastown auth pattern:
cloudflare-gastown/src/trpc/router.ts:141-188—resolveTownOwnership