Skip to content

probes: close the gaps #40 documents — drift guard, realtime flake, mock URL - #45

Merged
Harrolee merged 1 commit into
mainfrom
cleanup/handoff-40-probe-hygiene
Aug 20, 2026
Merged

Harrolee merged 1 commit into
mainfrom
cleanup/handoff-40-probe-hygiene

Conversation

@Harrolee

@Harrolee Harrolee commented Aug 20, 2026 •

Copy link
Copy Markdown
Owner

Picks the fixable items out of the #40 handoff issue. Three of the facts that issue records were fixable in code rather than prose.

Guard the duplicated prompt modules

Cloud Functions are zipped per-directory, so require('../shared/...') does not survive deploy and each function carries its own copy. The duplication is deliberate; the drift is not, and it has bitten before — a coach.tagline fix once landed in functions/shared/ and never reached the deployed copy, which means a prompt change that silently does not apply in production looks exactly like a prompt change that did not work.

Only crisis.js and visualization.js were ever checked. The section in crisis-probe.mjs is now a table covering all seven shared modules and all twelve copies, plus an assertion that every module in functions/shared/ appears in that table. That last check earned its keep immediately: it turned up coach-personas.js and reference-photo.js as unguarded.

Copies of coach-personas.js carry a "shared/ is the source of truth" banner and are otherwise byte-identical, so one leading block comment is stripped before comparing — the banner stays allowed, everything after it still has to match exactly.

Fix the viz-realtime-probe flake

Running the full suite in sequence produced 29 passed, 4 failed in viz-realtime-probe.mjs, all four in realtime delivery. It is not a regression. Realtime replays the WAL in order, and after the four write-heavy suites it was still draining their backlog when the probe's fixed 4-second wait expired. In isolation the same code gave 33/0.

The assertions now wait for delivery instead of sleeping. The no-leak assertion was the more interesting half: it asserted a negative against a bare sleep, which would pass just as happily against a realtime server that had stopped delivering anything at all. It is now pinned to a positive — the other member subscribes to their own thread, and we wait for the message to arrive there before asserting it did not arrive here. Once it has been delivered elsewhere, realtime has demonstrably processed that insert, so silence on our channel means filtered rather than in flight.

Verified by reproducing the original conditions: the four write-heavy suites back to back, then viz-realtime-probe.mjs immediately after. Was 29/4, now 35/0.

Accept both meanings of MOCK_OPENAI_URL

harness/function-gateway.js wants the OpenAI base URL (.../v1) because the SDK appends the route itself. sms-image-probe.mjs calls the endpoint directly and wants .../v1/chat/completions. Same variable, two meanings.

Passing the gateway's form to the probe returns 404 on every model call, which surfaces as ~20 unrelated-looking assertion failures — "a scene was sent", "exactly one Replicate call — 0", "the image is about drums" — rather than as a wiring mistake. I lost a cycle to exactly this. Either form now works.

Also

  • Deleted saveSelectedAvatar. It took a coachId and an avatar URL from the request body and wrote them straight onto that row in coach_profiles, with no token check and no ownership check. Terraform never gave it an entry_point, so it was never deployed and never reachable — it was only waiting for someone to wire it up and ship an IDOR. A comment in its place explains what is missing if saving comes back.
  • README. Said "Five suites" while listing seven, and omitted creator-probe.mjs from the run list. Documents the MOCK_OPENAI_URL ambiguity and the realtime ordering constraint.

The live exposure this does not fix — coach-avatar-generator itself is public, unauthenticated and burns Replicate credits — is #44. It needs a product decision rather than a patch, because /coach-builder/* is deliberately unauthenticated and requiring a session there would kill the top of the signup funnel.

Verification

Local stack, all seven suites plus the model-free probe:

Suite Result
rls-probe 77 / 0
club-probe 62 / 0
flow-probe 46 / 0
viz-realtime-probe 35 / 0 (was 33, +2 new checks)
sms-image-probe 55 / 0
account-deletion-probe 64 / 0
creator-probe 39 / 0
prompt-eval/crisis-probe 143 / 0

378 checks across the seven, and npx tsc --noEmit clean from mobile/. sms-image-probe verified green under both MOCK_OPENAI_URL forms.

