@substrat-run/contract-tests 0.142.0 → 0.144.0

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 (73) hide show
  1. package/dist/attachment-refusal-suite.d.ts +3 -0
  2. package/dist/attachment-refusal-suite.d.ts.map +1 -0
  3. package/dist/attachment-refusal-suite.js +128 -0
  4. package/dist/attachment-refusal-suite.js.map +1 -0
  5. package/dist/bind-module.d.ts +199 -0
  6. package/dist/bind-module.d.ts.map +1 -0
  7. package/dist/bind-module.js +104 -0
  8. package/dist/bind-module.js.map +1 -0
  9. package/dist/binding-invite-suite.d.ts +12 -0
  10. package/dist/binding-invite-suite.d.ts.map +1 -0
  11. package/dist/binding-invite-suite.js +507 -0
  12. package/dist/binding-invite-suite.js.map +1 -0
  13. package/dist/caused-by-module.d.ts.map +1 -1
  14. package/dist/caused-by-module.js +3 -2
  15. package/dist/caused-by-module.js.map +1 -1
  16. package/dist/derived-module.d.ts +690 -0
  17. package/dist/derived-module.d.ts.map +1 -0
  18. package/dist/derived-module.js +320 -0
  19. package/dist/derived-module.js.map +1 -0
  20. package/dist/derived-suite.d.ts +3 -0
  21. package/dist/derived-suite.d.ts.map +1 -0
  22. package/dist/derived-suite.js +481 -0
  23. package/dist/derived-suite.js.map +1 -0
  24. package/dist/entity-state-module.d.ts.map +1 -1
  25. package/dist/entity-state-module.js +12 -6
  26. package/dist/entity-state-module.js.map +1 -1
  27. package/dist/entity-trash-module.d.ts.map +1 -1
  28. package/dist/entity-trash-module.js +4 -4
  29. package/dist/entity-trash-module.js.map +1 -1
  30. package/dist/entity-trash-suite.d.ts.map +1 -1
  31. package/dist/entity-trash-suite.js +17 -8
  32. package/dist/entity-trash-suite.js.map +1 -1
  33. package/dist/erasure-module.d.ts.map +1 -1
  34. package/dist/erasure-module.js +3 -2
  35. package/dist/erasure-module.js.map +1 -1
  36. package/dist/index.d.ts +8 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +8 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/input-parse-suite.d.ts.map +1 -1
  41. package/dist/input-parse-suite.js +33 -2
  42. package/dist/input-parse-suite.js.map +1 -1
  43. package/dist/membership-module.d.ts.map +1 -1
  44. package/dist/membership-module.js +3 -2
  45. package/dist/membership-module.js.map +1 -1
  46. package/dist/migration-comments.d.ts.map +1 -1
  47. package/dist/migration-comments.js +3 -2
  48. package/dist/migration-comments.js.map +1 -1
  49. package/dist/modules.d.ts.map +1 -1
  50. package/dist/modules.js +78 -68
  51. package/dist/modules.js.map +1 -1
  52. package/dist/permission-suite.d.ts.map +1 -1
  53. package/dist/permission-suite.js +28 -0
  54. package/dist/permission-suite.js.map +1 -1
  55. package/dist/scope-host-suite.d.ts.map +1 -1
  56. package/dist/scope-host-suite.js +151 -31
  57. package/dist/scope-host-suite.js.map +1 -1
  58. package/dist/shape-backfill-ties.d.ts +29 -0
  59. package/dist/shape-backfill-ties.d.ts.map +1 -0
  60. package/dist/shape-backfill-ties.js +113 -0
  61. package/dist/shape-backfill-ties.js.map +1 -0
  62. package/dist/shape-reconcile-plan.d.ts +37 -0
  63. package/dist/shape-reconcile-plan.d.ts.map +1 -0
  64. package/dist/shape-reconcile-plan.js +143 -0
  65. package/dist/shape-reconcile-plan.js.map +1 -0
  66. package/dist/test-operations.d.ts +25 -0
  67. package/dist/test-operations.d.ts.map +1 -0
  68. package/dist/test-operations.js +22 -0
  69. package/dist/test-operations.js.map +1 -0
  70. package/dist/vertical-events-suite.d.ts.map +1 -1
  71. package/dist/vertical-events-suite.js +72 -7
  72. package/dist/vertical-events-suite.js.map +1 -1
  73. package/package.json +3 -3
