Handoff โ 2026-07-09
Frozen snapshot โ do not update. Kept for how a decision was reached, not for what is true now. Current state lives in Architecture โ Current State.
Full takeover brief for the next agent. Read this top-to-bottom before touching anything. It captures exact repo state, the one thing blocking production, every decision made this session and why, what still needs the human (Akshay), and how this workspace actually works.
0. TL;DR โ the one thing that mattersโ
A full backlog-clearing wave (6 parallel workstreams) is built, verified, committed, and
pushed to git main on 4 repos. But every Firebase deploy is blocked because the
Firebase CLI credentials on this machine are expired:
Error: Authentication Error: Your credentials are no longer valid. Please run firebase login --reauth
There is no FIREBASE_TOKEN, no GOOGLE_APPLICATION_CREDENTIALS, and no service-account
JSON available. Only Akshay can reauth (firebase login --reauth) โ an agent must not
attempt interactive login or handle credentials. Until that happens, production still runs
the previous code even though git is current.
Do not re-implement anything below thinking it's undone. It's done in git; it's just not deployed.
1. Repo state (as of handoff)โ
Product root: /Users/akshaysarode/Desktop/Workspace/RunningProjects/acesense/AcesenseProd/
Firebase project: acesense-prod. All repos on branch main, remote via SSH alias
git@github-acesense:Acesense/<repo>.git. Per-repo user.email = akshay.sarode18@gmail.com.
| Repo | HEAD | Pushed? | Deployed to prod? | Notes |
|---|---|---|---|---|
| acesense-frontend | 649c2e2 | yes | NO โ needs hosting:app | Flutter web/app. Has unrelated dirty WIP (see ยง5). |
| acesense-auth-function | 732d658 | yes | NO โ needs functions | Cloud Functions (TS). |
| acesense-admin | 20d63df | yes | NO โ needs hosting | Vite/React admin console. |
| acesense-landing | 6bcf8e6 | yes | NO โ needs hosting:landing | Vite/React marketing site. |
| acesense-gpu-backend | d71b7b5 | yes | YES โ auto-deploys on push | RunPod; nothing pending this wave. |
| acesense-docs | 3fec7d5 | yes | n/a | Docs. Unchanged this wave. |
| acesense-annotate | 7515067 | yes | n/a | Labelling tool. Unchanged. |
| acesense-api-hosting | 6ebd29f | yes | n/a | Unchanged. |
| acesense-launchpad | 2c36915 | yes | n/a | Investor bundle server. Unchanged. |
| acesense-brand | 91ad284 | โ | n/a | Untracked play-store/ assets present (not mine). |
plan/ at root is not a git repo (scratch).
2. THE DEPLOY SEQUENCE (do after Akshay reauths)โ
Order matters: functions must exist before the UIs that call them. From each repo dir:
firebase login --reauth # Akshay only, once
# 1. Backend first (new callables + AI-consent gate + bootstrapAdminClaims)
cd acesense-auth-function && firebase deploy --only functions
# codebase is named "functions"; to deploy a single fn use the doubled name:
# firebase deploy --only "functions:functions:generateSessionInsights"
# 2. Frontend app (ships consent self-heal so users aren't blocked by the gate)
cd ../acesense-frontend && flutter build web --release && firebase deploy --only hosting:app
# target app -> site acesense-prod-app (acesense-prod-app.web.app)
# 3. Admin console (UI now calls the callables from step 1)
cd ../acesense-admin && pnpm build && firebase deploy # its own firebase.json
# 4. Landing
cd ../acesense-landing && pnpm build && firebase deploy --only hosting:landing
# target landing -> site acesense-prod (acesense-prod.web.app)
Deploy-verify pattern used in this project (hosting): after deploy, shasum the deployed
main.dart.js (frontend) or a hashed asset and compare to the local build; retry with a
Cache-Control: no-cache fetch if the edge hasn't propagated (transient MISMATCH is normal
for a minute).
GPU backend: no Firebase โ it redeploys automatically on git push. Already pushed.
3. What shipped this wave (per repo)โ
Six independent workstreams. All anti-fabrication-clean (real data or gate/hide; never fake
numbers/identity/scores โ this is a hard product rule; the mock path is kUseMockData in
lib/config/build_flags.dart).
acesense-frontend (649c2e2)โ
- Real progression pipeline. New
lib/services/progress_service.dart(+lib/state/progress_provider.dart). Transparent skill score, per session:consistency*0.50 + pace*0.30 + rally*0.20, each term clamped 0โ1 (pace = avg_speed_kmh/80, rally = avg_rally_length/8); weights renormalize when a metric is absent; overall = rounded mean of per-session scores; null at 0 sessions. Tiers: Rookie<40, Club 40โ64, Competitor 65โ84, Elite 85+. Also streakDays, weeklyDelta (null unless two consecutive weeks each have a session), PBs. Snapshot computed once in the provider, shared by 6 surfaces (home, sessions, roadmap, badges, profile, wrapped). Removed fabricated percentile/per-stroke sub-scores/freeze-count/peer-rank. - Coach mode -> real backend.
lib/services/coach_service.dartrewritten to real callables (listCoachLinks,createCoachLink,respondToCoachLink,addCoachNote);coach_mock.dartdeleted. Features with no backend read path (student's full analysis, per-student skill data, drill assignment) are honestly disabled/hidden, not faked. - Help/FAQ. New
lib/features/help/help_screen.dart+/helproute + Settings entry. Body copy is English literals (documented in-file) to avoid a ~40-string x 6-locale balloon. - Durable AI consent + migration.
ai_consent_service.dartnow writes a versioned Firestore recordusers/{uid}.aiConsent = {version:'v1', acceptedAt:<serverTimestamp>}(viaFirestoreService.recordAiConsent) before caching locally.app_flows.dart _ensureAiConsentself-heals existing users: if the local flag is set but no server record exists, it writes the record through (best-effort) on next upload โ so the new backend gate (below) doesn't lock out users who consented before this shipped. - Tests: 198 pass;
flutter analyzeclean;flutter build web --releaseOK.
acesense-auth-function (732d658)โ
- Server-enforced AI consent.
video/index.ts:hasAiConsent(userData)predicate + gate inrequestUploadPathโ rejects withfailed-preconditionAI_CONSENT_REQUIREDunless the durable record exists. A device-only flag can no longer bypass analysis. Admins are not exempt (privacy, not paywall). - 9 audited admin callables in
admin/index.ts, eachrequireAdmin-gated + writes anadmin_auditrow:adminSetJobStatus,adminDeleteJob(Storage-cleaning),adminRevokeApiKey,adminModerateJob(takedown removes media + flags job),adminImpersonate(mints tagged custom token; needs SA roleroles/iam.serviceAccountTokenCreatorin prod; bounded by Firebase's fixed 1h token lifetime),adminCreateUser,adminUpdateUser,adminDeleteUser,adminPurgeJobs(batched<=500+moreflag). Exported from repo-rootindex.ts. bootstrapAdminClaims(pre-existing) grantsadmin:trueto the server email allowlist; the admin UI now relies on it (see ยง4).- Tests: 309 pass (19 files);
tscclean.
acesense-admin (20d63df)โ
- All UI mutations rerouted from raw Firestore/Storage writes to the audited callables above
(Jobs, Users, AdminApiKeys, Settings danger-zone, AuditLog moderation). Dashboard shows
"Recent Jobs (
<=500)" + a live health probe instead of a hardcoded "Operational". - Auth gate hardened (see ยง4): removed the in-bundle email allowlist; gate on the
admincustom claim only; self-heal viabootstrapAdminClaimson sign-in. Keptfirebase.jsonCOOPsame-origin-allow-popups(needed for Google popup sign-in). - Tests: 70 pass; build + lint clean.
acesense-landing (6bcf8e6)โ
- Analytics is now true opt-in. PostHog and Firebase Analytics don't initialize until the user
accepts in the cookie banner; they enable live on the
cookieConsentUpdatedevent. PostHog added to the privacy sub-processor list. - Legal/honesty reconcile. Min age 13->16 (18 in India); deduped Section 7; dropped founder
"professional-grade"/"high accuracy" overclaims; removed an internal SEO note; 17->33-point
MediaPipe skeleton; plausible NTRP-3.5 serve speeds; refund copy aligned to App Store/Play policy.
Reverted the unverified
acesense-annotateGitHub link to plain text. - Tests: 105 pass; build clean (93 prerendered routes).
4. Key decisions made this session (and why)โ
- Admin UI gate = custom claim only (not a client email list). A pre-existing working-tree
change had hardcoded 4 admin emails (incl. a personal gmail) into the browser bundle and gated
the UI on them. That leaks admin identities publicly and violates the "nothing hardcoded" rule.
Server
requireAdminis authoritative regardless, so the client list added no security โ only risk. Chosen (by Akshay): gate on theadminclaim only, and self-heal a genuine admin whose claim isn't set yet by callingbootstrapAdminClaimson sign-in (it'srequireAdmin-gated on the server via the verified-email allowlist, so real admins get the claim granted + token force- refreshed; everyone else gets an ignored permission-denied). Caveat for first deploy: Akshay's first sign-in after deploy triggers the grant, then the code force-refreshes the token โ one clean sign-in and the admin UI appears. - AI-consent migration = self-heal, not a backfill script. The backend gate is presence-based. Existing users have only the legacy local flag. Rather than run a one-off Firestore backfill (needs prod admin creds we don't have), the frontend writes the durable record through on next upload when the local flag is set. Genuine prior consent, just persisted. No user gets blocked.
- Landing legal facts: flag, never invent. Three items can't be fabricated (see ยง6). Shipped the safe honesty fixes; left the legal items as explicit flags in the commit body.
- Deploy order backend->frontend->admin->landing. Callables must exist before the UIs call them; the frontend consent self-heal must be live alongside the backend consent gate.
5. Uncommitted WIP in the tree โ DO NOT clobber, decide with Akshayโ
- acesense-frontend (dirty, intentionally left uncommitted):
android/app/build.gradle.ktsโ adds a real Android release signing config readingkey.properties(falls back to debug signing if absent). Legit release-prep.pubspec.yamlโ version bump1.0.4+1->1.0.4+2. These are Akshay's Android release work, unrelated to this wave. Committing them touches signing (andkey.propertiesmust stay gitignored). Leave for Akshay to commit when doing the store build.
- acesense-brand โ untracked
play-store/directory (store listing assets). Not this session's.
6. Open items that need Akshay (blocking or fact-only)โ
- Firebase reauth โ blocks all 4 deploys (ยง0/ยง2).
- Landing legal facts (agent refused to invent):
- Registered legal entity name + physical address (GDPR Art. 13/27). Three conflicting names in
the repo (
AceSense Technologies,AceSense Inc., "an EU company"); no postal address anywhere. - Delaware-Inc (
terms.htmlgoverns under Delaware law) vs "EU company" (sweden.md) โ pick the real incorporation, then terms + marketing get unified. - "No data leaves the EU" marketing (~20 pages) vs the privacy policy's US-servers + SCC clause
(
privacy.html). Founder decides true residency; then either the privacy clause is corrected or the marketing absolutes are softened. Sub-processors RunPod/Resend/Apple/PostHog are US-incorporated even if EU-hosted.
- Registered legal entity name + physical address (GDPR Art. 13/27). Three conflicting names in
the repo (
- Unverified GitHub link (pre-existing, already live):
acesense-annotatelink inacesense-landing/.../blog/how-accurate-is-acesense.mdโ confirm the repo is real/public or neutralize it (I already neutralized the two the agent added in accuracy.md/about.md). - Platform keys for Wave 2 (below): reCAPTCHA v3 site key (App Check) and APNs/VAPID keys (FCM push).
7. Wave 2 โ scoped, NOT startedโ
Akshay asked to "finish the open backlog except payment." Wave 1 (ยง3) did the fixable-without-keys items. Wave 2 items were scoped but not implemented โ some need keys only Akshay can provide:
- Notifications โ in-app Firestore feed + a send-on-analysis-done function (fully buildable now); push needs APNs (iOS) + VAPID (web) keys.
- Storage retention lifecycle โ auto-expire old uploads/artifacts (buildable now).
- App Check enforcement โ code path already inert-wired in admin
src/config/firebase.ts(VITE_APPCHECK_SITE_KEY); needs a reCAPTCHA v3 site key + enabling enforcement in console. - Full localization โ several screens use English literals (e.g. Help screen, coach previews);
arb +
flutter gen-l10nacross 6 locales.
Excluded by Akshay: real payment / IAP integration. Do not implement it.
8. How this workspace works (conventions + gotchas)โ
Toolchain (non-negotiable, from user global CLAUDE.md): runtimes via mise; JS via pnpm
(never npm/yarn); Python via uv (never raw pip); direnv for env. Clone with the SSH alias
git@github-acesense:.... Never edit global git config. Never firebase login globally โ per-project
service accounts.
Build/test commands:
- Frontend (Flutter):
flutter analyze,flutter test,flutter build web --release. Golden tests:flutter test --update-goldens test/preview/analysis_visual_test.dart. l10n:flutter gen-l10n(generated dir is gitignored). - auth-function / admin / landing (TS/Vite):
pnpm build,pnpm test(vitest),pnpm lint. - GPU backend:
uvenv; deploys on push.
Anti-fabrication contract (hard rule): never render fake numbers, identity, or scores. Use real
data or gate behind kUseMockData / show an honest empty state. Injury-prevention content (the
/body screen) must never make medical/diagnosis/injury-probability/"safe to play" claims โ
educational only.
Gemini / insights function gotcha: the LLM insights + PDF report Cloud Function
(acesense-auth-function/insights/) uses a Gemini AQ.-format key that works only via REST
generativelanguage.googleapis.com/v1beta/models/{model}:generateContent with the X-goog-api-key
header โ the @google/genai SDK 401s it (prefers OAuth/ADC). Use fetch, not the SDK. Key lives in
acesense-auth-function/secrets/key.json (GEMINI_API_KEY=AQ...); secrets/ is gitignored โ never
commit or echo it. The function triggers on {sid}_combined.json Storage finalize and writes
insightsUrl/pdfReportUrl back to the job doc.
Firebase specifics: project acesense-prod. Hosting targets: app->acesense-prod-app,
landing->acesense-prod. Functions codebase is literally named functions, so a single-function
deploy is --only "functions:functions:<name>". Region europe-west1 for all callables.
GateGuard hook ("Fact-Forcing Gate"): before Edit/Write/Bash it demands facts (importers, data
schema, user instruction verbatim). Present them, then retry the identical call. It also emits a
spurious "N files modified this session" scope warning that over-counts โ ignore it. To disable for
setup/repair: ECC_GATEGUARD=off or add the hook id to ECC_DISABLED_HOOKS.
Deploy mechanics recap: GPU backend auto-deploys on git push; everything else is manual Firebase CLI (currently blocked). Commit per repo; push == the deploy trigger for GPU only.
9. Persistent memory pointers (already written for you)โ
Agent memory lives at
~/.claude/projects/-Users-akshaysarode-Desktop-Workspace-RunningProjects-acesense-AcesenseProd/memory/.
Relevant files: acesense-fix-sweep-pending-deploy.md (this wave's deploy state โ kept current),
acesense-video-privesc.md (the paywall-bypass fixed earlier), acesense-insights-report-function.md
(Gemini/insights gotcha), acesense-working-style.md (boring/small/verified; commit per repo),
acesense-project-map.md (the 10-repo map). MEMORY.md is the index.
10. Suggested first moves for the takeover agentโ
- Confirm nothing regressed: in each of the 4 changed repos run the test command in ยง8; all should still be green at the HEADs in ยง1.
- Ask Akshay to reauth Firebase, then run ยง2 in order and verify each with the hash pattern.
- After admin deploy, have Akshay do one clean sign-in to trigger the claim self-heal.
- Get the ยง6 legal facts from Akshay and do the single unifying pass on landing terms/privacy/marketing.
- Only then consider Wave 2 (ยง7) โ and never payments.