@powerhousedao/reactor 6.2.3-dev.22 → 6.2.3-dev.24

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 (27) hide show
  1. package/dist/{build-worker-executor-DUjyF4NM.js → build-worker-executor-DxmDJ4QQ.js} +24 -8
  2. package/dist/build-worker-executor-DxmDJ4QQ.js.map +1 -0
  3. package/dist/{document-indexer-C1oY6CaR.js → document-indexer-Dn9RB0sg.js} +6 -5
  4. package/dist/document-indexer-Dn9RB0sg.js.map +1 -0
  5. package/dist/{drive-container-types-CS5IxDiA.js → drive-container-types-Be5-EY9W.js} +843 -144
  6. package/dist/drive-container-types-Be5-EY9W.js.map +1 -0
  7. package/dist/entry.js +10 -5
  8. package/dist/entry.js.map +1 -1
  9. package/dist/index.d.ts +1985 -1826
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +716 -302
  12. package/dist/index.js.map +1 -1
  13. package/dist/projection-entry.js +4 -4
  14. package/dist/{projection-shard-manager-CUo4bF4O.js → projection-shard-manager-mQhfVipO.js} +2 -2
  15. package/dist/{projection-shard-manager-CUo4bF4O.js.map → projection-shard-manager-mQhfVipO.js.map} +1 -1
  16. package/dist/{types-DMKLa0Ok.js → types-DxDCmtId.js} +3 -2
  17. package/dist/types-DxDCmtId.js.map +1 -0
  18. package/dist/{worker-g03lNM42.js → worker-BDVzCtuf.js} +2 -2
  19. package/dist/{worker-g03lNM42.js.map → worker-BDVzCtuf.js.map} +1 -1
  20. package/dist/{worker-handle-CrERzl8s.js → worker-handle-B3p-nuqG.js} +14 -9
  21. package/dist/worker-handle-B3p-nuqG.js.map +1 -0
  22. package/package.json +9 -7
  23. package/dist/build-worker-executor-DUjyF4NM.js.map +0 -1
  24. package/dist/document-indexer-C1oY6CaR.js.map +0 -1
  25. package/dist/drive-container-types-CS5IxDiA.js.map +0 -1
  26. package/dist/types-DMKLa0Ok.js.map +0 -1
  27. package/dist/worker-handle-CrERzl8s.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import { $ as parsePagingOptions, A as targetDocumentId, B as DuplicateOperationError, C as InvalidModuleError, D as createEmptyConsistencyToken, E as createConsistencyToken, F as buildDecisionModel, G as AuthorizationDeniedError, H as RevisionMismatchError, I as APPEND_CONDITION_FAILED_PREFIX, J as ExcessiveReshuffleError, K as DocumentDeletedError, L as AppendConditionFailedError, M as selectDecisionModel, N as documentDecisionModel, O as submittedActionIds, P as authDecisionModel, Q as matchesScope, R as DocumentAlreadyExistsError, S as DuplicateModuleError, T as GATED_DOCUMENT_ACTIONS, U as AuthEnforcementDisabledError, V as OptimisticLockError, W as AuthTimestampNotMonotonicError, X as RelationshipNotFoundError, Y as InvalidOperationTimestampError, Z as UpgradePreconditionFailedError, _ as DocumentMetaCache, a as createForwardingPoolInstrumentation, b as JobExecutorEventTypes, c as KyselyKeyframeStore, d as DriveCollectionId, et as throwIfAborted, f as KyselyExecutionScope, g as KyselyOperationIndex, h as KyselyWriteCache, i as runMigrations, j as decideAtHead, k as summarizeSubmittedActions, l as DocumentModelRegistry, m as EventBus, n as REACTOR_SCHEMA, o as instrumentPgPool, p as resolveFeatureFlags, q as DocumentNotFoundError, r as getMigrationStatus, s as KyselyOperationStore, t as DEFAULT_DRIVE_CONTAINER_TYPES, u as SimpleJobExecutor, v as CollectionMembershipCache, w as ModuleNotFoundError, x as DuplicateManifestError, y as DEFAULT_DEFERRED_JOB_TTL_MS, z as DocumentExistence } from "./drive-container-types-CS5IxDiA.js";
2
- import { n as ReactorEventTypes, t as EventBusAggregateError } from "./types-DMKLa0Ok.js";
1
+ import { $ as RelationshipNotFoundError, A as submittedActionIds, B as DocumentAlreadyExistsError, C as DuplicateManifestError, D as GATED_DOCUMENT_ACTIONS, E as ModuleNotFoundError, F as documentDecisionModel, G as AuthEnforcementDisabledError, H as DuplicateOperationError, I as authDecisionModel, J as DocumentDeletedError, K as AuthTimestampNotMonotonicError, L as buildDecisionModel, M as targetDocumentId, N as decideAtHead, O as createConsistencyToken, P as selectDecisionModel, Q as InvalidSignatureError, R as APPEND_CONDITION_FAILED_PREFIX, S as JobExecutorEventTypes, T as InvalidModuleError, U as OptimisticLockError, V as DocumentExistence, W as RevisionMismatchError, X as ExcessiveReshuffleError, Y as DocumentNotFoundError, Z as InvalidOperationTimestampError, _ as KyselyWriteCache, a as createForwardingPoolInstrumentation, b as CollectionMembershipCache, c as KyselyKeyframeStore, d as verifyActionSignature, et as UpgradePreconditionFailedError, f as PassthroughSigner, g as EventBus, h as resolveFeatureFlags, i as runMigrations, j as summarizeSubmittedActions, k as createEmptyConsistencyToken, l as DocumentModelRegistry, m as KyselyExecutionScope, n as REACTOR_SCHEMA, nt as parsePagingOptions, o as instrumentPgPool, p as DriveCollectionId, q as AuthorizationDeniedError, r as getMigrationStatus, rt as throwIfAborted, s as KyselyOperationStore, t as DEFAULT_DRIVE_CONTAINER_TYPES, tt as matchesScope, u as SimpleJobExecutor, v as KyselyOperationIndex, w as DuplicateModuleError, x as DEFAULT_DEFERRED_JOB_TTL_MS, y as DocumentMetaCache, z as AppendConditionFailedError } from "./drive-container-types-Be5-EY9W.js";
2
+ import { n as ReactorEventTypes, t as EventBusAggregateError } from "./types-DxDCmtId.js";
3
3
  import { i as WorkerInitFailedError, r as WorkerExitedError, t as WorkerAbortTimeoutError } from "./errors-D3S6Eysd.js";
4
- import { a as KyselyDocumentView, c as ReadModelCoordinator, d as DEFAULT_READ_MODEL_YIELD_DEADLINE_MS, f as defaultReadModelIndexingConfig, i as DeletedDocumentRead, l as BaseReadModel, n as ConsistencyTracker, o as DOCUMENT_INDEXER_READ_MODEL, p as unchunkedReadModelIndexingConfig, r as makeConsistencyKey, s as DOCUMENT_VIEW_READ_MODEL, t as KyselyDocumentIndexer, u as DEFAULT_COMMIT_CHUNK_SIZE } from "./document-indexer-C1oY6CaR.js";
4
+ import { a as KyselyDocumentView, c as ReadModelCoordinator, d as DEFAULT_READ_MODEL_YIELD_DEADLINE_MS, f as defaultReadModelIndexingConfig, i as DeletedDocumentRead, l as BaseReadModel, n as ConsistencyTracker, o as DOCUMENT_INDEXER_READ_MODEL, p as unchunkedReadModelIndexingConfig, r as makeConsistencyKey, s as DOCUMENT_VIEW_READ_MODEL, t as KyselyDocumentIndexer, u as DEFAULT_COMMIT_CHUNK_SIZE } from "./document-indexer-Dn9RB0sg.js";
5
5
  import { n as errorToInfo, r as sanitizeArg, t as createForwardingLogger } from "./forwarding-logger-BBkMSxuJ.js";
6
- import { t as workerEntryPath } from "./worker-g03lNM42.js";
7
- import { DowngradeNotSupportedError, MAX_AUTH_GRANTS, UnsupportedDocumentModelVersionError, actions, actions as documentActions, createPresignedHeader, decide, deserializeSignature, garbageCollect, generateId, groupDocumentType, groupMembershipActionTypes, hashDocumentStateForScope, normalizeDocumentModelVersion, sortOperations, toTransportAction } from "@powerhousedao/shared/document-model";
6
+ import { t as workerEntryPath } from "./worker-BDVzCtuf.js";
7
+ import { DEFAULT_SIGNATURE_POLICY, DowngradeNotSupportedError, MAX_AUTH_GRANTS, UnsupportedDocumentModelVersionError, actionSignerIdentity, actionSigningTarget, actions, actions as documentActions, createCopyHeader, decide, deserializeSignature, garbageCollect, generateId, groupDocumentType, groupMembershipActionTypes, hashDocumentStateForScope, normalizeDocumentModelVersion, requestedSignaturePolicy, sortOperations, toTransportAction, withSignaturePolicy } from "@powerhousedao/shared/document-model";
8
8
  import { addFile, addFolder, copyNode, deleteNode, driveCreateDocument, generateNodesCopy, getDescendants, handleTargetNameCollisions, isFileNode, isFolderNode, moveNode, updateNode } from "@powerhousedao/shared/document-drive";
9
9
  import { v4 } from "uuid";
10
10
  import { ConsoleLogger, childLogger } from "document-model";
@@ -237,37 +237,31 @@ function getSharedActionScope(actions) {
237
237
  return baseScope;
238
238
  }
239
239
  /**
240
- * Signs an action with the provided signer.
241
- * If the action already has valid signatures, it is returned unchanged.
240
+ * Signs an action for `target`, the log it is written to. An action already
241
+ * signed under a key is returned unchanged; one whose signer or last tuple has
242
+ * no key is unsigned, and is signed like any other.
242
243
  */