package/dist/modules.js CHANGED
@@ -11,9 +11,12 @@ import { dataSubjectId, moduleManifest, operationInputsOf, operationConcurrencyO
11
11
  import { membershipFixtureMod } from './membership-module.js';
12
12
  import { rebuildMod, stateMod } from './entity-state-module.js';
13
13
  import { trashMod } from './entity-trash-module.js';
14
+ import { derivedMod } from './derived-module.js';
14
15
  import { erasureMod, erasureOtherMod } from './erasure-module.js';
16
+ import { bindMod } from './bind-module.js';
15
17
  import { commentedDdlMod } from './migration-comments.js';
16
18
  import { assertAllowed, readAttachmentText, readHistory, readTimeline, readScopeTimeline, readScopeHistory, ulid, } from '@substrat-run/kernel';
19
+ import { testOperations } from './test-operations.js';
17
20
  /**
18
21
  * #1744: the machine `test/move` walks, in the form `model.json` emits — what the suite
19
22
  * hands `lifecycleFlow`. #1745: also the one `test/refuse` asserts against, where
@@ -129,7 +132,7 @@ export const freshnessModManifest = moduleManifest.parse({
129
132
  export const freshnessMod = {
130
133
  manifest: freshnessModManifest,
131
134
  migrations: [],
132
- operations: {},
135
+ ...testOperations({}),
133
136
  };
134
137
  /**
135
138
  * #1577: the module a resumable RUN acts through.
@@ -163,7 +166,7 @@ export const jobsMod = {
163
166
  migrations: [
164
167
  { version: '0001-init', sql: 'CREATE TABLE job_items (item TEXT NOT NULL)' },
165
168
  ],
166
- operations: {
169
+ ...testOperations({
167
170
  // Deliberately NOT idempotent and NOT unique-keyed: a repeated step shows up as
168
171
  // a DUPLICATE ROW rather than being silently absorbed, which is the only way the
169
172
  // resume assertion can fail loudly when the memo stops working.
@@ -174,7 +177,7 @@ export const jobsMod = {
174
177
  'jobs/items': ((ctx) => ctx.sql
175
178
  .query('SELECT item FROM job_items ORDER BY rowid')
176
179
  .map((r) => r.item)),
177
- },
180
+ }),
178
181
  };
179
182
  export const flowModManifest = moduleManifest.parse({
180
183
  id: '@test/flow',
@@ -1075,7 +1078,7 @@ export const testMod = {
1075
1078
  `,
1076
1079
  },
1077
1080
  ],
1078
- operations: {
1081
+ ...testOperations({
1079
1082
  'testmod/add': addItem,
1080
1083
  'testmod/link-again': linkItemAgain,
1081
1084
  'testmod/link-undeclared': linkUndeclared,
@@ -1104,14 +1107,14 @@ export const testMod = {
1104
1107
  'testmod/project-journal': projectJournal,
1105
1108
  'testmod/read-items': readItems,
1106
1109
  'testmod/read-notes': readNotes,
1107
- },
1110
+ }),
1108
1111
  };
1109
1112
  export const atomicMod = {
1110
1113
  manifest: atomicModManifest,
1111
1114
  migrations: [
1112
1115
  { version: '0001-init', sql: 'CREATE TABLE atomic_rows (id TEXT PRIMARY KEY, tag TEXT NOT NULL)' },
1113
1116
  ],
1114
- operations: {
1117
+ ...testOperations({
1115
1118
  'atomic/rollback': atomicRollback,
1116
1119
  'atomic/success': atomicSuccess,
1117
1120
  'atomic/stacked': atomicStacked,
@@ -1123,14 +1126,14 @@ export const atomicMod = {
1123
1126
  'atomic/read-outbox': atomicReadOutbox,
1124
1127
  'atomic/read-tuples': atomicReadTuples,
1125
1128
  'atomic/read-intents': atomicReadIntents,
1126
- },
1129
+ }),
1127
1130
  };
1128
1131
  export const scheduleMod = {
1129
1132
  manifest: scheduleModManifest,
1130
1133
  migrations: [
1131
1134
  { version: '0001-init', sql: 'CREATE TABLE sched_ticks (n INTEGER NOT NULL)' },
1132
1135
  ],
1133
- operations: {
1136
+ ...testOperations({
1134
1137
  // The scheduled operation: check the granted permission, then emit + record a
1135
1138
  // tick. Under a system caller its emitted event's actor must be { system: … }.
1136
1139
  'sched/tick': (async (ctx) => {
@@ -1177,7 +1180,7 @@ export const scheduleMod = {
1177
1180
  // on `sched/schedule-state` — proving a FAILED run carries the identical id, not
1178
1181
  // just a fired one.
1179
1182
  'sched/read-denials': readDenialsOp,
1180
- },
1183
+ }),
1181
1184
  };
1182
1185
  /**
1183
1186
  * #1525: the failure twin of `scheduleMod` itself — a SEPARATE module so registering
@@ -1207,11 +1210,11 @@ export const deniedScheduleModManifest = moduleManifest.parse({
1207
1210
  });
1208
1211
  export const deniedScheduleMod = {
1209
1212
  manifest: deniedScheduleModManifest,
1210
- operations: {
1213
+ ...testOperations({
1211
1214
  'sched-denied/tick': (async (ctx) => {
1212
1215
  assertAllowed(await ctx.check('sched-denied:admin'));
1213
1216
  }),
1214
- },
1217
+ }),
1215
1218
  };
1216
1219
  // -- #1654: a composed engine's own schedule, on a tenant holding only the vertical's key --
1217
1220
  /**
@@ -1246,7 +1249,7 @@ export function countComposedSweeps(ctx) {
1246
1249
  export const composedEngineMod = {
1247
1250
  manifest: composedEngineModManifest,
1248
1251
  migrations: [{ version: '0001-init', sql: 'CREATE TABLE composed_engine_sweeps (n INTEGER NOT NULL)' }],
1249
- operations: {
1252
+ ...testOperations({
1250
1253
  'composed-engine/sweep': (async (ctx) => {
1251
1254
  assertAllowed(await ctx.check('composed-engine:sweep'));
1252
1255
  ctx.sql.exec('INSERT INTO composed_engine_sweeps (n) VALUES (1)');
@@ -1262,7 +1265,7 @@ export const composedEngineMod = {
1262
1265
  assertAllowed(await ctx.check('composed-engine:sweep'));
1263
1266
  ctx.sql.exec('INSERT INTO composed_engine_sweeps (n) VALUES (1)');
1264
1267
  }),
1265
- },
1268
+ }),
1266
1269
  };
1267
1270
  /**
1268
1271
  * #1654's VERTICAL: holds the key a standard install grants (`composer`), and reads the
@@ -1282,9 +1285,9 @@ export const composerModManifest = moduleManifest.parse({
1282
1285
  });
1283
1286
  export const composerMod = {
1284
1287
  manifest: composerModManifest,
1285
- operations: {
1288
+ ...testOperations({
1286
1289
  'composer/sweeps': ((ctx) => countComposedSweeps(ctx)),
1287
- },
1290
+ }),
1288
1291
  };
1289
1292
  export const flowMod = {
1290
1293
  manifest: flowModManifest,
@@ -1294,7 +1297,7 @@ export const flowMod = {
1294
1297
  sql: 'CREATE TABLE flow_log (event_id TEXT PRIMARY KEY, type TEXT NOT NULL)',
1295
1298
  },
1296
1299
  ],
1297
- operations: {
1300
+ ...testOperations({
1298
1301
  'flow/produce': ((ctx) => {
1299
1302
  ctx.emit({
1300
1303
  type: 'flow.step1',
@@ -1317,7 +1320,7 @@ export const flowMod = {
1317
1320
  WHERE type IN ('flow.step1', 'flow.step2') ORDER BY id`)),
1318
1321
  'flow/causes': ((ctx) => ctx.sql.query(`SELECT id, type, operation, caused_by FROM _substrat_outbox
1319
1322
  WHERE type IN ('flow.step1', 'flow.step2') ORDER BY id`)),
1320
- },
1323
+ }),
1321
1324
  consumers: {
1322
1325
  'flow.step1': flowStep1Consumer,
1323
1326
  'flow.step2': flowStep2Consumer,
@@ -1326,7 +1329,7 @@ export const flowMod = {
1326
1329
  export const guardedMod = {
1327
1330
  manifest: guardedModManifest,
1328
1331
  migrations: [{ version: '0001-init', sql: 'CREATE TABLE guarded_t (v TEXT NOT NULL)' }],
1329
- operations: {
1332
+ ...testOperations({
1330
1333
  'guarded/act': ((ctx, input) => {
1331
1334
  ctx.sql.exec('INSERT INTO guarded_t (v) VALUES (?)', [input?.flag ?? 'none']);
1332
1335
  ctx.emit({
@@ -1344,7 +1347,7 @@ export const guardedMod = {
1344
1347
  'guarded/rows': ((ctx) => ctx.sql.query('SELECT v FROM guarded_t').map((r) => r.v)),
1345
1348
  'guarded/events': ((ctx) => ctx.sql.query('SELECT id FROM _substrat_outbox WHERE type = ?', ['guarded.acted'])
1346
1349
  .length),
1347
- },
1350
+ }),
1348
1351
  };
1349
1352
  export const gateMod = {
1350
1353
  manifest: gateModManifest,
@@ -1374,31 +1377,31 @@ export const gateMod = {
1374
1377
  export const withdrawEarlyMod = { manifest: withdrawEarlyManifest };
1375
1378
  export const victimMod = {
1376
1379
  manifest: victimModManifest,
1377
- operations: {
1380
+ ...testOperations({
1378
1381
  'victim/a': (() => 'a'),
1379
1382
  'victim/b': (() => 'b'),
1380
1383
  'victim/c': (() => 'c'),
1381
- },
1384
+ }),
1382
1385
  };
1383
1386
  export const withdrawLateMod = { manifest: withdrawLateManifest };
1384
1387
  export const lateMod = {
1385
1388
  manifest: lateModManifest,
1386
1389
  migrations: [{ version: '0001-init', sql: 'CREATE TABLE late_t (id TEXT PRIMARY KEY)' }],
1387
- operations: {
1390
+ ...testOperations({
1388
1391
  'late/check': ((ctx) => ctx.sql.query(`SELECT name FROM sqlite_master WHERE name = 'late_t'`)
1389
1392
  .length),
1390
- },
1393
+ }),
1391
1394
  };
1392
1395
  export const billedMod = {
1393
1396
  manifest: billedModManifest,
1394
- operations: {
1397
+ ...testOperations({
1395
1398
  'billed/act': (() => 'ran'),
1396
1399
  // #304: read the request-time entitlement view. Gated on 'billed' like every op in this
1397
1400
  // module, so the tenant holds 'billed' when it runs; the KEY read is the operation input,
1398
1401
  // letting a test read a held key, an absent key (→ null), or an expired one (→ null).
1399
1402
  'billed/read-entitlement': (async (ctx, key) => ctx.entitlement(key)),
1400
1403
  'billed/list-entitlements': (async (ctx) => ctx.entitlements()),
1401
- },
1404
+ }),
1402
1405
  };
1403
1406
  export const impersonationEchoMod = {
1404
1407
  manifest: impersonationEchoManifest,
@@ -1408,9 +1411,9 @@ export const impersonationEchoMod = {
1408
1411
  sql: 'CREATE TABLE imp_echo (event_id TEXT PRIMARY KEY, impersonation TEXT)',
1409
1412
  },
1410
1413
  ],
1411
- operations: {
1414
+ ...testOperations({
1412
1415
  'imp-echo/seen': ((ctx) => ctx.sql.query('SELECT event_id, impersonation FROM imp_echo ORDER BY event_id')),
1413
- },
1416
+ }),
1414
1417
  consumers: {
1415
1418
  // The stamp as the CONSUMER received it, JSON-encoded so an absent one and a
1416
1419
  // null one stay distinguishable in the assertion.
@@ -1424,7 +1427,7 @@ export const impersonationEchoMod = {
1424
1427
  };
1425
1428
  export const permMod = {
1426
1429
  manifest: permModManifest,
1427
- operations: {
1430
+ ...testOperations({
1428
1431
  'perm/link': linkOp,
1429
1432
  'perm/relink': relinkOp,
1430
1433
  'perm/probe': probeOp,
@@ -1499,7 +1502,7 @@ export const permMod = {
1499
1502
  await ctx.revoke(principalId.parse(i.principal), permissionKey.parse(i.permission), i.entity);
1500
1503
  return { revoked: true };
1501
1504
  }),
1502
- },
1505
+ }),
1503
1506
  consumers: {
1504
1507
  'perm.check-requested': checkRequestedConsumer,
1505
1508
  },
@@ -1541,7 +1544,7 @@ export const connectorMod = {
1541
1544
  sql: 'CREATE TABLE connector_requests (id TEXT PRIMARY KEY, principal TEXT NOT NULL)',
1542
1545
  },
1543
1546
  ],
1544
- operations: {
1547
+ ...testOperations({
1545
1548
  'connector/request-member': ((ctx, input) => {
1546
1549
  ctx.sql.exec('INSERT INTO connector_requests (id, principal) VALUES (?, ?)', [
1547
1550
  input.principal,
@@ -1602,7 +1605,7 @@ export const connectorMod = {
1602
1605
  });
1603
1606
  }),
1604
1607
  'connector/requests': ((ctx) => ctx.sql.query('SELECT id FROM connector_requests')),
1605
- },
1608
+ }),
1606
1609
  };
1607
1610
  /**
1608
1611
  * The modules the scope-host suite's `beforeAll` registers, in the exact order
@@ -1670,7 +1673,6 @@ export const listDeclaredOps = {
1670
1673
  };
1671
1674
  export const listMod = {
1672
1675
  manifest: listModManifest,
1673
- operationInputs: operationInputsOf(listDeclaredOps),
1674
1676
  migrations: [
1675
1677
  {
1676
1678
  version: '0001-init',
@@ -1680,7 +1682,7 @@ export const listMod = {
1680
1682
  },
1681
1683
  { version: '0002-hold', sql: 'ALTER TABLE list_orders ADD COLUMN hold TEXT;' },
1682
1684
  ],
1683
- operations: {
1685
+ ...testOperations({
1684
1686
  'list/add': (async (ctx, input) => {
1685
1687
  const i = input;
1686
1688
  ctx.sql.exec('INSERT INTO list_orders (id, number, status, kind, hold) VALUES (?, ?, ?, ?, ?)', [
@@ -1748,7 +1750,7 @@ export const listMod = {
1748
1750
  ]);
1749
1751
  return ctx.page('listorder', { limit: 50 });
1750
1752
  }),
1751
- },
1753
+ }, { inputs: operationInputsOf(listDeclaredOps) }),
1752
1754
  };
1753
1755
  export const searchModManifest = moduleManifest.parse({
1754
1756
  id: '@test/search',
@@ -1775,7 +1777,7 @@ export const searchMod = {
1775
1777
  CREATE TABLE search_notes (id TEXT PRIMARY KEY, body TEXT NOT NULL);`,
1776
1778
  },
1777
1779
  ],
1778
- operations: {
1780
+ ...testOperations({
1779
1781
  'search/add': (async (ctx, input) => {
1780
1782
  const i = input;
1781
1783
  ctx.sql.exec('INSERT INTO search_customers (id, number, name) VALUES (?, ?, ?)', [
@@ -1816,7 +1818,7 @@ export const searchMod = {
1816
1818
  ]);
1817
1819
  return ctx.search('searchcustomer', i.term);
1818
1820
  }),
1819
- },
1821
+ }),
1820
1822
  };
1821
1823
  // -- #893: the declared input the HOST parses ---------------------------------
1822
1824
  export const parseModManifest = moduleManifest.parse({
@@ -1873,12 +1875,11 @@ const parseDeclaration = {
1873
1875
  };
1874
1876
  export const parseMod = {
1875
1877
  manifest: parseModManifest,
1876
- operations: {
1878
+ ...testOperations({
1877
1879
  'parse/echo': echo,
1878
1880
  'parse/paged': echo,
1879
1881
  'parse/bare': echo,
1880
- },
1881
- operationInputs: operationInputsOf(parseDeclaration),
1882
+ }, { inputs: operationInputsOf(parseDeclaration) }),
1882
1883
  };
1883
1884
  // -- #129: the precondition the HOST compares ---------------------------------
1884
1885
  export const concurrencyModManifest = moduleManifest.parse({
@@ -1980,16 +1981,17 @@ const concurrencyDeclaration = {
1980
1981
  };
1981
1982
  export const concurrencyMod = {
1982
1983
  manifest: concurrencyModManifest,
1983
- operations: {
1984
+ ...testOperations({
1984
1985
  'conc/update': concUpdate,
1985
1986
  'conc/read': concRead,
1986
1987
  'conc/silent': concSilent,
1987
1988
  'conc/keyless': concRead,
1988
1989
  'conc/forbidden': concForbidden,
1989
1990
  'conc/unguarded': concUnguarded,
1990
- },
1991
- operationInputs: operationInputsOf(concurrencyDeclaration),
1992
- operationConcurrency: operationConcurrencyOf(concurrencyDeclaration),
1991
+ }, {
1992
+ inputs: operationInputsOf(concurrencyDeclaration),
1993
+ concurrency: operationConcurrencyOf(concurrencyDeclaration),
1994
+ }),
1993
1995
  };
1994
1996
  // -- #116: request idempotency ------------------------------------------------
1995
1997
  export const idempotencyModManifest = moduleManifest.parse({
@@ -2080,17 +2082,18 @@ const idempotencyDeclaration = {
2080
2082
  };
2081
2083
  export const idempotencyMod = {
2082
2084
  manifest: idempotencyModManifest,
2083
- operations: {
2085
+ ...testOperations({
2084
2086
  'idem/create': idemCreate,
2085
2087
  'idem/create-guarded': idemCreate,
2086
2088
  'idem/runs': idemRuns,
2087
2089
  'idem/fails': idemFails,
2088
2090
  'idem/big': idemBig,
2089
2091
  'idem/secret': idemSecret,
2090
- },
2091
- operationInputs: operationInputsOf(idempotencyDeclaration),
2092
- operationConcurrency: operationConcurrencyOf(idempotencyDeclaration),
2093
- operationIdempotencyOptOuts: operationIdempotencyOptOutsOf(idempotencyDeclaration),
2092
+ }, {
2093
+ inputs: operationInputsOf(idempotencyDeclaration),
2094
+ concurrency: operationConcurrencyOf(idempotencyDeclaration),
2095
+ idempotencyOptOuts: operationIdempotencyOptOutsOf(idempotencyDeclaration),
2096
+ }),
2094
2097
  };
2095
2098
  // -- capabilities (#1672) ------------------------------------------------------
2096
2099
  /**
@@ -2146,7 +2149,7 @@ export const capMod = {
2146
2149
  migrations: [
2147
2150
  { version: '0001-init', sql: 'CREATE TABLE cap_notes (id TEXT PRIMARY KEY, body TEXT NOT NULL)' },
2148
2151
  ],
2149
- operations: {
2152
+ ...testOperations({
2150
2153
  'cap/link': linkOp,
2151
2154
  'cap/share': ((ctx, input) => ctx.capabilities.mint(input)),
2152
2155
  // The link a vertical would hand back — the secret spliced into a URL fragment, so the
@@ -2269,7 +2272,7 @@ export const capMod = {
2269
2272
  }
2270
2273
  return out;
2271
2274
  }),
2272
- },
2275
+ }),
2273
2276
  consumers: {
2274
2277
  // Records what the mint answered rather than letting the delivery fail, so the test
2275
2278
  // reads an outcome instead of inferring one from a dead letter.
@@ -2329,7 +2332,7 @@ export const peerMod = {
2329
2332
  migrations: [
2330
2333
  { version: '0001-init', sql: 'CREATE TABLE peer_notes (id TEXT PRIMARY KEY, body TEXT NOT NULL)' },
2331
2334
  ],
2332
- operations: {
2335
+ ...testOperations({
2333
2336
  'peer/note': (async (ctx, input) => {
2334
2337
  assertAllowed(await ctx.check(PEER_WRITE));
2335
2338
  const { id, body } = input;
@@ -2366,7 +2369,7 @@ export const peerMod = {
2366
2369
  'peer/outbox': ((ctx) => ctx.sql.query("SELECT actor, authorization, operation, entity_id FROM _substrat_outbox WHERE type = 'peer.noted' ORDER BY id")),
2367
2370
  'peer/denials': readDenialsOp,
2368
2371
  'peer/tuples': ((ctx) => ctx.sql.query("SELECT subject, relation, object, revoked_at FROM _substrat_tuples WHERE substr(subject, 1, 9) = 'vertical:' ORDER BY subject, relation")),
2369
- },
2372
+ }),
2370
2373
  };
2371
2374
  // -- #1746: what an invocation emitted ----------------------------------------
2372
2375
  export const emittedModManifest = moduleManifest.parse({
@@ -2404,7 +2407,7 @@ const touchAll = (ctx, ids) => {
2404
2407
  */
2405
2408
  export const emittedMod = {
2406
2409
  manifest: emittedModManifest,
2407
- operations: {
2410
+ ...testOperations({
2408
2411
  'emitted/touch': ((ctx, input) => {
2409
2412
  touchAll(ctx, input.ids);
2410
2413
  return { touched: input.ids.length };
@@ -2428,7 +2431,7 @@ export const emittedMod = {
2428
2431
  }
2429
2432
  return { ok: true };
2430
2433
  }),
2431
- },
2434
+ }),
2432
2435
  consumers: {
2433
2436
  'emitted.touched': ((ctx, event) => {
2434
2437
  ctx.emit({
@@ -2462,7 +2465,7 @@ export const loggedModManifest = moduleManifest.parse({
2462
2465
  */
2463
2466
  export const loggedMod = {
2464
2467
  manifest: loggedModManifest,
2465
- operations: {
2468
+ ...testOperations({
2466
2469
  'logged/act': ((ctx, input) => {
2467
2470
  // `tenantId` and `operation` in the fields are the caller's own values: they stay
2468
2471
  // fields and never replace what the host stamps.
@@ -2480,7 +2483,7 @@ export const loggedMod = {
2480
2483
  ctx.log.error('could not reach {ticketId}', { ticketId: input.ticketId });
2481
2484
  throw new Error('logged/fail: refused after logging');
2482
2485
  }),
2483
- },
2486
+ }),
2484
2487
  consumers: {
2485
2488
  'logged.acted': ((ctx, event) => {
2486
2489
  ctx.log.warn('saw {type}', { type: event.type });
@@ -2517,7 +2520,7 @@ export const ASYNC_LOG_SECRET_ENTITY = 'alice@example.com';
2517
2520
  */
2518
2521
  export const asyncLogMod = {
2519
2522
  manifest: asyncLogModManifest,
2520
- operations: {
2523
+ ...testOperations({
2521
2524
  'asynclog/act': ((ctx, input) => {
2522
2525
  for (const tag of input.tags) {
2523
2526
  ctx.emit({
@@ -2548,7 +2551,7 @@ export const asyncLogMod = {
2548
2551
  ctx.log.info('ticking, about to refuse');
2549
2552
  throw substratError('conflict', `tick refused: ${ASYNC_LOG_SECRET_TEXT}`);
2550
2553
  }),
2551
- },
2554
+ }),
2552
2555
  consumers: {
2553
2556
  'asynclog.acted': ((ctx, event) => {
2554
2557
  const { tag, fail } = event.payload;
@@ -2594,6 +2597,9 @@ export const contractTestModules = [
2594
2597
  // its tables or invokes a `state/*` operation.
2595
2598
  stateMod,
2596
2599
  trashMod,
2600
+ // #1773: the derived-handler suite's module. Inert for every other suite — nothing else reads
2601
+ // its tables or invokes a `derived/*` operation.
2602
+ derivedMod,
2597
2603
  // #2068: the subject-erasure suite's modules. Every scope in the kit carries them, so every
2598
2604
  // `shredSubject` in every suite runs their declared erasure and their hook — which reaches
2599
2605
  // only `er_*` tables nothing else writes, and misbehaves only for a subject the erasure suite
@@ -2601,6 +2607,10 @@ export const contractTestModules = [
2601
2607
  // module half is harmless where it holds nothing.
2602
2608
  erasureMod,
2603
2609
  erasureOtherMod,
2610
+ // #1686: the binding-invite suite's record type. Every scope carries it, so every erasure in
2611
+ // every suite also runs its capture and release — over no recorded binding unless the binding
2612
+ // suite reconciled one onto that scope, and a hook that touches only `bind_*` tables.
2613
+ bindMod,
2604
2614
  // #2068 r5: a module whose migrations comment their DDL and then drop the last column. Every
2605
2615
  // scope in the kit migrates it, so a regression fails loudly everywhere.
2606
2616
  commentedDdlMod,
@@ -2764,7 +2774,7 @@ const liveReshelveOp = async (ctx, input) => {
2764
2774
  export const liveMod = {
2765
2775
  manifest: liveModManifest,
2766
2776
  migrations: [],
2767
- operations: {
2777
+ ...testOperations({
2768
2778
  'live/touch': liveTouchOp,
2769
2779
  'live/touch-ledger': liveTouchLedgerOp,
2770
2780
  'live/unshare': liveUnshareOp,
@@ -2773,7 +2783,7 @@ export const liveMod = {
2773
2783
  'live/touch-each': liveTouchEachOp,
2774
2784
  'live/shelve': liveShelveOp,
2775
2785
  'live/reshelve': liveReshelveOp,
2776
- },
2786
+ }),
2777
2787
  };
2778
2788
  export const brokenModManifest = moduleManifest.parse({
2779
2789
  id: '@test/broken',
@@ -2801,9 +2811,9 @@ export const brokenMod = {
2801
2811
  { version: '0001-ok', sql: 'CREATE TABLE broken_ok (id TEXT PRIMARY KEY)' },
2802
2812
  { version: '0002-broken', sql: 'CREATE TABLE broken_t (' },
2803
2813
  ],
2804
- operations: {
2814
+ ...testOperations({
2805
2815
  'broken/act': (() => 'ran'),
2806
- },
2816
+ }),
2807
2817
  };
2808
2818
  /**
2809
2819
  * #1898: a module whose migration declares a foreign key to a spine table, and its twin whose
@@ -2832,7 +2842,7 @@ export const spineParentMod = {
2832
2842
  sql: 'CREATE TABLE lists (id TEXT PRIMARY KEY);\nCREATE TABLE notes (t TEXT REFERENCES/**/"_Substrat_Tuples"(subject));',
2833
2843
  },
2834
2844
  ],
2835
- operations: {},
2845
+ ...testOperations({}),
2836
2846
  };
2837
2847
  /**
2838
2848
  * #2066: migrations that reach for the migration journal — one drops its digest fence, one writes
@@ -2845,14 +2855,14 @@ export const journalFenceDropMod = {
2845
2855
  migrations: [
2846
2856
  { version: '0001-init', sql: 'CREATE TABLE jt (id TEXT PRIMARY KEY);\nDROP TRIGGER "_Substrat_Migrations_Digest_Required";' },
2847
2857
  ],
2848
- operations: {},
2858
+ ...testOperations({}),
2849
2859
  };
2850
2860
  export const journalWriteMod = {
2851
2861
  manifest: foreignKeyModManifest('@test/journal-write'),
2852
2862
  migrations: [
2853
2863
  { version: '0001-init', sql: "CREATE TABLE jt (id TEXT PRIMARY KEY);\nDELETE FROM main._substrat_migrations WHERE module_id = 'x';" },
2854
2864
  ],
2855
- operations: {},
2865
+ ...testOperations({}),
2856
2866
  };
2857
2867
  /**
2858
2868
  * #2066 r3: authored migrations that reach for the rest of the spine — one shadows
@@ -2864,18 +2874,18 @@ export const spineShadowMod = {
2864
2874
  migrations: [
2865
2875
  { version: '0001-init', sql: 'CREATE TABLE jt (id TEXT PRIMARY KEY);\nCREATE TEMP TABLE "_Substrat_Tuples" (subject TEXT);' },
2866
2876
  ],
2867
- operations: {},
2877
+ ...testOperations({}),
2868
2878
  };
2869
2879
  export const spineDropMod = {
2870
2880
  manifest: foreignKeyModManifest('@test/spine-drop'),
2871
2881
  migrations: [{ version: '0001-init', sql: 'CREATE TABLE jt (id TEXT PRIMARY KEY);\nDROP TABLE main._substrat_outbox;' }],
2872
- operations: {},
2882
+ ...testOperations({}),
2873
2883
  };
2874
2884
  export const ownParentMod = {
2875
2885
  manifest: foreignKeyModManifest('@test/own-parent'),
2876
2886
  migrations: [
2877
2887
  { version: '0001-init', sql: 'CREATE TABLE lists (id TEXT PRIMARY KEY);\nCREATE TABLE notes (t TEXT REFERENCES lists(id));' },
2878
2888
  ],
2879
- operations: {},
2889
+ ...testOperations({}),
2880
2890
  };
2881
2891
  //# sourceMappingURL=modules.js.map