Blockers
What is stopping the build from working end to end, right now.
Said plainly by Rahul on the 7 Sept call, unprompted:
"I'm not giving you step count, by the way, just FYI… I just give you coins… There's no way you can say this person walked 10 steps based on the coins you have… It's just the calculation of coins, it's not steps."
And on the 8–9 Sept call he went further — the raw table was deliberately deleted: "originally we had a table that raw recorded the data, just to test, but then we didn't delete that table. Then came along Health Coin Awards, which does the conversion." He then ran a live migration during the call and said: "the only table you should have from our end is health_coin_awards."
These two requirements cannot both hold with a fixed linear rate:
- Compliance (Dr. Rob, 8–9 Sept): a coin total must not be reversible into a health value. "Somebody who hacks in, including a healthcare provider, there's no way they know for sure how many steps someone took."
- Product (Lucy, throughout): the member sets a step goal, and the 3/7/14/21/30-Day Streak Challenge advances when they hit that goal.
At 15 steps = 1 coin, coins × 15 ≈ steps. Rahul conceded this: "unless they figured out the formula and worked backwards." So either the coins are reversible — and the vault claim is not true — or they are not, and the server cannot tell whether a member hit their own goal. Today the platform has neither the raw value nor a goal verdict.
The fix — and it is cheap, because the app already holds both halves:
The phone has the raw value and the formula. Let it evaluate the member's goal locally and send a verdict, not a measurement: goal_hit: true, pct_of_goal: 92, streak_day: 14 alongside the coins. A boolean is not reducible to a step count, so the vault holds — and the streak engine gets exactly what it needs. This needs the member's goal to reach the app, which the JWT session already makes possible. Raise as Q11. Owner: Rahul + Lucy.
MIGRATION FOUND. Rahul's schema was in our own repo the whole time, one level above public/, which is why earlier passes missed it:
_shared-assets/supabase-migrations/2026-08-31-mobile-health-pipeline.sql — 9,274 bytes, committed 31 Aug (3ce79e7).
It states the answer in writing (lines 27–32), and our own coin_events RLS policy from May corroborates it independently:
-- coin_events RLS, 2026-05-22 — this only works if user_id holds the auth uuid using (auth.uid()::text = user_id)
| Table | Keyed on | Status |
|---|---|---|
health_samples | auth.users.id (uuid FK) | ✅ consistent |
health_coin_awards | auth.users.id (uuid FK) | ✅ consistent |
coin_events | auth.users.id (as text) | ✅ consistent |
lit_* (13 tables) | profiles.id | ❌ the outlier |
RKWO's pipeline is correctly aligned with our existing coin ledger. The mismatch is between the LIVE It Tracker tables and everything else. Because lit_* was never run in production (Gate 3 still open), it is the cheapest thing in the system to change.
lit_* at auth.users(id)
Edit 025-live-it-tracker-tables.sql before it is ever run — change all 13 FKs from profiles(id) to auth.users(id). This also fixes the P1 below for free: the lit_* RLS policies are written auth.uid() = user_id, which only ever matches if the column holds an auth uuid. Do not ask RKWO to change anything. Owner: Lucy.
Found 8 Sept in _shared-assets/supabase-migrations/2026-05-22-coin-events.sql. This is ours, not RKWO's — it predates the engagement.
create policy "insert_own" on public.coin_events for insert with check (true); -- intentionally permissive for V1
with check (true) is no check at all. Any authenticated user can insert a coin row with any user_id and any amount. Deliberate for V1, but it has to close before coins carry real value — it undercuts the whole "coins are trustworthy" story we are selling to partners. Owner: Lucy.
In the RKWO migration, lines 140–144:
create policy "health_coin_awards_update_own" on public.health_coin_awards for update using (auth.uid() = user_id) with check (auth.uid() = user_id);
He needs an update path — the re-score-the-whole-day logic depends on it, and that logic is correct. But as written the member can set coins to any number they like. Fair question for him (Q10): can the re-score go through a security definer function so the client never writes the total directly?
.github/workflows/run-migrations.yml auto-applies migrations on push to main. This one landed 31 Aug, so it has probably already been applied. Confirm rather than assume — it changes whether the RLS fix above is an edit or a live patch. Owner: Lucy.
Open Questions
Tick when answered. Type the answer in the box — it saves in your browser.
health_coin_awards) — not which id. Treat as documented, not confirmed. It is the Supabase auth.users.id — declared as a uuid FK on line 47, and the file says so explicitly in its header notes. Our coin_events.user_id holds the same value as text. The gap was never on his side; it is our lit_* tables keying on profiles.id. See §1.steps / 2332 / COUNT rows, two active_energy / 91, two workout / 34, two steps / 609. Summed, that user "walked" 6,996 steps instead of 2,332. Rahul's whole-day re-derivation neutralises this only if the day total itself is deduped.NOT NULL, which is a label, not a guarantee. If duplicates share a key, the constraint is missing (FFH-side one-line fix). If they carry different keys, the app is minting a fresh key per sync (RKWO-side fix).health_samples — keys on profile_id not user_id, id is uuid not bigint, metric_type not metric, and recorded_at defaults to CURRENT_TIMESTAMP (wrong for backfilled health data). If both are written, FFH has two sources of truth. Drop it before real data lands, not after.health_coin_awards_update_own lets a signed-in member update their own coin rows. Coins are spendable through the Force Marketplace, so that is currency minting, not leaderboard cheating. If the sync is insert-only we drop the policy. If it genuinely needs UPDATE, coin awards move server-side.lit_challenges.target_value already holds a per-challenge target and is the natural home. This layer is FFH's to build, and it is small.health_coin_awards_update_own policy lets a member set their own coin total to any value. The re-score logic itself is right — the question is only whether the client needs direct UPDATE rights to do it, or whether a security definer RPC that recomputes from health_samples would serve the same purpose without the hole.health_samples is gone, then §3 of this page, the Developer Handoff, and the 31 Aug migration in our repo all describe a schema that no longer exists — and the proof-of-concept page built on raw step values is invalid. Ask for the migration file so it lands in our repo like the last one did.coins × 5 returns sleep minutes and
coins × 100 returns millilitres. We are storing sleep duration and fluid intake at
full precision today. A flat rule — a fixed award for "sleep logged" regardless of duration —
is the only thing that actually makes a coin non-reversible. Note this changes member earnings;
pair it with Q14.HKMetadataKeyWasUserEntered != true; Android
RECORDING_METHOD_AUTOMATICALLY_RECORDED / ACTIVELY_RECORDED. Dr. Rob calls
provenance filtering "the single most important implementation detail in the document" — any app
can write steps into Apple Health. Yes or no. If no, the leaderboard is an honour system and we
need to know before we promote it. Also ask before Dr. Rob's watch test, or the test proves the wrong thing.n and which get a flat rule, and it is the document
counsel will ask for.Data Architecture
Verified against the live database, 5–6 September 2026.
The two halves, and the seam between them
RKWO SIDE (keys on auth.users.id) FFH SIDE (keys on profiles.id)
───────────────────────────────── ────────────────────────────────
health_samples raw stream lit_wellness_log steps/day
dedupe_key, device_model lit_challenges target_value
metric, value, unit lit_weight_entries …
│ (13 lit_* tables)
▼ ▲
health_daily_summary VIEW ─────╳──────────────┘
│ NO JOIN — the P0 blocker
▼
health_coin_awards idempotency ledger
PK (user_id, coin_group, local_day)
│
▼
coin_events FFH's existing balance table
coin_type='LIVE_IT', action='health_sync'
RKWO tables — all inside FFH's Supabase
| Object | What it is |
|---|---|
| health_samples | Raw event stream. id bigint, user_id uuid → auth.users, metric, value float8, unit, dedupe_key text NOT NULL, device_model, created_at. Raw values, not coins. |
| health_daily_summary | A view — the daily rollup. |
| health_coin_awards | Idempotency ledger. PK (user_id, coin_group, local_day), coins = running total for that group+day after cap. This is what makes re-sync safe. |
| health_metrics | Probable earlier generation — see Q5. |
All four live in FFH's own project. No contractor holds a copy.
How coins are written
coin_events — FFH's existing balance table:
| Field | Value |
|---|---|
| user_id | the uuid, as text |
| coin_type | LIVE_IT |
| amount | the delta only, never a running total |
| action | health_sync |
| item_id | the activity group — movement, sleep, hydration, climb, energy, mindfulness, stand |
| meta | {group, local_day, day_total, surface:'mobile_app'} |
item_id carries the group, never the specific metric. The ledger records that coins came from moving — not that a workout happened, or how many steps. meta.day_total is a coin figure, not a measurement.Row-level security — as it stands
| Table | SELECT | INSERT | UPDATE | DELETE |
|---|---|---|---|---|
| health_samples | own | own | none | none |
| health_metrics | own | own | none | none |
| health_coin_awards | own | own | own — see Q6 | none |
- Owner-scoped throughout (
auth.uid() = user_id). Correct shape, and more than many low-cost builds ship with. - No UPDATE on the sample tables — arguably right. An append-only health record is more defensible than an editable one.
- No DELETE anywhere. Both stores require an in-app account-deletion path and erasure requests are FFH's to answer. Confirm there is an
ON DELETE CASCADEfromprofiles. - No admin and no org-scoped read. The
lit_*tables carry both; these do not. So no admin dashboards through the client (a privacy win worth keeping) and no leaderboard can read these tables directly — leaderboards will need an aggregate view. - Verifying RLS from the dashboard as role
postgresproves nothing — superuser bypasses RLS. Switch the role selector toauthenticatedto test properly.
The Coin Model
RKWO's conversion table, as supplied 6 September 2026.
The anchor
150 steps = 1 minute of effort = 10 LIVE It Coins. Everything derives from that unit: one coin costs 15 steps, one active minute pays 10. A test asserts both stay in step, so neither can be edited alone.
| Metric | Rate | Cap/day | Cap in real terms | Group | iOS | Android |
|---|---|---|---|---|---|---|
| Workouts | 1 min = 10 | 1,500 | 150 min | movement ① | ✓ | ✓ |
| Exercise minutes | 1 min = 10 | 1,500 | 150 min | movement ② | ✓ | — |
| Steps | 15 = 1 | 1,500 | 22,500 steps | movement ③ | ✓ | ✓ |
| Mindful minutes | 1 min = 10 | 300 | 30 min | mindfulness | ✓ | — |
| Active energy | 5 kcal = 1 | 400 | 2,000 kcal | energy | ✓ | ✓ |
| Sleep | 5 min = 1 | 120 | 10 hours | sleep ① | — | ✓ |
| Time asleep | 5 min = 1 | 120 | 10 hours | sleep ② | ✓ | ✓ |
| Flights climbed | 1 = 2 | 100 | 50 flights | climb | ✓ | ✓ |
| Stand hours | 1 hr = 5 | 60 | 12 hours | stand | ✓ | — |
| Water | 100 ml = 1 | 30 | 3 litres | hydration | ✓ | ✓ |
① ② ③ = priority within the group.
Rule 1 · One metric per group pays, per day
A 30-minute run appears in Apple Health three times — as a workout, as 30 exercise minutes, and as the ~4,500 steps it produced. Paying all three would be 900 coins for half an hour. The highest-priority source with data wins; the rest are ignored.
Groups are independent, so a day of walking and good sleep and 3 litres of water earns from all three.
Rule 2 · Division floors, days re-scored on total
149 steps is 9 coins, not 9.93. Because each sync re-derives the whole day rather than a batch, the remainder isn't lost — 149 steps read ten times still pays 9, not 90.
This is the design that makes re-syncing safe, and it pairs with the health_coin_awards primary key.
Rule 1 resolves by priority, not by value. So:
| 14,500 steps, no workout logged | 966 coins |
| Same day, 30-minute run logged | 300 coins |
Workout outranks steps, so the 14,500 steps are discarded entirely. Logging the run costs the member 666 coins — the app teaches people not to record their exercise. Fix: take the highest value in the group rather than the highest priority. Tracked as Q3.
Worked examples — arithmetic verified
| A day of… | Earns | Check |
|---|---|---|
| 10,000 steps, nothing else | 666 | 10000÷15 = 666.67 → 666 ✓ |
| 30-min run (+4,500 steps) | 300 | workout only, not 300+300 ✓ |
| 8 hours' sleep | 96 | 480 min ÷ 5 = 96 ✓ |
| 2 litres of water | 20 | 2000 ÷ 100 = 20 ✓ |
| 10 flights | 20 | 10 × 2 = 20 ✓ |
| 400 kcal active | 80 | 400 ÷ 5 = 80 ✓ |
| All of the above together | ~516 | 300+96+20+20+80 = 516 ✓ |
The model is internally consistent. The only defect is the priority-vs-value resolution above.
Compliance Gates
No real member PHI enters the system until all five close.
Four of seven gaps found in the 5 April 2026 review were closed in code the same day — PHI localStorage guard, audit logging, consent gate, 30-minute inactivity timeout. These seven gates remain. Tick state saves in your browser.
Continuity & Custody
What FFH holds if RKWO stops being available tomorrow.
Already safe
- All data. Tables, view and ledger live in FFH's own Supabase project, on FFH's plan, under FFH's keys.
- The pipeline keeps running. A shipped app keeps writing regardless of the developer relationship.
- Raw values, not just coins.
health_samplesholds the underlying measurements, so FFH can re-derive coins under any rules it likes. - The schema is documented — on this page, and in project memory.
Confirm these — exposure sits here
Decision & Investigation Log
What was found, when, and what it changed.
health_metrics — "not my table" — leaving its origin unexplained.profiles.id or auth.users.id, he said he would have to check the code and never came back. He also diagnosed the "8 days behind" warning: FFH and RKWO both push single files to the same repo without telling each other, so GitHub is the only source of truth and the working copy must be pulled before each session. Confirmed again: "we don't do anything for the LIVE It trackers… we hand it over to you."public/ was visible) and found RKWO's migration had been in our own repo since 31 August: _shared-assets/supabase-migrations/2026-08-31-mobile-health-pipeline.sql. It answers the identity question in writing — health_samples.user_id is auth.users.id — and our own May-era coin_events RLS policy (auth.uid()::text = user_id) confirms it independently. RKWO's pipeline is correctly aligned; the lit_* tables are the outlier. Decision: re-point lit_* to auth.users(id) before it is ever run. Separately found two coin-integrity holes: coin_events insert policy is with check (true) (anyone can mint coins for anyone), and health_coin_awards lets a member rewrite their own totals. Also noted run-migrations.yml auto-applies on push, so the RKWO migration is probably already live.Opportunity Items / Rahul Development / steps-to-coins-proof.html) that takes raw step values in health_samples shape, applies the member's own goal, converts at 15 steps = 1 coin with a 1,500/day cap, pays a 50-coin goal-hit bonus, writes an Earnings Log row per day and drives the 3/7/14/21/30 ladder. Five logic tests pass, including the two that matter for compliance: the daily cap holds against a 50,000-step day, and a milestone pays exactly once across a broken-and-resumed streak. Confirmed separately that no commit in the repo's 1,001-commit history is authored by RKWO — the Flutter app code has never entered our custody.coin_events / health_coin_awards contract. Worked examples verified — all six check out. Found: group resolution by priority rather than value means logging a workout can cost a member 666 coins. Raised as Q3. Also confirmed: health_coin_awards PK (user_id, coin_group, local_day) solves idempotency, and local_day solves the timezone/day-boundary risk flagged in the handoff.profiles.id ≠ auth.users.id across a five-row sample. RKWO keys on auth.users; the 13 lit_* tables key on profiles. Health data and tracker data cannot currently be joined. Everything downstream blocked. Suspected cause: legacy WordPress/BuddyBoss migration ids.health_samples stores raw values with units, not coins — so the concern about the coin formula being locked in the app binary is moot. Found dedupe_key NOT NULL, device_model, and the health_daily_summary view (the daily rollup previously thought missing). RLS present and owner-scoped on all three tables; gaps found: member-editable coin awards, no DELETE policies, no admin or org-scoped read. Duplicate sample rows observed across four metrics.CLAUDE.md as a global rule with a mandatory incognito verification step. This page follows it.Opportunity Items / Rahul Development.lit_* tables, 59 RLS policies, seven compliance non-negotiables, the window.FFHNative bridge contract, the HealthKit / Health Connect field map, IP and security terms, twelve open decisions.Artifact Index
Everything this build touches.
| Artifact | Where |
|---|---|
| Developer Handoff (gated) | theforce.health/live-it-trackers-handoff/ |
| RKWO Phase 1 Proposal | Opportunity Items/Rahul Development/RKWO-TheForceForHealth_Mobile_App_Proposal.pdf |
| Conversion table screenshots | Opportunity Items/Rahul Development/ |
| App Deployment Tracker (32 steps, phases 0–6) | theforce.health/app-tracker/ |
| LIVE It Trackers | theforce.health/live-it-trackers/ |
| LIT schema migration — 13 tables, 59 policies | Supabase/025-live-it-tracker-tables.sql |
| Compliance & audit schema | Supabase/005-compliance-audit-tables.sql |
| Gamification schema — coin_transactions, badges, streaks | Supabase/002-learning-gamification-tables.sql |
| Compliance Check & Gameplan v1.0 | FFH-LIVE-It-Trackers-Compliance-Check-Gameplan.docx |
| Leaderboard Privacy Advisory v1.0 | FFH-Leaderboard-Privacy-Compliance-Advisory.docx |
| Supabase project | kfdwkiarqxlstlsnrilj · New FFHA Network Systems · main · PRO |
Meeting Log
Executive summaries from the three recorded calls. Source transcripts are credential-bearing — see Gate 6.
Call 3 · 8–9 September · Lucy, Rahul, Dr. Rob TestFlight delivered
In one line: the build finally reached a phone, a live migration reshaped the database mid-call, and the compliance principle was tested — and passed for the wrong reason.
Decisions taken
- The published "150 steps = 1 coin" line will be removed so the rate is not stated openly. Dr. Rob endorsed this as the layer that makes coins non-reversible.
- Surplus tables dropped by live migration, on the strength of an 08:47 backup found during the call.
- Lucy proposed rewarding at 25 / 50 / 75 / 100% of goal in place of the per-submittal 50 coins, now that sync is continuous. Not yet formalised — Q15.
Confirmed
- Sync runs automatically every two hours, plus a manual button under the profile icon.
- De-duplication works: syncing at 1,001 steps after a 1,000-step sync yields 1,001, not 2,001. This answers Q2.
- The conversion formula lives in the app, not the server.
health_metricsis not RKWO's table — origin unknown.- Apple Developer account is
lucy@myhealthyglobe.com;lucy@theforceforhealth.comis the Sickle Cell developer identity and is not on the developer programme.
Open — Q3 (the 666-coin group-resolution bug): Rahul could not explain it on the call and committed to redo the arithmetic by hand. Dr. Rob could not interpret it either. Still open.
⚠ Security: a new Apple Developer password was set and read aloud, along with several 2FA codes. It is in the recording and in the auto-generated transcript. Rotate it — Gate 6.
Call 2 · Monday 7 September · Lucy, Rahul Architecture-changing
In one line: Rahul said in passing that he is not sending raw values at all — only coins — which invalidates the assumption the tracker integration was being designed on.
The quote that matters
"I'm not giving you step count, by the way, just FYI… I just give you coins… There's no way you can say this person walked 10 steps based on the coins you have."
Takeaways
- The proof-of-concept page is hard-coded, not database-backed — Rahul spotted this correctly. It demonstrates the model; it does not demonstrate a live connection.
- Why the repo was "8 days behind": both sides push single files to one repo without telling each other. GitHub is the only source of truth. Pull before every working session.
- Local disk pressure (Lucy at ~2GB free) is self-inflicted — versions kept locally that GitHub already holds. Safe to clear, keeping the main working folder.
- Restated: "we don't do anything for the LIVE It trackers or the tracking pages… we hand it over to you." The seam is the database and nothing else.
- App Store copyright still read 2023; Rahul updating to 2026.
Asked and not answered: does the coin write key on profiles.id or auth.users.id? — "I'll have to check… one by one." No reply since.
Call 1 · Friday 4 September · Lucy, Rahul, Dr. Rob App demo
In one line: the Flutter shell works and is genuinely well-judged — a thin native frame around live web pages, so most future changes need no app release.
Decisions taken
- Replace the existing Force for Health app rather than publish a second one. Same App Store account; BuddyBoss users upgrade in place. WordPress/LearnDash stays behind the scenes until fully migrated.
- No in-app registration — "Create account" opens Safari, avoiding the 30–40% store commission on sales.
- Gating is FFH's responsibility. Rahul: "I wouldn't gate it on my end. I would gate it on your end" — the same URLs are reachable from any desktop browser, so an app-level gate is theatre. This validates the CLAUDE.md gated + noindex standing rule as the only real control.
- Bottom nav is fixed and requires an app release to change — Lucy to supply the final four destinations. Top bar gains Messages and Search icons.
Ten metrics confirmed capturable
Steps · workouts · exercise minutes · mindful minutes · activity & energy · sleep and sleep timing · flights climbed · standing hours · water. Android cannot supply mindful minutes or standing hours. Dr. Rob noted roughly 44 metrics exist in total; Rahul's position is to capture everything now — "you might as well have all the health data stored as coins because you might use it one day."
Notable: the conversion is built from Lucy's Meta message (150 steps = 10 points), not Dr. Rob's comprehensive clinical list. Asked directly, Rahul confirmed: "No. It was the one you sent me on Meta." Dr. Rob may not know his list went unused — worth raising with him directly.
Dates and commitments named across the three calls
| What | Who | When said | Status |
|---|---|---|---|
| Conversion table for all ten metrics | Rahul | 4 Sept, twice; again 7 Sept | Not delivered |
| List of which Supabase tables to wire up | Rahul | 4 Sept — "shortly after this call" | Partly — sent via Messenger |
| TestFlight build | Rahul | 4 Sept — "there will be" | Delivered 8–9 Sept |
| Migration dropping surplus tables | Rahul | 8–9 Sept, run live on the call | Done — file not in our repo (Q12) |
| Re-do the 666-coin group-resolution arithmetic | Rahul | 8–9 Sept | Outstanding (Q3) |
| Remove the published "150 steps = 1 coin" line | Rahul | 8–9 Sept | Outstanding |
| App Store copyright 2023 → 2026 | Rahul | 7 Sept | Outstanding |
| Final four bottom-nav destinations | Lucy | 4 Sept — homework | Outstanding |
| App logo file | Lucy | 4 Sept; chased 7 Sept | Outstanding |
| Apple HealthKit entitlement + extra terms | Rahul → Lucy signs | 7 Sept | Pending (Gate 5) |
| Weekly tech talk | Both | Mondays | Recurring |
PHI Posture & the Reversibility Problem
Executive summary — 13 September 2026. Whether pulling the raw count breaks our arm's-length stance, and what we actually hold today.
The finding, in one line
We are not deciding whether to start holding raw health values. We already hold every one of them, at full resolution, and have since August.
A coin is only a privacy boundary if the arithmetic cannot be run backwards. Every rate RKWO shipped is linear, so every one is reversible with a calculator. The coin is a unit conversion, not a one-way door.
| Coin group | Rob's tier | Recover the raw value by | Worked example |
|---|---|---|---|
| movement | Green | coins × 15 | 612 coins → 9,180 steps |
| sleep | Yellow | coins × 5 | 96 coins → 480 minutes of sleep |
| hydration | Yellow | coins × 100 | 18 coins → 1,800 ml |
| energy | Green | coins × 5 | 80 coins → 400 kcal |
| climb | Green | coins ÷ 2 | 20 coins → 10 flights |
| stand | Green | coins ÷ 5 | 45 coins → 9 hours |
96 sleep coins is not a proxy for eight hours of sleep. It is eight hours of sleep, written in a different unit. Sleep and hydration are Yellow tier in Dr. Rob's own sensitivity gate — "participation only, never the value" — and we are storing both at full precision right now.
This voids a decision taken on the 8–9 September call Correction
That call resolved to remove the published "150 steps = 1 coin" line so the rate is not stated openly, and recorded Dr. Rob endorsing this as "the layer that makes coins non-reversible." It does not make them non-reversible, for three independent reasons:
- One data point recovers the rate. Any member can read their own step count in Apple Health and their own coin total on our page. 8,742 ÷ 582 = 15.02. The rate is not a secret we can keep from the only people who can check it.
- Dr. Rob's own spec publishes the whole rate card — all ten metrics, rates and caps — and it has been circulated to the development team.
- It is the exact pattern our own standing rule forbids. "Obscurity is not access control" is already written into the gated-pages rule. Hiding a rate is not the same as not having one.
The real control is a non-linear rate, not a hidden one. Dr. Rob already designed it: Yellow-tier rules pay a flat amount — 75 coins for "blood pressure logged," whatever the reading. Flat is genuinely non-invertible. RKWO shipped no flat rules at all.
What Dr. Rob's spec actually says — n was always meant to cross the wire
Adding the raw count is not a departure from the architecture. It is the architecture. His ledger schema and wire format both carry it:
player_id · local_date · rule_id · n · coins · event_id · envelope_id · created_at "events": [ { "rule": "bio.g.1", "n": 87, "coins": 87 }, // green — n is the real count { "rule": "bio.g.4", "n": 22, "coins": 220 }, // green — 22 minutes { "rule": "bio.y.2", "n": 1, "coins": 75 } // yellow — participation only ]
Green carries the count. Yellow carries only "×1, it happened." That distinction is the whole
design, and it appears on the page of his spec headed "On the FFH server (the whole footprint)."
RKWO dropped n; Dr. Rob specified it.
The HIPAA question, answered plainly
FFH is not a HIPAA covered entity, and a step count from a member's own watch has never been PHI. HIPAA attaches to providers, health plans, clearinghouses and their business associates. Whoop, Oura, Strava, Fitbit and WW all sit outside it and all store raw heart rate, HRV, sleep stages and GPS — far more than we are discussing. Whoop's privacy policy states it is not a covered entity in as many words.
What does reach us is a thinner, different set of rules, and this is where the caution belongs:
- Washington My Health My Data and Nevada SB 370 — broad "consumer health data" definitions and consent duties. MHMD carries a private right of action.
- FTC Health Breach Notification Rule — already enforced against consumer health apps (GoodRx, Premom).
- Health Information Privacy Reform Act (Sen. Cassidy) — introduced to close the wearables gap. Track it; not law.
None of those are addressed by refusing to store n. They are addressed by consent
language, retention limits, a no-sale commitment and a breach procedure. Storing less raw data is a
nice-to-have there; not selling it and disclosing it clearly are the actual obligations. Counsel has
still not reviewed any of this — see Gate 3.
Recommendation — implement Rob's tiering properly, and we end up holding less
| Tier | Metrics | What crosses the wire | Rate shape |
|---|---|---|---|
| Green | steps, exercise minutes, workouts, flights, active energy, stand hours | real n + rule_id + coins | linear is fine |
| Yellow | sleep, hydration, and any nutrition or vitals added later | n: 1 — participation only | must be flat |
| Red | reproductive health, clinical records, workout routes | nothing — scope never requested | n/a |
Net effect: we hold less sensitive data than today, and more useful data than today. Sleep and water stop being recoverable; movement becomes provable. That is a better position for a payer conversation than either the current build or a blanket refusal — and it is Dr. Rob's design, faithfully implemented rather than half-implemented.
One caveat to state out loud: moving sleep and hydration to a flat rate changes what members earn. Sleep currently pays up to 120/day and water up to 30/day on a sliding scale. A flat rule pays the same for six hours as for nine. That is a product decision, not only a compliance one — it belongs with the wider economy calibration in Q14.
Rahul's Build vs Dr. Rob's Document
Plain language, no jargon. What exists, what was specified, and whether we have two paths or one.
Start here — the honest status of this page
Sections 1–9 of this Build Room describe Rahul's build. They were written from his migration file, the live database and the three call transcripts. Dr. Rob's spec is not woven through them — it arrived on 21 August and only entered this page on 13 September, through §10, and only on one issue.
This section closes that gap. The full line-by-line engineering comparison lives at /biometrics-spec-gap-analysis/ — also gated. What follows is the version you can read to a board member.
The comparison, in one table
| The question | What Rahul built | What Rob's document says | Does it matter? |
|---|---|---|---|
| Who works out the coins? | The phone. | The phone works it out — then the server checks it. | Yes Today nobody checks. Whatever number the phone sends is the number we keep. |
| What the phone sends us | "612 coins." | "9,180 steps, which is 612 coins." | Yes Without the steps we cannot check the maths, prove it to a partner, or export it to a health record. |
| Can we tell which activity earned a coin? | No — walking, a workout and exercise minutes all arrive as one word: "movement". | Yes — each coin names the rule that paid it. | Yes A health system needs to know whether it was steps or a workout. They are different medical records. |
| Can a member change their own coin total? | Yes. The database permits it. | No. The server recalculates and ignores what the phone claims. | Yes This is a live hole, open since August. |
| Can we change the coin rates without an app-store release? | No — the rates are baked into the app. | Yes — rates load from a spreadsheet the business controls. | Medium Every pricing change waits on Rahul and on Apple's review queue. |
| Can we prove a coin wasn't invented? | No. | Yes — every batch is signed by the phone and verified by the server. | Later Needed before money or prizes ride on a leaderboard. |
| Does a health measurement ever reach our server? | No. | No. | Both agree This is the part that is right, and it is the hard part. Neither design sends us a heart rate, a weight or a blood pressure. |
Read the last row first. The privacy architecture — the thing that took real design work and that we say out loud to partners — Rahul built correctly. What is missing is everything that makes the coins trustworthy, not everything that makes them private.
Why "two paths" is the wrong shape of question
Dr. Rob's document describes one system in two halves, and they are built by different people:
| Half | What it does | Who can build it | Status |
|---|---|---|---|
| The phone half | Reads Apple Health / Health Connect, applies the rates, sends the result. | Only Rahul. It is native app code inside the app he owns. | Built, and working. |
| The server half | Receives the result, re-checks the maths, enforces the daily caps, keeps the permanent record, versions the rates, exports to health-record format. | Us. Entirely. It is our Supabase. | Not built. Nobody has started it. |
A "parallel track" to Rob's document is not available for the phone half. Only one app on a member's phone can read Apple Health, and Rahul owns it. Building a parallel phone app does not run alongside his — it replaces his, and it needs a mobile developer we do not currently have.
But the server half needs nothing from Rahul except two extra fields. Everything else in Rob's server design — the permanent ledger, the re-check, the caps, the rate versions, the FHIR export — we can build ourselves, starting this month, without asking anyone's permission.
The three options, with honest costs
| Option | What it costs | What you get, and what you do not | |
|---|---|---|---|
| 1 | Carry on exactly as now Rahul's build, no server work |
Nothing. | Coins keep flowing and the app keeps working. You cannot prove a single coin, cannot export to a health record, cannot stop a member editing their own total, and cannot change a rate without an app release. Not a position to take into a payer conversation. |
| 2 | Build a parallel app to Rob's spec a second phone app, our own |
A mobile developer. Months, not weeks. Plus running two apps at once. | Not actually available today We have no mobile developer. And it is not parallel — two apps cannot both own the health-data connection on one phone. This is "replace Rahul", priced honestly. Keep it on the table as a contingency, not as a plan. |
| 3 | Keep Rahul's phone. Build Rob's server ourselves. the convergence path |
Our own Supabase work. No new hire. Rahul's only obligation is to send two extra fields. | Recommended Rahul's app feeds a ledger we control and verify. We get provable coins, the health-record export, rate versioning and cap enforcement — and we stop being dependent on his release schedule for anything except those two fields. |
Option 3 — what we build, and what we need from him
What we need from Rahul — this is the entire list:
- Send
rule_id— which rule paid the coin. - Send
n— the count it was worked out from.
That is Q16. Both fields are already in his own spec's ledger schema. Nothing else on this list depends on him.
What we build ourselves, in order:
| # | Piece | Effort | Why first / why later |
|---|---|---|---|
| 1 | Close the coin-editing hole | hours | Do this regardless of every other decision. Drop the update permission; clamp writes in a database function. No dependency on anyone. |
| 2 | Permanent append-only ledger | days | The table Rob specified. Write to it in parallel with the current one — nothing breaks while it fills. |
| 3 | Stamp the rate version on every award | hours | Cheap, and it stops a future rate change silently rewriting history. |
| 4 | Server-side re-check + cap enforcement | days | Needs Q16 first. Without n there is nothing to re-check against. |
| 5 | Health-record (FHIR) export | weeks | The Vault deliverable. Needs 2 and 4 in place, and the tier list signed off (Q19). |
Items 1 and 3 can start today and need nobody's agreement. That is the argument for Option 3: most of the value is on our side of the line and has been the whole time.
The one decision that changes the answer
If Rahul sends rule_id and n — Option 3 is fully open and we
should stop describing this as two paths. There is one system; he builds the phone half, we build
the server half, and Rob's document is the shared specification both halves are measured against.
If he refuses, or goes quiet for another fortnight — that is not an engineering problem to work around. It is the signal to price a second mobile developer, because it means the phone half cannot be specified by us and we are building a company asset on top of code we cannot see, cannot verify, and cannot change. That is a board decision, and it should be taken deliberately rather than arrived at by drift.
Set the deadline when you ask. Ask for a date, and diarise it. The pattern across all three calls is that unanswered questions simply lapse — Q1 was asked on 7 September and never came back.
What Rob's document covers that neither side has built
Worth knowing so it is not mistaken for a gap in Rahul's work — none of this was ever asked of him:
| In the document | Built? | Note |
|---|---|---|
| 187-metric catalogue with sensitivity tiers | No | Ten metrics shipped. The other 177 are a roadmap, not a shortfall. |
| Screen time as a self-set budget | No | Genuinely clever — scored as "stayed under my own budget", never app names. Nothing built. |
| FFH-native rules — Bingo squares, tracker entries, streak payouts | No | These are ours to build, not Rahul's — they do not touch health data at all. |
| Phase 2 — Apple depth layer (Time in Daylight, Walking Steadiness, Six-Minute Walk) | No | Still coins-only, still no health data to us. Sequenced after Phase 1 is trustworthy. |
| Phase 3 — clinical opt-in, member shares detail with their own clinician | No | Separate service, separate database, separate consent. Rob's "Wall". This is the phase that needs the signed BAA — not Phase 1. |