243
- const signAction = async (action, signer, signal) => {
244
- const existingSignatures = action.context?.signer?.signatures;
245
- if (existingSignatures && existingSignatures.length > 0) return action;
246
- const signature = await signer.signAction(action, signal);
244
+ const signAction = async (action, signer, target, signal) => {
245
+ const existing = action.context?.signer;
246
+ if (existing?.app?.key && existing.signatures.at(-1)?.[1]) return action;
247
+ const signature = await signer.signAction(action, target, signal);
247
248
  return {
248
249
  ...action,
249
250
  context: {
250
251
  ...action.context,
251
252
  signer: {
252
- user: {
253
- address: signer.user?.address || "",
254
- networkId: signer.user?.networkId || "",
255
- chainId: signer.user?.chainId || 0
256
- },
257
- app: {
258
- name: signer.app?.name || "",
259
- key: signer.app?.key || ""
260
- },
253
+ ...actionSignerIdentity(signer),
261
254
  signatures: [signature]
262
255
  }
263
256
  }
264
257
  };
265
258
  };
266
259
  /**
267
- * Signs multiple actions with the provided signer
260
+ * Signs the actions of a job on `job.documentId`, each for the log it is
261
+ * written to: a relationship action lands in its source document.
268
262
  */
269
- const signActions = async (actions, signer, signal) => {
270
- return Promise.all(actions.map((action) => signAction(action, signer, signal)));
263
+ const signActions = async (actions, signer, job, signal) => {
264
+ return Promise.all(actions.map((action) => signAction(action, signer, actionSigningTarget(action, job.documentId, job.branch), signal)));
271
265
  };
272
266
  function buildSingleJobMeta(jobId, callerMeta) {
273
267
  return {
@@ -329,11 +323,11 @@ var DriveClient = class {
329
323
  }
330
324
  async create(input, signal) {
331
325
  this.logger.verbose("drives.create(@input)", input);
332
- const driveDoc = driveCreateDocument({ global: {
326
+ const driveDoc = withSignaturePolicy(driveCreateDocument({ global: {
333
327
  name: input.global.name || "",
334
328
  icon: input.global.icon ?? null,
335
329
  nodes: []
336
- } });
330
+ } }), requestedSignaturePolicy(input, await this.client.getCreateSignaturePolicy()), { protocolVersions: input.protocolVersions });
337
331
  if (input.preferredEditor) driveDoc.header.meta = {
338
332
  ...driveDoc.header.meta,
339
333
  preferredEditor: input.preferredEditor
@@ -369,13 +363,19 @@ var DriveClient = class {
369
363
  initialState: document.state
370
364
  }),
371
365
  addRelationshipAction(driveIdentifier, documentId, "child")
372
- ], this.signer, signal);
366
+ ], this.signer, {
367
+ documentId,
368
+ branch: "main"
369
+ }, signal);
373
370
  const driveActions = await signActions([addFile({
374
371
  id: documentId,
375
372
  name: document.header.name || documentId,
376
373
  documentType: document.header.documentType,
377
374
  parentFolder
378
- })], this.signer, signal);
375
+ })], this.signer, {
376
+ documentId: driveIdentifier,
377
+ branch: "main"
378
+ }, signal);
379
379
  await this.runJobs([{
380
380
  key: "document",
381
381
  documentId,
@@ -465,18 +465,20 @@ var DriveClient = class {
465
465
  });
466
466
  resolvedNamesByTargetId.set(entry.targetId, resolved);
467
467
  }
468
+ const policy = await this.client.getCreateSignaturePolicy();
468
469
  for (const entry of copyPlan) {
469
470
  const node = drive.state.global.nodes.find((n) => n.id === entry.srcId);
470
471
  if (!node || !isFileNode(node)) continue;
471
472
  const srcDoc = await this.client.get(entry.srcId, void 0, signal);
472
473
  const duplicated = {
473
474
  ...srcDoc,
474
- header: createPresignedHeader(entry.targetId, srcDoc.header.documentType),
475
+ header: createCopyHeader(srcDoc.header, entry.targetId, policy),
475
476
  initialState: srcDoc.state,
476
477
  operations: {}
477
478
  };
478
479
  const resolvedName = resolvedNamesByTargetId.get(entry.targetId);
479
480
  if (resolvedName) duplicated.header.name = resolvedName;
481
+ entry.targetId = duplicated.header.id;
480
482
  await this.addFile(driveIdentifier, duplicated, entry.targetParentFolder ?? void 0, signal);
481
483
  }
482
484
  return this.client.execute(driveIdentifier, "main", copyPlan.map((entry) => copyNode(entry)), signal);
@@ -529,8 +531,14 @@ var DriveClient = class {
529
531
  }
530
532
  }
531
533
  async removeFileNode(driveId, fileId, signal) {
532
- const relationshipActions = await signActions([removeRelationshipAction(driveId, fileId, "child")], this.signer, signal);
533
- const driveActions = await signActions([deleteNode({ id: fileId })], this.signer, signal);
534
+ const relationshipActions = await signActions([removeRelationshipAction(driveId, fileId, "child")], this.signer, {
535
+ documentId: driveId,
536
+ branch: "main"
537
+ }, signal);
538
+ const driveActions = await signActions([deleteNode({ id: fileId })], this.signer, {
539
+ documentId: driveId,
540
+ branch: "main"
541
+ }, signal);
534
542
  const batchResult = await this.reactor.executeBatch({ jobs: [{
535
543
  key: "relationship",
536
544
  documentId: driveId,
@@ -1049,8 +1057,9 @@ var ReactorClient = class {
1049
1057
  documentView;
1050
1058
  readGate;
1051
1059
  actionEvaluation;
1060
+ createSignaturePolicy;
1052
1061
  drives;
1053
- constructor(logger, reactor, signer, subscriptionManager, jobAwaiter, documentIndexer, documentView, readGate = new BareReadGate(), actionEvaluation) {
1062
+ constructor(logger, reactor, signer, subscriptionManager, jobAwaiter, documentIndexer, documentView, readGate = new BareReadGate(), actionEvaluation, createSignaturePolicy = DEFAULT_SIGNATURE_POLICY) {
1054
1063
  this.logger = logger;
1055
1064
  this.reactor = reactor;
1056
1065
  this.signer = signer;
@@ -1060,6 +1069,7 @@ var ReactorClient = class {
1060
1069
  this.documentView = documentView;
1061
1070
  this.readGate = readGate;
1062
1071
  this.actionEvaluation = actionEvaluation;
1072
+ this.createSignaturePolicy = createSignaturePolicy;
1063
1073
  this.drives = new DriveClient(this, logger, reactor, signer);
1064
1074
  this.logger.verbose("ReactorClient initialized");
1065
1075
  }
@@ -1403,12 +1413,16 @@ var ReactorClient = class {
1403
1413
  scopeStates: document.state ?? {}
1404
1414
  };
1405
1415
  }
1416
+ getCreateSignaturePolicy() {
1417
+ return Promise.resolve(this.createSignaturePolicy);
1418
+ }
1406
1419
  /**
1407
1420
  * Creates a document and waits for completion
1408
1421
  */
1409
1422
  async create(document, parentIdentifier, signal) {
1410
1423
  this.logger.verbose("create(@id, @parentIdentifier)", document.header.id, parentIdentifier);
1411
1424
  const documentId = document.header.id;
1425
+ const branch = document.header.branch || "main";
1412
1426
  const createActions = await signActions([createDocumentAction({
1413
1427
  model: document.header.documentType,
1414
1428
  version: 0,
@@ -1431,17 +1445,23 @@ var ReactorClient = class {
1431
1445
  fromVersion: 0,
1432
1446
  toVersion: normalizeDocumentModelVersion(document.state.document?.version),
1433
1447
  initialState: document.state
1434
- })], this.signer, signal);
1448
+ })], this.signer, {
1449
+ documentId,
1450
+ branch
1451
+ }, signal);
1435
1452
  const jobs = [{
1436
1453
  key: "create",
1437
1454
  documentId,
1438
1455
  scope: getSharedActionScope(createActions),
1439
- branch: "main",
1456
+ branch,
1440
1457
  actions: createActions,
1441
1458
  dependsOn: []
1442
1459
  }];
1443
1460
  if (parentIdentifier) {
1444
- const parentActions = await signActions([addRelationshipAction(parentIdentifier, documentId, "child")], this.signer, signal);
1461
+ const parentActions = await signActions([addRelationshipAction(parentIdentifier, documentId, "child")], this.signer, {
1462
+ documentId: parentIdentifier,
1463
+ branch: "main"
1464
+ }, signal);
1445
1465
  jobs.push({
1446
1466
  key: "parent",
1447
1467
  documentId: parentIdentifier,
@@ -1475,7 +1495,7 @@ var ReactorClient = class {
1475
1495
  }, void 0);
1476
1496
  if (!module) throw new Error(`Document model not found for type: ${documentModelType}`);
1477
1497
  }
1478
- const document = module.utils.createDocument();
1498
+ const document = withSignaturePolicy(module.utils.createDocument(), requestedSignaturePolicy(options, this.createSignaturePolicy), { protocolVersions: options?.protocolVersions });
1479
1499
  document.state.document.version = normalizeDocumentModelVersion(module.version);
1480
1500
  return this.create(document, options?.parentIdentifier, signal);
1481
1501
  }
@@ -1510,7 +1530,10 @@ var ReactorClient = class {
1510
1530
  fromVersion,
1511
1531
  toVersion: targetVersion,
1512
1532
  revision: { ...document.header.revision }
1513
- })], this.signer, signal);
1533
+ })], this.signer, {
1534
+ documentId,
1535
+ branch
1536
+ }, signal);
1514
1537
  const jobInfo = await this.reactor.execute(documentId, branch, signedActions, signal);
1515
1538
  const completedJob = await this.waitForJob(jobInfo, signal);
