WebHost.Systems WebHost.Systems/docs/spec/progress/2026-01-23.md
Project: webhost.systems (v1) Focus: Slice B — Control plane (Convex) + Cloudflare runtime path + Dashboard (Vite + Clerk) Owner: Engineering

Progress Log — 2026-01-23

Project: webhost.systems (v1) Focus: Slice B — Control plane (Convex) + Cloudflare runtime path + Dashboard (Vite + Clerk) Owner: Engineering

Summary (what changed today)

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).

Deliverables

A) Control plane app created

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.

B) Cloudflare runtime provider path implemented (v1 baseline)

  • 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)

C) Telemetry integrity implemented

  • 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)

D) Dashboard app created

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

Endpoints implemented/updated today (control plane)

All control-plane endpoints require auth via Authorization: Bearer <JWT> unless noted.

Agents

  • GET /v1/agents

  • POST /v1/agents

  • GET /v1/agents/:agentId

  • PATCH /v1/agents/:agentId

  • POST /v1/agents/:agentId/disable

  • DELETE /v1/agents/:agentId

Deployments

  • 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

Invocation gateway

  • POST /v1/invoke/:agentId (non-streaming)

  • POST /v1/invoke/:agentId/stream (SSE; currently emulated)

Telemetry (no user auth; deployment-scoped signature auth)

  • POST /v1/telemetry/report

  • validates signature + ownership mapping

Usage + metrics reads (dashboard support)

  • GET /v1/usage/current

  • GET /v1/metrics/recent?agentId=...&sinceMs=...&limit=...

Spec alignment checklist (today’s status)

✅ Tenant isolation

  • Server-side mapping from Clerk identity → internal users._id

  • Ownership checks enforced on agent/deployment/metrics reads and writes

✅ Deployment immutability + active pointer (ADR-0005)

  • New deployment row per deploy attempt

  • Invocation routing via agents.activeDeploymentId only

  • Rollback implemented as activation endpoint (pointer flip)

✅ Invocation protocol (ADR-0006)

  • Gateway expects invoke/v1 and forwards to runtime

  • SSE endpoint exists with ordered event types (metadelta*usage?done|error)

  • Implementation is emulated streaming for now (buffered upstream)

✅ Telemetry integrity (ADR-0004)

  • Deployment-scoped signing secret injected into data plane

  • HMAC verification uses raw body bytes

  • Ownership cross-check enforced

✅ Multi-runtime foundation (ADR-0001)

  • Provider boundary exists (Cloudflare implemented)

  • AgentCore not implemented yet (planned later)

⚠️ Limits/entitlements/billing (ADR-0007)

  • 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.

⚠️ Secrets strategy (ADR-0003)

  • 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.

Operational notes / prerequisites added today

Control plane env vars (minimum for Slice B)

  • 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)

Dashboard env vars

  • VITE_CLERK_PUBLISHABLE_KEY

  • VITE_CONTROL_PLANE_URL (Convex HTTP base URL)

Known gaps / TODOs (next work items)

  1. Aggregation job: compute billingUsage from metricsEvents (idempotent, recomputable).

  2. Limits & runtime gating:

  • enforce request limits pre-invocation

  • gate AgentCore runtime by tier (defense in depth)

  1. Write-only secrets API for agent env vars (no plaintext in DB, provider injection).

  2. Cloudflare Durable Objects session mapping:

  • treat sessionId as opaque externally but map to DO ids internally.

  1. True streaming:

  • convert data plane invocation to real streaming if/when supported; map to SSE deltas.

  1. Hardening:

  • tighter CORS allowlist

  • richer audit logs for telemetry rejects and deploy failures

  • reconcile partial deploy failures (orphan cleanup by tags)

Quick verification steps (manual)

  1. Sign into dashboard (Clerk).

  2. Create agent.

  3. Deploy (Cloudflare).

  4. Invoke:

  • non-streaming: /v1/invoke/:agentId

  • SSE: /v1/invoke/:agentId/stream

  1. Confirm telemetry events appear in:

  • /v1/metrics/recent?agentId=...

  1. Confirm deployments list:

  • /v1/agents/:agentId/deployments

  1. Confirm rollback pointer flip:

  • activate older deployment and re-invoke.

Addendum (later on 2026-01-23)

Additional progress completed after the earlier log was written:

Limits & runtime gating (ADR-0007)

  • 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.

Billing usage aggregation (v1 incremental)

  • 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.

Notes on v1 semantics

  • Tokens/compute enforcement remains post-charge via telemetry/aggregation; requests are the only dimension hard-stopped pre-invoke in this baseline.

Cloudflare deploy hardening (unblocked real deploy/invoke)

  • 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.

Invocation reliability + debuggability

  • 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.

Cloudflare custom domain routes (recommended) — workers-api.webhost.systems

  • Added 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).

E2E status (confirmed working)

  • ✅ End-to-end verified:

  • create agent → deploy Cloudflare → invoke (non-streaming) → invoke (SSE) → telemetry ingestion accepted → metrics/usage visible.

Notes

This progress log reflects implementation work completed on 2026-01-23. The source-of-truth spec remains docs/spec/.

Open in the interactive atlas