diff --git a/.changeset/spec-notification-event-migration-id.md b/.changeset/spec-notification-event-migration-id.md new file mode 100644 index 0000000000..786e4da2a7 --- /dev/null +++ b/.changeset/spec-notification-event-migration-id.md @@ -0,0 +1,11 @@ +--- +"@objectstack/spec": minor +--- + +`@objectstack/spec/system` now names the ADR-0030 notification cut-over, so "has this deployment run it?" has a place to be answered. + +`sys_migration` is the ledger a deployment writes to record that a data migration ran against its own database, and consumers read it instead of the platform version. Its well-known ids were `adr-0104-file-references` and `adr-0104-value-shapes` — the two ADR-0104 scans, both driven by an `os migrate` command that records the row. `migrateSysNotificationToEvent` (`@objectstack/metadata/migrations`) had none. It is destructive and one-way, operators are handed the call verbatim in `docs/handoff/adr-0030-notification-convergence.md`, and it recorded nothing when it ran: a deployment that performed the cut-over and one that never did are indistinguishable from the ledger. A row can only be keyed by an id, so without one the question had nowhere to be answered even in principle. + +Added: `NOTIFICATION_EVENT_MIGRATION_ID = 'adr-0030-notification-event'`, exported from `@objectstack/spec/system`. Purely additive — no existing export, schema or predicate changes, and nothing reads the new id yet. + +Deliberately NOT decided here, and the constant's docblock says so rather than leaving its silence to be read as an answer: what a `sys_migration` row under this id means. The two ADR-0104 ids get their `last_run_at` / `applied_at` / `verified_at` / `blocking` semantics from a command that scans, self-checks and only then records; this migration has no command and no self-check, and reports `migrated` / `already_done` / `not_applicable` / `error` to its caller instead. Which of those columns one of its runs may claim, whether anything may gate on the row, and whether a datastore created after the cut-over belongs in `CREATION_ATTESTED_MIGRATION_IDS`, are contract questions on this surface and are left open. diff --git a/packages/spec/api-surface/system.json b/packages/spec/api-surface/system.json index efec0a6ea6..39ed8694f4 100644 --- a/packages/spec/api-surface/system.json +++ b/packages/spec/api-surface/system.json @@ -444,6 +444,7 @@ "MutualTLSConfig (type)", "MutualTLSConfigParsed (type)", "MutualTLSConfigSchema (const)", + "NOTIFICATION_EVENT_MIGRATION_ID (const)", "NavNodeLike (interface)", "NotificationChannel (type)", "NotificationChannelSchema (const)", diff --git a/packages/spec/export-origins/system.json b/packages/spec/export-origins/system.json index cfd6c1f57c..e00823c986 100644 --- a/packages/spec/export-origins/system.json +++ b/packages/spec/export-origins/system.json @@ -444,6 +444,7 @@ "MutualTLSConfig": "src/system/auth-config.zod.ts#MutualTLSConfig (type)", "MutualTLSConfigParsed": "src/system/auth-config.zod.ts#MutualTLSConfigParsed (type)", "MutualTLSConfigSchema": "src/system/auth-config.zod.ts#MutualTLSConfigSchema (const)", + "NOTIFICATION_EVENT_MIGRATION_ID": "src/system/migration.zod.ts#NOTIFICATION_EVENT_MIGRATION_ID (const)", "NavNodeLike": "src/system/i18n-resolver.ts#NavNodeLike (interface)", "NotificationChannel": "src/system/notification.zod.ts#NotificationChannel (type)", "NotificationChannelSchema": "src/system/notification.zod.ts#NotificationChannelSchema (const)", diff --git a/packages/spec/src/system/migration.zod.ts b/packages/spec/src/system/migration.zod.ts index de1d58347b..db06f15093 100644 --- a/packages/spec/src/system/migration.zod.ts +++ b/packages/spec/src/system/migration.zod.ts @@ -169,6 +169,36 @@ export const FILE_REFERENCES_MIGRATION_ID = 'adr-0104-file-references'; */ export const VALUE_SHAPES_MIGRATION_ID = 'adr-0104-value-shapes'; +/** + * Well-known migration id: ADR-0030 notification convergence — this + * deployment's legacy per-user `sys_notification` inbox rows split into + * `sys_inbox_message` + `sys_notification_receipt`, and the `sys_notification` + * row itself rewritten to the L2 event shape, by `migrateSysNotificationToEvent` + * (`@objectstack/metadata/migrations`). + * + * Registered so that "has this cut-over run here?" is ANSWERABLE at all. The + * cut-over is destructive and one-way, it is handed to operators as a call they + * make themselves (`docs/handoff/adr-0030-notification-convergence.md`, under + * "Data migration (not auto-run)"), and it shipped with no id — so a deployment + * that ran it recorded nothing, and one that did not is indistinguishable from + * one that did. A ledger row can only be keyed by an id; without one the + * question has no place to be answered even in principle. That absence, not the + * migration, is what this constant repairs. + * + * WARNING — what a row under this id MEANS is deliberately NOT settled here, + * and its silence is not an answer. The two ids above are written by an + * `os migrate` command that scans, self-checks, and only then records, which is + * what gives `last_run_at` / `applied_at` / `verified_at` / `blocking` their + * meaning for them. This migration has no such command and no self-check: it + * reports `migrated` / `already_done` / `not_applicable` / `error` to its + * caller and nothing else. Which of those columns a run of it may legitimately + * claim, whether anything may gate on the row, and whether a datastore created + * after the cut-over belongs in {@link CREATION_ATTESTED_MIGRATION_IDS}, are + * open contract questions on this surface (#14025) — not facts this constant + * asserts, and not ones to settle by copying the neighbours above. + */ +export const NOTIFICATION_EVENT_MIGRATION_ID = 'adr-0030-notification-event'; + /** * The migrations a datastore attests at CREATION rather than by scanning. *