Filed from the #15964 round, as the ruling required:
Any other creator that relied on the create-side ?? is enumerated in the PR body (it is a finding if one exists, not a reason to keep ??).
One exists. This is it.
The creator
packages/metadata/src/migrations/migrate-sys-notification-to-event.ts materializes each legacy sys_notification row into an inbox row and a receipt, and it deliberately reinstates the ORIGINAL timeline:
const createdAt = row.created_at != null ? canonicalTimestampText(row.created_at) : now();
...
await data.insert(INBOX_OBJECT, {
...
created_at: createdAt,
});
await data.insert(RECEIPT_OBJECT, {
...
at: isRead && row.read_at != null ? canonicalTimestampText(row.read_at) : createdAt,
created_at: createdAt,
});
Both calls pass no options bag at all, so the write context carries neither preserveAudit nor isSystem. Before the #15964 fix, record.created_at = record.created_at ?? now in the audit binder kept the legacy value and the engine's static-readonly strip spared it (a hook assigned the key). After the fix, the binder stamps the migration instant on the ordinary branch, so every migrated inbox row and receipt is stamped with the moment the migration ran instead of the moment the notification was created.
data here is a real IDataEngine (declared at :110), so in production these inserts run the shipped sys_stamp_audit_insert hook. The reliance is on the HOOK's ??, not on the strip, so it does not depend on whether the target object declares created_at as readonly.
Why no test caught it
migrate-sys-notification-to-event.test.ts drives the migration through a fake engine double whose insert records the payload directly. No audit hook runs there, so expect(inbox.row.created_at).toBe(REPORTED_INSTANT) passes on both sides of the change. Measured: that suite is 23 passed at the fixed head. The double is faithful about dispatch (it routes through assertEngineUpdateDispatch / assertEngineDeleteDispatch) and silent about the before-phase hooks, which is exactly the seam this defect lives in.
The remedy, and why it is a card
One context key on each of the two writes — { context: { preserveAudit: true } } — which is the explicit historical-import channel the same ruling preserved (treatAsHistorical sets exactly that, packages/rest/src/import-runner.ts). It is a card rather than a rider on the ruled PR because:
- it lands in a different package (
@objectstack/metadata) and needs its own changeset;
- it needs a test that actually exercises the audit hook, or the fix is unfalsifiable by the same double that hid the defect — that is the substantive half of the work;
- the ruling asked for enumeration, and named a finding as the outcome.
Enumeration this came out of
Every non-generated source under packages/, apps/ and examples/ carrying created_at as an object-literal key was classified by whether the value is the current instant (no reliance on preservation) or an external/back-dated one. Only this file supplies a value that is not "now":
| site |
value |
verdict |
metadata/src/migrations/migrate-sys-notification-to-event.ts:185,197 |
the legacy row's created_at |
RELIANT — this card |
metadata/src/loaders/database-loader.ts:1357 |
its own now |
same instant, no reliance |
rest/src/rest-server.ts:8826 |
new Date().toISOString() |
same instant |
services/service-messaging/src/{sql-outbox,sql-http-outbox}.ts |
its own now |
same instant |
services/service-messaging/src/{inbox-channel,messaging-service}.ts |
the delivery/read instant of the same call |
same instant |
services/service-automation/src/{flow-dispatch-store,suspended-run-store}.ts |
its own now (one already passes a system context) |
same instant |
objectql/src/engine.ts:6741 |
new Date().toISOString(), and it calls secretDriver.create DIRECTLY |
not an engine insert at all |
runtime/src/domains/share-links.ts:238 |
a response body field |
not an insert |
Generated by Claude Code
Filed from the #15964 round, as the ruling required:
One exists. This is it.
The creator
packages/metadata/src/migrations/migrate-sys-notification-to-event.tsmaterializes each legacysys_notificationrow into an inbox row and a receipt, and it deliberately reinstates the ORIGINAL timeline:Both calls pass no options bag at all, so the write context carries neither
preserveAuditnorisSystem. Before the #15964 fix,record.created_at = record.created_at ?? nowin the audit binder kept the legacy value and the engine's static-readonly strip spared it (a hook assigned the key). After the fix, the binder stamps the migration instant on the ordinary branch, so every migrated inbox row and receipt is stamped with the moment the migration ran instead of the moment the notification was created.datahere is a realIDataEngine(declared at:110), so in production these inserts run the shippedsys_stamp_audit_inserthook. The reliance is on the HOOK's??, not on the strip, so it does not depend on whether the target object declarescreated_atas readonly.Why no test caught it
migrate-sys-notification-to-event.test.tsdrives the migration through a fake engine double whoseinsertrecords the payload directly. No audit hook runs there, soexpect(inbox.row.created_at).toBe(REPORTED_INSTANT)passes on both sides of the change. Measured: that suite is23 passedat the fixed head. The double is faithful about dispatch (it routes throughassertEngineUpdateDispatch/assertEngineDeleteDispatch) and silent about the before-phase hooks, which is exactly the seam this defect lives in.The remedy, and why it is a card
One context key on each of the two writes —
{ context: { preserveAudit: true } }— which is the explicit historical-import channel the same ruling preserved (treatAsHistoricalsets exactly that,packages/rest/src/import-runner.ts). It is a card rather than a rider on the ruled PR because:@objectstack/metadata) and needs its own changeset;Enumeration this came out of
Every non-generated source under
packages/,apps/andexamples/carryingcreated_atas an object-literal key was classified by whether the value is the current instant (no reliance on preservation) or an external/back-dated one. Only this file supplies a value that is not "now":metadata/src/migrations/migrate-sys-notification-to-event.ts:185,197created_atmetadata/src/loaders/database-loader.ts:1357nowrest/src/rest-server.ts:8826new Date().toISOString()services/service-messaging/src/{sql-outbox,sql-http-outbox}.tsnowservices/service-messaging/src/{inbox-channel,messaging-service}.tsservices/service-automation/src/{flow-dispatch-store,suspended-run-store}.tsnow(one already passes a system context)objectql/src/engine.ts:6741new Date().toISOString(), and it callssecretDriver.createDIRECTLYruntime/src/domains/share-links.ts:238Generated by Claude Code