@proteinjs/db 1.34.2 → 1.34.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/generated/index.d.ts +0 -4
  3. package/dist/generated/index.d.ts.map +1 -1
  4. package/dist/generated/index.js +1 -5
  5. package/dist/generated/index.js.map +1 -1
  6. package/dist/generated/test/index.d.ts +0 -4
  7. package/dist/generated/test/index.d.ts.map +1 -1
  8. package/dist/generated/test/index.js +3 -5
  9. package/dist/generated/test/index.js.map +1 -1
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/src/Db.d.ts +12 -0
  15. package/dist/src/Db.d.ts.map +1 -1
  16. package/dist/src/Db.js +132 -22
  17. package/dist/src/Db.js.map +1 -1
  18. package/dist/src/RecordAccessError.d.ts +18 -0
  19. package/dist/src/RecordAccessError.d.ts.map +1 -0
  20. package/dist/src/RecordAccessError.js +46 -0
  21. package/dist/src/RecordAccessError.js.map +1 -0
  22. package/dist/src/Table.d.ts +28 -1
  23. package/dist/src/Table.d.ts.map +1 -1
  24. package/dist/src/source/SourceRecord.d.ts +35 -2
  25. package/dist/src/source/SourceRecord.d.ts.map +1 -1
  26. package/dist/src/source/SourceRecord.js +8 -1
  27. package/dist/src/source/SourceRecord.js.map +1 -1
  28. package/dist/src/source/SourceRecordLoader.d.ts +98 -7
  29. package/dist/src/source/SourceRecordLoader.d.ts.map +1 -1
  30. package/dist/src/source/SourceRecordLoader.js +348 -93
  31. package/dist/src/source/SourceRecordLoader.js.map +1 -1
  32. package/dist/src/tables/MigrationTable.d.ts +2 -0
  33. package/dist/src/tables/MigrationTable.d.ts.map +1 -1
  34. package/dist/test/reusable/SourceRecordSyncTests.d.ts.map +1 -1
  35. package/dist/test/reusable/SourceRecordSyncTests.js +803 -27
  36. package/dist/test/reusable/SourceRecordSyncTests.js.map +1 -1
  37. package/dist/test/util/tables/cascadeDeleteTestTables.d.ts +15 -115
  38. package/dist/test/util/tables/cascadeDeleteTestTables.d.ts.map +1 -1
  39. package/dist/test/util/tables/cascadeDeleteTestTables.js.map +1 -1
  40. package/dist/test/util/tables/crudTestTables.d.ts +2 -22
  41. package/dist/test/util/tables/crudTestTables.d.ts.map +1 -1
  42. package/dist/test/util/tables/crudTestTables.js.map +1 -1
  43. package/dist/test/util/tables/dynamicReferenceColumnTestTables.d.ts +4 -36
  44. package/dist/test/util/tables/dynamicReferenceColumnTestTables.d.ts.map +1 -1
  45. package/dist/test/util/tables/dynamicReferenceColumnTestTables.js.map +1 -1
  46. package/dist/test/util/tables/sourceRecordSyncTestTables.d.ts +20 -34
  47. package/dist/test/util/tables/sourceRecordSyncTestTables.d.ts.map +1 -1
  48. package/dist/test/util/tables/sourceRecordSyncTestTables.js +23 -1
  49. package/dist/test/util/tables/sourceRecordSyncTestTables.js.map +1 -1
  50. package/dist/test/util/tables/tableManagerTestTables.d.ts +3 -38
  51. package/dist/test/util/tables/tableManagerTestTables.d.ts.map +1 -1
  52. package/dist/test/util/tables/tableManagerTestTables.js.map +1 -1
  53. package/dist/test/util/tables/transactionTestTables.d.ts +2 -22
  54. package/dist/test/util/tables/transactionTestTables.d.ts.map +1 -1
  55. package/dist/test/util/tables/transactionTestTables.js.map +1 -1
  56. package/generated/index.ts +1 -5
  57. package/generated/test/index.ts +3 -5
  58. package/index.ts +1 -0
  59. package/package.json +5 -5
  60. package/src/Db.ts +65 -2
  61. package/src/RecordAccessError.ts +22 -0
  62. package/src/Table.ts +33 -1
  63. package/src/source/SourceRecord.ts +42 -4
  64. package/src/source/SourceRecordLoader.ts +333 -48
  65. package/test/reusable/SourceRecordSyncTests.ts +462 -11
  66. package/test/util/tables/cascadeDeleteTestTables.ts +15 -15
  67. package/test/util/tables/crudTestTables.ts +2 -2
  68. package/test/util/tables/dynamicReferenceColumnTestTables.ts +4 -4
  69. package/test/util/tables/sourceRecordSyncTestTables.ts +24 -4
  70. package/test/util/tables/tableManagerTestTables.ts +3 -3
  71. package/test/util/tables/transactionTestTables.ts +2 -2
  72. package/test.d.ts +2 -0
  73. package/test.js +4 -0
  74. package/tsconfig.json +2 -1