1516
1539
  if (completedJob.status !== JobStatus.FAILED) {
@@ -1540,8 +1563,12 @@ var ReactorClient = class {
1540
1563
  */
1541
1564
  async execute(documentIdentifier, branch, actions, signal, subject) {
1542
1565
  this.logger.verbose("execute(@documentIdentifier, @branch, @count actions)", documentIdentifier, branch, actions.length);
1543
- const signedActions = await signActions(actions, this.signer, signal);
1544
- const jobInfo = await this.reactor.execute(documentIdentifier, branch, signedActions, signal);
1566
+ const documentId = await this.resolveWriteTarget(documentIdentifier, branch, actions, signal);
1567
+ const signedActions = await signActions(actions, this.signer, {
1568
+ documentId,
1569
+ branch
1570
+ }, signal);
1571
+ const jobInfo = await this.reactor.execute(documentId, branch, signedActions, signal);
1545
1572
  const completedJob = await this.waitForJob(jobInfo, signal);
1546
1573
  if (completedJob.status === JobStatus.FAILED) throw new Error(completedJob.error?.message);
1547
1574
  const view = {
@@ -1556,15 +1583,38 @@ var ReactorClient = class {
1556
1583
  */
1557
1584
  async executeAsync(documentIdentifier, branch, actions, signal) {
1558
1585
  this.logger.verbose("executeAsync(@documentIdentifier, @branch, @count actions)", documentIdentifier, branch, actions.length);
1559
- const signedActions = await signActions(actions, this.signer, signal);
1560
- return this.reactor.execute(documentIdentifier, branch, signedActions, signal);
1586
+ const documentId = await this.resolveWriteTarget(documentIdentifier, branch, actions, signal);
1587
+ const signedActions = await signActions(actions, this.signer, {
1588
+ documentId,
1589
+ branch
1590
+ }, signal);
1591
+ return this.reactor.execute(documentId, branch, signedActions, signal);
1592
+ }
1593
+ /**
1594
+ * The id a write on `identifier` is stored under, which its signatures bind.
1595
+ * A create names its own id, and an id no slug maps to is taken as given, so
1596
+ * a document still in flight resolves to itself.
1597
+ */
1598
+ async resolveWriteTarget(identifier, branch, actions, signal) {
1599
+ if (actions.some((action) => action.type === "CREATE_DOCUMENT")) return identifier;
1600
+ const view = { branch };
1601
+ const bySlug = await this.documentView.resolveSlug(identifier, view, void 0, signal);
1602
+ if (bySlug === void 0 || bySlug === identifier) return identifier;
1603
+ return this.documentView.resolveIdOrSlug(identifier, view, void 0, signal);
1561
1604
  }
1562
1605
  async executeBatch(request, signal) {
1563
1606
  this.logger.verbose("executeBatch(@count jobs)", request.jobs.length);
1564
- const signedJobs = await Promise.all(request.jobs.map(async (job) => ({
1565
- ...job,
1566
- actions: await signActions(job.actions, this.signer, signal)
1567
- })));
1607
+ const signedJobs = await Promise.all(request.jobs.map(async (job) => {
1608
+ const documentId = await this.resolveWriteTarget(job.documentId, job.branch, job.actions, signal);
1609
+ return {
1610
+ ...job,
1611
+ documentId,
1612
+ actions: await signActions(job.actions, this.signer, {
1613
+ documentId,
1614
+ branch: job.branch
1615
+ }, signal)
1616
+ };
1617
+ }));
1568
1618
  const batchResult = await this.reactor.executeBatch({ jobs: signedJobs }, signal);
1569
1619
  const completedJobs = await Promise.all(Object.values(batchResult.jobs).map((job) => this.waitForJob(job, signal)));
1570
1620
  for (const job of completedJobs) if (job.status === JobStatus.FAILED) throw new Error(job.error?.message);
@@ -1870,7 +1920,7 @@ var JobResultHandler = class {
1870
1920
  callbacks.deferJob(result.error.documentId, handle.job);
1871
1921
  return;
1872
1922
  }
1873
- if (result.error && (DocumentDeletedError.isError(result.error) || DocumentAlreadyExistsError.isError(result.error) || DocumentNotFoundError.isError(result.error) || AuthorizationDeniedError.isError(result.error) || AuthTimestampNotMonotonicError.isError(result.error) || InvalidOperationTimestampError.isError(result.error) || ExcessiveReshuffleError.isError(result.error) || UpgradePreconditionFailedError.isError(result.error))) {
1923
+ if (result.error && (DocumentDeletedError.isError(result.error) || DocumentAlreadyExistsError.isError(result.error) || DocumentNotFoundError.isError(result.error) || AuthorizationDeniedError.isError(result.error) || AuthTimestampNotMonotonicError.isError(result.error) || InvalidOperationTimestampError.isError(result.error) || ExcessiveReshuffleError.isError(result.error) || InvalidSignatureError.isError(result.error) || UpgradePreconditionFailedError.isError(result.error))) {
1874
1924
  const errorInfo = toErrorInfo(result.error);
1875
1925
  this.jobTracker.markFailed(handle.job.id, errorInfo, handle.job);
1876
1926
  this.eventBus.emit(ReactorEventTypes.JOB_FAILED, {
@@ -2258,6 +2308,7 @@ var WorkerPoolJobExecutorManager = class {
2258
2308
  await this.tryDispatchFor(worker);
2259
2309
  return;
2260
2310
  }
2311
+ for (const refusal of outcome.signatureRefusals ?? []) this.eventBus.emit(ReactorEventTypes.SIGNATURE_REFUSED, refusal).catch(() => {});
2261
2312
  if (outcome.result.success) {
2262
2313
  this.totalJobsProcessed++;
2263
2314
  const completedEvent = {
@@ -2727,24 +2778,267 @@ function resolveProcessorSlots(records, legacy) {
2727
2778
  });
2728
2779
  }
2729
2780
  //#endregion
2781
+ //#region src/processors/processor-queue.ts
2782
+ function isDelivery(task) {
2783
+ return task?.kind === "live" || task?.kind === "advance";
2784
+ }
2785
+ function errorMessage(error) {
2786
+ return error instanceof Error ? error.message : String(error);
2787
+ }
2788
+ function lowestOf(ops) {
2789
+ let lowest = ops[0].context.ordinal;
2790
+ for (const op of ops) lowest = Math.min(lowest, op.context.ordinal);
2791
+ return lowest;
2792
+ }
2793
+ function highestOf(ops) {
2794
+ let highest = 0;
2795
+ for (const op of ops) highest = Math.max(highest, op.context.ordinal);
2796
+ return highest;
2797
+ }
2798
+ var ProcessorQueue = class {
2799
+ tasks = [];
2800
+ running = false;
2801
+ closed = false;
2802
+ replaysAhead = 0;
2803
+ unrouted = /* @__PURE__ */ new Set();
2804
+ constructor(options) {
2805
+ this.options = options;
2806
+ }
2807
+ get isClosed() {
2808
+ return this.closed;
2809
+ }
2810
+ /** Resolves at once behind a replay, so a pass never waits out a backfill. */
2811
+ live(ops) {
2812
+ const delivered = this.push((done) => ({
2813
+ kind: "live",
2814
+ ops,
2815
+ done
2816
+ }));
2817
+ return this.replaysAhead > 0 ? Promise.resolve() : delivered;
2818
+ }
2819
+ /** Raises the cursor past a batch with nothing for this processor. */
2820
+ advance(through) {
2821
+ const last = this.tasks.at(-1);
2822
+ if (last?.kind === "advance") {
2823
+ last.through = Math.max(last.through, through);
2824
+ return Promise.resolve();
2825
+ }
2826
+ return this.push((done) => ({
2827
+ kind: "advance",
2828
+ through,
2829
+ done
2830
+ }));
2831
+ }
2832
+ /** Replays from the cursor as it stands when the task runs. */
2833
+ backfill() {
2834
+ return this.replay("backfill", () => this.runBackfill());
2835
+ }
2836
+ /** Clears an error and replays from the cursor. No-op when active. */
2837
+ retry() {
2838
+ return this.replay("retry", async () => {
2839
+ const { cursor } = this.options;
2840
+ if (cursor.status !== "errored") return;
2841
+ cursor.status = "active";
2842
+ cursor.lastError = void 0;
2843
+ cursor.lastErrorTimestamp = void 0;
2844
+ await this.persist();
2845
+ await this.runBackfill();
2846
+ });
2847
+ }
2848
+ /** Stops cursor writes now; queued tasks still run before the disconnect. */
2849
+ close() {
2850
+ if (this.closed) return Promise.resolve();
2851
+ const disconnect = this.enqueue("disconnect", async () => {
2852
+ try {
2853
+ await this.options.processor.onDisconnect();
2854
+ } catch (error) {
2855
+ this.options.logger.error("Error disconnecting processor '@ProcessorId': @Error", this.options.processorId, error);
2856
+ }
2857
+ });
2858
+ this.closed = true;
2859
+ return disconnect;
2860
+ }
2861
+ replay(kind, run) {
2862
+ if (this.closed) return Promise.resolve();
2863
+ this.replaysAhead++;
2864
+ return this.enqueue(kind, async () => {
2865
+ try {
2866
+ await run();
2867
+ } finally {
2868
+ this.replaysAhead--;
2869
+ }
2870
+ });
2871
+ }
2872
+ enqueue(kind, run) {
2873
+ return this.push((done) => ({
2874
+ kind,
2875
+ run,
2876
+ done
2877
+ }));
2878
+ }
2879
+ push(task) {
2880
+ if (this.closed) return Promise.resolve();
2881
+ return new Promise((resolve) => {
2882
+ this.tasks.push(task(resolve));
2883
+ if (this.running) return;
2884
+ this.running = true;
2885
+ queueMicrotask(() => void this.drain());
2886
+ });
2887
+ }
2888
+ async drain() {
2889
+ while (this.tasks.length > 0) {
2890
+ const task = this.tasks.shift();
2891
+ const batch = [];
2892
+ if (isDelivery(task)) {
2893
+ batch.push(task);
2894
+ let size = task.kind === "live" ? task.ops.length : 0;
2895
+ let next = this.tasks[0];
2896
+ while (isDelivery(next) && size < 500) {
2897
+ if (next.kind === "live") size += next.ops.length;
2898
+ batch.push(next);
2899
+ this.tasks.shift();
2900
+ next = this.tasks[0];
2901
+ }
2902
+ }
2903
+ try {
2904
+ await (isDelivery(task) ? this.deliverLive(batch) : task.run());
2905
+ } catch (error) {
2906
+ this.options.logger.error("Processor '@ProcessorId' task '@Kind' failed: @Error", this.options.processorId, task.kind, error);
2907
+ } finally {
2908
+ task.done();
2909
+ for (const merged of batch.slice(1)) merged.done();
2910
+ }
2911
+ }
2912
+ this.running = false;
2913
+ }
2914
+ async deliverLive(batch) {
2915
+ const { cursor, floor } = this.options;
2916
+ const ops = [];
2917
+ let through = 0;
2918
+ for (const task of batch) if (task.kind === "live") ops.push(...task.ops);
2919
+ else through = Math.max(through, task.through);
2920
+ const fresh = ops.filter((op) => {
2921
+ const ordinal = op.context.ordinal;
2922
+ if (ordinal <= floor) return false;
2923
+ return !this.unrouted.delete(ordinal);
2924
+ });
2925
+ if (fresh.length === 0) {
2926
+ await this.raiseCursor(through);
2927
+ return;
2928
+ }
2929
+ if (cursor.status !== "active") {
2930
+ await this.parkBelow(fresh);
2931
+ return;
2932
+ }
2933
+ if (!await this.deliver(fresh)) {
2934
+ await this.parkBelow(fresh);
2935
+ return;
2936
+ }
2937
+ await this.raiseCursor(Math.max(highestOf(fresh), through));
2938
+ }
2939
+ async runBackfill() {
2940
+ const { cursor, filter, floor } = this.options;
2941
+ if (cursor.status !== "active") return;
2942
+ let page;
2943
+ try {
2944
+ page = await this.options.readSince(cursor.lastOrdinal);
2945
+ } catch (error) {
2946
+ await this.fail(error, "reading backfill");
2947
+ return;
2948
+ }
2949
+ while (page.results.length > 0) {
2950
+ if (this.closed) return;
2951
+ const matching = page.results.filter((op) => op.context.ordinal > floor && matchesFilter(op, filter));
2952
+ if (matching.length > 0) {
2953
+ if (!await this.deliver(matching)) {
2954
+ await this.persist();
2955
+ return;
2956
+ }
2957
+ this.dedupeQueued(matching);
2958
+ }
2959
+ await this.raiseCursor(highestOf(page.results));
2960
+ if (!page.next) break;
2961
+ try {
2962
+ page = await page.next();
2963
+ } catch (error) {
2964
+ await this.fail(error, "reading backfill");
2965
+ return;
2966
+ }
2967
+ }
2968
+ }
2969
+ dedupeQueued(delivered) {
2970
+ const routed = this.options.routedThrough();
2971
+ const queued = /* @__PURE__ */ new Set();
2972
+ for (const op of delivered) {
2973
+ const ordinal = op.context.ordinal;
2974
+ if (ordinal > routed) this.unrouted.add(ordinal);
2975
+ else queued.add(ordinal);
2976
+ }
2977
+ if (queued.size === 0) return;
2978
+ for (const task of this.tasks) {
2979
+ if (task.kind !== "live") continue;
2980
+ task.ops = task.ops.filter((op) => !queued.has(op.context.ordinal));
2981
+ }
2982
+ }
2983
+ async deliver(ops) {
2984
+ try {
2985
+ await this.options.processor.onOperations(ops);
2986
+ return true;
2987
+ } catch (error) {
2988
+ this.markErrored(error);
2989
+ this.options.logger.error("Processor '@ProcessorId' failed at ordinal @Ordinal: @Error", this.options.processorId, lowestOf(ops), error);
2990
+ return false;
2991
+ }
2992
+ }
2993
+ async fail(error, during) {
2994
+ this.markErrored(error);
2995
+ await this.persist();
2996
+ this.options.logger.error("Processor '@ProcessorId' failed @During: @Error", this.options.processorId, during, error);
2997
+ }
2998
+ markErrored(error) {
2999
+ const { cursor } = this.options;
3000
+ cursor.status = "errored";
3001
+ cursor.lastError = errorMessage(error);
3002
+ cursor.lastErrorTimestamp = /* @__PURE__ */ new Date();
3003
+ }
3004
+ async raiseCursor(through) {
3005
+ const { cursor } = this.options;
3006
+ if (cursor.status !== "active") return;
3007
+ if (through <= cursor.lastOrdinal) return;
3008
+ cursor.lastOrdinal = through;
3009
+ await this.persist();
3010
+ }
3011
+ async parkBelow(missed) {
3012
+ const { cursor } = this.options;
3013
+ cursor.lastOrdinal = Math.min(cursor.lastOrdinal, lowestOf(missed) - 1);
3014
+ await this.persist();
3015
+ }
3016
+ async persist() {
3017
+ if (this.closed) return;
3018
+ const { cursor } = this.options;
3019
+ try {
3020
+ await this.options.persist({
3021
+ lastOrdinal: cursor.lastOrdinal,
3022
+ status: cursor.status,
3023
+ lastError: cursor.lastError,
3024
+ lastErrorTimestamp: cursor.lastErrorTimestamp
3025
+ });
3026
+ } catch (error) {
3027
+ this.options.logger.error("Failed to persist cursor for '@ProcessorId': @Error", this.options.processorId, error);
3028
+ }
3029
+ }
3030
+ };
3031
+ //#endregion
2730
3032
  //#region src/processors/processor-manager.ts
2731
- /**
2732
- * Manages processor lifecycle based on operations.
2733
- * Extends BaseReadModel to receive operations from ReadModelCoordinator.
2734
- *
2735
- * Responsibilities:
2736
- * 1. Detect drive creation from CREATE_DOCUMENT operations
2737
- * 2. Create processors for each drive using registered factories
2738
- * 3. Route operations to matching processors based on filters
2739
- * 4. Clean up processors when drives are deleted or factories are unregistered
2740
- * 5. Track per-processor cursors for failure recovery and backfill
2741
- */
2742
3033
  var ProcessorManager = class extends BaseReadModel {
2743
3034
  factoryRegistry = /* @__PURE__ */ new Map();
2744
3035
  processorsByDrive = /* @__PURE__ */ new Map();
2745
- factoryToProcessors = /* @__PURE__ */ new Map();
3036
+ pendingSlots = /* @__PURE__ */ new Set();
2746
3037
  knownDrives = /* @__PURE__ */ new Map();
3038
+ highestRoutedOrdinal = 0;
2747
3039
  cursorCache = /* @__PURE__ */ new Map();
3040
+ cursorWrites = /* @__PURE__ */ new Map();
3041
+ draining = /* @__PURE__ */ new Map();
2748
3042
  logger;
2749
3043
  driveContainerTypes;
2750
3044
  legacyProcessorIds;
@@ -2759,39 +3053,31 @@ var ProcessorManager = class extends BaseReadModel {
2759
3053
  this.legacyProcessorIds = options.legacyProcessorIds ?? true;
2760
3054
  }
2761
3055
  async init() {
2762
- await super.init();
2763
3056
  await this.loadAllCursors();
3057
+ await super.init();
2764
3058
  await this.discoverExistingDrives();
2765
3059
  }
2766
3060
  async commitOperations(items) {
2767
- await this.detectAndRegisterNewDrives(items);
2768
- await this.detectAndCleanupDeletedDrives(items);
2769
- await this.routeOperationsToProcessors(items);
3061
+ const { runs, reserved } = this.detectNewDrives(items);
3062
+ const disconnects = this.detectDeletedDrives(items);
3063
+ const deliveries = this.enqueueRouted(items, reserved);
3064
+ await Promise.all([
3065
+ ...runs.map((run) => run()),
3066
+ ...disconnects,
3067
+ ...deliveries
3068
+ ]);
2770
3069
  }
2771
3070
  async registerFactory(identifier, factory) {
2772
- if (this.factoryRegistry.has(identifier)) await this.unregisterFactory(identifier);
3071
+ const removals = this.removeFactory(identifier);
2773
3072
  this.factoryRegistry.set(identifier, factory);
2774
- this.factoryToProcessors.set(identifier, /* @__PURE__ */ new Map());
2775
- for (const [driveId, documentType] of this.knownDrives) {
2776
- const driveHeader = createMinimalDriveHeader(driveId, documentType);
2777
- await this.createProcessorsForDrive(driveId, identifier, factory, driveHeader);
2778
- }
3073
+ const previous = this.draining.get(identifier);
3074
+ const creationOrdinal = this.highWater() + 1;
3075
+ const runs = [];
3076
+ for (const [driveId, documentType] of this.knownDrives) runs.push(this.reserveSlot(identifier, factory, driveId, createMinimalDriveHeader(driveId, documentType), creationOrdinal, void 0, false, previous).run);
3077
+ await Promise.all([...removals, ...runs.map((run) => run())]);
2779
3078
  }
2780
3079
  async unregisterFactory(identifier) {
2781
- const factoryProcessors = this.factoryToProcessors.get(identifier);
2782
- if (!factoryProcessors) return;
2783
- for (const [driveId, tracked] of factoryProcessors) {
2784
- for (const t of tracked) await this.safeDisconnect(t.record.processor);
2785
- const driveProcessors = this.processorsByDrive.get(driveId);
2786
- if (driveProcessors) {
2787
- const remaining = driveProcessors.filter((p) => !tracked.includes(p));
2788
- if (remaining.length > 0) this.processorsByDrive.set(driveId, remaining);
2789
- else this.processorsByDrive.delete(driveId);
2790
- }
2791
- }
2792
- await this.deleteProcessorCursors({ factoryId: identifier });
2793
- this.factoryToProcessors.delete(identifier);
2794
- this.factoryRegistry.delete(identifier);
3080
+ await Promise.all(this.removeFactory(identifier));
2795
3081
  }
2796
3082
  get(processorId) {
2797
3083
  for (const tracked of this.allTrackedProcessors()) if (tracked.processorId === processorId) return tracked;
@@ -2799,208 +3085,283 @@ var ProcessorManager = class extends BaseReadModel {
2799
3085
  getAll() {
2800
3086
  return Array.from(this.allTrackedProcessors());
2801
3087
  }
2802
- *allTrackedProcessors() {
2803
- for (const tracked of this.processorsByDrive.values()) yield* tracked;
2804
- }
2805
- async detectAndRegisterNewDrives(operations) {
2806
- for (const op of operations) {
3088
+ /** Synchronous: reserves a slot per registered factory for each new drive. */
3089
+ detectNewDrives(items) {
3090
+ const runs = [];
3091
+ const reserved = /* @__PURE__ */ new Set();
3092
+ for (const op of items) {
2807
3093
  if (!this.isDriveCreation(op)) continue;
2808
3094
  const driveId = op.context.documentId;
2809
3095
  if (this.knownDrives.has(driveId)) continue;
2810
3096
  this.knownDrives.set(driveId, op.context.documentType);
2811
3097
  const driveHeader = extractDriveHeader(op);
2812
3098
  if (!driveHeader) continue;
2813
- for (const [identifier, factory] of this.factoryRegistry) await this.createProcessorsForDrive(driveId, identifier, factory, driveHeader);
3099
+ for (const [identifier, factory] of this.factoryRegistry) {
3100
+ const { slot, run } = this.reserveSlot(identifier, factory, driveId, driveHeader, op.context.ordinal, items, true);
3101
+ reserved.add(slot);
3102
+ runs.push(run);
3103
+ }
2814
3104
  }
3105
+ return {
3106
+ runs,
3107
+ reserved
3108
+ };
2815
3109
  }
2816
- isDriveCreation(op) {
2817
- return op.operation.action.type === "CREATE_DOCUMENT" && this.driveContainerTypes.has(op.context.documentType);
2818
- }
2819
- async detectAndCleanupDeletedDrives(operations) {
2820
- for (const op of operations) {
3110
+ /** Synchronous: drops a deleted drive's processors from the tables. */
3111
+ detectDeletedDrives(items) {
3112
+ const pending = [];
3113
+ for (const op of items) {
2821
3114
  if (!isDriveDeletion(op)) continue;
2822
3115
  const driveId = extractDeletedDocumentId(op);
2823
3116
  if (!driveId || !this.knownDrives.has(driveId)) continue;
2824
- if (!this.isDeletedDocumentADrive(driveId)) continue;
2825
- await this.cleanupDriveProcessors(driveId);
2826
3117
  this.knownDrives.delete(driveId);
3118
+ for (const slot of this.pendingSlots) if (slot.driveId === driveId) this.pendingSlots.delete(slot);
3119
+ for (const { queue } of this.processorsByDrive.get(driveId) ?? []) queue.close();
3120
+ this.processorsByDrive.delete(driveId);
3121
+ pending.push(...this.deleteCursors((row) => row.driveId === driveId));
3122
+ }
3123
+ return pending;
3124
+ }
3125
+ /** Synchronous: puts each processor's share of the batch on its queue. */
3126
+ enqueueRouted(items, reserved) {
3127
+ if (items.length === 0) return [];
3128
+ let lowest = items[0].context.ordinal;
3129
+ let highest = 0;
3130
+ for (const item of items) {
3131
+ lowest = Math.min(lowest, item.context.ordinal);
3132
+ highest = Math.max(highest, item.context.ordinal);
3133
+ }
3134
+ this.highestRoutedOrdinal = Math.max(this.highestRoutedOrdinal, highest);
3135
+ for (const slot of this.pendingSlots) {
3136
+ if (reserved.has(slot)) continue;
3137
+ slot.lowestRoutedOrdinal = Math.min(slot.lowestRoutedOrdinal ?? lowest, lowest);
3138
+ }
3139
+ const deliveries = [];
3140
+ for (const { tracked, queue } of this.allBound()) {
3141
+ const matching = items.filter((op) => matchesFilter(op, tracked.record.filter));
3142
+ if (matching.length > 0) deliveries.push(queue.live(matching));
3143
+ else queue.advance(highest);
3144
+ }
3145
+ return deliveries;
3146
+ }
3147
+ /** Synchronous: binds records, or discards them if the slot was cancelled. */
3148
+ bind(slot, records, creationOrdinal, creationItems) {
3149
+ const released = this.pendingSlots.delete(slot);
3150
+ if (!records || records.length === 0) {
3151
+ const settled = Promise.resolve();
3152
+ return {
3153
+ persisted: settled,
3154
+ delivered: settled
3155
+ };
2827
3156
  }
3157
+ if (!released) {
3158
+ const discarded = records.map((record) => this.discard(slot, record.processor.onDisconnect.bind(record.processor)));
3159
+ const settled = Promise.all(discarded).then(() => void 0);
3160
+ return {
3161
+ persisted: settled,
3162
+ delivered: settled
3163
+ };
3164
+ }
3165
+ const { factoryId, driveId, lowestRoutedOrdinal } = slot;
3166
+ const ids = resolveProcessorSlots(records, this.legacyProcessorIds);
3167
+ const bound = records.map((record, i) => this.track(slot, record, i, `${factoryId}:${driveId}:${ids[i]}`, creationOrdinal, lowestRoutedOrdinal));
3168
+ const drive = this.processorsByDrive.get(driveId) ?? [];
3169
+ this.processorsByDrive.set(driveId, [...drive, ...bound]);
3170
+ const liveIds = new Set(bound.map((b) => b.tracked.processorId));
3171
+ const writes = this.deleteCursors((row) => row.factoryId === factoryId && row.driveId === driveId && !liveIds.has(row.processorId));
3172
+ for (const { tracked } of bound) writes.push(this.writeCursor(tracked, tracked));
3173
+ const deliveries = [];
3174
+ for (const { tracked, queue } of bound) {
3175
+ const missed = tracked.lastOrdinal < slot.reservedAt || lowestRoutedOrdinal !== void 0;
3176
+ if (tracked.status === "active" && missed) deliveries.push(queue.backfill());
3177
+ const matching = creationItems?.filter((op) => matchesFilter(op, tracked.record.filter));
3178
+ if (matching && matching.length > 0) deliveries.push(queue.live(matching));
3179
+ }
3180
+ return {
3181
+ persisted: Promise.all(writes).then(() => void 0),
3182
+ delivered: Promise.all(deliveries).then(() => void 0)
3183
+ };
2828
3184
  }
2829
- async discoverExistingDrives() {
2830
- const drives = await this.db.selectFrom("DocumentSnapshot").select(["documentId", "documentType"]).where("documentType", "in", [...this.driveContainerTypes]).where("isDeleted", "=", false).execute();
2831
- for (const drive of drives) this.knownDrives.set(drive.documentId, drive.documentType);
3185
+ /** Synchronous: removes a factory's slots, processors and cursor rows. */
3186
+ removeFactory(identifier) {
3187
+ if (!this.factoryRegistry.delete(identifier)) return [];
3188
+ const closing = [];
3189
+ for (const slot of this.pendingSlots) {
3190
+ if (slot.factoryId !== identifier) continue;
3191
+ this.pendingSlots.delete(slot);
3192
+ if (slot.settled) closing.push(slot.settled);
3193
+ }
3194
+ for (const [driveId, drive] of this.processorsByDrive) {
3195
+ const remaining = [];
3196
+ for (const b of drive) if (b.tracked.factoryId === identifier) closing.push(b.queue.close());
3197
+ else remaining.push(b);
3198
+ if (remaining.length > 0) this.processorsByDrive.set(driveId, remaining);
3199
+ else this.processorsByDrive.delete(driveId);
3200
+ }
3201
+ if (closing.length > 0) {
3202
+ const drained = Promise.all([this.draining.get(identifier), ...closing]).then(() => void 0);
3203
+ this.draining.set(identifier, drained);
3204
+ drained.then(() => {
3205
+ if (this.draining.get(identifier) === drained) this.draining.delete(identifier);
3206
+ });
3207
+ }
3208
+ return this.deleteCursors((row) => row.factoryId === identifier);
2832
3209
  }
2833
- isDeletedDocumentADrive(documentId) {
2834
- return this.knownDrives.has(documentId);
3210
+ reserveSlot(factoryId, factory, driveId, driveHeader, creationOrdinal, creationItems, awaitDelivery, previous) {
3211
+ const slot = {
3212
+ factoryId,
3213
+ driveId,
3214
+ reservedAt: this.highWater(),
3215
+ lowestRoutedOrdinal: void 0,
3216
+ settled: void 0
3217
+ };
3218
+ this.pendingSlots.add(slot);
3219
+ const run = async () => {
3220
+ const bound = (async () => {
3221
+ await previous;
3222
+ const records = await this.runFactory(slot, factory, driveHeader);
3223
+ return this.bind(slot, records, creationOrdinal, creationItems);
3224
+ })();
3225
+ slot.settled = bound.then(({ persisted }) => persisted);
3226
+ const { persisted, delivered } = await bound;
3227
+ await (awaitDelivery ? Promise.all([persisted, delivered]) : persisted);
3228
+ };
3229
+ return {
3230
+ slot,
3231
+ run
3232
+ };
2835
3233
  }
2836
- async createProcessorsForDrive(driveId, identifier, factory, driveHeader) {
2837
- let records;
3234
+ async runFactory(slot, factory, driveHeader) {
2838
3235
  try {
2839
- records = await factory(driveHeader);
3236
+ return await factory(driveHeader);
2840
3237
  } catch (error) {
2841
- this.logger.error("Factory '@FactoryId' failed for drive '@DriveId': @Error", identifier, driveId, error);
3238
+ this.logger.error("Factory '@FactoryId' failed for drive '@DriveId': @Error", slot.factoryId, slot.driveId, error);
2842
3239
  return;
2843
3240
  }
2844
- if (records.length === 0) return;
2845
- const trackedList = [];
2846
- const slots = resolveProcessorSlots(records, this.legacyProcessorIds);
2847
- for (let i = 0; i < records.length; i++) {
2848
- const record = records[i];
2849
- const processorId = `${identifier}:${driveId}:${slots[i]}`;
2850
- const cached = this.cursorCache.get(processorId);
2851
- let lastOrdinal;
2852
- let status;
2853
- let lastError;
2854
- let lastErrorTimestamp;
2855
- if (cached) {
2856
- lastOrdinal = cached.lastOrdinal;
2857
- status = cached.status;
2858
- lastError = cached.lastError ?? void 0;
2859
- lastErrorTimestamp = cached.lastErrorTimestamp ?? void 0;
2860
- } else {
2861
- lastOrdinal = (record.startFrom ?? "beginning") === "current" ? this.lastOrdinal : 0;
2862
- status = "active";
2863
- lastError = void 0;
2864
- lastErrorTimestamp = void 0;
2865
- }
2866
- const tracked = {
2867
- processorId,
2868
- factoryId: identifier,
2869
- driveId,
2870
- processorIndex: i,
2871
- record,
2872
- lastOrdinal,
2873
- status,
2874
- lastError,
2875
- lastErrorTimestamp,
2876
- retry: () => this.retryProcessor(tracked)
3241
+ }
3242
+ track(slot, record, processorIndex, processorId, creationOrdinal, lowestRoutedOrdinal) {
3243
+ const cached = this.cursorCache.get(processorId);
3244
+ let floor = 0;
3245
+ let cursor;
3246
+ if (cached) cursor = {
3247
+ lastOrdinal: cached.lastOrdinal,
3248
+ status: cached.status,
3249
+ lastError: cached.lastError ?? void 0,
3250
+ lastErrorTimestamp: cached.lastErrorTimestamp ?? void 0
3251
+ };
3252
+ else {
3253
+ if (record.startFrom === "current") floor = creationOrdinal - 1;
3254
+ cursor = {
3255
+ lastOrdinal: floor,
3256
+ status: "active",
3257
+ lastError: void 0,
3258
+ lastErrorTimestamp: void 0
2877
3259
  };
2878
- trackedList.push(tracked);
2879
- await this.saveProcessorCursor(tracked);
2880
- }
2881
- const liveIds = new Set(trackedList.map((t) => t.processorId));
2882
- await this.db.deleteFrom("ProcessorCursor").where("factoryId", "=", identifier).where("driveId", "=", driveId).where("processorId", "not in", [...liveIds]).execute();
2883
- for (const [id, row] of this.cursorCache) if (row.factoryId === identifier && row.driveId === driveId && !liveIds.has(id)) this.cursorCache.delete(id);
2884
- const factoryProcessors = this.factoryToProcessors.get(identifier);
2885
- if (factoryProcessors) factoryProcessors.set(driveId, trackedList);
2886
- const existingDriveProcessors = this.processorsByDrive.get(driveId) ?? [];
2887
- this.processorsByDrive.set(driveId, [...existingDriveProcessors, ...trackedList]);
2888
- for (const tracked of trackedList) if (tracked.status === "active" && tracked.lastOrdinal < this.lastOrdinal) await this.backfillProcessor(tracked);
2889
- }
2890
- async backfillProcessor(tracked) {
2891
- let page = await this.operationIndex.getSinceOrdinal(tracked.lastOrdinal);
2892
- while (page.results.length > 0) {
2893
- const matching = page.results.filter((op) => matchesFilter(op, tracked.record.filter));
2894
- if (matching.length > 0) try {
2895
- await tracked.record.processor.onOperations(matching);
2896
- } catch (error) {
2897
- tracked.status = "errored";
2898
- tracked.lastError = error instanceof Error ? error.message : String(error);
2899
- tracked.lastErrorTimestamp = /* @__PURE__ */ new Date();
2900
- await this.safeSaveProcessorCursor(tracked);
2901
- this.logger.error("Processor '@ProcessorId' failed during backfill at ordinal @Ordinal: @Error", tracked.processorId, tracked.lastOrdinal, error);
2902
- return;
2903
- }
2904
- tracked.lastOrdinal = page.results[page.results.length - 1].context.ordinal;
2905
- await this.safeSaveProcessorCursor(tracked);
2906
- if (!page.next) break;
2907
- page = await page.next();
2908
- }
2909
- }
2910
- async retryProcessor(tracked) {
2911
- if (tracked.status !== "errored") return;
2912
- tracked.status = "active";
2913
- tracked.lastError = void 0;
2914
- tracked.lastErrorTimestamp = void 0;
2915
- await this.saveProcessorCursor(tracked);
2916
- await this.backfillProcessor(tracked);
2917
- }
2918
- async cleanupDriveProcessors(driveId) {
2919
- const processors = this.processorsByDrive.get(driveId);
2920
- if (!processors) return;
2921
- for (const tracked of processors) await this.safeDisconnect(tracked.record.processor);
2922
- this.processorsByDrive.delete(driveId);
2923
- for (const factoryProcessors of this.factoryToProcessors.values()) factoryProcessors.delete(driveId);
2924
- await this.deleteProcessorCursors({ driveId });
2925
- }
2926
- async safeDisconnect(processor) {
3260
+ }
3261
+ if (lowestRoutedOrdinal !== void 0) cursor.lastOrdinal = Math.max(floor, Math.min(cursor.lastOrdinal, lowestRoutedOrdinal - 1));
3262
+ const tracked = {
3263
+ processorId,
3264
+ factoryId: slot.factoryId,
3265
+ driveId: slot.driveId,
3266
+ processorIndex,
3267
+ record,
3268
+ ...cursor,
3269
+ retry: () => queue.retry()
3270
+ };
3271
+ const queue = new ProcessorQueue({
3272
+ processorId,
3273
+ processor: record.processor,
3274
+ filter: record.filter,
3275
+ cursor: tracked,
3276
+ floor,
3277
+ readSince: (ordinal) => this.operationIndex.getSinceOrdinal(ordinal),
3278
+ routedThrough: () => this.highWater(),
3279
+ persist: (state) => this.writeCursor(tracked, state),
3280
+ logger: this.logger
3281
+ });
3282
+ return {
3283
+ tracked,
3284
+ queue
3285
+ };
3286
+ }
3287
+ async discard(slot, disconnect) {
2927
3288
  try {
2928
- await processor.onDisconnect();
3289
+ await disconnect();
2929
3290
  } catch (error) {
2930
- this.logger.error("Error disconnecting processor: @Error", error);
3291
+ this.logger.error("Error disconnecting discarded processor for '@FactoryId' on '@DriveId': @Error", slot.factoryId, slot.driveId, error);
2931
3292
  }
2932
3293
  }
2933
- async routeOperationsToProcessors(operations) {
2934
- let maxOrdinal = 0;
2935
- for (const op of operations) maxOrdinal = Math.max(maxOrdinal, op.context.ordinal);
2936
- const allTracked = Array.from(this.allTrackedProcessors());
2937
- await Promise.all(allTracked.map(async (tracked) => {
2938
- if (tracked.status !== "active") return;
2939
- const matching = operations.filter((op) => op.context.ordinal > tracked.lastOrdinal).filter((op) => matchesFilter(op, tracked.record.filter));
2940
- if (matching.length > 0) try {
2941
- await tracked.record.processor.onOperations(matching);
2942
- } catch (error) {
2943
- tracked.status = "errored";
2944
- tracked.lastError = error instanceof Error ? error.message : String(error);
2945
- tracked.lastErrorTimestamp = /* @__PURE__ */ new Date();
2946
- await this.safeSaveProcessorCursor(tracked);
2947
- this.logger.error("Processor '@ProcessorId' failed at ordinal @Ordinal: @Error", tracked.processorId, tracked.lastOrdinal, error);
2948
- return;
2949
- }
2950
- tracked.lastOrdinal = maxOrdinal;
2951
- await this.safeSaveProcessorCursor(tracked);
2952
- }));
3294
+ highWater() {
3295
+ return Math.max(this.lastOrdinal, this.highestRoutedOrdinal);
3296
+ }
3297
+ *allBound() {
3298
+ for (const drive of this.processorsByDrive.values()) yield* drive;
3299
+ }
3300
+ *allTrackedProcessors() {
3301
+ for (const { tracked } of this.allBound()) yield tracked;
3302
+ }
3303
+ isDriveCreation(op) {
3304
+ return op.operation.action.type === "CREATE_DOCUMENT" && this.driveContainerTypes.has(op.context.documentType);
3305
+ }
3306
+ async discoverExistingDrives() {
3307
+ const drives = await this.db.selectFrom("DocumentSnapshot").select(["documentId", "documentType"]).where("documentType", "in", [...this.driveContainerTypes]).where("isDeleted", "=", false).execute();
3308
+ for (const drive of drives) this.knownDrives.set(drive.documentId, drive.documentType);
2953
3309
  }
2954
3310
  async loadAllCursors() {
2955
3311
  const rows = await this.db.selectFrom("ProcessorCursor").selectAll().execute();
2956
3312
  for (const row of rows) this.cursorCache.set(row.processorId, row);
2957
3313
  }
2958
- async safeSaveProcessorCursor(tracked) {
2959
- try {
2960
- await this.saveProcessorCursor(tracked);
2961
- } catch (error) {
2962
- this.logger.error("Failed to persist cursor for '@ProcessorId': @Error", tracked.processorId, error);
2963
- }
3314
+ lane(processorId, write) {
3315
+ const next = (this.cursorWrites.get(processorId) ?? Promise.resolve()).then(write).then(() => void 0, (error) => {
3316
+ this.logger.error("Failed to write cursor for '@ProcessorId': @Error", processorId, error);
3317
+ });
3318
+ this.cursorWrites.set(processorId, next);
3319
+ next.then(() => {
3320
+ if (this.cursorWrites.get(processorId) === next) this.cursorWrites.delete(processorId);
3321
+ });
3322
+ return next;
2964
3323
  }
2965
- async saveProcessorCursor(tracked) {
2966
- await this.db.insertInto("ProcessorCursor").values({
3324
+ writeCursor(tracked, state) {
3325
+ const now = /* @__PURE__ */ new Date();
3326
+ const row = {
2967
3327
  processorId: tracked.processorId,
2968
3328
  factoryId: tracked.factoryId,
2969
3329
  driveId: tracked.driveId,
2970
3330
  processorIndex: tracked.processorIndex,
2971
- lastOrdinal: tracked.lastOrdinal,
2972
- status: tracked.status,
2973
- lastError: tracked.lastError ?? null,
2974
- lastErrorTimestamp: tracked.lastErrorTimestamp ?? null,
2975
- updatedAt: /* @__PURE__ */ new Date()
3331
+ lastOrdinal: state.lastOrdinal,
3332
+ status: state.status,
3333
+ lastError: state.lastError ?? null,
3334
+ lastErrorTimestamp: state.lastErrorTimestamp ?? null,
3335
+ createdAt: now,
3336
+ updatedAt: now
3337
+ };
3338
+ this.cursorCache.set(row.processorId, row);
3339
+ return this.lane(row.processorId, () => this.db.insertInto("ProcessorCursor").values({
3340
+ processorId: row.processorId,
3341
+ factoryId: row.factoryId,
3342
+ driveId: row.driveId,
3343
+ processorIndex: row.processorIndex,
3344
+ lastOrdinal: row.lastOrdinal,
3345
+ status: row.status,
3346
+ lastError: row.lastError,
3347
+ lastErrorTimestamp: row.lastErrorTimestamp,
3348
+ updatedAt: now
2976
3349
  }).onConflict((oc) => oc.column("processorId").doUpdateSet({
2977
- lastOrdinal: tracked.lastOrdinal,
2978
- status: tracked.status,
2979
- lastError: tracked.lastError ?? null,
2980
- lastErrorTimestamp: tracked.lastErrorTimestamp ?? null,
2981
- updatedAt: /* @__PURE__ */ new Date()
2982
- })).execute();
2983
- this.cursorCache.set(tracked.processorId, {
2984
- processorId: tracked.processorId,
2985
- factoryId: tracked.factoryId,
2986
- driveId: tracked.driveId,
2987
- processorIndex: tracked.processorIndex,
2988
- lastOrdinal: tracked.lastOrdinal,
2989
- status: tracked.status,
2990
- lastError: tracked.lastError ?? null,
2991
- lastErrorTimestamp: tracked.lastErrorTimestamp ?? null,
2992
- createdAt: /* @__PURE__ */ new Date(),
2993
- updatedAt: /* @__PURE__ */ new Date()
2994
- });
2995
- }
2996
- async deleteProcessorCursors(filter) {
2997
- if ("factoryId" in filter) {
2998
- await this.db.deleteFrom("ProcessorCursor").where("factoryId", "=", filter.factoryId).execute();
2999
- for (const [id, row] of this.cursorCache) if (row.factoryId === filter.factoryId) this.cursorCache.delete(id);
3000
- } else {
3001
- await this.db.deleteFrom("ProcessorCursor").where("driveId", "=", filter.driveId).execute();
3002
- for (const [id, row] of this.cursorCache) if (row.driveId === filter.driveId) this.cursorCache.delete(id);
3003
- }
3350
+ lastOrdinal: row.lastOrdinal,
3351
+ status: row.status,
3352
+ lastError: row.lastError,
3353
+ lastErrorTimestamp: row.lastErrorTimestamp,
3354
+ updatedAt: now
3355
+ })).execute());
3356
+ }
3357
+ deleteCursors(matches) {
3358
+ const deletes = [];
3359
+ for (const [processorId, row] of this.cursorCache) {
3360
+ if (!matches(row)) continue;
3361
+ this.cursorCache.delete(processorId);
3362
+ deletes.push(this.lane(processorId, () => this.db.deleteFrom("ProcessorCursor").where("processorId", "=", processorId).execute()));
3363
+ }
3364
+ return deletes;
3004
3365
  }
3005
3366
  };
3006
3367
  //#endregion
@@ -7497,7 +7858,7 @@ var Reactor = class {
7497
7858
  this.logger.verbose("create(@id, @type, @slug)", document.header.id, document.header.documentType, document.header.slug);
7498
7859
  const createdAtUtcIso = (/* @__PURE__ */ new Date()).toISOString();
7499
7860
  throwIfAborted(signal, () => new AbortError());
7500
- let actions = [createDocumentAction({
7861
+ const createAction = createDocumentAction({
7501
7862
  model: document.header.documentType,
7502
7863
  version: 0,
7503
7864
  documentId: document.header.id,
@@ -7513,14 +7874,20 @@ var Reactor = class {
7513
7874
  branch: document.header.branch,
7514
7875
  meta: document.header.meta,
7515
7876
  protocolVersions: document.header.protocolVersions ?? { "base-reducer": 2 }
7516
- }), upgradeDocumentAction({
7877
+ });
7878
+ const upgradeAction = upgradeDocumentAction({
7517
7879
  documentId: document.header.id,
7518
7880
  model: document.header.documentType,
7519
7881
  fromVersion: 0,
7520
7882
  toVersion: normalizeDocumentModelVersion(document.state.document?.version),
7521
7883
  initialState: document.state
7522
- })];
7523
- if (signer) actions = await signActions(actions, signer, signal);
7884
+ });
7885
+ const branch = document.header.branch || "main";
7886
+ let actions = [createAction, upgradeAction];
7887
+ if (signer) actions = await signActions(actions, signer, {
7888
+ documentId: document.header.id,
7889
+ branch
7890
+ }, signal);
7524
7891
  const jobId = v4();
7525
7892
  const jobMeta = buildSingleJobMeta(jobId, meta);
7526
7893
  const job = {
@@ -7528,7 +7895,7 @@ var Reactor = class {
7528
7895
  kind: "mutation",
7529
7896
  documentId: document.header.id,
7530
7897
  scope: "document",
7531
- branch: "main",
7898
+ branch,
7532
7899
  actions,
7533
7900
  operations: [],
7534
7901
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -7559,7 +7926,10 @@ var Reactor = class {
7559
7926
  const createdAtUtcIso = (/* @__PURE__ */ new Date()).toISOString();
7560
7927
  throwIfAborted(signal, () => new AbortError());
7561
7928
  let action = deleteDocumentAction(id);
7562
- if (signer) action = await signAction(action, signer, signal);
7929
+ if (signer) action = await signAction(action, signer, {
7930
+ documentId: id,
7931
+ branch: "main"
7932
+ }, signal);
7563
7933
  const jobId = v4();
7564
7934
  const jobMeta = buildSingleJobMeta(jobId, meta);
7565
7935
  const job = {
@@ -7816,21 +8186,30 @@ var Reactor = class {
7816
8186
  this.logger.verbose("addRelationship(@sourceId, @targetId, @relationshipType, @metadata, @branch)", sourceId, targetId, relationshipType, metadata, branch);
7817
8187
  throwIfAborted(signal, () => new AbortError());
7818
8188
  let actions = [addRelationshipAction(sourceId, targetId, relationshipType, metadata)];
7819
- if (signer) actions = await signActions(actions, signer, signal);
8189
+ if (signer) actions = await signActions(actions, signer, {
8190
+ documentId: sourceId,
8191
+ branch
8192
+ }, signal);
7820
8193
  return await this.execute(sourceId, branch, actions, signal);
7821
8194
  }
7822
8195
  async updateRelationship(sourceId, targetId, relationshipType, metadata, branch = "main", signer, signal) {
7823
8196
  this.logger.verbose("updateRelationship(@sourceId, @targetId, @relationshipType, @metadata, @branch)", sourceId, targetId, relationshipType, metadata, branch);
7824
8197
  throwIfAborted(signal, () => new AbortError());
7825
8198
  let actions = [updateRelationshipAction(sourceId, targetId, relationshipType, metadata)];
7826
- if (signer) actions = await signActions(actions, signer, signal);
8199
+ if (signer) actions = await signActions(actions, signer, {
8200
+ documentId: sourceId,
8201
+ branch
8202
+ }, signal);
7827
8203
  return await this.execute(sourceId, branch, actions, signal);
7828
8204
  }
7829
8205
  async removeRelationship(sourceId, targetId, relationshipType, branch = "main", signer, signal) {
7830
8206
  this.logger.verbose("removeRelationship(@sourceId, @targetId, @relationshipType, @branch)", sourceId, targetId, relationshipType, branch);
7831
8207
  throwIfAborted(signal, () => new AbortError());
7832
8208
  let actions = [removeRelationshipAction(sourceId, targetId, relationshipType)];
7833
- if (signer) actions = await signActions(actions, signer, signal);
8209
+ if (signer) actions = await signActions(actions, signer, {
8210
+ documentId: sourceId,
8211
+ branch
8212
+ }, signal);
7834
8213
  return await this.execute(sourceId, branch, actions, signal);
7835
8214
  }
7836
8215
  getJobStatus(jobId, signal) {
@@ -7934,8 +8313,11 @@ var ReactorBuilder = class {
7934
8313
  eventBus;
7935
8314
  readModelCoordinator;
7936
8315
  readModelCoordinatorFactory;
7937
- signatureVerifier;
7938
8316
  kyselyInstance;
8317
+ signer;
8318
+ workerSigner;
8319
+ trustPolicy;
8320
+ workerTrustPolicy;
7939
8321
  signalHandlersEnabled = false;
7940
8322
  queueInstance;
7941
8323
  channelScheme;
@@ -8034,10 +8416,34 @@ var ReactorBuilder = class {
8034
8416
  this.eventBus = eventBus;
8035
8417
  return this;
8036
8418
  }
8037
- withSignatureVerifier(verifier) {
8038
- this.signatureVerifier = verifier;
8419
+ /**
8420
+ * Signs the operations the executor synthesizes: the NOOP an UNDO becomes and
8421
+ * the action a REDO rebuilds. Without one they are stored unsigned. Pooled
8422
+ * workers import `workerSigner` to build the same signer.
8423
+ */
8424
+ withSigner(signer, workerSigner) {
8425
+ this.signer = signer;
8426
+ this.workerSigner = workerSigner;
8427
+ return this;
8428
+ }
8429
+ /** Whether {@link withSigner} was called. */
8430
+ hasSigner() {
8431
+ return this.signer !== void 0;
8432
+ }
8433
+ /**
8434
+ * Decides at admission whether a key may sign as the user it claims; see
8435
+ * {@link SignatureTrustPolicy} for the contract and the default. Pooled
8436
+ * workers import `workerTrustPolicy` to build the same policy.
8437
+ */
8438
+ withTrustPolicy(trustPolicy, workerTrustPolicy) {
8439
+ this.trustPolicy = trustPolicy;
8440
+ this.workerTrustPolicy = workerTrustPolicy;
8039
8441
  return this;
8040
8442
  }
8443
+ /** Whether {@link withTrustPolicy} was called. */
8444
+ hasTrustPolicy() {
8445
+ return this.trustPolicy !== void 0;
8446
+ }
8041
8447
  withKysely(kysely) {
8042
8448
  this.kyselyInstance = kysely;
8043
8449
  return this;
@@ -8091,8 +8497,7 @@ var ReactorBuilder = class {
8091
8497
  * this enables the pool — there is no `enabled` flag. Provide `db`
8092
8498
  * (each worker opens its own Postgres pool; the parent database is built
8093
8499
  * from it too unless {@link withKysely} is set) or a custom `factory`
8094
- * transport. `verifier` is imported by the default transport's workers;
8095
- * omitted = no executor-side signature verification.
8500
+ * transport.
8096
8501
  */
8097
8502
  withWorkerPool(options) {
8098
8503
  this.workerPool = options;
@@ -8185,13 +8590,15 @@ var ReactorBuilder = class {
8185
8590
  let factory = pool.factory;
8186
8591
  if (!factory) {
8187
8592
  if (pool.db === void 0) throw new Error("unreachable: worker pool configured without db or factory");
8188
- factory = await this.createDefaultWorkerFactory(pool.numWorkers, pool.db, pool.verifier);
8593
+ if (this.signer && !this.workerSigner) this.logger.warn("Worker pool has no signer spec; pooled workers store synthesized operations unsigned");
8594
+ if (this.trustPolicy && !this.workerTrustPolicy) this.logger.warn("Worker pool has no trust policy spec; pooled workers apply the default trust policy");
8595
+ factory = await this.createDefaultWorkerFactory(pool.numWorkers, pool.db);
8189
8596
  }
8190
8597
  const poolManager = new WorkerPoolJobExecutorManager(factory, eventBus, queue, jobTracker, this.logger, resolver, operationIndex, this.executorConfig.jobTimeoutMs, this.executorConfig.deferredJobTtlMs);
8191
8598
  executorManager = poolManager;
8192
8599
  executorStartCount = pool.numWorkers;
8193
8600
  if (resolver instanceof DocumentModelResolver) resolver.setBroadcastHook((entry) => poolManager.loadModel(entry));
8194
- } else executorManager = new SimpleJobExecutorManager(() => new SimpleJobExecutor(this.logger, documentModelRegistry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, this.driveContainerTypes, this.executorConfig, this.signatureVerifier, executionScope), eventBus, queue, jobTracker, this.logger, resolver, this.executorConfig.jobTimeoutMs, this.executorConfig.deferredJobTtlMs);
8601
+ } else executorManager = new SimpleJobExecutorManager(() => new SimpleJobExecutor(this.logger, documentModelRegistry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, this.driveContainerTypes, this.executorConfig, executionScope, this.signer, this.trustPolicy), eventBus, queue, jobTracker, this.logger, resolver, this.executorConfig.jobTimeoutMs, this.executorConfig.deferredJobTtlMs);
8195
8602
  await executorManager.start(executorStartCount);
8196
8603
  const callerReadModels = Array.from(new Set([...this.readModels]));
8197
8604
  const degradedComponents = [];
@@ -8364,7 +8771,7 @@ var ReactorBuilder = class {
8364
8771
  poolInstrumentations.push(forwarder);
8365
8772
  this.instrumentedPools.push(forwarder);
8366
8773
  }
8367
- const { ProjectionShardManager } = await import("./projection-shard-manager-CUo4bF4O.js");
8774
+ const { ProjectionShardManager } = await import("./projection-shard-manager-mQhfVipO.js");
8368
8775
  const manager = new ProjectionShardManager({
8369
8776
  shardCount: config.shardCount,
8370
8777
  db,
@@ -8406,11 +8813,11 @@ var ReactorBuilder = class {
8406
8813
  * custom `factory`. Each worker spawns a real `node:worker_threads`
8407
8814
  * Worker pointing at the compiled `worker/entry.js`.
8408
8815
  */
8409
- async createDefaultWorkerFactory(numWorkers, db, signatureVerifier) {
8816
+ async createDefaultWorkerFactory(numWorkers, db) {
8410
8817
  const [{ WorkerHandle }, { createThreadTransport }, { workerEntryPath }] = await Promise.all([
8411
- import("./worker-handle-CrERzl8s.js"),
8818
+ import("./worker-handle-B3p-nuqG.js"),
8412
8819
  import("./transport-ByGviWdZ.js"),
8413
- import("./worker-g03lNM42.js").then((n) => n.n)
8820
+ import("./worker-BDVzCtuf.js").then((n) => n.n)
8414
8821
  ]);
8415
8822
  const poolConfig = {
8416
8823
  enabled: true,
@@ -8430,9 +8837,10 @@ var ReactorBuilder = class {
8430
8837
  initPayload: {
8431
8838
  poolConfig,
8432
8839
  db,
8433
- signatureVerifier,
8434
8840
  models,
8435
- executorConfig: this.executorConfig
8841
+ executorConfig: this.executorConfig,
8842
+ signer: this.workerSigner,
8843
+ trustPolicy: this.workerTrustPolicy
8436
8844
  },
8437
8845
  logger,
8438
8846
  poolInstrumentation
@@ -8506,7 +8914,7 @@ var ReactorBuilder = class {
8506
8914
  }
8507
8915
  this.logger.info("Shutdown complete");
8508
8916
  nodeProcess.exit = realExit;
8509
- realExit(pendingExitCode ?? 0);
8917
+ realExit(pendingExitCode || Number(nodeProcess.exitCode ?? 0));
8510
8918
  };
8511
8919
  nodeProcess.prependListener("SIGINT", () => void handler("SIGINT"));
8512
8920
  nodeProcess.prependListener("SIGTERM", () => void handler("SIGTERM"));
@@ -8675,30 +9083,6 @@ function createHybridProjectionCoordinatorFactory(options = {}) {
8675
9083
  };
8676
9084
  }
8677
9085
  //#endregion
8678
- //#region src/signer/passthrough-signer.ts
8679
- /**
8680
- * A no-op signer that returns empty values for all methods.
8681
- * Used when signing is not required.
8682
- */
8683
- var PassthroughSigner = class {
8684
- publicKey = {};
8685
- sign() {
8686
- return Promise.resolve(new Uint8Array(0));
8687
- }
8688
- verify() {
8689
- return Promise.resolve();
8690
- }
8691
- signAction() {
8692
- return Promise.resolve([
8693
- "",
8694
- "",
8695
- "",
8696
- "",
8697
- ""
8698
- ]);
8699
- }
8700
- };
8701
- //#endregion
8702
9086
  //#region src/core/reactor-client-builder.ts
8703
9087
  /**
8704
9088
  * Builder class for constructing ReactorClient instances with proper configuration
@@ -8711,7 +9095,10 @@ var ReactorClientBuilder = class {
8711
9095
  documentIndexer;
8712
9096
  documentView;
8713
9097
  signer;
8714
- signatureVerifier;
9098
+ workerSigner;
9099
+ trustPolicy;
9100
+ workerTrustPolicy;
9101
+ createSignaturePolicy = DEFAULT_SIGNATURE_POLICY;
8715
9102
  subscriptionManager;
8716
9103
  jobAwaiter;
8717
9104
  documentModelLoader;
@@ -8745,17 +9132,28 @@ var ReactorClientBuilder = class {
8745
9132
  return this;
8746
9133
  }
8747
9134
  /**
8748
- * Sets the signer configuration for signing and verifying actions.
8749
- *
8750
- * @param config - Either an ISigner for signing only, or a SignerConfig for both signing and verification
9135
+ * Signs submitted actions, and the operations a reactor built from
9136
+ * `withReactorBuilder` synthesizes unless that builder has its own signer.
9137
+ * A `trustPolicy` reaches that builder unless it has its own.
8751
9138
  */
8752
9139
  withSigner(config) {
8753
9140
  if ("signer" in config) {
8754
9141
  this.signer = config.signer;
8755
- this.signatureVerifier = config.verifier;
9142
+ this.workerSigner = config.workerSigner;
9143
+ this.trustPolicy = config.trustPolicy;
9144
+ this.workerTrustPolicy = config.workerTrustPolicy;
8756
9145
  } else this.signer = config;
8757
9146
  return this;
8758
9147
  }
9148
+ /**
9149
+ * What the client creates new documents as when a call does not say:
9150
+ * `v2-required` (the default) or `legacy`, for a fleet with peers that
9151
+ * predate v2-required documents. Existing documents keep their policy.
9152
+ */
9153
+ withCreateSignaturePolicy(policy) {
9154
+ this.createSignaturePolicy = policy;
9155
+ return this;
9156
+ }
8759
9157
  withSubscriptionManager(subscriptionManager) {
8760
9158
  this.subscriptionManager = subscriptionManager;
8761
9159
  return this;
@@ -8820,8 +9218,9 @@ var ReactorClientBuilder = class {
8820
9218
  let documentView;
8821
9219
  let reactorModule;
8822
9220
  if (this.reactorBuilder) {
8823
- if (this.signatureVerifier) this.reactorBuilder.withSignatureVerifier(this.signatureVerifier);
8824
9221
  if (this.documentModelLoader) this.reactorBuilder.withDocumentModelLoader(this.documentModelLoader);
9222
+ if (this.signer && !this.reactorBuilder.hasSigner()) this.reactorBuilder.withSigner(this.signer, this.workerSigner);
9223
+ if (this.trustPolicy && !this.reactorBuilder.hasTrustPolicy()) this.reactorBuilder.withTrustPolicy(this.trustPolicy, this.workerTrustPolicy);
8825
9224
  reactorModule = await this.reactorBuilder.buildModule();
8826
9225
  reactor = reactorModule.reactor;
8827
9226
  eventBus = reactorModule.eventBus;
@@ -8839,7 +9238,7 @@ var ReactorClientBuilder = class {
8839
9238
  const jobAwaiter = this.jobAwaiter ?? new JobAwaiter(eventBus, (jobId, signal) => reactor.getJobStatus(jobId, signal));
8840
9239
  const decisionModel = reactorModule ? readDecisionModel(reactorModule.featureFlags, reactorModule.documentModelRegistry) : void 0;
8841
9240
  return {
8842
- client: new ReactorClient(this.logger, reactor, signer, subscriptionManager, jobAwaiter, documentIndexer, documentView, this.readGate ?? this.resolveReadGate(reactorModule, documentView, decisionModel), this.resolveActionEvaluation(reactorModule, decisionModel)),
9241
+ client: new ReactorClient(this.logger, reactor, signer, subscriptionManager, jobAwaiter, documentIndexer, documentView, this.readGate ?? this.resolveReadGate(reactorModule, documentView, decisionModel), this.resolveActionEvaluation(reactorModule, decisionModel), this.createSignaturePolicy),
8843
9242
  reactor,
8844
9243
  eventBus,
8845
9244
  documentIndexer,
@@ -8877,6 +9276,21 @@ function driveIdFromUrl(url) {
8877
9276
  return url.split("/").pop() ?? "";
8878
9277
  }
8879
9278
  //#endregion
9279
+ //#region src/signer/types.ts
9280
+ const SIGNATURE_REFUSAL_CODES = [
9281
+ "UNSIGNED_REQUIRED",
9282
+ "UNSIGNED_IDENTITY",
9283
+ "KEY_MISMATCH",
9284
+ "MALFORMED_TUPLE",
9285
+ "TIMESTAMP_MISMATCH",
9286
+ "HASH_MISMATCH",
9287
+ "BAD_SIGNATURE",
9288
+ "SCHEME_BELOW_POLICY",
9289
+ "ID_MISMATCH",
9290
+ "DUPLICATE_ACTION",
9291
+ "SIGNER_UNAUTHORIZED"
9292
+ ];
9293
+ //#endregion
8880
9294
  //#region src/decision/sync-scope-gate.ts
8881
9295
  /** What a subject may read of a document that is not there to be read. */
8882
9296
  const META_ONLY = (scope) => ALWAYS_READABLE_SCOPES.has(scope);
@@ -9104,6 +9518,6 @@ var DocumentIntegrityService = class {
9104
9518
  }
9105
9519
  };
9106
9520
  //#endregion
9107
- export { ALWAYS_READABLE_SCOPES, APPEND_CONDITION_FAILED_PREFIX, AppendConditionFailedError, AuthEnforcementDisabledError, BareReadGate, BaseReadModel, ChannelError, ChannelErrorSource, ChannelScheme, ConsistencyTracker, DEFAULT_COMMIT_CHUNK_SIZE, DEFAULT_DRIVE_CONTAINER_TYPES, DEFAULT_READ_MODEL_YIELD_DEADLINE_MS, DOCUMENT_INDEXER_READ_MODEL, DOCUMENT_VIEW_READ_MODEL, DRIVE_AUTH_ERROR_MESSAGES, DefaultSubscriptionErrorHandler, DeletedDocumentRead, DocumentAlreadyExistsError, DocumentChangeType, DocumentExistence, DocumentIntegrityService, DocumentModelRegistry, DocumentModelResolver, DriveClient, DriveCollectionId, DriveRequestError, DuplicateManifestError, DuplicateModuleError, DuplicateOperationError, EventBus, EventBusAggregateError, GqlRequestChannel, GqlRequestChannelFactory, GqlResponseChannel, GqlResponseChannelFactory, HybridProjectionCoordinator, SimpleJobExecutor as InMemoryJobExecutor, SimpleJobExecutor, InMemoryJobTracker, InMemoryQueue, IntervalPollTimer, InvalidModuleError, JobAwaiter, JobExecutorEventTypes, JobStatus, KyselyDocumentIndexer, KyselyDocumentView, KyselyKeyframeStore, KyselyOperationStore, KyselySyncCursorStorage, KyselySyncRemoteStorage, KyselyWriteCache, Mailbox, ModelReadGate, ModuleNotFoundError, NullDocumentModelResolver, OptimisticLockError, PollBehavior, PollingChannelError, ProcessorManager, PropagationMode, QueueEventTypes, REACTOR_SCHEMA, RECOVERABLE_GRAPHQL_ERROR_CODES, Reactor, ReactorBuilder, ReactorClient, ReactorClientBuilder, ReactorEventTypes, ReactorSubscriptionManager, ReadModelCoordinator, RelationalDbProcessor, RelationshipChangeType, RelationshipNotFoundError, RetryAccounting, RevisionMismatchError, SeededStateReader, SimpleJobExecutorManager, SyncBuilder, SyncEventTypes, SyncOperation, SyncOperationAggregateError, SyncOperationStatus, SyncScopeGate, SyncStatus, SyncStatusTracker, addRelationshipAction, authDecisionModel, batchOperationsByDocument, buildDecisionModel, classifyJobFailure, consolidateSyncOperations, createDocumentAction, createForwardingLogger, createHybridProjectionCoordinatorFactory, createMutableShutdownStatus, createReactorHostModuleBase, createRelationalDb, decideAtHead, defaultReadModelIndexingConfig, deleteDocumentAction, documentActions, documentDecisionModel, driveIdFromUrl, envelopesToSyncOperations, errorToInfo, firstOutOfOrderPair, getMigrationStatus, instrumentPgPool, isDriveAuthError, isRecoverableGraphQLError, makeConsistencyKey, parseDriveUrl, parsePagingOptions, quarantinesDocument, readDecisionModel, removeRelationshipAction, runMigrations, sanitizeArg, selectDecisionModel, supportsLiveReadModelRegistration, syncOperationErrorType, trimMailboxFromAckOrdinal, unchunkedReadModelIndexingConfig, updateRelationshipAction, upgradeDocumentAction, workerEntryPath };
9521
+ export { ALWAYS_READABLE_SCOPES, APPEND_CONDITION_FAILED_PREFIX, AppendConditionFailedError, AuthEnforcementDisabledError, BareReadGate, BaseReadModel, ChannelError, ChannelErrorSource, ChannelScheme, ConsistencyTracker, DEFAULT_COMMIT_CHUNK_SIZE, DEFAULT_DRIVE_CONTAINER_TYPES, DEFAULT_READ_MODEL_YIELD_DEADLINE_MS, DOCUMENT_INDEXER_READ_MODEL, DOCUMENT_VIEW_READ_MODEL, DRIVE_AUTH_ERROR_MESSAGES, DefaultSubscriptionErrorHandler, DeletedDocumentRead, DocumentAlreadyExistsError, DocumentChangeType, DocumentExistence, DocumentIntegrityService, DocumentModelRegistry, DocumentModelResolver, DriveClient, DriveCollectionId, DriveRequestError, DuplicateManifestError, DuplicateModuleError, DuplicateOperationError, EventBus, EventBusAggregateError, GqlRequestChannel, GqlRequestChannelFactory, GqlResponseChannel, GqlResponseChannelFactory, HybridProjectionCoordinator, SimpleJobExecutor as InMemoryJobExecutor, SimpleJobExecutor, InMemoryJobTracker, InMemoryQueue, IntervalPollTimer, InvalidModuleError, InvalidSignatureError, JobAwaiter, JobExecutorEventTypes, JobStatus, KyselyDocumentIndexer, KyselyDocumentView, KyselyKeyframeStore, KyselyOperationStore, KyselySyncCursorStorage, KyselySyncRemoteStorage, KyselyWriteCache, Mailbox, ModelReadGate, ModuleNotFoundError, NullDocumentModelResolver, OptimisticLockError, PollBehavior, PollingChannelError, ProcessorManager, PropagationMode, QueueEventTypes, REACTOR_SCHEMA, RECOVERABLE_GRAPHQL_ERROR_CODES, Reactor, ReactorBuilder, ReactorClient, ReactorClientBuilder, ReactorEventTypes, ReactorSubscriptionManager, ReadModelCoordinator, RelationalDbProcessor, RelationshipChangeType, RelationshipNotFoundError, RetryAccounting, RevisionMismatchError, SIGNATURE_REFUSAL_CODES, SeededStateReader, SimpleJobExecutorManager, SyncBuilder, SyncEventTypes, SyncOperation, SyncOperationAggregateError, SyncOperationStatus, SyncScopeGate, SyncStatus, SyncStatusTracker, addRelationshipAction, authDecisionModel, batchOperationsByDocument, buildDecisionModel, classifyJobFailure, consolidateSyncOperations, createDocumentAction, createForwardingLogger, createHybridProjectionCoordinatorFactory, createMutableShutdownStatus, createReactorHostModuleBase, createRelationalDb, decideAtHead, defaultReadModelIndexingConfig, deleteDocumentAction, documentActions, documentDecisionModel, driveIdFromUrl, envelopesToSyncOperations, errorToInfo, firstOutOfOrderPair, getMigrationStatus, instrumentPgPool, isDriveAuthError, isRecoverableGraphQLError, makeConsistencyKey, parseDriveUrl, parsePagingOptions, quarantinesDocument, readDecisionModel, removeRelationshipAction, runMigrations, sanitizeArg, selectDecisionModel, supportsLiveReadModelRegistration, syncOperationErrorType, trimMailboxFromAckOrdinal, unchunkedReadModelIndexingConfig, updateRelationshipAction, upgradeDocumentAction, verifyActionSignature, workerEntryPath };
9108
9522
 
9109
9523
  //# sourceMappingURL=index.js.map