Scope step 5 of the calendar migration, and gate it on step 4 being live

Step 5 removes the legacy path, so it removes the fallback step 4 still leans
on: the join that renders the author of every pre-cutover event, and the
routes an old cached bundle talks to. Building it before step 4 has been
deployed and watched turns a recoverable deploy into an unrecoverable one, so
this records the shape rather than implementing it.

Two decisions worth having in the runbook rather than in someone's memory.
The legacy calendar user module gets deleted outright rather than unmounted -
a survey confirmed nothing outside that directory imports it, and it is the
API's last unauthenticated account-creation and mail-sending endpoint. The
users and sessions tables get renamed aside rather than dropped: the display
names are already snapshotted so nothing visible depends on those rows, but
they still hold e-mail addresses and password hashes, and a rename makes them
unreachable without destroying anything.

Also notes the two consequences worth accepting deliberately: activation links
already in inboxes become 404s, and createdById leaves the wire format.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-06 23:01:44 +02:00
parent d960ac8e24
commit b4c8c91795
+58 -9
View File
@@ -6,7 +6,8 @@ left to write.
> Read the deploy checklist under step 4 before applying anything. "Done" below means the > Read the deploy checklist under step 4 before applying anything. "Done" below means the
> code exists on a branch, **not** that production has it - and in particular production has > code exists on a branch, **not** that production has it - and in particular production has
> none of the three migrations. > none of the three migrations. Step 5 is scoped but deliberately unstarted: it must not be
> built on top of a step 4 that has not been deployed and watched.
Written 2026-09-05 alongside the admin module (step 2 of `docs/plan-admin-auth.md` in the Written 2026-09-05 alongside the admin module (step 2 of `docs/plan-admin-auth.md` in the
nachklang-admin repo), which deliberately left the calendar alone. Steps 1-4 of that plan nachklang-admin repo), which deliberately left the calendar alone. Steps 1-4 of that plan
@@ -156,14 +157,62 @@ Each step is meant to leave production working on its own.
**One-way door:** any iCal subscription whose URL carries `?sessionId=&sessionKey=` rather **One-way door:** any iCal subscription whose URL carries `?sessionId=&sessionKey=` rather
than `?password=` stops working permanently. The shared-password URLs are unaffected. than `?password=` stops working permanently. The shared-password URLs are unaffected.
5. **Drop the legacy path.** Remove `users.service.ts`'s session handling, the `sessions` 5. **Drop the legacy path.** Not started - and deliberately not started until step 4 has been
table, `created_by_id`, and the legacy half of the step 3 read (the `users` join and its deployed and watched, because it removes the fallback step 4 still leans on. Scoped and
`legacy_*` aliases - the snapshot fallback stays, it is what makes dropping the table decided 2026-09-06; what follows is the agreed shape, not a suggestion.
safe). The names were archived ahead of time by
`sql/calendar/002_snapshot_legacy_creator_names.sql`, so nothing is lost here. Legacy `/calendar/users/*` stays **Prerequisite: step 4 live in production and behaving.** Until then the legacy join is
only if something still calls it - otherwise delete it too. `X-Session-Id` / what renders the author of every pre-cutover event, and the legacy routes are what an old
`X-Session-Key` can then come out of the CORS `allowedHeaders` list in cached bundle talks to. Doing this first turns a recoverable deploy into an unrecoverable
`src/app.factory.ts`. one.
Code, in one branch:
- **Delete `src/models/calendar/users/` entirely** - `users.router.ts`, `users.service.ts`,
`session.interface.ts`, `user.interface.ts` - and the `calendarRouter.use('/users', ...)`
line in `Calendar.router.ts`. *(Decided: delete outright rather than unmount.)* This
removes the last unauthenticated account-creation and mail-sending endpoint in the API.
A survey on 2026-09-06 confirmed nothing outside that directory imports it, and nothing
outside it touches the `users`/`sessions` tables except the two joins below.
- **Drop the legacy half of the read** in `events.service.ts`: the two
`LEFT OUTER JOIN users` clauses, the `legacy_*` aliases, and `created_by_id` /
`version_created_by_id` from the SELECT and the row mapper. The snapshot fallback stays -
it is what makes this safe. Remove `createdById` / `lastModifiedById` from
`event.interface.ts` and their (already deprecated) swagger properties.
- **Remove `X-Session-Id` / `X-Session-Key`** from the CORS `allowedHeaders` in
`src/app.factory.ts`. Nothing has sent them since the tickets and feedback frontends were
redeployed.
- **Drop the obsolete test mocks**: `test/feedback/feedback.auth.test.ts`,
`test/tickets/tickets.auth.test.ts` and `test/admin/auth-binding.ts` each mock
`calendar/users/users.service.js` and assert `checkSession` is never called. That
tripwire is meaningless once the module does not exist; remove the mock and the
assertion, keep the rest.
Database, as `sql/calendar/004_*.sql`:
- Drop the foreign keys `events_users_user_id_fk` and `event_versions_users_user_id_fk`,
then the `created_by_id` and `version_created_by_id` columns.
- **`RENAME TABLE users TO users_legacy_archive`**, same for `sessions`. *(Decided: rename
rather than drop.)* The reasoning: the display names are already snapshotted so nothing
visible depends on these rows, but they still hold the old e-mail addresses and password
hashes, and a rename makes the tables unreachable without destroying anything. Dropping
them later is one statement, at a moment when nobody is mid-deploy.
- Mirror all of it in `docker/init/01-calendar-schema-dev.sql` (the archive tables need no
mirror - a fresh dev database has nothing to archive).
Documentation: `DEFERRED_SECURITY.md` items **3** (activation token has no expiry) and
**4** (password reset token has no expiry) close outright - both describe code that ceases
to exist. Item 2 (no event ownership check) stays open.
Two consequences to accept explicitly rather than discover:
- Any activation or password-reset e-mail already sent points at
`api.nachklang.art/calendar/users/activate` and becomes a 404. Those links were only ever
valid for legacy accounts, which no longer open anything.
- `Event.createdById` disappears from the API response. The Angular frontend never read it
(its `Event` model has only `createdBy`, the name), so this is not a breaking change for
the only known consumer - but it is a wire-format removal, so check anything else that
reads `/calendar/events/*/json` first.
## What the code actually looks like (surveyed 2026-09-06) ## What the code actually looks like (surveyed 2026-09-06)