Project: webhost.systems (v1) Focus: Slice B — Control plane (Convex) + Cloudflare runtime path + Dashboard (Vite + Clerk) Owner: Engineering
Implemented a greenfield, spec-aligned baseline that supports the v1 “Slice B” E2E demo flow:
Control plane (Convex):
Tenant-isolated agent CRUD
Immutable deployments with agents.activeDeploymentId routing pointer
Cloudflare deploy orchestration (server-side provider calls via internal action)
Invocation gateway: non-streaming + SSE endpoint (streaming currently emulated)
Signed telemetry ingestion: deployment-scoped HMAC verification over raw bytes + ownership cross-check
Added dashboard-support read endpoints: deployments list, current usage, recent telemetry events
Dashboard (Vite + React + Clerk):
Clerk auth integrated using @clerk/clerk-react@latest
Calls control plane endpoints with Authorization: Bearer <JWT>
UI flows: create agent → deploy → invoke (non-streaming + SSE) → view deployments/usage/recent telemetry
This establishes a functioning control-plane/data-plane boundary and the core invariants from the spec (tenant isolation, deployment immutability, invoke routing via active pointer, telemetry integrity model).
Location:
WebHost.Systems/apps/control-plane
Highlights:
convex/schema.ts: initial v1 control-plane schema:
users, agents, deployments, metricsEvents, billingUsage
optional: subscriptions, auditLog
convex/auth.config.ts: Clerk authentication configuration via CLERK_JWT_ISSUER_DOMAIN
convex/http.ts: HTTP API router for Slice B endpoints
Notes:
Convex _generated/ types are not committed; generated via dev/codegen.
Cloudflare adapter that:
uploads Worker module code
injects deployment-scoped TELEMETRY_SECRET as a Worker secret binding
injects per-deployment metadata (report URL, attribution ids) as secret bindings for simplicity in Slice B
Deterministic Worker template:
Implements invoke/v1 (text-first)
Emits signed telemetry asynchronously (best-effort, does not block invocations)
Telemetry ingestion endpoint validates:
X-Telemetry-Deployment-Id
X-Telemetry-Signature: v1=<hex(hmac_sha256(raw_bytes))>
ownership cross-check {userId, agentId, deploymentId} against deployments table
Ingestion writes append-only metricsEvents with dedupe:
preferred key: (deploymentId, eventId)
fallback: (deploymentId, traceId)
Location:
WebHost.Systems/apps/web
Key points:
Vite + React + TypeScript
Clerk auth wrapper in src/main.tsx
UI uses <SignedIn>, <SignedOut>, <SignInButton>, <SignUpButton>, <UserButton>
Calls the control plane base URL from VITE_CONTROL_PLANE_URL
Uses VITE_CLERK_PUBLISHABLE_KEY
All control-plane endpoints require auth via Authorization: Bearer <JWT> unless noted.
GET /v1/agents
POST /v1/agents
GET /v1/agents/:agentId
PATCH /v1/agents/:agentId
POST /v1/agents/:agentId/disable
DELETE /v1/agents/:agentId
POST /v1/agents/:agentId/deploy
moduleCode optional; if omitted, deploys built-in deterministic worker template
GET /v1/agents/:agentId/deployments (dashboard support)
POST /v1/agents/:agentId/deployments/:deploymentId/activate
POST /v1/invoke/:agentId (non-streaming)
POST /v1/invoke/:agentId/stream (SSE; currently emulated)
POST /v1/telemetry/report
validates signature + ownership mapping
GET /v1/usage/current
GET /v1/metrics/recent?agentId=...&sinceMs=...&limit=...
Server-side mapping from Clerk identity → internal users._id
Ownership checks enforced on agent/deployment/metrics reads and writes
New deployment row per deploy attempt
Invocation routing via agents.activeDeploymentId only
Rollback implemented as activation endpoint (pointer flip)
Gateway expects invoke/v1 and forwards to runtime
SSE endpoint exists with ordered event types (meta → delta* → usage? → done|error)
Implementation is emulated streaming for now (buffered upstream)
Deployment-scoped signing secret injected into data plane
HMAC verification uses raw body bytes
Ownership cross-check enforced
Provider boundary exists (Cloudflare implemented)
AgentCore not implemented yet (planned later)
Implemented request-limit hard-stop (pre-invocation) and runtime gating foundations (tier entitlements mapping).
Requests are enforced pre-invoke via a reservation counter; tokens/compute remain post-charge via telemetry/aggregation (subsequent blocking not yet implemented).
Billing provider integration (checkout + webhook-driven tier updates) is still not implemented.
No plaintext secrets stored for customer secrets (not yet implemented)
Telemetry signing secret is stored encrypted-at-rest in Convex to allow verification (Cloudflare secrets are write-only, so control plane cannot retrieve secret values later).
This is consistent with “no plaintext secrets in DB” but is a pragmatic deviation from “store only provider secret references” ideal.
Future option: move telemetry secret storage to a dedicated secret store / KMS-backed vault so the DB stores only references.
CLERK_JWT_ISSUER_DOMAIN
TELEMETRY_SECRETS_ENCRYPTION_KEY (32 bytes key material; encoding per implementation)
CLOUDFLARE_ACCOUNT_ID
CLOUDFLARE_API_TOKEN
CLOUDFLARE_WORKERS_DEV_SUBDOMAIN
CONTROL_PLANE_TELEMETRY_REPORT_URL (public URL to /v1/telemetry/report)
VITE_CLERK_PUBLISHABLE_KEY
VITE_CONTROL_PLANE_URL (Convex HTTP base URL)
Aggregation job: compute billingUsage from metricsEvents (idempotent, recomputable).
Limits & runtime gating:
enforce request limits pre-invocation
gate AgentCore runtime by tier (defense in depth)
Write-only secrets API for agent env vars (no plaintext in DB, provider injection).
Cloudflare Durable Objects session mapping:
treat sessionId as opaque externally but map to DO ids internally.
True streaming:
convert data plane invocation to real streaming if/when supported; map to SSE deltas.
Hardening:
tighter CORS allowlist
richer audit logs for telemetry rejects and deploy failures
reconcile partial deploy failures (orphan cleanup by tags)
Sign into dashboard (Clerk).
Create agent.
Deploy (Cloudflare).
Invoke:
non-streaming: /v1/invoke/:agentId
SSE: /v1/invoke/:agentId/stream
Confirm telemetry events appear in:
/v1/metrics/recent?agentId=...
Confirm deployments list:
/v1/agents/:agentId/deployments
Confirm rollback pointer flip:
activate older deployment and re-invoke.
Additional progress completed after the earlier log was written:
Implemented pre-invocation request-limit hard-stop in the invocation gateway:
Added a new requestUsageCounters table (per-user, per-period) to support a fast counter.
Added an internal reservation mutation that validates {userId, agentId, deploymentId} + deployment state and reserves 1 request before calling the runtime (reduces concurrency gaps).
Added tier entitlements mapping in convex/lib/entitlements.ts and wired deploy/invoke gating through it.
Implemented incremental updates to billingUsage during telemetry ingestion:
When a telemetry event is accepted, billingUsage totals for the telemetry event’s periodKey are updated.
Note: this is still considered derived data; a future idempotent recompute job remains planned for hardening.
Tokens/compute enforcement remains post-charge via telemetry/aggregation; requests are the only dimension hard-stopped pre-invoke in this baseline.
Fixed deterministic Worker template generation to emit valid JavaScript (Cloudflare parses uploaded code as JS, not TS).
Removed TypeScript-only syntax that caused Cloudflare upload rejection (Unexpected token 'export').
Improved Cloudflare adapter error reporting to include deploy step + endpoint + Cloudflare error message (sanitized), making failures debuggable from the dashboard.
Improved invocation gateway error diagnostics for Cloudflare:
Distinguish network errors vs non-2xx upstream status.
Include safe upstream context (invokeHost, bounded upstreamBodySnippet) to rapidly diagnose routing/host/path issues without leaking secrets.
Improved dashboard error display to include status, retryable, requestId, and details so production-ish debugging can happen from the UI.
workers-api.webhost.systemsAdded a custom-domain routing strategy to avoid relying on workers.dev activation state:
Uses a single stable hostname, e.g. workers-api.webhost.systems.
Creates a per-deployment route so multiple deployments can coexist safely:
route prefix: /dep/<deploymentId>
invoke path: /dep/<deploymentId>/invoke
invoke URL: https://workers-api.webhost.systems/dep/<deploymentId>/invoke
Deploy action now supports:
workers.dev fallback (simple) OR
custom domain routing when CLOUDFLARE_WORKERS_CUSTOM_DOMAIN + CLOUDFLARE_ZONE_ID are configured.
Added guidance to ensure the Cloudflare API token includes zone Workers Routes permissions (required to create routes).
✅ End-to-end verified:
create agent → deploy Cloudflare → invoke (non-streaming) → invoke (SSE) → telemetry ingestion accepted → metrics/usage visible.
This progress log reflects implementation work completed on 2026-01-23. The source-of-truth spec remains docs/spec/.