Does not close #40. That issue is a standing read-me-first for anyone picking up work here,
not a work item — closing it would hide it. Its body has been refreshed to match what this PR
changes, and every number in it re-measured on merged main.

🤖 Generated with Claude Code

…ock URL

The handoff issue (#40) records facts that cost sessions real time. Three of
them were fixable in code rather than prose, so they are fixed here.

Guard the duplicated prompt modules. Cloud Functions are zipped per-directory,
so each function carries its own copy of the shared modules; the duplication is
deliberate but the drift is not, and only crisis.js and visualization.js were
ever checked. The check in crisis-probe.mjs is now a table covering all seven
shared modules and all twelve copies, plus an assertion that every module in
functions/shared/ appears in that table — which immediately turned up
coach-personas.js and reference-photo.js as unguarded. Copies may carry the
"shared/ is the source of truth" banner and nothing else.

Fix the viz-realtime-probe flake. Realtime replays the WAL in order, so running
that suite straight after the write-heavy ones could leave its event still in
the queue when the fixed 4s wait expired — reported as four realtime failures
that looked like a regression and were not. The assertions now wait for
delivery, and the no-leak assertion is pinned to a positive delivery on a second
subscription: a bare sleep passes just as happily against a realtime server that
has stopped delivering anything at all.

Accept both meanings of MOCK_OPENAI_URL. The gateway wants the base /v1 URL
because the SDK appends the route; sms-image-probe.mjs calls the endpoint
directly and wants /v1/chat/completions. Passing the gateway's form returned 404
and surfaced as twenty unrelated-looking assertion failures. Either form now
works.

Also: delete the never-deployed saveSelectedAvatar handler, which wrote a
caller-supplied avatar URL onto any coachId with no token and no ownership
check. Terraform never gave it an entry_point, so it was unreachable; it was
only waiting for someone to wire it up and ship an IDOR. The README said "five
suites" while listing seven and omitted creator-probe from the run list.

Verified on a local stack: 378 checks green across all seven suites
(rls 77, club 62, flow 46, viz 35, sms 55, deletion 64, creator 39),
crisis-probe 143, tsc clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Harrolee
Harrolee merged commit 5cfb93d into main Aug 20, 2026
1 check passed
@Harrolee
Harrolee deleted the cleanup/handoff-40-probe-hygiene branch August 20, 2026 16:30
Harrolee added a commit to cekuu35/cabo that referenced this pull request Aug 20, 2026
Resolves the conflict with Harrolee#45 and fixes three things in the contributed
change.

Rate-limit key. The limiter read `req.ip` first and fell back to
`X-Forwarded-For`. Express only populates `req.ip` from that header when
`trust proxy` is set, which the functions framework does not do here, so
`req.ip` is Cloud Run's front end — the same value for every caller. The
per-IP ceiling would have been a global one, and the pre-signup avatar step
would have started 429ing everybody after a handful of builds, which is the
exact funnel this endpoint exists to serve. Now keyed on the left-most
X-Forwarded-For entry.

saveSelectedAvatar stays deleted. Harrolee#46 restored it with resolveCaller() and
isCoachOwner() in front, which does fix the IDOR, but Terraform gives this
directory one entry_point and it is not that one — so it is unreachable code
on a write path, one Terraform line away from being live. Both helpers remain,
so bringing saving back is a small change; do it with an entry_point and a
caller in the same PR.

AVATAR_STYLES is required at the top rather than lazily inside the 400 branch.

Adds mobile/e2e/avatar-auth-probe.mjs — 25 checks over the authorization
matrix, which Harrolee#44 asked for and the PR did not have. It loads the real
index.js with ./avatar-generation replaced by a recording stub, the technique
crisis-probe.mjs uses on openai, so it spends no Replicate credits and can
assert the part that matters: a rejected call reaches generation zero times.
A 403 that still burned a credit looks identical from the outside. Supabase is
deliberately not stubbed — token verification and the ownership lookup run
against the real stack.

Verified the probe can fail: disabling isCoachOwner() and the temp- prefix
check turns 25/0 into 18/7, including an anonymous caller generating against a
real coach id.

Co-Authored-By: cekuu35 <cekuu35@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[handoff] Facts every agent rediscovers the hard way: probe baseline, local stack, duplicated prompt modules

1 participant