Skip to content

RAID Report — 2026-04-29

Date: 29-04-2026 Subject: RAID Report

Source: docs/architecture/RISKS_GAPS_ASSUMPTIONS.md (read from main via git show main:…). Previous report: RAID_REPORT_2026-04-28.md (1 day ago).

Issue NumberIssue TypeStatusDays OpenSuggested Action
1. Monolithic UI/control plane (src/App.jsx ~500 lines, owns auth gates, layout, route tree, prop fan-out)RiskOpenUnknown (predates 2026-04-23)Begin v1.1 refactor — split into focused screen components + a dedicated routing module; lift modal/overlay refs into a context.
2. Oversized hooks acting as service layers (useNoteForm ~470 lines, useSettings)RiskOpenUnknown (predates 2026-04-23)Decompose into UI-state hook + pure persistence module + ingest/PostHog dispatcher; add unit tests around the seams.
3. Schema probe only runs once after first failure [SUR-61]RiskClosed (resolved per 2026-04-26 audit — useAuth.js:175 only sets the flag after success)n/aNone — verify regression coverage in next sync test pass.
4. Manual Supabase migration application — drift detection automated but apply still manual via SQL editor [SUR-60]DependencyOpenUnknown (predates 2026-04-23)Add a CI gate that fails the build if scripts/check-schema.js reports drift on main; longer-term, adopt Supabase CLI db push from CI.
5. Managed proxy env fragility — missing SUPABASE_SERVICE_ROLE_KEY / ANTHROPIC_API_KEY surfaces as generic 500s with no operator detail in the user toastDependencyOpenUnknown (predates 2026-04-23)Add a startup self-check Edge Function that pings each required env at deploy; route 500s to a structured error code surfaced in the toast for ops.
6. Direct Anthropic exposure (BYOK) — plaintext + key from compromised browsers [SUR-63 / SUR-91]RiskClosed (SUR-91 fully sunset; only a one-time apiKey meta cleanup remains in db.js:156)n/aNone — schedule removal of the cleanup shim once telemetry confirms no remaining BYOK rows.
7. No per-note image cleanup — note-images bucket retains orphans after note delete (per-account sweep only on full account deletion)GapOpenUnknown (predates 2026-04-23)Mirror deleteCloudData’s sweep into cloudWrite for soft-deleted notes; add a nightly orphan-blob job comparing notes.imagePath to the bucket.
8. Full-table sync only — fetchSince helper unused [SUR-62]GapClosed (fetchAllCloud(since) now wired through lastSyncRef.current with backfill in useAuth.js:189-210)n/aNone — keep the backfill regression covered by incremental-sync.test.
9. Outbox single queue, opaque failure mode — one stuck payload (RLS reject, oversized sourceMeta) blocks the rest; user sees no quarantine surfaceRiskOpenUnknown (predates 2026-04-23)Split the outbox by entity type or add a poison-message quarantine + a Settings panel that lists stuck items with retry/discard actions.
10. No OCR regression coverage for callTranscribeImageGapClosed (covered by api.test.js, photoAdapter.test.js, capture.test.jsx)n/aNone.
11. Accessibility gaps — long-press only for edit/delete, no keyboard / context-menu / visible button alternative; bottom-nav-hidden state on capture/note compounds itGapOpenUnknown (predates 2026-04-23)Add a visible “more actions” overflow button on note rows + keyboard shortcuts; document target sizes against WCAG 2.5.5.
12. Usage opacity — ai_usage_daily not surfaced; users only see “Monthly limit reached” toast at the capGapOpenUnknown (predates 2026-04-23)Add a usage meter (“X / 1000 calls this month”) in ProfileScreen or SettingsModal, sourced from ai_usage_daily.
13. SUR-209 — broken in-app help links (/#faq, /#what-is-surfc resolve to nothing post-SUR-218)RiskClosed (SUR-223 Phase 1 — ProfileScreen and SettingsModal now point to https://help.surfc.app)n/aPhase 2 (in-app help renderer reading from docs/getting-started/) remains outstanding — track separately.
14. How-It-Works placeholders — removed by SUR-215RiskClosed (already in prior report)n/aNone.
15. Dexie ↔ Supabase lockstep is human-driven — Dexie v1→v9 migration chain not cross-checked against the schema contractAssumptionOpenUnknown (predates 2026-04-23)Extend scripts/check-schema.js to also assert Dexie’s expected columns; add a PR template item for “Dexie + Supabase + mergeCloudRecords updated together”.
16. Free-tier 1000-call/month managed quota is sufficient; per-tier pricing deferred [SUR-67]AssumptionOpenUnknown (predates 2026-04-23)Pull token-cost data from ai_usage_daily.input_tokens/output_tokens into a simple weekly cost dashboard; revisit once paid tiers land.
17. VitePWA autoUpdate is acceptable — no onNeedRefresh UI; users won’t be told a new version existsAssumptionOpenUnknown (predates 2026-04-23)Wire a registerSW({ onNeedRefresh }) toast with a “Reload” CTA; combine with fix for deprecated apple-mobile-web-app-capable meta.
18. Cascading book + note tombstones acceptable despite orphan-image risk on failed image-upload racesAssumptionOpenUnknown (predates 2026-04-23)Same fix as #7 — once orphan-image sweep exists, this assumption can be retired.
19. Future ingest adapters (Readwise / Kindle) reuse the existing ingest interface without backend changesAssumptionOpenUnknown (predates 2026-04-23)Build a Readwise PoC against the current adapter to validate; if backend changes are needed, surface them before adapter v2 lands.
20. CaptureFabMenu speed-dial provides sufficient navigation on capture/note views with bottom-nav hidden [SUR-238]AssumptionOpen6 (added 2026-04-23)Run a 5-user usability test on capture → save → return-to-library; if friction shows, add a persistent back affordance.
21. How are Supabase credentials and ANTHROPIC_API_KEY managed in Netlify and Supabase Edge environments — and what is the rotation cadence?DependencyOpenUnknown (predates 2026-04-23)Document the secret-management process in docs/architecture/DEPLOYMENT_ARCHITECTURE.md; agree a rotation cadence (90 days) and put a calendar reminder.
22. Monitoring / alerting for sync failures and Edge Function errors — console.log-based, not piped to PagerDuty / Slack / emailGapOpenUnknown (predates 2026-04-23)Add a Logflare or Logtail drain on the Edge Functions; alert on >N 5xx/min and on decrypt_failure PostHog event spikes.
23. Should exports include Supabase storage paths or binary blobs to guarantee a fully portable backup?GapOpenUnknown (predates 2026-04-23)Add an opt-in “Include images” toggle to buildExport; emit a zip with JSON + image blobs when enabled.
24. SLA for clearing orphaned note-images on still-active accounts (deleted notes, replaced images)GapOpenUnknown (predates 2026-04-23)Define a 7-day orphan window; bundle into the same nightly sweep proposed in #7.
25. Multi-device editing conflict UI beyond last-write-wins not surfaced — mergeCloudRecords silently picks higher updated_atGapOpenUnknown (predates 2026-04-23)Log conflicts to a local audit table; surface a “Your edit was overwritten” toast with a “Restore my version” link.
26. Background sync (service-worker sync events) for queued writes when app is closedGapOpenUnknown (predates 2026-04-23)Register a background-sync event in the VitePWA service worker; gate behind a feature-flag for browsers that support it.
27. Edge Function debug logs include user_id on every authenticated request — broader audit trail than the rest of the appRiskOpen3 (added 2026-04-26)Strip or hash user.id in anthropic-proxy/index.ts:399-400, 423; keep only error-path logs at warn/error level.
28. Rate-limit race window — getMonthlyUsage check and recordUsage write are not atomic; documented worst-case overrun equal to in-flight requests [TODO SUR-67]RiskOpen3 (added 2026-04-26)Move enforcement into a CHECK on the upsert_ai_usage RPC, or wrap in a SELECT … FOR UPDATE; revisit once paid tiers land.
29. Full re-decrypt on every sync — loadAll(decryptFn) re-decrypts every encrypted note text after each merge; sync time scales linearly with library sizeRiskOpen3 (added 2026-04-26)Build an encryption-only delta path keyed off updatedAt; batch decrypts with a Web Worker; add a progress indicator.
30. Sync image fetch is sequential — for loop, O(N) round-trips on a fresh device; failures silently swallowedRiskOpen3 (added 2026-04-26)Parallelise with Promise.allSettled and a small concurrency limit (e.g. 6); surface per-image failures as a retry list.
31. Decrypt-failure recovery is dead-end — flagged with decryptError: true, no UI retry path beyond full sign-out, no count surfaced to userGapOpen3 (added 2026-04-26)Add a “Decryption issues” panel in Settings showing affected count + a “Retry unlock” button that re-runs getEncryptionPrfOutput.
32. Transfer-code auto-expire is best-effort — 60 s deactivation upsert can fail silently, leaving wrapper active until next create cycleRiskOpen3 (added 2026-04-26)Add a server-side scheduled job (Supabase pg_cron) to deactivate transfer-v1 blobs older than 90 s; alert on backlog growth.
33. No PWA update prompt — VitePWA autoUpdate with no onNeedRefresh UI; iOS standalone install path most exposedRiskOpen3 (added 2026-04-26)Same fix as #17 — implement onNeedRefresh toast and update the deprecated meta tag in index.html.
34. db.js has no dedicated unit tests — CRUD helpers and v1→v9 Dexie upgrade chain only covered transitivelyGapOpen3 (added 2026-04-26)Add src/test/db.test.js covering each migration step + cascade behaviours; add a CI gate that runs migration replay against a fixture DB.
35. Duplicate-source UX gap [SUR-257] — duplicate hint is non-blocking, no merge / de-dupe path; orphan sources accumulateGapOpen3 (added 2026-04-26)Make the hint a confirm-step (“Looks like a duplicate — Use existing / Create anyway”); add an admin de-dupe tool in Settings.
36. Branded auth-email template lives in repo only [SUR-261] — magic-link.html must be manually pasted into Supabase dashboard with no CI syncGapOpen3 (added 2026-04-26)Add a scripts/sync-auth-templates.js using the Supabase Management API; gate on a manual approval but make it one command.
37. Approved waitlist rows with user_id IS NULL stranded for email-OTP [SUR-261] — match_waitlist_on_signup only back-fills on auth.users INSERT and OTP refuses shouldCreateUser:falseGapOpen3 (added 2026-04-26)Add a nightly job that runs the audit query (status='approved' AND user_id IS NULL) and auto-sends Supabase invites; alert on backlog.
38. Email-OTP error-string mapping is best-effort [SUR-261] — relies on Supabase Auth’s Signups not allowed for otp substring staying stableAssumptionOpen3 (added 2026-04-26)Add an integration test that pings the live Supabase Auth response for an unknown email and asserts the substring; alert on regression.
39. Email signups stay disabled at the Supabase platform level — requestEmailOtp pins shouldCreateUser:false as defence-in-depthAssumptionOpen3 (added 2026-04-26)Document the dashboard toggle state in DEPLOYMENT_ARCHITECTURE.md; add a check to scripts/check-schema.js that asserts the auth config.
40. Email-OTP code length and expiry are Supabase defaults (6 digits / 1 hour)AssumptionOpen3 (added 2026-04-26)Acceptable for personal scale; revisit if abuse signals appear (failed-attempt rate-limit telemetry).
41. isStandaloneOrTwa() decides OTP-code vs magic-link delivery once at component mount [SUR-261] — no hot-swap mid-sessionAssumptionOpen3 (added 2026-04-26)Acceptable today; add a comment in src/lib/platform.js explaining the constraint and revisit when iOS/Android expose surface hot-swapping.
42. Cross-domain help.surfc.app (Cloudflare Pages) remains reachable from authenticated app shell links — no in-app fallback if DNS / cert / Pages outageAssumptionOpen3 (added 2026-04-26)Phase 2 in-app help renderer (already on backlog) addresses this; add a temporary fallback message on link click.
43. session.access_token expiry handled by getSession() calling _callRefreshToken immediately before each Edge Function invokeAssumptionOpen3 (added 2026-04-26)Add a regression test that simulates a backgrounded-PWA “Invalid JWT” and asserts the refresh path fires.
44. ANTHROPIC_API_KEY rotation is a manual operator task — no client-side hint of expiry; rotated-then-misconfigured key surfaces as blanket 500AssumptionOpenUnknown (predates 2026-04-23)Same fix as #5 — startup self-check Edge Function with structured error codes.
45. Project-level Anthropic spend cap unknown — only per-user ai_usage_daily counters; no automated alertDependencyOpenUnknown (predates 2026-04-23)Set a project-level monthly cap in Anthropic Console; pipe spend metrics into PostHog or a weekly digest email.
46. Account recovery without passkey + no transfer code — data unrecoverable by design; copy points to “the device where you originally enabled encryption”GapOpenUnknown (predates 2026-04-23)Document this as a deliberate trade-off in the help site; consider an opt-in “recovery key” downloaded at enrolment for users who accept the risk.
47. Deletion-success audit trail beyond in-flight delete-account 200 response — if auth.admin.deleteUser succeeds but client crashes before finalizeAccountDeletion, user sees stale sessionGapOpenUnknown (predates 2026-04-23)Add a server-side account_deletions audit table written inside the Edge Function transaction; reconcile on next sign-in attempt.
48. Prompt-injection defence is partial [SUR-242] — Phase 1 system-prompt fencing is live; image content in handleTranscribe cannot be XML-tagged so its defence is meta-instruction-only; Azure Content Safety (Phase 2) outstandingRiskOpen2 (added 2026-04-27)Land SUR-242 Phase 2 (Azure Content Safety pre-screen on image and text payloads); add a regression suite of injection-style prompts; document the residual image-vector exposure in docs/research/sur-242-guardrails-evaluation.md.
49. Dual-hosting transitional period [SUR-254] — surfc-web/ simultaneously configured for Netlify (netlify.toml, currently live) and Cloudflare Pages (public/_headers + .nvmrc); cache-header changes must be applied to both files; www→apex 301 has no equivalent in _redirects and must be set as a Cloudflare zone-level Redirect Rule before DNS cutoverRiskOpen2 (added 2026-04-27)Lock cache-header edits behind a checklist in surfc-web/README.md until cutover; configure the Cloudflare Redirect Rule and verify www.surfc.appsurfc.app 301 before flipping DNS; delete netlify.toml once Pages is authoritative.
50. Supabase Edge runtime uses a single Anthropic API key with no per-org isolation and no project-level spend capAssumptionOpenUnknown (predates 2026-04-23)Treat per-user ai_usage_daily + PostHog as the early-warning signal until paid tiers land; pair with #45 to add a project-level cap in Anthropic Console; revisit org isolation once multi-tenant becomes a thing.

Summary

  • 50 items total in this report (up from 47 in the 28-04-2026 report).
  • Closed since last report: 0 net change. The 6 strikethroughs (#3, #6, #8, #10, #13, #14) were already closed in the prior report.
  • New items added since 28-04-2026: 3 items — #48 (SUR-242 prompt-injection partial defence) and #49 (SUR-254 dual-hosting transitional period) were added to the source on 2026-04-27 but missed by the 28-04-2026 report; #50 (single Anthropic API key / no per-org isolation / no project-level cap) was an assumption present in the source’s “Key assumptions to validate” list but not surfaced separately in the prior report (was bundled implicitly into #45). Each carries 2 days open for the SUR-242/254 pair, and “Unknown” for #50.
  • Days-open updated: All Open items dated 2026-04-23 (#20) → 6 days; items dated 2026-04-26 (#27–#43) → 3 days; new SUR-242/254 (#48–#49) → 2 days; pre-2026-04-23 items still report Unknown because the source document does not record a per-item Logged date.
  • By type: 12 Risks, 16 Gaps, 4 Dependencies, 12 Assumptions, plus 6 Closed.
  • No corrections needed vs the prior report content (the 1000/month quota correction landed in 28-04-2026).

Notes / autonomous decisions

  • Read the file from main via git show main:docs/architecture/RISKS_GAPS_ASSUMPTIONS.md (current branch is feat/sur-242-guardrails) — verified this matches the version published on the main branch as of today.
  • Classification follows the prior report: capability/coverage missing → Gap; external requirement that could break things → Dependency; future event/condition that could damage the product → Risk; “Key assumptions to validate” entries → Assumption. Open Questions in the source were classified as Gap or Dependency depending on whether the unknown is internal (Gap) or third-party (Dependency).
  • “Closed” items are kept in the table with strikethrough and n/a days, per the task spec (“Mark these by crossing them out in the report”).
  • New items inherit a “days open” count derived from the Change Summary date in the source, since the file still does not record a per-item Logged date.
  • Suggested actions are concrete and ticket-sized; they are suggestions, not commitments.
  • Delivery: The task asked for the report to be sent to a chat in the Surfc project on claude.ai. The Chrome MCP extension was not connected at run time (list_connected_browsers returned []), so auto-delivery was not possible. The report has been saved to docs/architecture/RAID_REPORT_2026-04-29.md and to the session outputs folder so it can be pasted into the chat manually, or auto-posted on the next run if the Chrome extension is connected.