From ad313cc1d31cb2fa788808cb110d289a302fd66a Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 22:43:14 +0000 Subject: [PATCH 1/2] feat(spec): adr-0030-notification-event joins CREATION_ATTESTED_MIGRATION_IDS; docblock states the ledger-claim matrix The maintainer ruled the three questions the registration reserved: last_run_at on every completed non-error run, applied_at only on migrated, verified_at never (no self-check); receipt, not gate; and the id is creation-attested. The docblock states the matrix in place of "silence is not an answer"; the array gains the member; the pins that read the array literally move with it, and a spec pin holds the literal membership, the docblock relations and the arbiter's answer to the receipt shape. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf --- ...ification-event-migration-ledger-claims.md | 38 +++ packages/platform-objects/src/plugin.test.ts | 1 + .../src/system/migration-flag.test.ts | 28 +- packages/spec/src/system/migration.zod.ts | 73 ++++-- ...ication-event-migration-ledger.pin.test.ts | 244 ++++++++++++++++++ 5 files changed, 365 insertions(+), 19 deletions(-) create mode 100644 .changeset/notification-event-migration-ledger-claims.md create mode 100644 packages/spec/src/system/notification-event-migration-ledger.pin.test.ts diff --git a/.changeset/notification-event-migration-ledger-claims.md b/.changeset/notification-event-migration-ledger-claims.md new file mode 100644 index 0000000000..4a107de6f6 --- /dev/null +++ b/.changeset/notification-event-migration-ledger-claims.md @@ -0,0 +1,38 @@ +--- +'@objectstack/spec': minor +--- + +feat(spec): `adr-0030-notification-event` joins `CREATION_ATTESTED_MIGRATION_IDS`, and its docblock states what a run may claim in the `sys_migration` ledger (maintainer ruling 2026-09-05 on #15710) + +The ADR-0030 notification-convergence migration id was registered so that +"has this cut-over run here?" is answerable at all, with its ledger semantics +deliberately left open on the constant. The maintainer has now ruled them +(decision batch #47 item 5, verbatim 「同意」 — the question batch #21 reserved), +and this release lands the spec half: + +- **Creation-attested.** A datastore created after the cut-over has no legacy + `sys_notification` inbox rows by construction, so the id is now a member of + `CREATION_ATTESTED_MIGRATION_IDS`. A store created from empty on this release + therefore carries a third attestation row in `sys_migration` at boot, in the + same uniform shape as the two ADR-0104 rows (`details.attested: + 'datastore-created-empty'`, `applied_at: null`, `blocking: 0`, `verified_at` + set for the fact observed at birth). Existing stores are untouched: + `attestFreshDatastore` writes only on a store it observed being created and + never overwrites a row, so a store created before this release attests + nothing new — its row for this id arrives with the first run of the migration. +- **The ledger-claim matrix**, on the constant's docblock, replacing the + registration-era "silence is not an answer": `last_run_at` on every completed + non-`error` run (`migrated`, `already_done`, `not_applicable`); `applied_at` + only on `migrated`; `verified_at` never set by a run (the migration has no + self-check, and `verified_at` means one passed); `blocking: 0`; + `details.outcome` carries the four-valued result; an `error` run writes no + claim at all. +- **Receipt, not gate.** Nothing reads the row as a precondition, and nothing + may: it is what an operator reads, in the shape the seed-tenancy repair + already uses (`verified_at: null`, `blocking: 0`), which + `isDataMigrationFlagVerified` answers `false` to by design. + +Additive: no authorable key, export or accept-set narrows, so no BREAKING +banner applies. Which caller writes the run receipt when the migration runs is +the runner's own contract (`@objectstack/metadata/migrations`) and lands +separately. diff --git a/packages/platform-objects/src/plugin.test.ts b/packages/platform-objects/src/plugin.test.ts index 649a9ec658..d4304bffef 100644 --- a/packages/platform-objects/src/plugin.test.ts +++ b/packages/platform-objects/src/plugin.test.ts @@ -186,6 +186,7 @@ describe('PlatformObjectsPlugin: fresh-datastore attestation (#3438, ADR-0104)', await boot(engine); expect(engine.rows.map((r: any) => r.id).sort()).toEqual([ + 'adr-0030-notification-event', // #15710 ruling 3 — creation-attested 'adr-0104-file-references', 'adr-0104-value-shapes', ]); diff --git a/packages/platform-objects/src/system/migration-flag.test.ts b/packages/platform-objects/src/system/migration-flag.test.ts index b748d0ff06..7cd223c8a4 100644 --- a/packages/platform-objects/src/system/migration-flag.test.ts +++ b/packages/platform-objects/src/system/migration-flag.test.ts @@ -9,7 +9,10 @@ import { describe, it, expect, vi } from 'vitest'; // why this file's `update` entry sat in the gate's DEBT ledger until #5619 sank // the predicate into a package that depends on neither side. import { assertEngineUpdateDispatch } from '@objectstack/metadata-core'; -import { CREATION_ATTESTED_MIGRATION_IDS } from '@objectstack/spec/system'; +import { + CREATION_ATTESTED_MIGRATION_IDS, + NOTIFICATION_EVENT_MIGRATION_ID, +} from '@objectstack/spec/system'; import { readDataMigrationFlag, isDataMigrationVerified, @@ -161,6 +164,29 @@ describe('fresh-datastore attestation (ADR-0104, 2026-07-30 addendum)', () => { ); }); + /** + * #15710 ruling 3. The ADR-0030 cut-over id is attested at birth like the + * two ADR-0104 ids — a store created after the cut-over never held a legacy + * inbox row — and in the SAME uniform shape: `applied_at: null`, + * `blocking: 0`, `details.attested`, `verified_at` set for the birth fact. + * What a RUN of that migration may claim (never `verified_at`) lives on the + * id's docblock in `@objectstack/spec`; this pins that the writer treats it + * as one more member and invents no per-id shape. Named by symbol, not by + * iterating the array: the loop above would stay green with the member gone. + */ + it('attests the ADR-0030 notification-event id at birth, in the uniform shape', async () => { + const engine = fakeEngine(); + + const attested = await attestFreshDatastore(engine); + + expect(attested).toContain(NOTIFICATION_EVENT_MIGRATION_ID); + const row = engine.tables.sys_migration.find((r) => r.id === NOTIFICATION_EVENT_MIGRATION_ID)!; + expect(row).toMatchObject({ applied_at: null, blocking: 0, advisory: 0 }); + expect(row.verified_at).toBeTruthy(); + expect(JSON.parse(String(row.details))).toEqual(CREATION_ATTESTATION_DETAIL); + expect(await isDataMigrationVerified(engine, NOTIFICATION_EVENT_MIGRATION_ID)).toBe(true); + }); + /** * The load-bearing safety property. An existing row means this store is not * one being created — whatever the caller believed — so attestation must diff --git a/packages/spec/src/system/migration.zod.ts b/packages/spec/src/system/migration.zod.ts index db06f15093..0f57558f50 100644 --- a/packages/spec/src/system/migration.zod.ts +++ b/packages/spec/src/system/migration.zod.ts @@ -185,38 +185,75 @@ export const VALUE_SHAPES_MIGRATION_ID = 'adr-0104-value-shapes'; * 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. + * What a run of it may claim in the ledger is RULED (maintainer 「同意」 to + * decision batch #47 item 5, recorded on #15710 — the question batch #21 + * reserved when the id was registered under #14025). The two ids above take + * their column semantics from an `os migrate` command that scans, self-checks, + * and only then records; this migration has no such command and no self-check + * — it reports `migrated` / `already_done` / `not_applicable` / `error` to its + * caller and nothing else — so its claims are narrower than theirs: + * + * - `last_run_at`: set on every COMPLETED non-`error` run — `migrated`, + * `already_done` and `not_applicable` alike. + * - `applied_at`: set only on `migrated` (legacy inbox rows were rewritten). + * - `verified_at`: NEVER set by a run of this migration. `verified_at` means + * a self-check passed, and there is no self-check to pass. + * - `blocking`: `0` by construction — nothing counts discrepancies. + * - `details.outcome`: the four-valued result, verbatim. + * - an `error` run writes NO ledger claim at all. + * + * Receipt, not gate. Nothing reads a row under this id as a precondition, and + * nothing may: a gate would need the self-check that does not exist. The row + * is what an operator reads, in the shape `sys-migration.object.ts` + * (`@objectstack/platform-objects`) already documents for the seed-tenancy + * repair — `verified_at: null`, `blocking: 0` by construction — which is + * exactly the shape {@link isDataMigrationFlagVerified} answers `false` to. + * + * Creation-attested. A datastore created after the cut-over has no legacy + * inbox rows by construction, and its creator observed it come into being + * with none — the same "true by birth, observably" argument + * {@link CREATION_ATTESTED_MIGRATION_IDS} makes for its other members — so + * this id is a member of that array. Leaving it out would make a fresh + * store's ledger read "never ran" for a registered id, which is false. The + * attestation is not a run: `attestFreshDatastore` + * (`@objectstack/platform-objects`) writes one uniform shape for every member + * — `details.attested: 'datastore-created-empty'`, `applied_at: null`, + * `blocking: 0`, and `verified_at` set for the fact observed at birth — so on + * a fresh store this row does read as verified, by birth and never by a run, + * and it still gates nothing. + * + * Which caller writes the run receipt when the migration runs is the runner's + * contract (`@objectstack/metadata/migrations`), decided in its own lane — not + * here. */ export const NOTIFICATION_EVENT_MIGRATION_ID = 'adr-0030-notification-event'; /** * The migrations a datastore attests at CREATION rather than by scanning. * - * Both facts these ids stand for — no legacy file value here, no malformed - * stored value here — are true by construction of an empty store, and true - * *observably*: the creator watched it come into being with no rows at all. - * That is the same observed-transition discipline the gates run on, not - * version-gating in disguise; a store that merely *looks* empty when found - * earns nothing, because "found empty" is an inference and "created empty" is - * an observation. + * Every fact these ids stand for — no legacy file value here, no malformed + * stored value here, no legacy `sys_notification` inbox row here — is true by + * construction of an empty store, and true *observably*: the creator watched + * it come into being with no rows at all. That is the same observed-transition + * discipline the gates run on, not version-gating in disguise; a store that + * merely *looks* empty when found earns nothing, because "found empty" is an + * inference and "created empty" is an observation. * * Without this, every deployment born on a version that already ships the * migrations would start lax and stay lax until someone ran a command that is, * for them, a no-op — so the warn regime would never die out. + * + * The third member is the ADR-0030 cut-over + * ({@link NOTIFICATION_EVENT_MIGRATION_ID}): a store created after it never + * held a per-user inbox row for the migration to split, so the birth + * observation settles that fact exactly as it settles the two ADR-0104 ones. + * Its attestation row is the same uniform shape as theirs; what a RUN of that + * migration may claim differs, and lives on the id's own docblock. */ export const CREATION_ATTESTED_MIGRATION_IDS = [ FILE_REFERENCES_MIGRATION_ID, VALUE_SHAPES_MIGRATION_ID, + NOTIFICATION_EVENT_MIGRATION_ID, ] as const; export const DataMigrationFlagSchema = lazySchema(() => z.object({ diff --git a/packages/spec/src/system/notification-event-migration-ledger.pin.test.ts b/packages/spec/src/system/notification-event-migration-ledger.pin.test.ts new file mode 100644 index 0000000000..b089765089 --- /dev/null +++ b/packages/spec/src/system/notification-event-migration-ledger.pin.test.ts @@ -0,0 +1,244 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * [#15710] What a run of `adr-0030-notification-event` may claim in the + * `sys_migration` ledger — the ruled matrix, pinned to the docblock that + * states it, to the array that carries the creation-attestation half, and to + * the arbiter that makes "receipt, not gate" a fact rather than a sentence. + * + * When the id was registered (#14025) its docblock deliberately said the + * column semantics were NOT settled and that "silence is not an answer": the + * two ADR-0104 ids take their meaning from an `os migrate` command that scans, + * self-checks and only then records, and this migration has neither. The + * maintainer then ruled (decision batch #47 item 5, verbatim 「同意」; batch #21 + * had reserved the question): `last_run_at` on every completed non-`error` + * run, `applied_at` only on `migrated`, `verified_at` never, `details.outcome` + * carrying the four-valued result, an `error` run writing no claim; the row is + * a receipt, not a gate; and the id joins `CREATION_ATTESTED_MIGRATION_IDS`. + * + * ## What is pinned, and why each half + * + * 1. The MEMBERSHIP, as a literal list in order. The runtime reader + * (`attestFreshDatastore` in `@objectstack/platform-objects`) and its own + * pins iterate the array, so they follow any change to it by construction — + * a member silently dropped would leave every one of them green. Only a + * literal notices. + * 2. The DOCBLOCK, as relations rather than wording. The matrix is read as + * bullets: the `last_run_at` bullet names all three non-`error` outcomes, + * the `applied_at` bullet ties itself to `migrated` alone, the + * `verified_at` bullet carries the negation, the `details.outcome` and + * `error` bullets exist, and the retired "silence is not an answer" + * sentence is gone. Rewording freely is fine; dropping an outcome, or + * re-entering `verified_at` as a claim a run may make, goes red. + * 3. The ARBITER. The receipt shape the ruling names (`verified_at: null`, + * `blocking: 0`, `last_run_at` set, `applied_at` set or not) must answer + * `false` to {@link isDataMigrationFlagVerified} — otherwise "receipt, not + * gate" would be prose over a row that authorises something. The control + * beside it proves the `false` is not vacuous. + * + * The self-test feeds the historical paragraph to the same readers, so the + * docblock half cannot pass merely by the prose falling silent. + */ + +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { describe, it, expect } from 'vitest'; + +import { + CREATION_ATTESTED_MIGRATION_IDS, + FILE_REFERENCES_MIGRATION_ID, + NOTIFICATION_EVENT_MIGRATION_ID, + VALUE_SHAPES_MIGRATION_ID, + isDataMigrationFlagVerified, + type DataMigrationFlag, +} from './migration.zod'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const SOURCE = join(HERE, 'migration.zod.ts'); + +/** The JSDoc block attached to the `NOTIFICATION_EVENT_MIGRATION_ID` constant. */ +function notificationEventDoc(): string { + const source = readFileSync(SOURCE, 'utf8'); + const decl = source.indexOf( + "export const NOTIFICATION_EVENT_MIGRATION_ID = 'adr-0030-notification-event'", + ); + expect(decl, 'the `NOTIFICATION_EVENT_MIGRATION_ID` declaration moved — re-anchor this pin').toBeGreaterThan(-1); + const open = source.lastIndexOf('/**', decl); + const close = source.indexOf('*/', open); + expect(open, 'no JSDoc block precedes `NOTIFICATION_EVENT_MIGRATION_ID`').toBeGreaterThan(-1); + expect(close, 'unterminated JSDoc block').toBeLessThan(decl); + return source.slice(open, close + 2); +} + +/** A JSDoc block as flat prose — decorations dropped, wrapped lines rejoined. */ +function flatten(block: string): string { + return block + .replace(/^\/\*\*/, '') + .replace(/\*\/$/, '') + .split('\n') + .map((line) => line.replace(/^\s*\*\s?/, '').trim()) + .join(' ') + .replace(/\s+/g, ' ') + .trim(); +} + +/** + * The `- ` bullets of a JSDoc block, each rejoined with its continuation + * lines. A bullet ends at the next bullet or at a blank comment line. + */ +function bullets(block: string): string[] { + const out: string[] = []; + let current: string[] | null = null; + for (const raw of block.split('\n')) { + const line = raw.replace(/^\s*\*\s?/, '').replace(/^\s*\*\/?\s*$/, '').trim(); + if (line.startsWith('- ')) { + if (current) out.push(current.join(' ')); + current = [line.slice(2)]; + } else if (line === '' || line === '/**' || line === '*/') { + if (current) out.push(current.join(' ')); + current = null; + } else if (current) { + current.push(line); + } + } + if (current) out.push(current.join(' ')); + return out.map((b) => b.replace(/\s+/g, ' ').trim()); +} + +/** The bullet whose subject is `column` — written as a backticked lead. */ +function bulletFor(block: string, column: string): string { + const found = bullets(block).find((b) => b.startsWith('`' + column + '`')); + expect(found, `the matrix has no \`${column}\` bullet`).toBeDefined(); + return found as string; +} + +/** The sentence the registration wrote and the ruling retired. */ +const RETIRED_CLAIM = /silence is not an answer/i; + +/** + * The registration's paragraph, verbatim (#14025 / PR #15450). The self-test + * feeds it to every reader above: it must fail the matrix and trip the + * retired-claim predicate, or a docblock that quietly went back to it would + * pass. + */ +const HISTORICAL_PARAGRAPH = `/** + * 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. + */`; + +const AT = '2026-09-05T00:00:00.000Z'; + +/** The receipt a run may write, per outcome — never `verified_at`. */ +function runReceipt(outcome: 'migrated' | 'already_done' | 'not_applicable'): DataMigrationFlag { + return { + id: NOTIFICATION_EVENT_MIGRATION_ID, + last_run_at: AT, + verified_at: null, + applied_at: outcome === 'migrated' ? AT : null, + blocking: 0, + details: JSON.stringify({ outcome }), + }; +} + +describe('adr-0030-notification-event: creation-attested membership (#15710 ruling 3)', () => { + it('the id is the well-known string the runner is registered under', () => { + expect(NOTIFICATION_EVENT_MIGRATION_ID).toBe('adr-0030-notification-event'); + }); + + it('CREATION_ATTESTED_MIGRATION_IDS is exactly the three ids, in registration order', () => { + // Literal on purpose — see the header. A `toContain` alone would not + // notice a member swapped for another, and a length would not notice + // which one went. + expect([...CREATION_ATTESTED_MIGRATION_IDS]).toEqual([ + FILE_REFERENCES_MIGRATION_ID, + VALUE_SHAPES_MIGRATION_ID, + NOTIFICATION_EVENT_MIGRATION_ID, + ]); + expect([...CREATION_ATTESTED_MIGRATION_IDS]).toEqual([ + 'adr-0104-file-references', + 'adr-0104-value-shapes', + 'adr-0030-notification-event', + ]); + }); + + it('the member is a TYPE of the array too, not only a value', () => { + // Fails to compile if the tuple type loses the member: the writer's + // `migrationIds` option is typed against `readonly string[]`, so nothing + // downstream would notice a narrowing at the type level either. + const member: (typeof CREATION_ATTESTED_MIGRATION_IDS)[number] = 'adr-0030-notification-event'; + expect(CREATION_ATTESTED_MIGRATION_IDS.includes(member)).toBe(true); + }); +}); + +describe('adr-0030-notification-event: the docblock states the ruled ledger-claim matrix (#15710 ruling 1)', () => { + it('`last_run_at` is claimed on every completed non-`error` run — all three outcomes named', () => { + const bullet = bulletFor(notificationEventDoc(), 'last_run_at'); + expect(bullet).toMatch(/non-`error`/); + expect(bullet).toMatch(/`migrated`/); + expect(bullet).toMatch(/`already_done`/); + expect(bullet).toMatch(/`not_applicable`/); + }); + + it('`applied_at` is claimed only on `migrated`', () => { + const bullet = bulletFor(notificationEventDoc(), 'applied_at'); + expect(bullet).toMatch(/only on `migrated`/); + expect(bullet).not.toMatch(/`already_done`|`not_applicable`/); + }); + + it('`verified_at` is never claimed by a run — the bullet carries the negation and says why', () => { + const bullet = bulletFor(notificationEventDoc(), 'verified_at'); + expect(bullet).toMatch(/\bNEVER\b/); + expect(bullet).toMatch(/self-check/); + }); + + it('`details.outcome` carries the four-valued result, and an `error` run writes no claim', () => { + const doc = notificationEventDoc(); + expect(bulletFor(doc, 'details.outcome')).toMatch(/four-valued/); + const error = bullets(doc).find((b) => /^an `error` run/.test(b)); + expect(error, 'the matrix has no `error` bullet').toBeDefined(); + expect(error).toMatch(/\bNO\b[^.]*claim/); + }); + + it('says "receipt, not gate", cites the receipt precedent, and no longer defers the question', () => { + const prose = flatten(notificationEventDoc()); + expect(prose).toMatch(/Receipt, not gate/); + expect(prose).toMatch(/sys-migration\.object\.ts/); + expect(prose).toMatch(/`verified_at: null`, `blocking: 0`/); + expect(prose).not.toMatch(RETIRED_CLAIM); + }); + + it('points at the array for the creation-attested half', () => { + expect(flatten(notificationEventDoc())).toMatch(/\{@link CREATION_ATTESTED_MIGRATION_IDS\}/); + }); + + it('self-test: the registration-era paragraph fails every reader above', () => { + // No bullets → no matrix; the retired sentence trips the predicate. A + // docblock that went back to deferring cannot pass by saying less. + expect(bullets(HISTORICAL_PARAGRAPH)).toEqual([]); + expect(flatten(HISTORICAL_PARAGRAPH)).toMatch(RETIRED_CLAIM); + }); +}); + +describe('adr-0030-notification-event: the run receipt authorises nothing (#15710 ruling 2)', () => { + it.each(['migrated', 'already_done', 'not_applicable'] as const)( + 'a `%s` receipt reads as not verified to the one arbiter', + (outcome) => { + expect(isDataMigrationFlagVerified(runReceipt(outcome))).toBe(false); + }, + ); + + it('control: the same row with `verified_at` set WOULD authorise — the `false` above is the null, not the shape', () => { + expect(isDataMigrationFlagVerified({ ...runReceipt('migrated'), verified_at: AT })).toBe(true); + }); +}); From 35819dff25579a60a24f6fe3a65f339e0a1ccccd Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 23:04:08 +0000 Subject: [PATCH 2/2] test(platform-objects): the boot-attestation pins carry the third creation-attested id The timing and deferral cases in plugin.test.ts assert the attested set as a sorted literal list or a literal count; both move with the new member, and the contradicted-gate case now attests the other two ids in array order. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf --- packages/platform-objects/src/plugin.test.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/platform-objects/src/plugin.test.ts b/packages/platform-objects/src/plugin.test.ts index d4304bffef..2e52cff776 100644 --- a/packages/platform-objects/src/plugin.test.ts +++ b/packages/platform-objects/src/plugin.test.ts @@ -265,6 +265,7 @@ describe('PlatformObjectsPlugin: fresh-datastore attestation (#3438, ADR-0104)', await ctx._flush('app:seeded'); expect(engine.rows.map((r: any) => r.id).sort()).toEqual([ + 'adr-0030-notification-event', 'adr-0104-file-references', 'adr-0104-value-shapes', ]); @@ -281,7 +282,7 @@ describe('PlatformObjectsPlugin: fresh-datastore attestation (#3438, ADR-0104)', await ctx._flush('app:seeded'); await ctx._flushReady(); - expect(engine.rows).toHaveLength(2); + expect(engine.rows).toHaveLength(3); }); /** @@ -307,7 +308,10 @@ describe('PlatformObjectsPlugin: fresh-datastore attestation (#3438, ADR-0104)', await ctx._flush('app:seeded'); await ctx._flushReady(); - expect(engine.rows.map((r: any) => r.id)).toEqual(['adr-0104-value-shapes']); + expect(engine.rows.map((r: any) => r.id)).toEqual([ + 'adr-0104-value-shapes', + 'adr-0030-notification-event', + ]); }); }); @@ -369,6 +373,7 @@ describe('PlatformObjectsPlugin: fresh-datastore attestation (#3438, ADR-0104)', await ctx._flush('app:seeded'); expect(engine.rows.map((r: any) => r.id).sort()).toEqual([ + 'adr-0030-notification-event', 'adr-0104-file-references', 'adr-0104-value-shapes', ]); @@ -391,7 +396,7 @@ describe('PlatformObjectsPlugin: fresh-datastore attestation (#3438, ADR-0104)', state.inFlight = 0; // app B settled await ctx._flush('app:seeded'); - expect(engine.rows).toHaveLength(2); + expect(engine.rows).toHaveLength(3); }); /** @@ -447,6 +452,7 @@ describe('PlatformObjectsPlugin: fresh-datastore attestation (#3438, ADR-0104)', await ctx._flushReady(); expect(engine.rows.map((r: any) => r.id).sort()).toEqual([ + 'adr-0030-notification-event', 'adr-0104-file-references', 'adr-0104-value-shapes', ]); @@ -462,7 +468,7 @@ describe('PlatformObjectsPlugin: fresh-datastore attestation (#3438, ADR-0104)', await ctx._flushReady(); - expect(engine.rows).toHaveLength(2); + expect(engine.rows).toHaveLength(3); }); /**