What this does
Tracks which Terms of Service version each user accepted and which Privacy Notice version they acknowledged, starting at signup. This is the backend foundation for a future UI that will prompt users to re-accept when we publish updated legal documents.
User flow (not built yet, this PR is backend only)
- User signs up → backend records "accepted TOS v2026-02-12" and "acknowledged Privacy Notice v2026-04-03"
- We publish a new TOS → ops runs
./utils/admin activate-legal-doc <id>
- Next time the user loads the app,
/api/user/status returns requires_action: true for that document
- Frontend (future PR) shows a modal → user clicks accept →
POST /api/legal/accept records it → requires_action clears
What's in this PR
Database (024_legal_documents.sql):
legal_documents table — versioned registry of TOS and Privacy Notice with one-active-per-type constraint
user_legal_events table — append-only audit trail of all acceptance/acknowledgment events
- CHECK constraints on document_type, event_type, event_source
Gateway (db.rs, handlers.rs):
- User creation + legal event recording wrapped in a single transaction — no account without consent records
- Captures IP and user-agent at signup
API (legal.rs, onboarding.rs, main.rs):
/api/user/status now includes a legal field with per-document active version, user's latest version, and requires_action
POST /api/legal/accept — re-acceptance endpoint for when we update legal documents
Admin tooling (utils/admin):
list-legal-docs, add-legal-doc, activate-legal-doc, user-legal-status
Tests (tests/e2e/test_legal_tracking.sh, unit tests in legal.rs)
Frontend (CSS/HTML only):
- Split "Already have an account?" into its own element, removed ~160 lines of dead CSS
What's NOT in this PR
- No modal/banner UI
- No middleware gating (blocking API access until re-acceptance)
- No backfill for existing users — they get requires_action: false
# What this does
Tracks which Terms of Service version each user accepted and which Privacy Notice version they acknowledged, starting at signup. This is the backend foundation for a future UI that will prompt users to re-accept when we publish updated legal documents.
# User flow (not built yet, this PR is backend only)
1. User signs up → backend records "accepted TOS v2026-02-12" and "acknowledged Privacy Notice v2026-04-03"
2. We publish a new TOS → ops runs `./utils/admin activate-legal-doc <id>`
3. Next time the user loads the app, `/api/user/status` returns `requires_action: true` for that document
4. Frontend (future PR) shows a modal → user clicks accept → `POST /api/legal/accept` records it → `requires_action` clears
# What's in this PR
Database (`024_legal_documents.sql`):
- `legal_documents` table — versioned registry of TOS and Privacy Notice with one-active-per-type constraint
- `user_legal_events` table — append-only audit trail of all acceptance/acknowledgment events
- CHECK constraints on document_type, event_type, event_source
Gateway (db.rs, handlers.rs):
- User creation + legal event recording wrapped in a single transaction — no account without consent records
- Captures IP and user-agent at signup
API (`legal.rs`, `onboarding.rs`, `main.rs`):
- `/api/user/status` now includes a legal field with per-document active version, user's latest version, and requires_action
- `POST /api/legal/accept` — re-acceptance endpoint for when we update legal documents
Admin tooling (utils/admin):
- `list-legal-docs`, `add-legal-doc`, `activate-legal-doc`, `user-legal-status`
Tests (`tests/e2e/test_legal_tracking.sh`, unit tests in `legal.rs`)
Frontend (CSS/HTML only):
- Split "Already have an account?" into its own element, removed ~160 lines of dead CSS
# What's NOT in this PR
- No modal/banner UI
- No middleware gating (blocking API access until re-acceptance)
- No backfill for existing users — they get requires_action: false