@@ -185,38 +185,75 @@ export const VALUE_SHAPES_MIGRATION_ID = 'adr-0104-value-shapes';
185185 * question has no place to be answered even in principle. That absence, not the
186186 * migration, is what this constant repairs.
187187 *
188- * WARNING — what a row under this id MEANS is deliberately NOT settled here,
189- * and its silence is not an answer. The two ids above are written by an
190- * `os migrate` command that scans, self-checks, and only then records, which is
191- * what gives `last_run_at` / `applied_at` / `verified_at` / `blocking` their
192- * meaning for them. This migration has no such command and no self-check: it
193- * reports `migrated` / `already_done` / `not_applicable` / `error` to its
194- * caller and nothing else. Which of those columns a run of it may legitimately
195- * claim, whether anything may gate on the row, and whether a datastore created
196- * after the cut-over belongs in {@link CREATION_ATTESTED_MIGRATION_IDS}, are
197- * open contract questions on this surface (#14025) — not facts this constant
198- * asserts, and not ones to settle by copying the neighbours above.
188+ * What a run of it may claim in the ledger is RULED (maintainer 「同意」 to
189+ * decision batch #47 item 5, recorded on #15710 — the question batch #21
190+ * reserved when the id was registered under #14025). The two ids above take
191+ * their column semantics from an `os migrate` command that scans, self-checks,
192+ * and only then records; this migration has no such command and no self-check
193+ * — it reports `migrated` / `already_done` / `not_applicable` / `error` to its
194+ * caller and nothing else — so its claims are narrower than theirs:
195+ *
196+ * - `last_run_at`: set on every COMPLETED non-`error` run — `migrated`,
197+ * `already_done` and `not_applicable` alike.
198+ * - `applied_at`: set only on `migrated` (legacy inbox rows were rewritten).
199+ * - `verified_at`: NEVER set by a run of this migration. `verified_at` means
200+ * a self-check passed, and there is no self-check to pass.
201+ * - `blocking`: `0` by construction — nothing counts discrepancies.
202+ * - `details.outcome`: the four-valued result, verbatim.
203+ * - an `error` run writes NO ledger claim at all.
204+ *
205+ * Receipt, not gate. Nothing reads a row under this id as a precondition, and
206+ * nothing may: a gate would need the self-check that does not exist. The row
207+ * is what an operator reads, in the shape `sys-migration.object.ts`
208+ * (`@objectstack/platform-objects`) already documents for the seed-tenancy
209+ * repair — `verified_at: null`, `blocking: 0` by construction — which is
210+ * exactly the shape {@link isDataMigrationFlagVerified} answers `false` to.
211+ *
212+ * Creation-attested. A datastore created after the cut-over has no legacy
213+ * inbox rows by construction, and its creator observed it come into being
214+ * with none — the same "true by birth, observably" argument
215+ * {@link CREATION_ATTESTED_MIGRATION_IDS} makes for its other members — so
216+ * this id is a member of that array. Leaving it out would make a fresh
217+ * store's ledger read "never ran" for a registered id, which is false. The
218+ * attestation is not a run: `attestFreshDatastore`
219+ * (`@objectstack/platform-objects`) writes one uniform shape for every member
220+ * — `details.attested: 'datastore-created-empty'`, `applied_at: null`,
221+ * `blocking: 0`, and `verified_at` set for the fact observed at birth — so on
222+ * a fresh store this row does read as verified, by birth and never by a run,
223+ * and it still gates nothing.
224+ *
225+ * Which caller writes the run receipt when the migration runs is the runner's
226+ * contract (`@objectstack/metadata/migrations`), decided in its own lane — not
227+ * here.
199228 */
200229export const NOTIFICATION_EVENT_MIGRATION_ID = 'adr-0030-notification-event' ;
201230
202231/**
203232 * The migrations a datastore attests at CREATION rather than by scanning.
204233 *
205- * Both facts these ids stand for — no legacy file value here, no malformed
206- * stored value here — are true by construction of an empty store, and true
207- * *observably*: the creator watched it come into being with no rows at all.
208- * That is the same observed-transition discipline the gates run on, not
209- * version-gating in disguise; a store that merely *looks* empty when found
210- * earns nothing, because "found empty" is an inference and "created empty" is
211- * an observation.
234+ * Every fact these ids stand for — no legacy file value here, no malformed
235+ * stored value here, no legacy `sys_notification` inbox row here — is true by
236+ * construction of an empty store, and true *observably*: the creator watched
237+ * it come into being with no rows at all. That is the same observed-transition
238+ * discipline the gates run on, not version-gating in disguise; a store that
239+ * merely *looks* empty when found earns nothing, because "found empty" is an
240+ * inference and "created empty" is an observation.
212241 *
213242 * Without this, every deployment born on a version that already ships the
214243 * migrations would start lax and stay lax until someone ran a command that is,
215244 * for them, a no-op — so the warn regime would never die out.
245+ *
246+ * The third member is the ADR-0030 cut-over
247+ * ({@link NOTIFICATION_EVENT_MIGRATION_ID}): a store created after it never
248+ * held a per-user inbox row for the migration to split, so the birth
249+ * observation settles that fact exactly as it settles the two ADR-0104 ones.
250+ * Its attestation row is the same uniform shape as theirs; what a RUN of that
251+ * migration may claim differs, and lives on the id's own docblock.
216252 */
217253export const CREATION_ATTESTED_MIGRATION_IDS = [
218254 FILE_REFERENCES_MIGRATION_ID ,
219255 VALUE_SHAPES_MIGRATION_ID ,
256+ NOTIFICATION_EVENT_MIGRATION_ID ,
220257] as const ;
221258
222259export const DataMigrationFlagSchema = lazySchema ( ( ) => z . object ( {
0 commit comments