fix(clio-probes): review wave - write-probe boundary, rail verdicts, coverage

↗ view on GitHub · sloth-ninja · 2026-08-14 · fd29a7b5

Independent review of the probe runner: one Critical, three Important, six
minors. All applied.

CRITICAL - the write probe escaped its own boundary. The billed sub-probe took
its candidate from probe 9, which runs against an ARBITRARY live matter (probe
2's first row or --matter-id), not CLIO_PROBE_TEST_MATTER_ID - so the "no-op"
PATCH would have landed on a real client's invoiced time entry, contradicting
the file's own promise. Worse, a null/withheld note degraded to "", so the
no-op could have BLANKED a note on an invoiced record. The sub-probe now runs
its own search on the test matter, restricted to the caller's own entries, and
skips with an UNANSWERED verdict unless the note is readable (also skips when
the caller's Clio user id is unknown, rather than widening to colleagues').

IMPORTANT - inner catches swallowed the safety rails. Probe 3 scored ANY
failure as "pass"; probes 6/8 absorbed a blocked DB write or a rate-limit abort
into `controlRejected` and carried on issuing requests. Every inner catch now
calls rethrowRailErrors first, and probe 3's non-400 branch is "inconclusive" -
only an outright rejection answers open question 1.

IMPORTANT - the "never initiates OAuth" claim was imprecise: a 401 can still
make the shipped client attempt one refresh-token exchange before the (blocked)
persist. Reworded to say what is actually true - no authorisation flow ever, and
the stored row is never rewritten. client.ts deliberately untouched.

IMPORTANT - probe 2 now also exercises the list `query=` search and `status=`
filter, the exact surface #71 rebuilt and the one part of the Matters header
that had never been called live.

Minors: cleanup DELETE sleeps once and retries a single time past a drained
rate-limit window (what it is deleting is live data); a billed-edit refusal
counts only 401/403/409/422, so a 500 no longer reads as "Clio refuses";
`npm run typecheck:scripts` added to the backend CI job; the readOnlyDb proxy
inverted to an ALLOW-list (only a wrapped `from`) after the reviewer showed
`schema().from().update()` walks past a deny-list; the contacts selector hoisted
into an exported CONTACT_SEARCH_FIELDS in manageTools.ts so the probe tests the
shipped string, not a copy; matter-id bound tightened to 15 digits. Also fixed
en route: a preflight failure printed "Unknown error" because Supabase rejects
with a plain object, not an Error.

Verified: tsc --noEmit clean; tsc -p tsconfig.scripts.json --noEmit clean;
prettier clean; full backend suite 888 passed / 50 files; --dry-run walks all
ten probes; all four refusal paths plus the missing-env path refuse correctly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Repository Sloth-ninja/JessicaOSS
Author sloth-ninja <ezanahaddis@gmail.com>
Authored
Committed
Parents aa97f630
Stats 3 files changed , +264 , -71
Part of Guard-railed Clio live-probe runner

Capture this commit into my fork

Download a Markdown prompt that tells Claude how to port this exact commit into your working tree. Run it via claude -p < capture-commit-fd29a7b5.md from inside the repo you want the change in.

⬇ Download capture-commit-fd29a7b5.md