diff --git a/database/migrations/002_drop_token_version.sql b/database/migrations/002_drop_token_version.sql new file mode 100644 index 0000000..b11b015 --- /dev/null +++ b/database/migrations/002_drop_token_version.sql @@ -0,0 +1,27 @@ +\c dcntrlized; + +-- 002_drop_token_version.sql — remove the rotation counter. +-- +-- Contract decision (2026-10-18): `rotateSubscribeToken` is gone. The subscribe +-- token is minted once at creation and lives as long as the alert; subscribers +-- are never evicted and there is no rotation lifecycle. `token_version` was the +-- rotation counter and has no remaining reader: no query, view or wire field +-- depends on it (the API stopped serialising `tokenVersion` in the same change). +-- +-- `001_alarm_schema.sql` is already applied to the dev database and is never +-- edited in place, so the column is dropped here instead of being removed from +-- the CREATE TABLE. A fresh database applies 001 (which still creates the +-- column) and immediately drops it below; that is intentional and keeps the +-- migration history append-only. The CHECK constraint +-- `alerts_token_version_positive` disappears with the column. +-- +-- Idempotent: guarded on the schema/column existing, so re-applying the whole +-- sequence (or applying this file alone to a virgin database) is a clean no-op. + +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'alarm') THEN + ALTER TABLE alarm.alerts DROP COLUMN IF EXISTS token_version; + END IF; +END +$$; diff --git a/src/internal/alarmtoken/token.go b/src/internal/alarmtoken/token.go index d288918..04b2766 100644 --- a/src/internal/alarmtoken/token.go +++ b/src/internal/alarmtoken/token.go @@ -22,7 +22,8 @@ // constant-time over the digest. // - the SUBSCRIBE token is 24 random bytes, base64url (32 chars). It is // stored in plaintext on purpose: it is shared through the QR code with -// every subscriber, and rotation is a cheap UPDATE (token_version). It is +// every subscriber and is minted once per alert — there is no rotation +// (subscribers are permanent for the lifetime of the alert). It is // still compared in constant time so a timing oracle cannot be used to // recover it byte by byte. package alarmtoken diff --git a/src/internal/qrpayload/qrpayload.go b/src/internal/qrpayload/qrpayload.go index 112e433..8efda38 100644 --- a/src/internal/qrpayload/qrpayload.go +++ b/src/internal/qrpayload/qrpayload.go @@ -21,7 +21,7 @@ // // The fragment (not the query string) carries the token so the secret is never // sent to the host if a scanner ever resolves the URL in a browser. Build is -// used by createAlert/getAlert/rotateSubscribeToken, and Parse is used by any +// used by createAlert/getAlert, and Parse is used by any // component that accepts a scanned payload (the Android client's scanner and // the service's own tests). //