Why
Instant-encoded gig clocks (ENG-580) store Chicago 5:00 PM as22: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:00Zas 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.- Every authenticated product
/api/v1request (not portal) emits at most oneclock_contract_snapshotper user per UTC day perclock_contract+soundcheck_client. Properties:clock_contract(instant|legacy),soundcheck_client(mobile|web|mcp|e2e|absent),has_soundcheck_revision,snapshot_date, andsoundcheck_revisionwhen the header is present. The same values are$seton the person. - Service-layer product and billing captures also receive
clock_contractandsoundcheck_clientwhen a request scope is set. Cron, webhooks, and unit tests with no ALS stay unlabeled.
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) usee2e