Skip to main content
Companion: Event clock writeup. Ticket: ENG-595.

Why

Instant-encoded gig clocks (ENG-580) store Chicago 5:00 PM as 22:00Z. The App Store crew app still treats 17:00Z as 5:00 PM on the watch (getUTCHours()). The backend cannot infer the client from Clerk auth or User-Agent.

Headers

Every Instant-aware first-party client sends both on every api-events request:
Soundcheck-Client is mobile | web | mcp | e2e. Local builds may send dev as the revision. MCP images bake SOUNDCHECK_REVISION from github.sha at deploy time. Azure web images bake NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA from github.sha so the browser bundle sends the deployed commit, not dev. Mobile OTA publishes set EXPO_PUBLIC_GIT_COMMIT_SHA from github.sha so app.config.js extra gitCommitHash is the deployed commit, not dev. RFC 6648: these are unprefixed vendor-namespaced headers, not X-Soundcheck-*. Capability is presence, not the SHA. Any non-empty Soundcheck-Revision means Instant. Do not switch on User-Agent. Mobile, mcp, and e2e also send a diagnostic User-Agent (Soundcheck-Mobile/<semver>) for logs only.

Backend contract

On product /api/v1 routes:
  • Header present → Instant in, Instant out (current writers/readers).
  • Header absent → legacy App Store mobile: GET projects stored Instants to fake-Z wall digits through Event → org → Chicago; POST/PATCH treat inbound 17:00Z as wall clock and Instant-encode. Storage stays true UTC Instants.
/webhooks, /health, and /swagger stay Instant even without the header (integrations already send real Instants). Unset request context (unit tests, Pub/Sub processors) stays Instant.

When to delete the fake-Z path

api-events records the headers in PostHog so the legacy translator can be removed when old App Store traffic is gone.
  1. Every authenticated product /api/v1 request (not portal) emits at most one clock_contract_snapshot per user per UTC day per clock_contract + soundcheck_client. Properties: clock_contract (instant | legacy), soundcheck_client (mobile | web | mcp | e2e | absent), has_soundcheck_revision, snapshot_date, and soundcheck_revision when the header is present. The same values are $set on the person.
  2. Service-layer product and billing captures also receive clock_contract and soundcheck_client when a request scope is set. Cron, webhooks, and unit tests with no ALS stay unlabeled.
Delete the translator when unique users on clock_contract_snapshot where clock_contract = legacy stay at zero. soundcheck_client = absent on those events is the old crew app. Do not use last-seen person properties alone — a user can hit Instant web and leftover mobile the same day.

Client attach points

Headers belong on the shared interceptor, not per call:
  • Mobile Axios: lib/api/generatedApiCore.ts
  • Mobile leftover fetch: lib/api/httpClient.ts
  • Web Axios (covers the generated client): web/src/lib/services/apiService.ts
  • Web public Axios: web/src/lib/services/publicApiService.ts
  • Web leftover fetch: positions and subscriptions
  • Web Playwright API helpers: web/automated-tests/utils/apiEvents.ts
  • Web Playwright leftover page.request: web/automated-tests/assets.spec.ts
  • mcp-ts authenticated + public clients
  • e2e undici: e2e/src/client/apiClient.ts
  • Ops scripts that call product /api/v1 (imports HTTP client, venue Places apply) use e2e
Spotify, Clerk, and Stripe callers are not api-events and must not send these headers.