Adopt 8 PR-agent (Qodo) findings; 4 declined (concurrency already guarded by
the putSyncedRecord/markRecordSynced guards + RPC optimistic concurrency;
SQL-injection / won-equality / microtask-race are false positives — see PR reply).
- markRecordSynced: guard on updatedAt too — softDeleteStory doesn't bump rev,
so a same-rev newer local tombstone must not be marked synced by an older
push's ack (symmetric with putSyncedRecord's guard)
- recordToEnvelope: fallback timestamps to 0 not Date.now() (a corrupt record
should lose LWW, not win as "now")
- push/delete routes: validate rev/updatedAt as finite -> 400 (was silent 200);
push: Content-Length pre-check before buffering the body
- pushDeletion: idbGet a single record instead of a full-store scan
- manifest: Cache-Control private,no-store + client fetch cache:no-store
- cloudSyncClient: Array.isArray narrowing on items/blobs
- RPC: `if found` instead of `v_row.id is not null` after RETURNING INTO
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- coerceEpoch: early-return fallback on null/undefined input (was falling
through to new Date(null) → epoch 0, surfacing as 1970-01-01 in the
stories list); also unify the tail branch to Number.isFinite for
consistency with the number-type fast path
- autosave .catch: roll back lastSavedFingerprintRef on unexpected throw
so the next session change retries instead of silently marking the
content as saved
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
From PR #114 external review agent — adopted the real, low-cost findings;
remaining items (false positives / design trade-offs) explained in PR replies:
- coerceEpoch: !Number.isNaN → Number.isFinite — reject ±Infinity, which
previously slipped through and produced Invalid Date via new Date(Infinity)
- enforceRetentionCap pass2/pass3: decrement overflow only when idbDelete
actually succeeds — a failed best-effort delete no longer under-evicts
- cloudListStories: explicit column list instead of select() — avoid pulling
the bulky session_jsonb when only metadata is needed
- Supabase stories: composite primary key (user_id, id) + onConflict user_id,id
— avoid a cross-user Session.id collision rejecting the second user's save
(skeleton not yet deployed, so the migration is edited in place)
typecheck + build:cf green.