RAID Report — 2026-05-04 (Scheduled)
Date: 04-05-2026 Subject: RAID Report
Source: docs/architecture/RISKS_GAPS_ASSUMPTIONS.md on main.
This is the first RAID report from the scheduled task. Items already marked resolved in the source doc are carried in as closed (struck-through) so future runs have a baseline to diff against. “Days open” is measured from the date the item first appeared in the document’s CHANGE SUMMARY where that can be derived; otherwise it is given as the conservative lower bound and flagged with ~.
| Issue Number | Issue Type | Status | Days open | Suggested Action |
|---|---|---|---|---|
R-01 Monolithic shell (src/App.jsx ~500 lines) | Gap | Open | ~30 | Extract auth gates, route tree, modal refs into focused components per the v1.1 refactor noted in CLAUDE.md. Open a Linear issue if one does not yet exist. |
R-02 Oversized hooks as service layers (useNoteForm.js, useSettings.js) | Gap | Open | ~30 | Split UI state from Dexie/Supabase/PostHog side-effects; introduce a thin service layer so a regression in one concern does not break sync + AI simultaneously. |
| R-03 SUR-60 Manual Supabase migration apply | Risk | Open | ~30 | Add a CI gate that fails the build when client code references a column not present in scripts/schema-contract.js, and automate supabase db push from the migrations folder. |
| R-04 Managed proxy env fragility (Edge Function 500s on missing env) | Risk | Open | ~30 | Surface an operator-actionable code in the toast (useNoteForm.js:254) and add a startup self-check in the Edge Function that returns a typed MISCONFIGURED error rather than 500. |
R-05 Edge Function logs user identifiers (anthropic-proxy/index.ts:399-400, 423) | Risk | Open | 8 | Strip user.id and action type from console.log lines, or hash them, before any production-grade load. |
| R-06 SUR-242 Azure Content Safety fail-open with no alerting | Risk | Open | 3 | Emit a PostHog guardrail_fail_open event on every fail-open path; add a Supabase dashboard / log alert so operators learn within minutes, not days. |
| R-07 SUR-242 Azure Content Safety latency budget (~5–15 s on degradation) | Risk | Open | 3 | Document the per-call timeout in guardrail.ts, cap total per-request guardrail latency, and degrade gracefully with a single fail-open path rather than three sequential retries. |
| R-08 SUR-242 Client-side PII detection is regex-only (no NER) | Gap | Open | 3 | Track SUR-246 (Llama Prompt Guard 2 + GLiNER NER) explicitly on the v1.5 plan; until then, expand the warn copy so users understand names/addresses are not detected. |
| R-09 SUR-242 v1.4 warn-not-block PII policy | Gap | Open | 3 | Decide whether v1.5 moves to soft-block on high-confidence card/SSN matches; document the policy in docs/architecture. |
| R-10 SUR-256 Blog requires full rebuild per post | Gap | Open | 3 | Acceptable for solo founder cadence; revisit if posting cadence rises. No action this cycle. |
| R-11 Rate-limit race window (check vs write not atomic) | Risk | Open | 8 | Move the increment into a single upsert_ai_usage RPC with a CHECK constraint; required before paid tiers raise the cap. |
| R-12 SUR-92 Per-request quota lookup latency | Risk (low) | Open | 5 | No action at current scale; revisit with a 30-60 s {userId → resolvedLimit} cache if managed-AI traffic grows ≥10×. |
| R-13 Usage opacity (no in-app remaining-quota surface) | Gap | Open | ~30 | Add a “remaining managed-AI calls this month” line to ProfileScreen / SettingsModal reading from ai_usage_daily. |
R-14 No per-note image cleanup in note-images bucket | Gap | Open | 8 | Add a counterpart to uploadImage that removes the storage object on deleteNote; or a periodic sweep keyed off soft-deleted note rows. |
| R-15 Outbox single queue with opaque failure mode | Risk | Open | 8 | Tag outbox entries by error class (RLS / schema / 5xx); quarantine poison messages after N retries and surface them in a settings panel. |
| R-16 Full re-decrypt on every sync | Gap | Open | 8 | Add an encryption-only delta path keyed off updatedAt; show a progress indicator so the cost is visible during large syncs. |
| R-17 Sync image fetch is sequential | Gap | Open | 8 | Switch the for-loop in useAuth.js:213-221 to bounded Promise.all (e.g. 4-8 in flight); record per-image failures so they retry next cycle. |
| R-18 Decrypt-failure recovery is dead-end | Gap | Open | ~30 | Add a “retry unlock” affordance that calls getEncryptionPrfOutput() without a full sign-out; surface count and remediation copy. |
| R-19 Transfer-code auto-expire is best-effort | Risk | Open | 8 | Add a server-side cron / scheduled function to deactivate expired transfer-v1 blobs rather than relying on the next createDeviceTransfer. |
R-20 No PWA update prompt (registerType: 'autoUpdate') | Gap | Open | 8 | Switch to prompt mode and wire an onNeedRefresh toast; replace the deprecated apple-mobile-web-app-capable meta tag at the same time. |
| R-21 Accessibility gaps (long-press is the only edit/delete path) | Gap | Open | ~30 | Add a visible action button or context menu fallback; ensure escape paths exist on capture/note views where the bottom nav is hidden. |
R-22 db.js has no dedicated unit tests | Gap | Open | 8 | Add direct unit tests for saveNote, updateNote, deleteBook cascade, mergeCloudRecords, importMerge/Replace, and the v1→v9 migration chain. |
| R-23 SUR-257 Duplicate-source UX gap (non-blocking duplicate hint) | Gap | Open | 8 | Decide whether v1.5 ships hard-block on exact match, plus a merge tool to clean up existing duplicates. |
| R-24 SUR-261 Branded auth-email template lives in repo only | Gap | Open | 8 | Add a CI step (or a Supabase Management API call) that diffs the dashboard template against supabase/email-templates/magic-link.html. |
R-25 SUR-233 device_label immutable after enrolment | Gap | Open | 2 | Cosmetic; add an authenticated PATCH path on wrapped_key_blobs scoped by user_id if user feedback warrants. |
| R-26 SUR-235 Add-idea Free-tier cap not yet enforced | Gap | Open | 2 | Implement the useEntitlements() gate on the AddIdeaSheet “Create” CTA; coordinate with Pro upsell copy. |
R-27 SUR-261 Approved waitlist rows with user_id IS NULL stranded | Gap | Open | 8 | Run the audit query on a schedule; have admin tooling auto-invite stranded rows or surface them in the queue. |
| Gap | Closed | — | Resolved by SUR-223 Phase 1 (2026-04-25). Phase 2 (in-app help renderer) tracked separately. | |
| Risk | Closed | — | Resolved. All AI calls now go through the managed Edge Function via invokeAnthropicProxy. | |
| Risk | Closed | — | Resolved. useAuth.js:166-176 only sets schemaProbed.current = true after success. | |
| Risk | Closed | — | Resolved. fetchAllCloud(since) is incremental via lastSyncRef.current. | |
| Gap | Closed | — | Resolved by api.test.js, photoAdapter.test.js, capture.test.jsx. | |
| Gap | Closed | — | Resolved. HowItWorksPage.jsx and LandingPage.jsx removed (2026-04-23). | |
| A-01 Dexie ↔ Supabase lockstep is human-driven | Assumption | Open | ~30 | Add a CI check that cross-references the Dexie schema declaration in db.js against scripts/schema-contract.js. |
| A-02 SUR-92 Free-tier monthly cap default 50, cross-action | Assumption | Open | 5 | Validate the 50/month cap against actual usage curves once paid tiers go live; correlate with ai_usage_daily token columns to estimate cost. |
A-03 ANTHROPIC_API_KEY rotation is manual | Assumption | Open | ~30 | Document the rotation runbook; consider migrating to a secrets manager so a stale key surfaces with an explicit error rather than a 500. |
A-04 VitePWA autoUpdate is acceptable | Assumption | Open | ~30 | Linked to R-20; flip to prompt once a refresh-toast UI exists. |
| A-05 Orphaned image blobs on book delete are tolerable | Assumption | Open | ~30 | Linked to R-14 / unknown SLA; document the cleanup-sweep plan. |
| A-06 Single Anthropic API key, no project-level spend cap | Assumption | Open | ~30 | Set a per-project Anthropic spend cap or a daily $-threshold alert tied to posthog-node. |
| A-07 SUR-242 Azure severity threshold ≥5 calibrated for Surfc content | Assumption | Open | 3 | Re-evaluate threshold once managed usage exceeds ~500 calls/month using real false-positive data. |
A-08 Future ingest adapters reuse existing ingest interface | Assumption | Open | ~30 | Validate by sketching the Readwise adapter against photoAdapter.js / manualAdapter.js; surface required Dexie additions early. |
| A-09 SUR-238 CaptureFabMenu speed-dial sufficient | Assumption | Open | 11 | Schedule a usability check with 3-5 users; if confused, add a persistent minimal nav or back-button. |
A-10 help.surfc.app (Cloudflare Pages) remains reachable | Assumption | Open | ~30 | Linked to Phase 2 in-app fallback. Add a synthetic uptime check on the help domain. |
A-11 session.access_token refresh covers all expiry races | Assumption | Open | ~30 | Add a regression test that backgrounds the PWA past expiry and confirms the next Edge Function call succeeds without an “Invalid JWT” error. |
| A-12 SUR-261 Email-OTP error-string mapping is best-effort | Assumption | Open | 8 | Add a contract test that pings the live Supabase Auth endpoint with an unknown email and asserts the substring is still present. |
| A-13 Email signups stay disabled at Supabase platform level | Assumption | Open | 8 | Document the dashboard toggle in the SUR-261 PR + migration playbook; add a periodic check via Supabase Management API. |
| A-14 Email-OTP code length/expiry are Supabase defaults | Assumption | Open | 8 | No action while abuse signals are absent; revisit once managed AI volume gives a baseline for abuse detection. |
A-15 SUR-261 isStandaloneOrTwa() decided once at mount | Assumption | Open | 8 | Acceptable today; revisit if iOS/Android ever hot-swap install context within a session. |
| D-01 Supabase + Anthropic key management / rotation cadence | Dependency | Open | ~30 | Owner: ops. Document Netlify + Supabase Edge env vault choice and rotation cadence in docs/architecture/. |
| D-02 Monitoring/alerting for sync + Edge Function errors | Dependency | Open | ~30 | Pipe console.log from the Edge Function into a log drain with an alert on error-rate spikes; surface client-side syncStatus failures via PostHog. |
| D-03 Project-level Anthropic spend cap vs per-user counters | Dependency | Open | ~30 | Set a project-level monthly spend cap in the Anthropic console; correlate ai_usage_daily.input_tokens/output_tokens with cost in a dashboard. |
| D-04 Exports including storage paths or binary blobs | Dependency | Open | ~30 | Decide whether buildExport ships embedded image data; if yes, scope a v1.5 task to update the export schema + import path. |
D-05 SLA for clearing orphaned note-images on active accounts | Dependency | Open | ~30 | Owner question. Linked to R-14. |
| D-06 Multi-device editing conflict UI beyond last-write-wins | Dependency | Open | ~30 | Decide product policy: silent LWW vs. surfaced “your edit was overwritten” hint. |
D-07 Background sync via service worker sync events | Dependency | Open | ~30 | Spike feasibility on iOS Safari / Android TWA; today the outbox only flushes on online or manual sync. |
| D-08 Account recovery path when passkey destroyed and no transfer code | Dependency | Open | ~30 | Confirm the by-design “data is unrecoverable” stance with product, or define a support-side reset flow that wipes the encrypted note text. |
D-09 Deletion-success audit trail beyond delete-account 200 | Dependency | Open | ~30 | Add a server-side audit row in a deletion_log table written by the Edge Function; reconcile against auth.admin.deleteUser outcomes. |
Notes for the next run.
- This is the baseline. Future runs should diff this report against the new
mainversion ofRISKS_GAPS_ASSUMPTIONS.mdand strike through any item whose corresponding bullet has moved to the “Resolved” /~~struck-through~~block. - “Days open” for items first introduced in the document’s earliest section (“Updated:” entries with no explicit date) is rendered as
~30— the document’s earliest dated entries are 2026-04-23, so anything older than that is at least ~30 days as of 2026-05-04. Refine with git blame on a future pass if a more precise number is needed. - Closed items in this baseline are kept as a record so that, if any of them re-open, the next run can flip Status back to Open without losing the issue number.