@@ -105,20 +105,56 @@ var sourceRecordSyncTests = function (driver, transactionContextFactory, dropTab
105
105
  var machineTable = sourceRecordSyncTestTables_1.sourceRecordSyncTestTables.SyncMachineAccount;
106
106
  var defaultPolicyTable = sourceRecordSyncTestTables_1.sourceRecordSyncTestTables.SyncDefaultPolicy;
107
107
  var objectCache = function () { return reflection_1.SourceRepository.get().objectCache; };
108
+ var namedObjectCache = function () { return reflection_1.SourceRepository.get().namedObjectCache; };
108
109
  var originalWatchers;
109
- /** Seed the boot declarations and run the sync one boot of `Db.init`'s loader leg. */
110
- var boot = function (declarations) { return __awaiter(void 0, void 0, void 0, function () {
111
- return __generator(this, function (_a) {
112
- switch (_a.label) {
113
- case 0:
114
- objectCache()['@proteinjs/db/SourceRecordLoader'] = declarations;
115
- return [4 /*yield*/, new SourceRecordLoader_1.SourceRecordLoader().load()];
116
- case 1:
117
- _a.sent();
118
- return [2 /*return*/];
110
+ /** The single source most of the suite boots as (multi-source tests name their own). */
111
+ var DEFAULT_TEST_SOURCE = '@test/source-a';
112
+ /**
113
+ * One boot of the loader leg as a BUILD: each declaration owned by its declaring package
114
+ * (`source`), seeded through the named-object cache the loader resolves declarations (and
115
+ * their owning packages) from. `versions` pins each package's version for the boot (the
116
+ * loader's version resolution is overridden — a package absent from the map is versionless,
117
+ * matching a build whose package.json could not be resolved).
118
+ */
119
+ var bootBuild = function (declarations, versions) {
120
+ if (versions === void 0) { versions = {}; }
121
+ return __awaiter(void 0, void 0, void 0, function () {
122
+ var loader;
123
+ return __generator(this, function (_a) {
124
+ switch (_a.label) {
125
+ case 0:
126
+ namedObjectCache()['@proteinjs/db/SourceRecordLoader'] = declarations.map(function (declaration, i) { return ({
127
+ qualifiedName: "".concat(declaration.source, "/SeededLoader").concat(i),
128
+ packageName: declaration.source,
129
+ object: { table: declaration.table, record: declaration.record },
130
+ }); });
131
+ loader = new SourceRecordLoader_1.SourceRecordLoader();
132
+ loader.resolveSourceVersion = function (source) {
133
+ return Object.prototype.hasOwnProperty.call(versions, source) ? versions[source] : undefined;
134
+ };
135
+ return [4 /*yield*/, loader.load()];
136
+ case 1: return [2 /*return*/, _a.sent()];
137
+ }
138
+ });
139
+ });
140
+ };
141
+ /** One boot of the loader leg as a build declaring everything from a single package. */
142
+ var bootAsSource = function (source, declarations, version) { return __awaiter(void 0, void 0, void 0, function () {
143
+ var _a;
144
+ return __generator(this, function (_b) {
145
+ switch (_b.label) {
146
+ case 0: return [4 /*yield*/, bootBuild(declarations.map(function (declaration) { return (__assign({ source: source }, declaration)); }), version === undefined ? {} : (_a = {}, _a[source] = version, _a))];
147
+ case 1: return [2 /*return*/, _b.sent()];
119
148
  }
120
149
  });
121
150
  }); };
151
+ /** Seed the boot declarations and run the sync — one boot of `Db.init`'s loader leg. */
152
+ var boot = function (declarations) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
153
+ switch (_a.label) {
154
+ case 0: return [4 /*yield*/, bootAsSource(DEFAULT_TEST_SOURCE, declarations)];
155
+ case 1: return [2 /*return*/, _a.sent()];
156
+ }
157
+ }); }); };
122
158
  var machineDeclaration = function (record) { return ({
123
159
  table: machineTable,
124
160
  record: __assign({ status: 'active' }, record),
@@ -160,6 +196,7 @@ var sourceRecordSyncTests = function (driver, transactionContextFactory, dropTab
160
196
  case 0: return [4 /*yield*/, testEnv.afterAll()];
161
197
  case 1:
162
198
  _a.sent();
199
+ delete namedObjectCache()['@proteinjs/db/SourceRecordLoader'];
163
200
  delete objectCache()['@proteinjs/db/SourceRecordLoader'];
164
201
  delete objectCache()['@proteinjs/db/DefaultDbDriverFactory'];
165
202
  delete objectCache()['@proteinjs/db/DefaultTransactionContextFactory'];
@@ -186,6 +223,9 @@ var sourceRecordSyncTests = function (driver, transactionContextFactory, dropTab
186
223
  _a.sent();
187
224
  return [4 /*yield*/, db.delete(defaultPolicyTable, {})];
188
225
  case 2:
226
+ _a.sent();
227
+ return [4 /*yield*/, db.delete(sourceRecordSyncTestTables_1.sourceRecordSyncTestTables.InheritedStamp, {})];
228
+ case 3:
189
229
  _a.sent();
190
230
  RecordingMachineAccountWatcher.updates = [];
191
231
  return [2 /*return*/];
@@ -315,7 +355,7 @@ var sourceRecordSyncTests = function (driver, transactionContextFactory, dropTab
315
355
  });
316
356
  }); });
317
357
  test('onSourceRemoved update: removed rows are flagged through Db.update (watchers fire), never deleted; re-declaring reverts', function () { return __awaiter(void 0, void 0, void 0, function () {
318
- var db, human, removed, _a, _b;
358
+ var db, human, keeper, removed, _a, _b;
319
359
  return __generator(this, function (_c) {
320
360
  switch (_c.label) {
321
361
  case 0:
@@ -323,11 +363,14 @@ var sourceRecordSyncTests = function (driver, transactionContextFactory, dropTab
323
363
  return [4 /*yield*/, db.insert(machineTable, { email: 'human@test.local', displayName: 'A human' })];
324
364
  case 1:
325
365
  human = _c.sent();
326
- return [4 /*yield*/, boot([machineDeclaration({ id: 'machine-1', email: 'machine@test.local' })])];
366
+ keeper = machineDeclaration({ id: 'machine-keep', email: 'keeper@test.local' });
367
+ return [4 /*yield*/, boot([machineDeclaration({ id: 'machine-1', email: 'machine@test.local' }), keeper])];
327
368
  case 2:
328
369
  _c.sent();
370
+ // The source's next build drops machine-1 (while still declaring from this table —
371
+ // ownership is per source, and a source only reconciles what it still speaks for).
329
372
  RecordingMachineAccountWatcher.updates = [];
330
- return [4 /*yield*/, boot([])];
373
+ return [4 /*yield*/, boot([keeper])];
331
374
  case 3:
332
375
  _c.sent();
333
376
  return [4 /*yield*/, db.get(machineTable, { id: 'machine-1' })];
@@ -347,12 +390,12 @@ var sourceRecordSyncTests = function (driver, transactionContextFactory, dropTab
347
390
  _a.apply(void 0, [(_c.sent()).status]).toBeFalsy();
348
391
  // Idempotent: an already-flagged row is not re-written on the next boot.
349
392
  RecordingMachineAccountWatcher.updates = [];
350
- return [4 /*yield*/, boot([])];
393
+ return [4 /*yield*/, boot([keeper])];
351
394
  case 6:
352
395
  _c.sent();
353
396
  expect(RecordingMachineAccountWatcher.updates).toHaveLength(0);
354
397
  // Removal is reversible in source: re-declaring reverts the patch via drift reversion.
355
- return [4 /*yield*/, boot([machineDeclaration({ id: 'machine-1', email: 'machine@test.local' })])];
398
+ return [4 /*yield*/, boot([machineDeclaration({ id: 'machine-1', email: 'machine@test.local' }), keeper])];
356
399
  case 7:
357
400
  // Removal is reversible in source: re-declaring reverts the patch via drift reversion.
358
401
  _c.sent();
@@ -365,36 +408,734 @@ var sourceRecordSyncTests = function (driver, transactionContextFactory, dropTab
365
408
  });
366
409
  }); });
367
410
  test("onSourceRemoved default: removed source rows are deleted; human rows survive", function () { return __awaiter(void 0, void 0, void 0, function () {
368
- var db, human, _a, _b, _c;
369
- return __generator(this, function (_d) {
370
- switch (_d.label) {
411
+ var db, human, keeper, _a, _b, _c, _d;
412
+ return __generator(this, function (_e) {
413
+ switch (_e.label) {
371
414
  case 0:
372
415
  db = (0, db_1.getDbAsSystem)();
373
416
  return [4 /*yield*/, db.insert(defaultPolicyTable, { email: 'human@test.local' })];
374
417
  case 1:
375
- human = _d.sent();
376
- return [4 /*yield*/, boot([{ table: defaultPolicyTable, record: { id: 'default-1', email: 'temp@test.local' } }])];
418
+ human = _e.sent();
419
+ keeper = { table: defaultPolicyTable, record: { id: 'default-keep', email: 'keeper@test.local' } };
420
+ return [4 /*yield*/, boot([{ table: defaultPolicyTable, record: { id: 'default-1', email: 'temp@test.local' } }, keeper])];
377
421
  case 2:
378
- _d.sent();
422
+ _e.sent();
379
423
  _a = expect;
380
424
  return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'default-1' })];
381
425
  case 3:
382
- _a.apply(void 0, [_d.sent()]).toBeDefined();
383
- return [4 /*yield*/, boot([])];
426
+ _a.apply(void 0, [_e.sent()]).toBeDefined();
427
+ return [4 /*yield*/, boot([keeper])];
384
428
  case 4:
385
- _d.sent();
429
+ _e.sent();
386
430
  _b = expect;
387
431
  return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'default-1' })];
388
432
  case 5:
389
- _b.apply(void 0, [_d.sent()]).toBeUndefined();
433
+ _b.apply(void 0, [_e.sent()]).toBeUndefined();
390
434
  _c = expect;
391
- return [4 /*yield*/, db.get(defaultPolicyTable, { id: human.id })];
435
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'default-keep' })];
392
436
  case 6:
393
- _c.apply(void 0, [_d.sent()]).toBeDefined();
437
+ _c.apply(void 0, [_e.sent()]).toBeDefined();
438
+ _d = expect;
439
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: human.id })];
440
+ case 7:
441
+ _d.apply(void 0, [_e.sent()]).toBeDefined();
394
442
  return [2 /*return*/];
395
443
  }
396
444
  });
397
445
  }); });
