- Worker/static application:
snapmeter - D1 binding:
DB - Durable Object binding:
LIVE_ROOM - Ingest secret:
SNAPMETER_INGEST_SECRET - Deploy package:
@snapmeter/dashboard
The deployment serves UI assets and /api/v1/* from one origin. Do not publish a URL until the full smoke test succeeds.
pnpm install --frozen-lockfile
pnpm lint
pnpm typecheck
pnpm test
pnpm build
pnpm test:e2e
pnpm --filter @snapmeter/dashboard exec wrangler whoamiIf whoami is unauthenticated, use wrangler login interactively or provide a scoped API token through the environment/CI. Do not put tokens in Wrangler config, source, command history, or the browser bundle.
The checked-in Wrangler configuration targets D1 database snapmeter-prod with its connected database ID. For a new Cloudflare account or deliberate replacement, create D1 once, then replace that ID without changing the DB binding name:
pnpm --filter @snapmeter/dashboard exec wrangler d1 create snapmeter-prodGenerate the ingest secret with a cryptographically secure system facility and keep the local collector copy only in gitignored .env. Store the Worker copy interactively:
pnpm --filter @snapmeter/dashboard exec wrangler secret put SNAPMETER_INGEST_SECRETApply D1 and Durable Object migrations before deploying code that depends on them:
pnpm --filter @snapmeter/dashboard exec wrangler d1 migrations apply snapmeter-prod --remote
pnpm --filter @snapmeter/dashboard run deployRecord the exact URL returned by Wrangler, but do not commit it yet.
Migration 0005_collector_binding.sql creates the single-row collector_binding table. The first authenticated, non-doctor ingest atomically claims slot=1 with that batch's collectorId; an empty doctor probe does not claim it. Later batches with a different ID receive HTTP 409 { "error": "collector_identity_conflict" } before rate limiting or persistence, even when they have a valid ingest signature. This binding prevents a newly initialized database with a different local pseudonym key from overlapping the authoritative dataset and inflating DAU.
Normal failover restores the complete stopped, WAL-consistent collector SQLite state, preserving its ID, pseudonym key, cursors, and outbox. Never export or provision the pseudonym key separately. An intentional fresh-database replacement requires a D1 backup, a reviewed metric-continuity plan, and the manual statement DELETE FROM collector_binding WHERE slot=1. Run it only while both collectors are stopped. Prefer a UTC-day boundary when no older-day reconciliation will occur, or clear and deliberately rebuild the affected actor_day_membership rows before accepting the new ID. Confirm the next intended batch claims the slot, then verify health and DAU continuity before resuming. Record and review the intervention; neither ingest-secret rotation nor a code rollback resets the binding.
Against the exact returned origin:
GET /returns the production dashboard successfully and static assets load without mixed content or console errors.GET /api/v1/statussucceeds and truthfully distinguishes application health from source/collector freshness.GET /api/v1/metadatareports the expected schema/policy/source-pin metadata without secrets.GET /api/v1/summaryand all24h|7d|30dtime-series variants validate against their runtime schemas./api/v1/liveperforms a WebSocket upgrade, sends an initial snapshot/status, survives reconnect, and accepts no write operation from a browser client.- An HMAC-signed test batch with a unique batch ID is accepted once; exact replay is rejected/idempotent and a stale timestamp is rejected.
- The accepted live test packet creates one coalesced visible dashboard pulse with the preserved count. Replay/demo data cannot impersonate a live pulse.
- Demo mode is clearly labelled and disabled in normal production behavior.
- Mobile views at 360x800, 390x844, 393x852, and 430x932 have no horizontal overflow; Snapchain is above Hypersnap in the first viewport.
Only after those checks:
- confirm the
Open SnapMeterlink inREADME.mdstill matches the verified production URL; - add the inspected production screenshot if the file exists and was visually checked;
- commit/push the README and deployment configuration;
- verify the remote README link and repeat homepage/status/WebSocket checks after the push.
Never guess a workers.dev hostname or claim deployment success from a local build.
Pull requests and pushes to main run lint, typecheck, tests, build, and end-to-end coverage. Development is halted, so automatic production deployment on push is retired. The production job runs only for an explicit workflow_dispatch from main, and only when these protected secrets are configured:
CLOUDFLARE_API_TOKENCLOUDFLARE_ACCOUNT_IDSNAPMETER_INGEST_SECRET
The deployment workflow always validates the full workspace. On an authorized manual run from main, its production job applies remote migrations to snapmeter-prod, writes the ingest secret to a permission-restricted temporary file, and passes it to Wrangler during deployment. Push-triggered runs skip that job. Missing secrets on a manual run produce a clear skipped deployment rather than a false success claim. Protect the production environment with required reviewers if the repository supports it.
Use a least-privilege Cloudflare token scoped to the target account's Workers Scripts, D1, and required deployment resources. Rotate it after suspected disclosure and remove stale GitHub environment approvals/runs as appropriate.
- Stop collector delivery only if the deployed ingest contract is unsafe; keep the local outbox intact.
- Use Cloudflare deployment history to roll back Worker code to the last compatible version.
- Do not roll back D1 destructively. Migrations must be forward-safe; add a corrective migration.
- Verify homepage, API, WebSocket, signature acceptance, and source-quality display.
- Resume collectors and watch outbox drain/idempotency records.
Schema changes require backward compatibility across at least the collector retry horizon or a coordinated staged release. A deployment is not healthy merely because static assets respond.