docs(store): push prose describes the enqueue, registration and dispatcher as owed, not built (TASK-3937 part A r4 L1)

Ruled a fix once part B could not merge in the same window as A
(r4 hunter L1). 155's header, migrated.go's push-delivery notes and
the settings model now say the enqueue is part B's, registration
TASK-3938's and the dispatcher TASK-3939's. nulExcluded's push reasons
gain a header saying they are the writers' contract, and each names the
unit that owes it: device columns TASK-3938, queue columns part B, the
claim and send columns TASK-3939. user_notification_settings keeps
TASK-3937; PutNotificationSettings writes it today.

Claude-Session: https://claude.ai/code/session_01JRXHP9ZVfEN3YEuzmucxP3
This commit is contained in:
xarmian
2026-10-20 21:02:52 +00:00
parent 55e7f769ad
commit 07dc06285a
4 changed files with 45 additions and 35 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
package models
// NotificationSettings are a person's recipient-side push filters (TASK-3937).
// The dispatcher (TASK-3939) is to apply them; the enqueue does not, so
// changing one never rewrites the queue.
// The dispatcher (TASK-3939) is to apply them and the enqueue (TASK-3937
// part B) is not to, so changing one will never rewrite the queue.
type NotificationSettings struct {
PushEnabled bool `json:"push_enabled"`
NotifyAssigned bool `json:"notify_assigned"`
+5 -5
View File
@@ -114,9 +114,9 @@ const MigratedMarker = "PAD_MIGRATED_TO_POSTGRES"
// database. Refusing them would claim a loss the migration never promised
// to prevent.
// - PUSH DELIVERY STATE (push_devices, user_notifications; TASK-3937):
// a device row is bound to the session that registered it, and the
// migration does not carry sessions, so every device is re-registered
// after the operator signs in again; the queue is delivery state, not a
// a device row is bound to the session that registers it (TASK-3938),
// and the migration does not carry sessions, so every device is to
// re-register after the operator signs in again; the queue is delivery state, not a
// person's data: a lost row is one push not sent. (Its dispatcher and
// 7-day sweep land with TASK-3939; until then nothing reads or deletes
// it.) Both are excluded as "push-delivery" in migratedExcludedTables,
@@ -282,8 +282,8 @@ var migratedExcludedTables = map[string]string{
"sqlite_sequence": "bookkeeping",
// PUSH DELIVERY STATE (TASK-3937): a device row is bound to a session the
// migration does not carry, so it is re-registered after sign-in; a queue
// row lost is one push not sent. user_notification_settings is refused.
// migration does not carry, so it is to re-register after sign-in
// (TASK-3938); a queue row lost is one push not sent. user_notification_settings is refused.
"push_devices": "push-delivery",
"user_notifications": "push-delivery",
@@ -1,16 +1,18 @@
-- Migration 155: push notifications (TASK-3937, PLAN-3908 phase 2 unit 1).
--
-- push_devices: one row per signed-in app session that registered a push
-- token. Every token is an FCM registration token (iOS routes through FCM),
-- push_devices: one row per signed-in app session that registers a push
-- token (the register endpoint lands with TASK-3938; nothing writes this
-- table yet). Every token is an FCM registration token (iOS routes through FCM),
-- so there is no environment column: FCM picks the APNs gateway itself.
-- platform stays because the FCM payload differs per platform. The row dies
-- with its session or its user.
--
-- user_notifications: the per-event push queue. A row is written ONLY for a
-- user who has at least one device, on the transaction of the write that
-- caused it. dedupe_key makes the enqueue idempotent per (user, key). item_id
-- and comment_id carry no foreign key: the dispatcher skips a row whose
-- subject is gone, and retention removes the rest. Retention is NOT in this
-- user_notifications: the per-event push queue. Nothing writes it yet: the
-- enqueue is TASK-3937 part B, and it is to write a row ONLY for a user who
-- has at least one device, on the transaction of the write that caused it.
-- dedupe_key is what makes that enqueue idempotent per (user, key). item_id
-- and comment_id carry no foreign key: the dispatcher is to skip a row whose
-- subject is gone, and retention to remove the rest. Retention is NOT in this
-- migration: the 7-day sweep lands with the dispatcher (TASK-3939), and until
-- then nothing deletes a queue row. workspace_id cascades, so a purged
-- workspace leaves no queue behind. actor_user_id is indexed because deleting
@@ -18,7 +20,7 @@
--
-- user_notification_settings: a person's recipient-side filters. No row means
-- the defaults. The dispatcher (TASK-3939) is to apply them; the enqueue
-- does not.
-- (part B) is not to.
--
-- workspace_read_cursors (149) stays separate: it must exist for every user,
-- with or without a device, and this queue is to be deleted after 7 days.
+28 -20
View File
@@ -303,7 +303,8 @@ var nulColumns = []nulColumn{
{"item_verifications", "created_by", classText},
{"item_verifications", "agent_name", classText},
// push_devices (TASK-3937): all three arrive in a registration request body.
// push_devices (TASK-3937): all three are to arrive in a registration
// request body, whose endpoint lands with TASK-3938.
{"push_devices", "connection_id", classText},
{"push_devices", "token", classText},
{"push_devices", "app_version", classText},
@@ -433,27 +434,34 @@ func NULProtectedColumns() []nulColumn {
// column someone looked at and excluded" — without it, the test's only options
// on an unlisted column are to fail on every known exclusion or to ignore the
// class entirely.
//
// The push_devices and user_notifications reasons (TASK-3937) state the
// CONTRACT their writers must keep, and none of those writers exists yet: the
// device register endpoint is TASK-3938, the enqueue is TASK-3937 part B, and
// the dispatcher's claim and send columns are TASK-3939. Each names the unit
// that owes it. user_notification_settings is written today, by
// PutNotificationSettings.
var nulExcluded = map[string]string{
"push_devices.id": "server-minted device UUID (TASK-3937, migration 155)",
"push_devices.user_id": "the authenticated user's server-minted UUID (TASK-3937)",
"push_devices.session_id": "server-minted session id read from the authenticated session (TASK-3937)",
"push_devices.platform": "fixed enum ios | android, a CHECK constraint (TASK-3937)",
"push_devices.created_at": "server timestamp from now() (TASK-3937)",
"push_devices.updated_at": "server timestamp from now() (TASK-3937)",
"user_notifications.id": "server-minted notification UUID (TASK-3937, migration 155)",
"user_notifications.user_id": "server-minted user UUID read from items and comments rows (TASK-3937)",
"user_notifications.workspace_id": "server-minted workspace UUID from the written row (TASK-3937)",
"user_notifications.item_id": "server-minted item UUID from the written row (TASK-3937)",
"user_notifications.comment_id": "server-minted comment UUID from the written row (TASK-3937)",
"user_notifications.kind": "fixed server constant, a CHECK constraint (TASK-3937)",
"user_notifications.actor_user_id": "server-minted user UUID of the authenticated writer (TASK-3937)",
"user_notifications.dedupe_key": "server-built from a fixed kind and server-minted ids and timestamps (TASK-3937)",
"user_notifications.created_at": "server timestamp from now() (TASK-3937)",
"user_notifications.push_state": "fixed server constant, a CHECK constraint (TASK-3937)",
"push_devices.id": "server-minted device UUID (TASK-3938, migration 155)",
"push_devices.user_id": "the authenticated user's server-minted UUID (TASK-3938)",
"push_devices.session_id": "server-minted session id read from the authenticated session (TASK-3938)",
"push_devices.platform": "fixed enum ios | android, a CHECK constraint (TASK-3938)",
"push_devices.created_at": "server timestamp from now() (TASK-3938)",
"push_devices.updated_at": "server timestamp from now() (TASK-3938)",
"user_notifications.id": "server-minted notification UUID (TASK-3937 part B, migration 155)",
"user_notifications.user_id": "server-minted user UUID read from items and comments rows (TASK-3937 part B)",
"user_notifications.workspace_id": "server-minted workspace UUID from the written row (TASK-3937 part B)",
"user_notifications.item_id": "server-minted item UUID from the written row (TASK-3937 part B)",
"user_notifications.comment_id": "server-minted comment UUID from the written row (TASK-3937 part B)",
"user_notifications.kind": "fixed server constant, a CHECK constraint (TASK-3937 part B)",
"user_notifications.actor_user_id": "server-minted user UUID of the authenticated writer (TASK-3937 part B)",
"user_notifications.dedupe_key": "server-built from a fixed kind and server-minted ids and timestamps (TASK-3937 part B)",
"user_notifications.created_at": "server timestamp from now() (TASK-3937 part B)",
"user_notifications.push_state": "fixed server constant, a CHECK constraint (TASK-3939)",
"user_notifications.skip_reason": "fixed server constant set by the dispatcher (TASK-3939)",
"user_notifications.claim_token": "server-minted UUID (TASK-3937)",
"user_notifications.claimed_at": "server timestamp from now() (TASK-3937)",
"user_notifications.sent_at": "server timestamp from now() (TASK-3937)",
"user_notifications.claim_token": "server-minted UUID (TASK-3939)",
"user_notifications.claimed_at": "server timestamp from now() (TASK-3939)",
"user_notifications.sent_at": "server timestamp from now() (TASK-3939)",
"user_notification_settings.user_id": "the authenticated user's server-minted UUID (TASK-3937, migration 155)",
"user_notification_settings.updated_at": "server timestamp from now() (TASK-3937)",
"workspace_import_status.workspace_id": "server-minted workspace UUID, a foreign key to workspaces(id) (TASK-896, migration 131)",