446
+ /**
447
+ * Source-scoped pruning: multiple servers running DIFFERENT builds against ONE shared
448
+ * database (the thought_type dev topology — two dev servers on one Spanner database).
449
+ * Ownership grain is the declaring package: each declaration's source is the package that
450
+ * compiled it into the build. The removed-reconcile must stay authoritative WITHIN a source
451
+ * (a source still deletes declarations it itself removed) and be a strict no-op ACROSS
452
+ * sources (a boot never deletes rows owned by a package absent from its build).
453
+ */
454
+ describe('source-scoped pruning (shared DB, per-package ownership)', function () {
455
+ var decl = function (id, email) { return ({ table: defaultPolicyTable, record: { id: id, email: email } }); };
456
+ test("one source's sync never deletes rows owned by a different source", function () { return __awaiter(void 0, void 0, void 0, function () {
457
+ var db, _a, _b, _c, _d, _e, _f, _g, _h;
458
+ return __generator(this, function (_j) {
459
+ switch (_j.label) {
460
+ case 0:
461
+ db = (0, db_1.getDbAsSystem)();
462
+ // Build A (e.g. this checkout's flow types) declares {a-1, a-2}; build B (the other
463
+ // checkout's media types) declares a disjoint set {b-1}.
464
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('a-1', 'a1@test.local'), decl('a-2', 'a2@test.local')])];
465
+ case 1:
466
+ // Build A (e.g. this checkout's flow types) declares {a-1, a-2}; build B (the other
467
+ // checkout's media types) declares a disjoint set {b-1}.
468
+ _j.sent();
469
+ return [4 /*yield*/, bootAsSource('@test/pkg-b', [decl('b-1', 'b1@test.local')])];
470
+ case 2:
471
+ _j.sent();
472
+ // B's boot must NOT have pruned A's rows — this is the cross-server nuke.
473
+ _a = expect;
474
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-1' })];
475
+ case 3:
476
+ // B's boot must NOT have pruned A's rows — this is the cross-server nuke.
477
+ _a.apply(void 0, [_j.sent()]).toBeDefined();
478
+ _b = expect;
479
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-2' })];
480
+ case 4:
481
+ _b.apply(void 0, [_j.sent()]).toBeDefined();
482
+ _c = expect;
483
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'b-1' })];
484
+ case 5:
485
+ _c.apply(void 0, [_j.sent()]).toBeDefined();
486
+ // Each row is stamped with its owning package — the scoping the prune runs on.
487
+ _d = expect;
488
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-1' })];
489
+ case 6:
490
+ // Each row is stamped with its owning package — the scoping the prune runs on.
491
+ _d.apply(void 0, [(_j.sent()).sourcePackage]).toBe('@test/pkg-a');
492
+ _e = expect;
493
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'b-1' })];
494
+ case 7:
495
+ _e.apply(void 0, [(_j.sent()).sourcePackage]).toBe('@test/pkg-b');
496
+ // And symmetrically: A re-booting must not prune B's row.
497
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('a-1', 'a1@test.local'), decl('a-2', 'a2@test.local')])];
498
+ case 8:
499
+ // And symmetrically: A re-booting must not prune B's row.
500
+ _j.sent();
501
+ _f = expect;
502
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'b-1' })];
503
+ case 9:
504
+ _f.apply(void 0, [_j.sent()]).toBeDefined();
505
+ _g = expect;
506
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-1' })];
507
+ case 10:
508
+ _g.apply(void 0, [_j.sent()]).toBeDefined();
509
+ _h = expect;
510
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-2' })];
511
+ case 11:
512
+ _h.apply(void 0, [_j.sent()]).toBeDefined();
513
+ return [2 /*return*/];
514
+ }
515
+ });
516
+ }); });
517
+ test('a source still prunes its OWN removed declarations without touching other sources', function () { return __awaiter(void 0, void 0, void 0, function () {
518
+ var db, _a, _b, _c;
519
+ return __generator(this, function (_d) {
520
+ switch (_d.label) {
521
+ case 0:
522
+ db = (0, db_1.getDbAsSystem)();
523
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('a-1', 'a1@test.local'), decl('a-2', 'a2@test.local')])];
524
+ case 1:
525
+ _d.sent();
526
+ return [4 /*yield*/, bootAsSource('@test/pkg-b', [decl('b-1', 'b1@test.local')])];
527
+ case 2:
528
+ _d.sent();
529
+ // A's next build drops a-2: A's own removed declaration is pruned...
530
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('a-1', 'a1@test.local')])];
531
+ case 3:
532
+ // A's next build drops a-2: A's own removed declaration is pruned...
533
+ _d.sent();
534
+ _a = expect;
535
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-2' })];
536
+ case 4:
537
+ _a.apply(void 0, [_d.sent()]).toBeUndefined();
538
+ // ...while A's still-declared row and B's row both survive.
539
+ _b = expect;
540
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-1' })];
541
+ case 5:
542
+ // ...while A's still-declared row and B's row both survive.
543
+ _b.apply(void 0, [_d.sent()]).toBeDefined();
544
+ _c = expect;
545
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'b-1' })];
546
+ case 6:
547
+ _c.apply(void 0, [_d.sent()]).toBeDefined();
548
+ return [2 /*return*/];
549
+ }
550
+ });
551
+ }); });
552
+ test('a build declaring nothing for a table prunes nothing', function () { return __awaiter(void 0, void 0, void 0, function () {
553
+ var db, _a;
554
+ return __generator(this, function (_b) {
555
+ switch (_b.label) {
556
+ case 0:
557
+ db = (0, db_1.getDbAsSystem)();
558
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('a-1', 'a1@test.local')])];
559
+ case 1:
560
+ _b.sent();
561
+ // A build whose declaring packages are all absent (or declare nothing for this table)
562
+ // makes no claim about it — pre-fix this boot deleted EVERY source-loaded row.
563
+ return [4 /*yield*/, bootAsSource('@test/pkg-b', [])];
564
+ case 2:
565
+ // A build whose declaring packages are all absent (or declare nothing for this table)
566
+ // makes no claim about it — pre-fix this boot deleted EVERY source-loaded row.
567
+ _b.sent();
568
+ _a = expect;
569
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-1' })];
570
+ case 3:
571
+ _a.apply(void 0, [_b.sent()]).toBeDefined();
572
+ return [2 /*return*/];
573
+ }
574
+ });
575
+ }); });
576
+ test("onSourceRemoved update is source-scoped too: a foreign boot does not flag another source's rows", function () { return __awaiter(void 0, void 0, void 0, function () {
577
+ var db, _a, _b, _c, _d;
578
+ return __generator(this, function (_e) {
579
+ switch (_e.label) {
580
+ case 0:
581
+ db = (0, db_1.getDbAsSystem)();
582
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [
583
+ { table: machineTable, record: { id: 'm-a', email: 'ma@test.local', status: 'active' } },
584
+ ])];
585
+ case 1:
586
+ _e.sent();
587
+ return [4 /*yield*/, bootAsSource('@test/pkg-b', [
588
+ { table: machineTable, record: { id: 'm-b', email: 'mb@test.local', status: 'active' } },
589
+ ])];
590
+ case 2:
591
+ _e.sent();
592
+ // B's boot must not have treated A's machine account as removed.
593
+ _a = expect;
594
+ return [4 /*yield*/, db.get(machineTable, { id: 'm-a' })];
595
+ case 3:
596
+ // B's boot must not have treated A's machine account as removed.
597
+ _a.apply(void 0, [(_e.sent()).status]).toBe('active');
598
+ _b = expect;
599
+ return [4 /*yield*/, db.get(machineTable, { id: 'm-b' })];
600
+ case 4:
601
+ _b.apply(void 0, [(_e.sent()).status]).toBe('active');
602
+ // A dropping its own declaration still deactivates it — and leaves B's untouched.
603
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [
604
+ { table: machineTable, record: { id: 'm-a2', email: 'ma2@test.local', status: 'active' } },
605
+ ])];
606
+ case 5:
607
+ // A dropping its own declaration still deactivates it — and leaves B's untouched.
608
+ _e.sent();
609
+ _c = expect;
610
+ return [4 /*yield*/, db.get(machineTable, { id: 'm-a' })];
611
+ case 6:
612
+ _c.apply(void 0, [(_e.sent()).status]).toBe('deactivated');
613
+ _d = expect;
614
+ return [4 /*yield*/, db.get(machineTable, { id: 'm-b' })];
615
+ case 7:
616
+ _d.apply(void 0, [(_e.sent()).status]).toBe('active');
617
+ return [2 /*return*/];
618
+ }
619
+ });
620
+ }); });
621
+ test('a declaration migrating to a new source is re-owned, not pruned by its old source', function () { return __awaiter(void 0, void 0, void 0, function () {
622
+ var db, _a, _b;
623
+ return __generator(this, function (_c) {
624
+ switch (_c.label) {
625
+ case 0:
626
+ db = (0, db_1.getDbAsSystem)();
627
+ // k-1 starts life declared by pkg-a, then moves to pkg-b (package rename/move).
628
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('k-1', 'k1@test.local')])];
629
+ case 1:
630
+ // k-1 starts life declared by pkg-a, then moves to pkg-b (package rename/move).
631
+ _c.sent();
632
+ return [4 /*yield*/, bootAsSource('@test/pkg-b', [decl('k-1', 'k1@test.local')])];
633
+ case 2:
634
+ _c.sent();
635
+ // The later declaration re-owns the row in place (adoption keeps the id).
636
+ _a = expect;
637
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-1' })];
638
+ case 3:
639
+ // The later declaration re-owns the row in place (adoption keeps the id).
640
+ _a.apply(void 0, [(_c.sent()).sourcePackage]).toBe('@test/pkg-b');
641
+ // pkg-a's next build no longer carries k-1 — but the row now belongs to pkg-b.
642
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('a-x', 'ax@test.local')])];
643
+ case 4:
644
+ // pkg-a's next build no longer carries k-1 — but the row now belongs to pkg-b.
645
+ _c.sent();
646
+ _b = expect;
647
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-1' })];
648
+ case 5:
649
+ _b.apply(void 0, [_c.sent()]).toBeDefined();
650
+ return [2 /*return*/];
651
+ }
652
+ });
653
+ }); });
654
+ test('legacy rows (source_package NULL) are never pruned; still-declared ones are adopted and stamped', function () { return __awaiter(void 0, void 0, void 0, function () {
655
+ var db, legacyDeclared, legacyForeign, _a, _b;
656
+ return __generator(this, function (_c) {
657
+ switch (_c.label) {
658
+ case 0:
659
+ db = (0, db_1.getDbAsSystem)();
660
+ return [4 /*yield*/, db.insert(defaultPolicyTable, {
661
+ email: 'l1@test.local',
662
+ isLoadedFromSource: true,
663
+ })];
664
+ case 1:
665
+ legacyDeclared = _c.sent();
666
+ return [4 /*yield*/, db.insert(defaultPolicyTable, {
667
+ email: 'l2@test.local',
668
+ isLoadedFromSource: true,
669
+ })];
670
+ case 2:
671
+ legacyForeign = _c.sent();
672
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl(legacyDeclared.id, 'l1@test.local')])];
673
+ case 3:
674
+ _c.sent();
675
+ // The declared legacy row converges: adopted by its declaring package and stamped.
676
+ _a = expect;
677
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: legacyDeclared.id })];
678
+ case 4:
679
+ // The declared legacy row converges: adopted by its declaring package and stamped.
680
+ _a.apply(void 0, [(_c.sent()).sourcePackage]).toBe('@test/pkg-a');
681
+ // The undeclared legacy row is out of prune scope — it may belong to a build that has
682
+ // not rebooted onto the stamped world yet; its owner's next boot will claim it.
683
+ _b = expect;
684
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: legacyForeign.id })];
685
+ case 5:
686
+ // The undeclared legacy row is out of prune scope — it may belong to a build that has
687
+ // not rebooted onto the stamped world yet; its owner's next boot will claim it.
688
+ _b.apply(void 0, [_c.sent()]).toBeDefined();
689
+ return [2 /*return*/];
690
+ }
691
+ });
692
+ }); });
693
+ test('a declaration moving between packages WITHIN one build is re-owned in place — never deleted and re-inserted', function () { return __awaiter(void 0, void 0, void 0, function () {
694
+ var db, before, after, _a;
695
+ return __generator(this, function (_b) {
696
+ switch (_b.label) {
697
+ case 0:
698
+ db = (0, db_1.getDbAsSystem)();
699
+ // k-1 and a-other both start under pkg-a.
700
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('k-1', 'k1@test.local'), decl('a-other', 'aother@test.local')])];
701
+ case 1:
702
+ // k-1 and a-other both start under pkg-a.
703
+ _b.sent();
704
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-1' })];
705
+ case 2:
706
+ before = _b.sent();
707
+ // The next build moves k-1 to pkg-b while pkg-a still declares a-other — ONE boot.
708
+ // pkg-a's prune scope must not treat k-1 as removed (it is declared elsewhere in the
709
+ // build): the row survives in place, then the stamp leg re-owns it.
710
+ return [4 /*yield*/, bootBuild([
711
+ __assign({ source: '@test/pkg-a' }, decl('a-other', 'aother@test.local')),
712
+ __assign({ source: '@test/pkg-b' }, decl('k-1', 'k1@test.local')),
713
+ ])];
714
+ case 3:
715
+ // The next build moves k-1 to pkg-b while pkg-a still declares a-other — ONE boot.
716
+ // pkg-a's prune scope must not treat k-1 as removed (it is declared elsewhere in the
717
+ // build): the row survives in place, then the stamp leg re-owns it.
718
+ _b.sent();
719
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-1' })];
720
+ case 4:
721
+ after = _b.sent();
722
+ expect(after).toBeDefined();
723
+ // Row continuity is the outcome: created unchanged means no delete+re-insert cycle
724
+ // (a re-insert restamps created), so watchers saw no delete and references held.
725
+ expect(after.created.valueOf()).toBe(before.created.valueOf());
726
+ expect(after.sourcePackage).toBe('@test/pkg-b');
727
+ _a = expect;
728
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-other' })];
729
+ case 5:
730
+ _a.apply(void 0, [_b.sent()]).toBeDefined();
731
+ return [2 /*return*/];
732
+ }
733
+ });
734
+ }); });
735
+ test('a within-build package move on an update-policy table never passes through the removed patch', function () { return __awaiter(void 0, void 0, void 0, function () {
736
+ var db, moved, deactivations;
737
+ return __generator(this, function (_a) {
738
+ switch (_a.label) {
739
+ case 0:
740
+ db = (0, db_1.getDbAsSystem)();
741
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [
742
+ { table: machineTable, record: { id: 'm-move', email: 'mmove@test.local', status: 'active' } },
743
+ { table: machineTable, record: { id: 'm-stay', email: 'mstay@test.local', status: 'active' } },
744
+ ])];
745
+ case 1:
746
+ _a.sent();
747
+ // Runtime-owned state that must survive the move untouched (the credential stand-in).
748
+ return [4 /*yield*/, db.update(machineTable, { id: 'm-move', runtimeNote: 'provisioned' })];
749
+ case 2:
750
+ // Runtime-owned state that must survive the move untouched (the credential stand-in).
751
+ _a.sent();
752
+ RecordingMachineAccountWatcher.updates = [];
753
+ return [4 /*yield*/, bootBuild([
754
+ {
755
+ source: '@test/pkg-a',
756
+ table: machineTable,
757
+ record: { id: 'm-stay', email: 'mstay@test.local', status: 'active' },
758
+ },
759
+ {
760
+ source: '@test/pkg-b',
761
+ table: machineTable,
762
+ record: { id: 'm-move', email: 'mmove@test.local', status: 'active' },
763
+ },
764
+ ])];
765
+ case 3:
766
+ _a.sent();
767
+ return [4 /*yield*/, db.get(machineTable, { id: 'm-move' })];
768
+ case 4:
769
+ moved = _a.sent();
770
+ expect(moved).toMatchObject({ status: 'active', runtimeNote: 'provisioned', sourcePackage: '@test/pkg-b' });
771
+ deactivations = RecordingMachineAccountWatcher.updates.filter(function (u) { return u.status === 'deactivated'; });
772
+ expect(deactivations).toHaveLength(0);
773
+ return [2 /*return*/];
774
+ }
775
+ });
776
+ }); });
777
+ test('cross-source boots leave a natural-key-adopted row in place — adopted id preserved', function () { return __awaiter(void 0, void 0, void 0, function () {
778
+ var db, handMade, _a, adopted, foreignTouches;
779
+ return __generator(this, function (_b) {
780
+ switch (_b.label) {
781
+ case 0:
782
+ db = (0, db_1.getDbAsSystem)();
783
+ return [4 /*yield*/, db.insert(machineTable, { email: 'bridge2@test.local', runtimeNote: 'cred' })];
784
+ case 1:
785
+ handMade = _b.sent();
786
+ return [4 /*yield*/, bootAsSource('@test/pkg-b', [
787
+ { table: machineTable, record: { id: 'declared-bridge2', email: 'bridge2@test.local', status: 'active' } },
788
+ ])];
789
+ case 2:
790
+ _b.sent();
791
+ _a = expect;
792
+ return [4 /*yield*/, db.get(machineTable, { email: 'bridge2@test.local' })];
793
+ case 3:
794
+ _a.apply(void 0, [(_b.sent()).id]).toBe(handMade.id);
795
+ // A DIFFERENT source booting (with its own machine declaration) must not prune, flag,
796
+ // or disturb the adopted row — same id, same runtime state, still active.
797
+ RecordingMachineAccountWatcher.updates = [];
798
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [
799
+ { table: machineTable, record: { id: 'm-a3', email: 'ma3@test.local', status: 'active' } },
800
+ ])];
801
+ case 4:
802
+ _b.sent();
803
+ return [4 /*yield*/, db.get(machineTable, { email: 'bridge2@test.local' })];
804
+ case 5:
805
+ adopted = _b.sent();
806
+ expect(adopted).toMatchObject({ id: handMade.id, status: 'active', runtimeNote: 'cred' });
807
+ foreignTouches = RecordingMachineAccountWatcher.updates.filter(function (u) { return u.id === handMade.id; });
808
+ expect(foreignTouches).toHaveLength(0);
809
+ return [2 /*return*/];
810
+ }
811
+ });
812
+ }); });
813
+ test('legacy rows (source_package NULL) are excluded from the update-policy leg too', function () { return __awaiter(void 0, void 0, void 0, function () {
814
+ var db, legacy, _a, legacyTouches;
815
+ return __generator(this, function (_b) {
816
+ switch (_b.label) {
817
+ case 0:
818
+ db = (0, db_1.getDbAsSystem)();
819
+ return [4 /*yield*/, db.insert(machineTable, {
820
+ email: 'legacy@test.local',
821
+ status: 'active',
822
+ isLoadedFromSource: true,
823
+ })];
824
+ case 1:
825
+ legacy = _b.sent();
826
+ RecordingMachineAccountWatcher.updates = [];
827
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [
828
+ { table: machineTable, record: { id: 'm-a4', email: 'ma4@test.local', status: 'active' } },
829
+ ])];
830
+ case 2:
831
+ _b.sent();
832
+ // NULL-stamped rows are out of BOTH reconcile legs — not deleted (covered above) and
833
+ // not flagged: the legacy machine account stays active, unobserved by watchers.
834
+ _a = expect;
835
+ return [4 /*yield*/, db.get(machineTable, { id: legacy.id })];
836
+ case 3:
837
+ // NULL-stamped rows are out of BOTH reconcile legs — not deleted (covered above) and
838
+ // not flagged: the legacy machine account stays active, unobserved by watchers.
839
+ _a.apply(void 0, [(_b.sent()).status]).toBe('active');
840
+ legacyTouches = RecordingMachineAccountWatcher.updates.filter(function (u) { return u.id === legacy.id; });
841
+ expect(legacyTouches).toHaveLength(0);
842
+ return [2 /*return*/];
843
+ }
844
+ });
845
+ }); });
846
+ test('a package in the build that no longer declares anything for a table prunes its rows there — dropping the last declaration is a removal', function () { return __awaiter(void 0, void 0, void 0, function () {
847
+ var db, _a, _b, _c;
848
+ return __generator(this, function (_d) {
849
+ switch (_d.label) {
850
+ case 0:
851
+ db = (0, db_1.getDbAsSystem)();
852
+ // pkg-a declares a-1 on the default-policy table and m-1 on the machine table; pkg-b owns b-1.
853
+ return [4 /*yield*/, bootBuild([
854
+ __assign({ source: '@test/pkg-a' }, decl('a-1', 'a1@test.local')),
855
+ {
856
+ source: '@test/pkg-a',
857
+ table: machineTable,
858
+ record: { id: 'm-1', email: 'm1@test.local', status: 'active' },
859
+ },
860
+ __assign({ source: '@test/pkg-b' }, decl('b-1', 'b1@test.local')),
861
+ ])];
862
+ case 1:
863
+ // pkg-a declares a-1 on the default-policy table and m-1 on the machine table; pkg-b owns b-1.
864
+ _d.sent();
865
+ // pkg-a's next build keeps m-1 but drops its LAST default-policy declaration. The package is
866
+ // still in the build, so it is authoritative for its own rows on every table: a-1 is removed.
867
+ return [4 /*yield*/, bootBuild([
868
+ {
869
+ source: '@test/pkg-a',
870
+ table: machineTable,
871
+ record: { id: 'm-1', email: 'm1@test.local', status: 'active' },
872
+ },
873
+ ])];
874
+ case 2:
875
+ // pkg-a's next build keeps m-1 but drops its LAST default-policy declaration. The package is
876
+ // still in the build, so it is authoritative for its own rows on every table: a-1 is removed.
877
+ _d.sent();
878
+ _a = expect;
879
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-1' })];
880
+ case 3:
881
+ _a.apply(void 0, [_d.sent()]).toBeUndefined();
882
+ // pkg-b's row on that table is untouched, and pkg-a's still-declared machine row survives.
883
+ _b = expect;
884
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'b-1' })];
885
+ case 4:
886
+ // pkg-b's row on that table is untouched, and pkg-a's still-declared machine row survives.
887
+ _b.apply(void 0, [_d.sent()]).toBeDefined();
888
+ _c = expect;
889
+ return [4 /*yield*/, db.get(machineTable, { id: 'm-1' })];
890
+ case 5:
891
+ _c.apply(void 0, [(_d.sent()).status]).toBe('active');
892
+ return [2 /*return*/];
893
+ }
894
+ });
895
+ }); });
896
+ test('unowned legacy rows no declaration in the build claims are counted, never pruned', function () { return __awaiter(void 0, void 0, void 0, function () {
897
+ var db, unowned, summary, _a, claimed, _b;
898
+ return __generator(this, function (_c) {
899
+ switch (_c.label) {
900
+ case 0:
901
+ db = (0, db_1.getDbAsSystem)();
902
+ return [4 /*yield*/, db.insert(defaultPolicyTable, { email: 'u@test.local', isLoadedFromSource: true })];
903
+ case 1:
904
+ unowned = _c.sent();
905
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('a-1', 'a1@test.local')])];
906
+ case 2:
907
+ summary = _c.sent();
908
+ expect(summary[defaultPolicyTable.name].unowned).toBe(1);
909
+ _a = expect;
910
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: unowned.id })];
911
+ case 3:
912
+ _a.apply(void 0, [_c.sent()]).toBeDefined();
913
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [
914
+ decl('a-1', 'a1@test.local'),
915
+ decl(unowned.id, 'u@test.local'),
916
+ ])];
917
+ case 4:
918
+ claimed = _c.sent();
919
+ expect(claimed[defaultPolicyTable.name].unowned).toBe(0);
920
+ _b = expect;
921
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: unowned.id })];
922
+ case 5:
923
+ _b.apply(void 0, [(_c.sent()).sourcePackage]).toBe('@test/pkg-a');
924
+ return [2 /*return*/];
925
+ }
926
+ });
927
+ }); });
928
+ });
929
+ /**
930
+ * Version-scoped pruning WITHIN one package: two servers running different VERSIONS of the
931
+ * same package against one shared database (the actual brent-dev-2 incident — the main app
932
+ * at thought-common 3.29.0 and a feature worktree at 3.32.0, whose added thought types the
933
+ * older build deleted on every boot). Every row carries the declaring package's version;
934
+ * a boot never prunes (or flags) a row stamped by a NEWER version of the same package.
935
+ * Within the same or an older stamped version, the package stays authoritative:
936
+ * removals still land, and equal-version skew is last-writer-wins by design.
937
+ */
938
+ describe('version-scoped pruning (same package, different builds)', function () {
939
+ var decl = function (id, email) { return ({ table: defaultPolicyTable, record: { id: id, email: email } }); };
940
+ test('an older build of a package never prunes rows a newer build of the same package declared (the incident)', function () { return __awaiter(void 0, void 0, void 0, function () {
941
+ var db, _a, _b, _c, _d;
942
+ return __generator(this, function (_e) {
943
+ switch (_e.label) {
944
+ case 0:
945
+ db = (0, db_1.getDbAsSystem)();
946
+ // The newer build (e.g. thought-common 3.32.0) declares an added type k-2.
947
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('k-1', 'k1@test.local'), decl('k-2', 'k2@test.local')], '3.32.0')];
948
+ case 1:
949
+ // The newer build (e.g. thought-common 3.32.0) declares an added type k-2.
950
+ _e.sent();
951
+ // The older build (3.29.0) boots without k-2 — pre-fix this deleted k-2 every boot.
952
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('k-1', 'k1@test.local')], '3.29.0')];
953
+ case 2:
954
+ // The older build (3.29.0) boots without k-2 — pre-fix this deleted k-2 every boot.
955
+ _e.sent();
956
+ _a = expect;
957
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-2' })];
958
+ case 3:
959
+ _a.apply(void 0, [_e.sent()]).toBeDefined();
960
+ _b = expect;
961
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-1' })];
962
+ case 4:
963
+ _b.apply(void 0, [_e.sent()]).toBeDefined();
964
+ // The NEWER build remains authoritative for genuine removals: it drops k-2 for real.
965
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('k-1', 'k1@test.local')], '3.32.0')];
966
+ case 5:
967
+ // The NEWER build remains authoritative for genuine removals: it drops k-2 for real.
968
+ _e.sent();
969
+ _c = expect;
970
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-2' })];
971
+ case 6:
972
+ _c.apply(void 0, [_e.sent()]).toBeUndefined();
973
+ _d = expect;
974
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-1' })];
975
+ case 7:
976
+ _d.apply(void 0, [_e.sent()]).toBeDefined();
977
+ return [2 /*return*/];
978
+ }
979
+ });
980
+ }); });
981
+ test('an older build never writes over a row a newer build of the same package stamped — content and stamp survive', function () { return __awaiter(void 0, void 0, void 0, function () {
982
+ var db, k1, summary, row, _a;
983
+ return __generator(this, function (_b) {
984
+ switch (_b.label) {
985
+ case 0:
986
+ db = (0, db_1.getDbAsSystem)();
987
+ k1 = function (displayName) { return ({
988
+ table: defaultPolicyTable,
989
+ record: { id: 'k-1', email: 'k1@test.local', displayName: displayName },
990
+ }); };
991
+ // The newer build (3.32.0) declares k-1 with its current definition.
992
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [k1('newer')], '3.32.0')];
993
+ case 1:
994
+ // The newer build (3.32.0) declares k-1 with its current definition.
995
+ _b.sent();
996
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [k1('older')], '3.29.0')];
997
+ case 2:
998
+ summary = _b.sent();
999
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-1' })];
1000
+ case 3:
1001
+ row = _b.sent();
1002
+ expect(row.displayName).toBe('newer');
1003
+ expect(row.sourcePackageVersion).toBe('3.32.0');
1004
+ expect(summary[defaultPolicyTable.name].skippedNewer).toBe(1);
1005
+ expect(summary[defaultPolicyTable.name].updates).toBe(0);
1006
+ // The newer build remains authoritative over its own rows: its next definition lands.
1007
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [k1('newest')], '3.32.0')];
1008
+ case 4:
1009
+ // The newer build remains authoritative over its own rows: its next definition lands.
1010
+ _b.sent();
1011
+ _a = expect;
1012
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-1' })];
1013
+ case 5:
1014
+ _a.apply(void 0, [(_b.sent()).displayName]).toBe('newest');
1015
+ return [2 /*return*/];
1016
+ }
1017
+ });
1018
+ }); });
1019
+ test('version skew on an update-policy table: an older build does not flag newer rows', function () { return __awaiter(void 0, void 0, void 0, function () {
1020
+ var db, _a, deactivations;
1021
+ return __generator(this, function (_b) {
1022
+ switch (_b.label) {
1023
+ case 0:
1024
+ db = (0, db_1.getDbAsSystem)();
1025
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [
1026
+ { table: machineTable, record: { id: 'mv-1', email: 'mv1@test.local', status: 'active' } },
1027
+ { table: machineTable, record: { id: 'mv-2', email: 'mv2@test.local', status: 'active' } },
1028
+ ], '2.0.0')];
1029
+ case 1:
1030
+ _b.sent();
1031
+ RecordingMachineAccountWatcher.updates = [];
1032
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [{ table: machineTable, record: { id: 'mv-1', email: 'mv1@test.local', status: 'active' } }], '1.0.0')];
1033
+ case 2:
1034
+ _b.sent();
1035
+ // The newer build's machine account is not deactivated by the older build's boot.
1036
+ _a = expect;
1037
+ return [4 /*yield*/, db.get(machineTable, { id: 'mv-2' })];
1038
+ case 3:
1039
+ // The newer build's machine account is not deactivated by the older build's boot.
1040
+ _a.apply(void 0, [(_b.sent()).status]).toBe('active');
1041
+ deactivations = RecordingMachineAccountWatcher.updates.filter(function (u) { return u.status === 'deactivated'; });
1042
+ expect(deactivations).toHaveLength(0);
1043
+ return [2 /*return*/];
1044
+ }
1045
+ });
1046
+ }); });
1047
+ test('equal versions with differing sets remain last-writer-wins — the deliberate residual', function () { return __awaiter(void 0, void 0, void 0, function () {
1048
+ var db, _a, _b;
1049
+ return __generator(this, function (_c) {
1050
+ switch (_c.label) {
1051
+ case 0:
1052
+ db = (0, db_1.getDbAsSystem)();
1053
+ // Two builds at the SAME version (uncommitted local skew) cannot be ordered; the
1054
+ // package stays authoritative within its own version: the later boot's set wins.
1055
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('e-1', 'e1@test.local'), decl('e-2', 'e2@test.local')], '1.0.0')];
1056
+ case 1:
1057
+ // Two builds at the SAME version (uncommitted local skew) cannot be ordered; the
1058
+ // package stays authoritative within its own version: the later boot's set wins.
1059
+ _c.sent();
1060
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('e-1', 'e1@test.local')], '1.0.0')];
1061
+ case 2:
1062
+ _c.sent();
1063
+ _a = expect;
1064
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'e-2' })];
1065
+ case 3:
1066
+ _a.apply(void 0, [_c.sent()]).toBeUndefined();
1067
+ _b = expect;
1068
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'e-1' })];
1069
+ case 4:
1070
+ _b.apply(void 0, [_c.sent()]).toBeDefined();
1071
+ return [2 /*return*/];
1072
+ }
1073
+ });
1074
+ }); });
1075
+ test('a build without a resolvable version never prunes version-stamped rows; unversioned stamps stay last-writer-wins', function () { return __awaiter(void 0, void 0, void 0, function () {
1076
+ var db, _a, _b, _c, _d;
1077
+ return __generator(this, function (_e) {
1078
+ switch (_e.label) {
1079
+ case 0:
1080
+ db = (0, db_1.getDbAsSystem)();
1081
+ // Unversioned stamps carry no ordering: prunable by any build of the package —
1082
+ // versionless-to-versionless keeps the pre-version last-writer-wins behavior.
1083
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('n-1', 'n1@test.local'), decl('n-2', 'n2@test.local')])];
1084
+ case 1:
1085
+ // Unversioned stamps carry no ordering: prunable by any build of the package —
1086
+ // versionless-to-versionless keeps the pre-version last-writer-wins behavior.
1087
+ _e.sent();
1088
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('n-1', 'n1@test.local')])];
1089
+ case 2:
1090
+ _e.sent();
1091
+ _a = expect;
1092
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'n-2' })];
1093
+ case 3:
1094
+ _a.apply(void 0, [_e.sent()]).toBeUndefined();
1095
+ // Versioned rows survive an unorderable (versionless) build's boot: it cannot place
1096
+ // itself before or after the stamp, so it must not delete what might be newer.
1097
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('u-1', 'u1@test.local'), decl('u-2', 'u2@test.local')], '1.0.0')];
1098
+ case 4:
1099
+ // Versioned rows survive an unorderable (versionless) build's boot: it cannot place
1100
+ // itself before or after the stamp, so it must not delete what might be newer.
1101
+ _e.sent();
1102
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('u-1', 'u1@test.local')])];
1103
+ case 5:
1104
+ _e.sent();
1105
+ _b = expect;
1106
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'u-2' })];
1107
+ case 6:
1108
+ _b.apply(void 0, [_e.sent()]).toBeDefined();
1109
+ // A versioned build prunes older-or-equal stamps as usual (1.0.0 <= 1.0.1).
1110
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('u-1', 'u1@test.local')], '1.0.1')];
1111
+ case 7:
1112
+ // A versioned build prunes older-or-equal stamps as usual (1.0.0 <= 1.0.1).
1113
+ _e.sent();
1114
+ _c = expect;
1115
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'u-2' })];
1116
+ case 8:
1117
+ _c.apply(void 0, [_e.sent()]).toBeUndefined();
1118
+ _d = expect;
1119
+ return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'u-1' })];
1120
+ case 9:
1121
+ _d.apply(void 0, [_e.sent()]).toBeDefined();
1122
+ return [2 /*return*/];
1123
+ }
1124
+ });
1125
+ }); });
1126
+ test("the version resolver reads the declaring package's real package.json", function () { return __awaiter(void 0, void 0, void 0, function () {
1127
+ var internals;
1128
+ return __generator(this, function (_a) {
1129
+ internals = new SourceRecordLoader_1.SourceRecordLoader();
1130
+ // A real dependency of this build resolves to its actual version (entry resolution +
1131
+ // walk-up works for exports-mapped packages, where `<pkg>/package.json` is not requireable).
1132
+ expect(internals.resolveSourceVersion('@proteinjs/db-query')).toMatch(/^\d+\.\d+\.\d+/);
1133
+ // Resolution failure degrades to versionless, never throws.
1134
+ expect(internals.resolveSourceVersion('@test/no-such-package')).toBeUndefined();
1135
+ return [2 /*return*/];
1136
+ });
1137
+ }); });
1138
+ });
398
1139
  test('a natural key without a unique guarantee fails boot loudly', function () { return __awaiter(void 0, void 0, void 0, function () {
399
1140
  var MisdeclaredNaturalKeyTable;
400
1141
  return __generator(this, function (_a) {
@@ -477,6 +1218,41 @@ var sourceRecordSyncTests = function (driver, transactionContextFactory, dropTab
477
1218
  }
478
1219
  });
479
1220
  }); });
