mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-05-07 18:18:42 -05:00
Three small polish items flagged in the pre-release audit: 1. DROP release_hash pin The regression guard in staffml-validate-vault.yml compared vault.db's computed release_hash against a pinned value in interviews/vault/corpus-equivalence-hash.txt. That pin was load-bearing when corpus.json was the source of truth (guarded drift between committed-JSON and computed-from-YAMLs hash), but post-v1.0 the YAMLs ARE the source of truth and the hash is deterministic from them. The pin became a circular check that would bounce every YAML-touching PR unless the contributor remembered to manually bump the hash. Removed the pin comparison; the step now just runs vault build as a reproducibility smoke test. Real integrity still comes from vault check --strict + codegen drift earlier in the same workflow. Deleted interviews/vault/corpus-equivalence-hash.txt. 2. Hydration SKELETON for scenario Summary bundle ships scenario: "" and details with empty strings; useFullQuestion fetches the real content from the worker (~100-300ms warm, <5s cold). Before this commit the practice + plans pages showed a visibly empty region for that hydration window, then popped the scenario in — a text-FOUC. Added ScenarioSkeleton component (three pulsing bars of approximate paragraph height, aria-busy) and rendered it when current.scenario is empty on both practice and plans. Layout no longer jumps when real text arrives. 3. CLIENT-SIDE ERROR REPORTER Silent production regressions (like the getQuestionFullDetail shape mismatch in PR #1440) were only discoverable when a user said 'getting an error'. Added a lightweight error reporter that hooks window.error + unhandledrejection, scrubs email patterns, rate-limits to 20 unique reports per tab, and pipes into the existing analytics worker as 'client_error' events. No new vendor dependency — reuses analytics-worker KV storage. Worker allowlist extended: adds 'client_error' event type + larger 8 KiB per-event cap to fit stack traces + 'message/stack/url/ userAgent' to the allowed-fields list. Installed from Providers.tsx at app mount. Build verified green.
StaffML Analytics Worker
Lightweight Cloudflare Worker for collecting anonymous usage analytics from StaffML.
What It Collects
Anonymous events with no PII, no cookies, no persistent user IDs:
- Question scores (topic, zone, level, track, score 0-3)
- Gauntlet starts/completions
- Issue reports and improvement suggestions
- Daily challenge completions
Session IDs are ephemeral UUIDs that reset when the browser tab closes.
Setup
1. Install Wrangler
npm install -g wrangler
wrangler login
2. Create KV Namespace
cd interviews/staffml/analytics-worker
wrangler kv:namespace create STAFFML_ANALYTICS
Copy the returned namespace ID and update wrangler.toml:
[[kv_namespaces]]
binding = "STAFFML_ANALYTICS"
id = "<YOUR_KV_NAMESPACE_ID>"
3. Deploy
wrangler deploy
Note the URL (e.g., https://staffml-analytics.<your-subdomain>.workers.dev).
4. Configure StaffML
Set the analytics endpoint in your build environment:
# In the GitHub Actions workflow or .env.local:
NEXT_PUBLIC_ANALYTICS_URL=https://staffml-analytics.<your-subdomain>.workers.dev
Without this variable, analytics works in local-only mode (dashboard shows local data).
Endpoints
POST /
Accepts a batch of events:
{
"events": [
{ "type": "question_scored", "topic": "roofline-analysis", "zone": "recall", "level": "L3", "track": "cloud", "score": 2, "_ts": 1712000000000, "_sid": "abc-123" }
]
}
Response: { "accepted": 1 }
GET /
Returns aggregate summary:
{
"totalEvents": 1234,
"last7Days": {
"uniqueSessions": 42,
"questionsScored": 380,
"gauntletsCompleted": 15,
"eventsByDay": { "2026-04-01": 50, ... },
"scoresByLevel": { "L3": { "total": 120, "count": 50, "avg": "2.40" } }
}
}
Security
- CORS restricted to
mlsysbook.ai,harvard-edge.github.io, andlocalhost - Max 100 events per request
- Max 1KB per event
- Email-pattern detection (rejects events containing PII)
- Field allowlist (strips unknown fields)
- 90-day TTL on stored data
- No authentication required (anonymous by design)
Data Retention
Events are stored with a 90-day TTL in Cloudflare KV. After 90 days, they are automatically deleted. The running event counter (meta:total_events) persists indefinitely.