Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions plans/2026-10-01-next-15.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Next 15: DebateLab work after Current Debate

**Status:** Plan, partly shipped. Written 2026-10-01 against `main` at `fe6100a`. On 2026-10-02 (`main` at `791ccb0`), these items had shipped: 3 (account deletion and export, #83), 5 (pgTAP coverage, #80), 6 (backend drift check, #79), 8 (staging switcher, #81) and 13 (first-run debate, #82). Recheck paths and assumptions before executing any remaining item.

Open follow-ups from those PRs:
- #80's tests found that anyone can read global prompts and that signed-out visitors can read the global rate limits. They also found that the `anon` and `authenticated` roles can `TRUNCATE` six tables, and that three older tests check privileges too loosely.
- #83 leaves one race open. A Checkout started in another tab while an account is being deleted can leave a subscription with no account behind it. The fix is a backstop in `stripe-webhook`.
- Staging has no Stripe secrets, so its `stripe-webhook` cannot start.

Current Debate (#72), debate-aware AI (#75), Sentry releases (#76) and the pin-state fixes (#77) have shipped. This list covers what comes next. Items are ordered by risk: security and data protection first, then operational safety, then code health, then product polish. Each item is sized to one PR unless it says otherwise.

## Security and data protection

### 1. Confirm the leaked provider keys were rotated

OpenAI, Anthropic and Jina keys were committed to git history at the start of the project. History was scrubbed on 2026-08-20, but scrubbing does not revoke a key. At that time the Supabase secrets still showed `updated_at` 2026-01-29, the day the project was created.

- Adam: rotate the keys in each provider dashboard, or confirm they were already rotated. Confirm the Google key too.
- Then set the new values on production and staging with `supabase secrets set`. This needs Adam's authorization.
- Done when `supabase secrets list` shows a fresh `updated_at` for every provider key on both projects. Record only the date, never the value.

If every model call now goes through OpenRouter, delete the unused direct-provider secrets instead of rotating them.

### 2. Limit promo-code guessing

`supabase/functions/redeem-promo/index.ts` has no attempt counter. `public.redeem_promo_code_atomic(text)` is granted to `authenticated`, so any signed-in user, including a demo-code user, can call it directly through PostgREST and skip the Edge Function. The limit therefore has to live in SQL.

- Add a per-user failed-attempt window inside `redeem_promo_code_atomic`, modelled on `demo_access_attempts`.
- Return the same error for an unknown code and for a locked-out user.
- Prove it with a pgTAP test: N failures lock the user, success resets the count, and another user is unaffected.

### 3. Account deletion and data export (shipped: #83)

There is no `auth.admin.deleteUser` path and no export. Settings only clears local session data. Most users are school students in the EU, so they need both.

- Add one Edge Function with two actions: export everything the user owns as JSON, and delete the account. Deletion cascades through debates, sources, source files in storage, results and usage rows, then deletes the auth user.
- Cancel any Stripe subscription before deleting, or refuse to delete while one is active. Pick one and document it.
- Settings gets a "Download my data" button and a "Delete account" button that asks the user to type a confirmation.
- Prove it with a pgTAP or full-stack test: after deletion, no row in any public table references the user id. Also prove one user cannot export or delete another.
- Update `src/pages/PrivacyPage.tsx` if it promises anything different.

### 4. Tighten the Content Security Policy

`vercel.json` `connect-src` still allows `api.openai.com`, `api.anthropic.com`, `generativelanguage.googleapis.com`, `r.jina.ai`, `s.jina.ai` and the wildcard `*.supabase.co`. The browser has not called any provider directly since AI moved server-side.

- Remove the provider hosts. Remove `*.supabase.co` once staging and preview origins are listed explicitly; keep the production project host.
- Check Google Docs export (`oauth2.googleapis.com`, `www.googleapis.com`) before removing those.
- Add `form-action 'self'`. Drop `X-Frame-Options: SAMEORIGIN`, which contradicts `frame-ancestors 'none'`.
- Prove it with the Chromium E2E suite plus a manual prod-preview check that the browser console shows no CSP violations on every route.

### 5. pgTAP coverage for untested tables (shipped: #80)

These tables have RLS enabled but nothing in `supabase/tests/` asserts their policies: `shared_items`, `user_roles`, `admin_grants`, `api_keys`, `source_files`, `rate_limits`, `global_prompts`, `webhook_events`, `user_data_clear_markers`.

- Start with `user_roles` (a user must not be able to grant themselves admin) and `shared_items` (public read only through the share code, owner-only write).
- For the deny-all tables, assert that `authenticated` and `anon` see zero rows and cannot write.
- Run through `./.superset/db-gate.sh`.

## Operational safety

### 6. A read-only backend drift check (shipped: #79)

Vercel ships the frontend on merge, while migrations and Edge Functions are deployed by hand. That gap has already caused drift: on 2026-09-30 production lacked two migrations and ran pre-#75 functions. Nothing in the repo detects it.

- Add `npm run backend:status`. It runs `supabase migration list --linked` and `supabase functions list`, compares them with `supabase/migrations/` and the function folders on the current commit, and prints what is pending. It must be read-only.
- Optionally add a step to `prod-canary.yml` that fails when production is missing a migration that `main` has. That check would need a read-only token, which is Adam's call.
- Document it in `docs/operations.md` as the first step of every backend deploy.

### 7. Alerts that reach Adam

The production canary runs daily, but a failure only shows in GitHub. Edge Functions log only to `console`, so backend errors never reach Sentry.

- Route `prod-canary.yml` failures to a channel Adam reads.
- Add Sentry for the Deno Edge Functions through a small shared wrapper in `_shared/`, with the same scrubbing rules as `src/lib/observability.ts`. Start with `ai`, `ai-stream` and `stripe-webhook`.
- Alert on repeated `stripe-webhook` failures, since a missed webhook means a paying user without access.

### 8. Make staging usable (shipped: #81)

`staging-e2e.yml` is skipped unless `STAGING_E2E_ENABLED=true`, and a skipped job is not a pass. Staging has no migration or function sync procedure, and the admin staging switcher from the backlog was never built.

- Write the staging sync procedure, reusing `backend:status` from item 6 against the staging ref.
- Build the admin-only switcher that sets the `debate-supabase-env` localStorage key that `src/lib/supabase/env.ts` already reads.
- Then enable the staging workflow. Applying anything to staging needs Adam's authorization.

## Code health

### 9. Tests for the payment path and other untested functions

These Edge Functions have no co-located tests: `stripe-webhook`, `create-checkout`, `create-portal`, `redeem-promo`, `share`, `export-gdoc`, `demo-code-login`, `announcements`, `workspace-invites`. Some are touched indirectly by `src/test/*security*` and `webhook-event-lifecycle.test.ts`.

- Start with money and access: `stripe-webhook` (signature rejection, idempotent replay, each subscription event), `create-checkout`, `create-portal`, then `share` and `demo-code-login`.
- Use the existing Deno test pattern in `supabase/functions/ai/*.test.ts`.

### 10. Restore the coverage thresholds

`vitest.config.ts` lowered branches to 80 and functions to 89. The TODO there names the gaps: `stripe.ts`, `AuthProvider.tsx`, `session-store.ts` and `ai/client.ts`.

- Cover those branches and raise the thresholds back to 90. Do not exclude files to get there.
- Add tests for the new Current Debate code, which no test references by name: `src/lib/debate/context-state.ts`, `src/pages/CurrentDebatePage.tsx`, `src/components/features/debate/DebateForm.tsx`, `UnsortedWork.tsx` and `debate-items.ts`. Also cover `src/components/features/sparring/SparringRound.tsx` and `src/lib/supabase/edge-function-error.ts`.

### 11. Split the client AI module

`src/lib/ai/client.ts` is 1,560 lines and `src/lib/stores/session-store.ts` is 1,372. The server side was split per feature in #75; the client was not.

- Move each feature's request builder and response parsing into `src/lib/ai/features/<feature>.ts` behind the same exports, so callers do not change.
- Move code without changing behavior. Existing tests must pass unchanged.
- Do `session-store.ts` in a separate PR afterwards if the first split goes well.

### 12. Dependency and dead-code cleanup

- `pg` and `@hocuspocus/server` sit in the client `dependencies`; they belong to `services/collaboration`.
- The package is still named `vite_react_shadcn_ts`.
- `@typescript-eslint/no-unused-vars` is turned off in `eslint.config.js`. Turn it on, fix what it finds, and run `knip` once to list unused exports and files.
- Three `no-control-regex` sanitizers (`ai/request.ts:29`, `ai-stream/index.ts:66`, `_shared/prompts/context.ts:45`) duplicate one job. Share one helper.
- Major upgrades (React 19, Tailwind 4, Recharts 3, date-fns 4, i18next 24+) are separate PRs. Plan them only after this cleanup.

## Product polish

### 13. First-run flow that starts with a debate (shipped: #82)

A new user lands on `/app`, sees a "No current debate" card, and gets a tour of the tools that skips Current Debate. The first tool they open may then show the motion-switch dialog.

- Make creating the first debate the first tour step, then point at Library and one Prepare tool.
- Do not show `DebateMotionGate` to a user who has no Current Debate yet. Offer to create one from the motion they typed.
- Prove it with a Playwright journey for a fresh account, in English and Hungarian.

### 14. Accessibility pass

- Add `@axe-core/playwright` checks to the existing route matrix, failing on serious and critical issues.
- Replace the `div role="button"` handlers in `FlowchartPage.tsx` and `LibraryPage.tsx` with real buttons.
- Add a skip link, and check focus return after the Review sheet and debate dialogs close.

### 15. Close out loose plans and PWA gaps

- Update `plans/README.md`: #75 shipped, and this plan is the new backlog.
- Decide PR #63 (Workspace import/export). Workspace is on hold and the PR has open P1 findings, so close it and keep the branch.
- Add 192px, 512px and maskable icons to `public/manifest.webmanifest`. Localize the offline page in `public/sw.js`.
- Gate `/app/admin` on the client as well. The `admin` function already returns 403 to non-admins, so this only stops non-admins from seeing an empty admin screen.

## Not on this list

- Running the legacy purge (`private.purge_legacy_debate_material`). Adam decided on 2026-09-30 to keep production's legacy rows.
- Collaborative Workspace. It stays hidden until Adam restarts it.
- Changing which checks are required. `live-e2e` costs money on every PR, but relaxing a required check is Adam's decision, not a cleanup.
11 changes: 5 additions & 6 deletions plans/README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
# Plans

**Status:** Plan index. Plans describe intended work, not current implementation truth. Reconciled against `main` at `0bda733` on 2026-09-27.
**Status:** Plan index. Plans describe intended work, not current implementation truth. Reconciled against `main` at `791ccb0` on 2026-10-02.

Every plan lives in this folder. Before executing one, recheck its paths, assumptions, product decisions, and verification commands against current source.

## In progress

- **Debate-aware AI and smaller modules (PR 2).** Every feature gets server-loaded Current Debate context with a visible Review control, each feature gets its own prompt module, and the 88 KB `supabase/functions/ai/index.ts` is split into per-feature handlers behind the same endpoints. Plan: [2026-09-25 debate AI quality modules](2026-09-25_074255-debate-ai-quality-modules.md).

- **Voice Sparring and Crossfire.** Students speak their turns through the browser's speech recognition and the opponent answers aloud through ElevenLabs Eleven v4, with the browser voice as fallback. Plan: [2026-10-01 voice sparring and crossfire](2026-10-01-voice-sparring-crossfire.md).
Nothing is in progress.

## Backlog

- **Admin staging switcher.** `src/lib/supabase/env.ts` already resolves staging from the `debate-supabase-env` localStorage key, but no admin UI sets it. Staging migration and function sync remain operations that need Adam's authorization.
- **Document portability PR.** [Programcode/DebateLab#63](https://github.com/Programcode/DebateLab/pull/63) adds Workspace import and export. Workspace is on hold and the PR has open P1 review findings, so decide whether to close or park it.
- **Next 15.** Security and data protection, operational safety, code health, and product polish, ordered by risk. Items 3, 5, 6, 8 and 13 have shipped; the plan lists the follow-ups they left. Plan: [2026-10-01 next 15](2026-10-01-next-15.md). Item 15 covers the PR #63 decision.

## On hold

Expand All @@ -23,6 +20,8 @@ Every plan lives in this folder. Before executing one, recheck its paths, assump

These plans are finished. Their files were removed; git history keeps them.

- Voice Sparring and Crossfire: #84. Plan kept as the record: [2026-10-01 voice sparring and crossfire](2026-10-01-voice-sparring-crossfire.md).
- Debate-aware AI and smaller modules: #75. Plan kept as the record: [2026-09-25 debate AI quality modules](2026-09-25_074255-debate-ai-quality-modules.md).
- Current Debate foundation: #72. Plan kept as the record: [2026-09-25 current debate foundation](2026-09-25_074255-current-debate-foundation.md).
- Email OTP and demo-code sign-in: #40, #41.
- AI control enforcement at the edge: #32, #42.
Expand Down
Loading