1221
+ test('a new source-record table inherits the ownership stamp columns from SourceRecord — never declared per table', function () { return __awaiter(void 0, void 0, void 0, function () {
1222
+ var table, row;
1223
+ var _a, _b;
1224
+ return __generator(this, function (_c) {
1225
+ switch (_c.label) {
1226
+ case 0:
1227
+ table = sourceRecordSyncTestTables_1.sourceRecordSyncTestTables.InheritedStamp;
1228
+ // Type layer: the minted table carries the stamp columns, at their physical names,
1229
+ // without declaring them.
1230
+ expect((_a = table.columns.sourcePackage) === null || _a === void 0 ? void 0 : _a.name).toBe('source_package');
1231
+ expect((_b = table.columns.sourcePackageVersion) === null || _b === void 0 ? void 0 : _b.name).toBe('source_package_version');
1232
+ expect((0, db_1.isSourceRecordTable)(table)).toBe(true);
1233
+ // Schema layer: the harness minted the physical table through TableManager, which derives
1234
+ // DDL from the type — so the stamp columns must have been emitted without the fixture
1235
+ // declaring them. One boot of the sync stamps a row through them; the write itself fails
1236
+ // if the physical columns were not emitted.
1237
+ return [4 /*yield*/, bootAsSource('@test/pkg-a', [{ table: table, record: { id: 's-1', email: 's1@test.local' } }], '1.0.0')];
1238
+ case 1:
1239
+ // Schema layer: the harness minted the physical table through TableManager, which derives
1240
+ // DDL from the type — so the stamp columns must have been emitted without the fixture
1241
+ // declaring them. One boot of the sync stamps a row through them; the write itself fails
1242
+ // if the physical columns were not emitted.
1243
+ _c.sent();
1244
+ return [4 /*yield*/, (0, db_1.getDbAsSystem)().get(table, { id: 's-1' })];
1245
+ case 2:
1246
+ row = _c.sent();
1247
+ expect(row).toMatchObject({
1248
+ sourcePackage: '@test/pkg-a',
1249
+ sourcePackageVersion: '1.0.0',
1250
+ isLoadedFromSource: true,
1251
+ });
1252
+ return [2 /*return*/];
1253
+ }
1254
+ });
1255
+ }); });
480
1256
  };
481
1257
  };
482
1258
  exports.sourceRecordSyncTests = sourceRecordSyncTests;