@powerhousedao/reactor 6.2.3-dev.9 → 6.2.3

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 (42) hide show
  1. package/dist/{build-worker-executor-BN30c2xw.js → build-worker-executor-BonPsKdx.js} +24 -8
  2. package/dist/build-worker-executor-BonPsKdx.js.map +1 -0
  3. package/dist/{document-indexer-CDX1D2RZ.js → document-indexer-I6i_YzsR.js} +866 -87
  4. package/dist/document-indexer-I6i_YzsR.js.map +1 -0
  5. package/dist/entry.d.ts +2 -1
  6. package/dist/entry.js +10 -6
  7. package/dist/entry.js.map +1 -1
  8. package/dist/{errors-D3S6Eysd.js → errors-BM_ULVWd.js} +9 -1
  9. package/dist/{errors-D3S6Eysd.js.map → errors-BM_ULVWd.js.map} +1 -1
  10. package/dist/{drive-container-types-aO-PASn7.js → forwarding-logger-BkwlvD9W.js} +1727 -306
  11. package/dist/forwarding-logger-BkwlvD9W.js.map +1 -0
  12. package/dist/index.d.ts +3946 -2817
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +2557 -712
  15. package/dist/index.js.map +1 -1
  16. package/dist/projection-entry.d.ts +2 -1
  17. package/dist/projection-entry.js +62 -20
  18. package/dist/projection-entry.js.map +1 -1
  19. package/dist/{projection-shard-manager-CPQc5XHL.js → projection-shard-manager-CJIdjsd8.js} +24 -4
  20. package/dist/projection-shard-manager-CJIdjsd8.js.map +1 -0
  21. package/dist/{projection-worker-wI4PwcV2.js → projection-worker-D8uNFdwX.js} +1 -1
  22. package/dist/{projection-worker-wI4PwcV2.js.map → projection-worker-D8uNFdwX.js.map} +1 -1
  23. package/dist/{transport-CuogVKN_.js → transport-B3G50KVd.js} +1 -1
  24. package/dist/{transport-CuogVKN_.js.map → transport-B3G50KVd.js.map} +1 -1
  25. package/dist/{transport-ByGviWdZ.js → transport-CZDHzfvy.js} +1 -1
  26. package/dist/{transport-ByGviWdZ.js.map → transport-CZDHzfvy.js.map} +1 -1
  27. package/dist/{worker-handle-CrERzl8s.js → worker-handle-DjbEQkq-.js} +22 -16
  28. package/dist/worker-handle-DjbEQkq-.js.map +1 -0
  29. package/package.json +20 -10
  30. package/dist/build-worker-executor-BN30c2xw.js.map +0 -1
  31. package/dist/document-indexer-CDX1D2RZ.js.map +0 -1
  32. package/dist/drive-container-types-aO-PASn7.js.map +0 -1
  33. package/dist/error-info-Cpu4OY3o.js +0 -62
  34. package/dist/error-info-Cpu4OY3o.js.map +0 -1
  35. package/dist/forwarding-logger-BBkMSxuJ.js +0 -85
  36. package/dist/forwarding-logger-BBkMSxuJ.js.map +0 -1
  37. package/dist/projection-shard-manager-CPQc5XHL.js.map +0 -1
  38. package/dist/types-DMKLa0Ok.js +0 -33
  39. package/dist/types-DMKLa0Ok.js.map +0 -1
  40. package/dist/worker-Bb0u-X9m.js +0 -22
  41. package/dist/worker-Bb0u-X9m.js.map +0 -1
  42. package/dist/worker-handle-CrERzl8s.js.map +0 -1
@@ -1,7 +1,6 @@
1
- import { n as ReactorEventTypes, t as EventBusAggregateError } from "./types-DMKLa0Ok.js";
2
- import { AUTH_ACTION_TYPES, AUTH_DENIED_BY_GRANT_REASON, AUTH_NO_GRANT_REASON, AUTH_VERSION_UNSUPPORTED_REASON, DOCUMENT_DELETED_REASON, DowngradeNotSupportedError as DowngradeNotSupportedError$1, appendWithoutApplying, applyAuthAction, applyDeleteDocumentAction, applyDeleteDocumentAction as applyDeleteDocumentAction$1, applyUpgradeDocumentAction, applyUpgradeDocumentAction as applyUpgradeDocumentAction$1, baseReducerVersion, createPresignedHeader, decide, defaultBaseState, deriveOperationId, evaluate, garbageCollect, groupDocumentType, groupMembershipActionTypes, hashDocumentStateForScope, isDenied, isUndoRedo, mentionedGroupIds, normalizeDocumentModelVersion, referencedGroupIds, sortOperations } from "@powerhousedao/shared/document-model";
1
+ import { AUTH_ACTION_TYPES, AUTH_DENIED_BY_GRANT_REASON, AUTH_NO_GRANT_REASON, AUTH_VERSION_UNSUPPORTED_REASON, DOCUMENT_DELETED_REASON, DOCUMENT_SCOPE_ACTION_TYPES, DowngradeNotSupportedError as DowngradeNotSupportedError$1, PEER_CAPABILITIES, actionSignerIdentity, actionSigningTarget, appendWithoutApplying, applyAuthAction, applyDeleteDocumentAction, applyDeleteDocumentAction as applyDeleteDocumentAction$1, applyUpgradeDocumentAction, applyUpgradeDocumentAction as applyUpgradeDocumentAction$1, base58Decode, baseReducerVersion, buildOperationSignatureMessage, buildOperationSignatureParams, canonicalJson, createPresignedHeader, decide, defaultBaseState, deriveDocumentId, deriveOperationId, evaluate, garbageCollect, groupDocumentType, groupMembershipActionTypes, hashActionV2, hashBrowser, hashDocumentStateForScope, isDenied, isDerivedDocumentId, isUndoRedo, isV2ActionHash, localSupports, mentionedGroupIds, normalizeDocumentModelVersion, operationOutcome, referencedGroupIds, signaturePolicyOf, sortOperations, targetDocumentId, v2TupleProblem, withProtocolVersions } from "@powerhousedao/shared/document-model";
3
2
  import { v4 } from "uuid";
4
- import { Migrator, sql } from "kysely";
3
+ import { Migrator, TableNode, sql } from "kysely";
5
4
  //#region \0rolldown/runtime.js
6
5
  var __defProp = Object.defineProperty;
7
6
  var __exportAll = (all, no_symbols) => {
@@ -52,6 +51,38 @@ function parsePagingOptions(paging, defaultLimit) {
52
51
  };
53
52
  }
54
53
  //#endregion
54
+ //#region src/events/types.ts
55
+ /**
56
+ * Custom error class that aggregates multiple errors from event subscribers.
57
+ */
58
+ var EventBusAggregateError = class extends Error {
59
+ errors;
60
+ constructor(errors) {
61
+ const message = `EventBus emit failed with ${errors.length} error(s): ${errors.map((e) => {
62
+ if (e && typeof e === "object" && "message" in e) return e.message;
63
+ return String(e);
64
+ }).join("; ")}`;
65
+ super(message);
66
+ this.name = "EventBusAggregateError";
67
+ this.errors = errors;
68
+ }
69
+ };
70
+ /**
71
+ * Event types for reactor lifecycle events.
72
+ */
73
+ const ReactorEventTypes = {
74
+ JOB_PENDING: 10001,
75
+ JOB_RUNNING: 10002,
76
+ JOB_WRITE_READY: 10003,
77
+ JOB_READ_READY: 10004,
78
+ JOB_FAILED: 10005,
79
+ READMODEL_BATCH_COMPLETED: 10006,
80
+ READMODEL_INDEXED: 10007,
81
+ MODEL_LOADED: 10008,
82
+ SIGNATURE_REFUSED: 10009,
83
+ CATCHUP_SWEPT: 10010
84
+ };
85
+ //#endregion
55
86
  //#region src/shared/errors.ts
56
87
  /**
57
88
  * Error thrown when attempting to access a deleted document.
@@ -143,6 +174,23 @@ var InvalidOperationTimestampError = class InvalidOperationTimestampError extend
143
174
  return Error.isError(error) && error.name === "InvalidOperationTimestampError";
144
175
  }
145
176
  };
177
+ /** A document requires a protocol version this reactor does not run. Terminal. */
178
+ var UnsupportedProtocolVersionError = class UnsupportedProtocolVersionError extends Error {
179
+ documentId;
180
+ protocol;
181
+ version;
182
+ constructor(documentId, protocol, version) {
183
+ super(`Document ${documentId} requires ${protocol} ${version}, which this reactor does not support`);
184
+ this.name = "UnsupportedProtocolVersionError";
185
+ this.documentId = documentId;
186
+ this.protocol = protocol;
187
+ this.version = version;
188
+ Error.captureStackTrace(this, UnsupportedProtocolVersionError);
189
+ }
190
+ static isError(error) {
191
+ return Error.isError(error) && error.name === "UnsupportedProtocolVersionError";
192
+ }
193
+ };
146
194
  /**
147
195
  * A load would move more operations than the bound allows, indicating a real
148
196
  * divergence between local and incoming history. Counts only first-time moves,
@@ -167,19 +215,22 @@ var ExcessiveReshuffleError = class ExcessiveReshuffleError extends Error {
167
215
  return Error.isError(error) && error.name === "ExcessiveReshuffleError";
168
216
  }
169
217
  };
170
- /**
171
- * Error thrown when an operation has an invalid signature.
172
- */
218
+ /** The message carries the code: only name and message cross the queue. */
173
219
  var InvalidSignatureError = class InvalidSignatureError extends Error {
174
220
  documentId;
221
+ code;
175
222
  reason;
176
- constructor(documentId, reason) {
177
- super(`Invalid signature in document ${documentId}: ${reason}`);
223
+ constructor(documentId, code, reason) {
224
+ super(`Invalid signature in document ${documentId} [${code}]: ${reason}`);
178
225
  this.name = "InvalidSignatureError";
179
226
  this.documentId = documentId;
227
+ this.code = code;
180
228
  this.reason = reason;
181
229
  Error.captureStackTrace(this, InvalidSignatureError);
182
230
  }
231
+ static isError(error) {
232
+ return Error.isError(error) && error.name === "InvalidSignatureError";
233
+ }
183
234
  };
184
235
  /**
185
236
  * An UPGRADE_DOCUMENT action's preconditions (fromVersion and the per-scope
@@ -250,6 +301,46 @@ var AuthEnforcementDisabledError = class AuthEnforcementDisabledError extends Er
250
301
  return Error.isError(error) && error.name === "AuthEnforcementDisabledError";
251
302
  }
252
303
  };
304
+ /**
305
+ * Error thrown when a relationship edge an operation names does not exist.
306
+ *
307
+ * Detection is by `name`, not `instanceof`: the SharedWorker RPC boundary
308
+ * rebuilds a thrown error from `{ name, message, stack, cause }` alone
309
+ * (`reactor-browser/src/rpc/error-info.ts`), so the class identity is lost in
310
+ * transit.
311
+ */
312
+ var RelationshipNotFoundError = class RelationshipNotFoundError extends Error {
313
+ sourceId;
314
+ targetId;
315
+ relationshipType;
316
+ constructor(sourceId, targetId, relationshipType) {
317
+ super(`No ${relationshipType} relationship from ${sourceId} to ${targetId}`);
318
+ this.name = "RelationshipNotFoundError";
319
+ this.sourceId = sourceId;
320
+ this.targetId = targetId;
321
+ this.relationshipType = relationshipType;
322
+ Error.captureStackTrace(this, RelationshipNotFoundError);
323
+ }
324
+ static isError(error) {
325
+ return Error.isError(error) && error.name === "RelationshipNotFoundError";
326
+ }
327
+ };
328
+ /** The store holds documents at protocol versions this reactor does not run. */
329
+ var UnsupportedStoredProtocolError = class UnsupportedStoredProtocolError extends Error {
330
+ versions;
331
+ documents;
332
+ constructor(versions, documents) {
333
+ const named = versions.map(({ protocol, version }) => `${protocol} ${version}`).join(", ");
334
+ super(`${documents} stored document(s) require ${named}, which this reactor does not run. Start a build that runs them, or accept them read-only with withUnsupportedStoredDocuments("read-only")`);
335
+ this.name = "UnsupportedStoredProtocolError";
336
+ this.versions = versions;
337
+ this.documents = documents;
338
+ Error.captureStackTrace(this, UnsupportedStoredProtocolError);
339
+ }
340
+ static isError(error) {
341
+ return Error.isError(error) && error.name === "UnsupportedStoredProtocolError";
342
+ }
343
+ };
253
344
  //#endregion
254
345
  //#region src/storage/interfaces.ts
255
346
  /**
@@ -302,6 +393,7 @@ const APPEND_CONDITION_FAILED_PREFIX = "Append condition failed: ";
302
393
  * conflict, not a fault: the caller retries against the new stream heads.
303
394
  */
304
395
  var AppendConditionFailedError = class extends Error {
396
+ condition;
305
397
  constructor(condition) {
306
398
  const streams = condition.streams.map((s) => `${s.documentId}:${s.scope}:${s.branch}@${s.revision}`).join(", ");
307
399
  super(`${APPEND_CONDITION_FAILED_PREFIX}a read-set stream advanced [${streams}]`);
@@ -335,6 +427,52 @@ let DocumentExistence = /* @__PURE__ */ function(DocumentExistence) {
335
427
  return DocumentExistence;
336
428
  }({});
337
429
  //#endregion
430
+ //#region src/cache/operation-index-types.ts
431
+ const DRIVE_COLLECTION_PREFIX = "drive.";
432
+ /**
433
+ * Identifies the collection a remote synchronizes. Collections are drive-level
434
+ * abstractions (document-drive and reactor-drive), so a collection id is the
435
+ * drive document id plus the branch it scopes to rather than an opaque string.
436
+ *
437
+ * The canonical string form (`drive.${branch}.${driveId}`) is produced only by
438
+ * `key` and parsed only by `fromKey`; that string is the wire and storage
439
+ * representation and is byte-for-byte identical to the legacy
440
+ * `driveCollectionId(branch, driveId)` output, so existing `document_collections`
441
+ * rows and persisted remotes remain valid without migration.
442
+ */
443
+ var DriveCollectionId = class DriveCollectionId {
444
+ driveId;
445
+ branch;
446
+ constructor(driveId, branch) {
447
+ this.driveId = driveId;
448
+ this.branch = branch;
449
+ }
450
+ static forDrive(driveId, branch = "main") {
451
+ return new DriveCollectionId(driveId, branch);
452
+ }
453
+ /**
454
+ * The single deserializer for the wire/storage form. `branch` may contain
455
+ * dots, while `driveId` is a dot-free document id, so the drive id is the
456
+ * final dot-delimited segment.
457
+ */
458
+ static fromKey(key) {
459
+ if (!key.startsWith(DRIVE_COLLECTION_PREFIX)) throw new Error(`Unsupported collection id: ${key}`);
460
+ const rest = key.slice(6);
461
+ const lastDot = rest.lastIndexOf(".");
462
+ if (lastDot === -1 || lastDot === rest.length - 1) throw new Error(`Malformed drive collection id: ${key}`);
463
+ return new DriveCollectionId(rest.slice(lastDot + 1), rest.slice(0, lastDot));
464
+ }
465
+ get key() {
466
+ return `${DRIVE_COLLECTION_PREFIX}${this.branch}.${this.driveId}`;
467
+ }
468
+ toString() {
469
+ return this.key;
470
+ }
471
+ equals(other) {
472
+ return this.driveId === other.driveId && this.branch === other.branch;
473
+ }
474
+ };
475
+ //#endregion
338
476
  //#region src/decision/build-decision-model.ts
339
477
  /**
340
478
  * Reads each projection's stream through the supplied reader, recording the
@@ -690,34 +828,13 @@ function selectDecisionModel(flags, registry) {
690
828
  //#endregion
691
829
  //#region src/executor/util.ts
692
830
  /** Actions the reactor reduces itself, onto the document scope. */
693
- const DOCUMENT_SCOPE_ACTIONS = new Set([
694
- "CREATE_DOCUMENT",
695
- "DELETE_DOCUMENT",
696
- "UPGRADE_DOCUMENT",
697
- "ADD_RELATIONSHIP",
698
- "REMOVE_RELATIONSHIP",
699
- "UPDATE_RELATIONSHIP"
700
- ]);
831
+ const DOCUMENT_SCOPE_ACTIONS = DOCUMENT_SCOPE_ACTION_TYPES;
701
832
  /**
702
833
  * `CREATE_DOCUMENT` is exempt by necessity: it runs before the document exists,
703
834
  * so building a decision model would throw and defer the job forever.
704
835
  */
705
836
  const GATED_DOCUMENT_ACTIONS = new Set([...DOCUMENT_SCOPE_ACTIONS].filter((type) => type !== "CREATE_DOCUMENT"));
706
837
  /**
707
- * The document a document-scope action writes to, which is not always the job's
708
- * own document: delete and upgrade name it in `input.documentId`, and the
709
- * relationship actions in `input.sourceId`. `execute` only checks that a batch
710
- * shares one scope, so a caller can submit an action whose target is a document
711
- * other than the one the job is keyed by. The policy gate has to follow the
712
- * action rather than the job, or it decides against a policy the caller may
713
- * control instead of the one guarding the write.
714
- */
715
- function targetDocumentId(action, fallback) {
716
- const input = action.input;
717
- if (action.type === "ADD_RELATIONSHIP" || action.type === "REMOVE_RELATIONSHIP" || action.type === "UPDATE_RELATIONSHIP") return typeof input?.sourceId === "string" && input.sourceId.length > 0 ? input.sourceId : fallback;
718
- return typeof input?.documentId === "string" && input.documentId.length > 0 ? input.documentId : fallback;
719
- }
720
- /**
721
838
  * Creates a PHDocument from a CREATE_DOCUMENT action input.
722
839
  * Reconstructs the document header and initializes the base state.
723
840
  *
@@ -896,6 +1013,40 @@ var TouchedStreams = class {
896
1013
  return this.streams.values();
897
1014
  }
898
1015
  };
1016
+ /**
1017
+ * The ids of the actions the caller handed to a job. Load and reevaluation
1018
+ * jobs write operations nobody submitted, so they report none.
1019
+ */
1020
+ function submittedActionIds(job) {
1021
+ return job.kind === "mutation" ? job.actions.map((action) => action.id) : [];
1022
+ }
1023
+ /**
1024
+ * Reports what became of each submitted action.
1025
+ *
1026
+ * A job's operations can include ones it only moved to a new index, so only
1027
+ * those carrying a submitted action are reported. Returns undefined when the
1028
+ * job submitted nothing, which keeps `JobInfo.result` null for the jobs that
1029
+ * have no caller to answer to.
1030
+ */
1031
+ function summarizeSubmittedActions(operations, submitted) {
1032
+ if (!submitted || submitted.length === 0) return;
1033
+ const ids = new Set(submitted);
1034
+ const actions = [];
1035
+ for (const { operation, context } of operations) {
1036
+ if (!ids.has(operation.action.id)) continue;
1037
+ actions.push({
1038
+ actionId: operation.action.id,
1039
+ scope: context.scope,
1040
+ index: operation.index,
1041
+ ...operationOutcome(operation)
1042
+ });
1043
+ }
1044
+ if (actions.length === 0) return;
1045
+ return {
1046
+ actions,
1047
+ allApplied: actions.every((action) => action.kind === "applied")
1048
+ };
1049
+ }
899
1050
  //#endregion
900
1051
  //#region src/registry/errors.ts
901
1052
  /**
@@ -993,6 +1144,7 @@ const JobExecutorEventTypes = {
993
1144
  //#endregion
994
1145
  //#region src/cache/collection-membership-cache.ts
995
1146
  var CollectionMembershipCache = class CollectionMembershipCache {
1147
+ operationIndex;
996
1148
  cache = /* @__PURE__ */ new Map();
997
1149
  constructor(operationIndex) {
998
1150
  this.operationIndex = operationIndex;
@@ -1206,10 +1358,231 @@ var DocumentMetaCache = class DocumentMetaCache {
1206
1358
  return {
1207
1359
  state: document.state.document,
1208
1360
  documentType,
1361
+ protocolVersions: createAction.input.protocolVersions,
1209
1362
  documentScopeRevision: documentScopeRevision + 1
1210
1363
  };
1211
1364
  }
1212
1365
  };
1366
+ //#endregion
1367
+ //#region src/catch-up/settled-watermark.ts
1368
+ const MAX_PENDING_PROBES = 64;
1369
+ /** pg_sequence_last_value needs 10; pg_current_* replaced txid_* in 13. */
1370
+ function snapshotFunctionsFor(serverVersionNum) {
1371
+ if (serverVersionNum >= 13e4) return {
1372
+ snapshot: "pg_current_snapshot",
1373
+ currentXid: "pg_current_xact_id",
1374
+ xidIfAssigned: "pg_current_xact_id_if_assigned"
1375
+ };
1376
+ if (serverVersionNum >= 1e5) return {
1377
+ snapshot: "txid_current_snapshot",
1378
+ currentXid: "txid_current",
1379
+ xidIfAssigned: "txid_current_if_assigned"
1380
+ };
1381
+ throw new Error(`PostgreSQL server_version_num ${serverVersionNum} is not supported: read-side catch-up needs 10 or later`);
1382
+ }
1383
+ /** Parses the `xmin:xmax:xip,...` text form of a snapshot. */
1384
+ function parseSnapshot(text) {
1385
+ const parts = text.split(":");
1386
+ if (parts.length !== 3) throw new Error(`Malformed snapshot: ${text}`);
1387
+ const [xmin, xmax, xip] = parts;
1388
+ return {
1389
+ xmin: BigInt(xmin),
1390
+ xmax: BigInt(xmax),
1391
+ xip: xip === "" ? [] : xip.split(",").map((xid) => BigInt(xid))
1392
+ };
1393
+ }
1394
+ async function readSnapshotFunctions(db) {
1395
+ const result = await sql`select current_setting('server_version_num') as version`.execute(db);
1396
+ return snapshotFunctionsFor(Number(result.rows[0].version));
1397
+ }
1398
+ /** A probe settles once an xmin passes the xid it took after its head. */
1399
+ var ProbeSettler = class {
1400
+ settled = 0;
1401
+ lastHead = 0;
1402
+ covered = 0;
1403
+ lastXip = [];
1404
+ pending = [];
1405
+ get settledThrough() {
1406
+ return this.settled;
1407
+ }
1408
+ get head() {
1409
+ return this.lastHead;
1410
+ }
1411
+ /** The highest head a settled or pending probe covers. */
1412
+ get probedHead() {
1413
+ return this.covered;
1414
+ }
1415
+ observe(head, xid, snapshot, nowUtcMs) {
1416
+ this.lastHead = Math.max(this.lastHead, head);
1417
+ this.lastXip = snapshot.xip;
1418
+ if (xid !== null && head > this.covered) {
1419
+ this.covered = head;
1420
+ this.pending.push({
1421
+ head,
1422
+ xid,
1423
+ takenAtUtcMs: nowUtcMs
1424
+ });
1425
+ if (this.pending.length > MAX_PENDING_PROBES) this.pending.shift();
1426
+ }
1427
+ const stillPending = [];
1428
+ for (const probe of this.pending) if (snapshot.xmin > probe.xid) this.settled = Math.max(this.settled, probe.head);
1429
+ else stillPending.push(probe);
1430
+ this.pending = stillPending.filter((probe) => probe.head > this.settled);
1431
+ return this.settled;
1432
+ }
1433
+ /** Xids an unsettled probe still waits on. */
1434
+ waitingOn() {
1435
+ if (this.pending.length === 0) return [];
1436
+ const xid = this.pending[0].xid;
1437
+ return this.lastXip.filter((open) => open < xid).map((open) => open.toString());
1438
+ }
1439
+ stalledSinceUtcMs() {
1440
+ return this.pending[0]?.takenAtUtcMs;
1441
+ }
1442
+ };
1443
+ function operationIndexTableName(db) {
1444
+ const from = db.selectFrom("operation_index_operations").selectAll().toOperationNode().from?.froms[0];
1445
+ const schema = from !== void 0 && TableNode.is(from) ? from.table.schema?.name : void 0;
1446
+ const quote = (identifier) => `"${identifier.replace(/"/g, "\"\"")}"`;
1447
+ return schema === void 0 ? quote("operation_index_operations") : `${quote(schema)}.${quote("operation_index_operations")}`;
1448
+ }
1449
+ /** Reads the head, takes and commits an xid, then reads a snapshot, in order. */
1450
+ function createKyselyWatermarkProbe(db) {
1451
+ let resolved = null;
1452
+ const resolve = async () => {
1453
+ const fns = await readSnapshotFunctions(db);
1454
+ const table = operationIndexTableName(db);
1455
+ const sequence = (await sql`select pg_get_serial_sequence(${table}, 'ordinal') as sequence`.execute(db)).rows[0]?.sequence;
1456
+ if (!sequence) throw new Error(`No ordinal sequence found for ${table}`);
1457
+ return {
1458
+ fns,
1459
+ sequence
1460
+ };
1461
+ };
1462
+ return async (probedHead) => {
1463
+ resolved ??= resolve().catch((error) => {
1464
+ resolved = null;
1465
+ throw error;
1466
+ });
1467
+ const { fns, sequence } = await resolved;
1468
+ const headRow = (await sql`select coalesce(pg_sequence_last_value(${sequence}::regclass), 0) as head, ${sql.raw(fns.xidIfAssigned)}() is null as outside_write`.execute(db)).rows[0];
1469
+ const head = Number(headRow.head);
1470
+ let xid = null;
1471
+ if (headRow.outside_write && head > probedHead) {
1472
+ const xidResult = await sql`select ${sql.raw(fns.currentXid)}()::text as xid`.execute(db);
1473
+ xid = BigInt(xidResult.rows[0].xid);
1474
+ }
1475
+ const row = (await sql`select ${sql.raw(fns.snapshot)}()::text as snapshot, ${sql.raw(fns.xidIfAssigned)}() is null as outside_write`.execute(db)).rows[0];
1476
+ return {
1477
+ head,
1478
+ xid,
1479
+ snapshot: parseSnapshot(row.snapshot),
1480
+ outsideWrite: headRow.outside_write && row.outside_write
1481
+ };
1482
+ };
1483
+ }
1484
+ /** Knows when an ordinal gap is final by asking which transactions are open. */
1485
+ var SettledWatermark = class {
1486
+ probe;
1487
+ logger;
1488
+ now;
1489
+ settler = new ProbeSettler();
1490
+ listeners = /* @__PURE__ */ new Set();
1491
+ current;
1492
+ next;
1493
+ warnedInsideWrite = false;
1494
+ constructor(probe, logger, now = Date.now) {
1495
+ this.probe = probe;
1496
+ this.logger = logger;
1497
+ this.now = now;
1498
+ }
1499
+ get settledThrough() {
1500
+ return this.settler.settledThrough;
1501
+ }
1502
+ refresh(signal) {
1503
+ signal?.throwIfAborted();
1504
+ if (this.next !== void 0) return this.next;
1505
+ if (this.current === void 0) return this.start();
1506
+ const next = this.current.catch(() => this.settledThrough).then(() => {
1507
+ this.next = void 0;
1508
+ return this.start();
1509
+ });
1510
+ this.next = next;
1511
+ return next;
1512
+ }
1513
+ onAdvance(listener) {
1514
+ this.listeners.add(listener);
1515
+ return () => {
1516
+ this.listeners.delete(listener);
1517
+ };
1518
+ }
1519
+ status() {
1520
+ const stalledSinceUtcMs = this.settler.head > this.settledThrough ? this.settler.stalledSinceUtcMs() : void 0;
1521
+ return {
1522
+ head: this.settler.head,
1523
+ settledThrough: this.settledThrough,
1524
+ waitingOn: this.settler.waitingOn(),
1525
+ ...stalledSinceUtcMs !== void 0 ? { stalledSinceUtcMs } : {}
1526
+ };
1527
+ }
1528
+ start() {
1529
+ const run = this.probeOnce().finally(() => {
1530
+ if (this.current === run) this.current = void 0;
1531
+ });
1532
+ this.current = run;
1533
+ return run;
1534
+ }
1535
+ async probeOnce() {
1536
+ const reading = await this.probe(this.settler.probedHead);
1537
+ if (!reading.outsideWrite) {
1538
+ if (!this.warnedInsideWrite) {
1539
+ this.warnedInsideWrite = true;
1540
+ this.logger.warn("settled watermark probe ran inside another session's write transaction; discarded");
1541
+ }
1542
+ return this.settledThrough;
1543
+ }
1544
+ const before = this.settledThrough;
1545
+ const after = this.settler.observe(reading.head, reading.xid, reading.snapshot, this.now());
1546
+ if (after > before) for (const listener of this.listeners) try {
1547
+ listener(after);
1548
+ } catch (error) {
1549
+ this.logger.error("settled watermark listener failed: @error", error);
1550
+ }
1551
+ return after;
1552
+ }
1553
+ };
1554
+ /** Sessions holding these xids open; empty without pg_stat_activity. */
1555
+ async function describeWaitingSessions(db, xids) {
1556
+ if (xids.length === 0) return [];
1557
+ const epochless = xids.map((xid) => (BigInt(xid) % 2n ** 32n).toString());
1558
+ try {
1559
+ return (await sql`select pid, application_name, state, xact_start, backend_xid::text as xid
1560
+ from pg_stat_activity
1561
+ where backend_xid::text in (${sql.join(epochless)})`.execute(db)).rows.map((row) => ({
1562
+ pid: Number(row.pid),
1563
+ applicationName: row.application_name ?? "",
1564
+ state: row.state ?? "",
1565
+ xactStart: row.xact_start === null ? null : new Date(row.xact_start).toISOString(),
1566
+ xid: row.xid
1567
+ }));
1568
+ } catch {
1569
+ return [];
1570
+ }
1571
+ }
1572
+ /** Bind slots per statement: 16-bit on the wire, read signed in-process. */
1573
+ const MAX_BIND_PARAMETERS = 32767;
1574
+ /** Splits rows into runs that each bind at most MAX_BIND_PARAMETERS. */
1575
+ function chunkRows(rows) {
1576
+ if (rows.length === 0) return [];
1577
+ const columnsPerRow = Math.max(1, Object.keys(rows[0]).length);
1578
+ const perChunk = Math.max(1, Math.floor(MAX_BIND_PARAMETERS / columnsPerRow));
1579
+ if (rows.length <= perChunk) return [rows];
1580
+ const chunks = [];
1581
+ for (let i = 0; i < rows.length; i += perChunk) chunks.push(rows.slice(i, i + perChunk));
1582
+ return chunks;
1583
+ }
1584
+ /** Per database: the function that assigns the transaction's xid. */
1585
+ const xidFunctions = /* @__PURE__ */ new WeakMap();
1213
1586
  var KyselyOperationIndexTxn = class {
1214
1587
  collections = [];
1215
1588
  collectionMemberships = [];
@@ -1275,6 +1648,7 @@ var KyselyOperationIndexTxn = class {
1275
1648
  }
1276
1649
  };
1277
1650
  var KyselyOperationIndex = class KyselyOperationIndex {
1651
+ db;
1278
1652
  trx;
1279
1653
  constructor(db) {
1280
1654
  this.db = db;
@@ -1331,10 +1705,11 @@ var KyselyOperationIndex = class KyselyOperationIndex {
1331
1705
  leftOrdinal: null
1332
1706
  }));
1333
1707
  for (const collectionId of collections) kyselyTxn.recordMembershipInvalidation(collectionId);
1334
- await trx.insertInto("document_collections").values(collectionRows).onConflict((oc) => oc.doNothing()).execute();
1708
+ for (const chunk of chunkRows(collectionRows)) await trx.insertInto("document_collections").values(chunk).onConflict((oc) => oc.doNothing()).execute();
1335
1709
  }
1336
1710
  let operationOrdinals = [];
1337
1711
  if (operations.length > 0) {
1712
+ await this.assignXid(trx);
1338
1713
  const operationRows = operations.map((op) => ({
1339
1714
  opId: op.id || "",
1340
1715
  documentId: op.documentId,
@@ -1349,7 +1724,10 @@ var KyselyOperationIndex = class KyselyOperationIndex {
1349
1724
  deniedReason: op.deniedReason ?? null,
1350
1725
  sourceRemote: op.sourceRemote
1351
1726
  }));
1352
- operationOrdinals = (await trx.insertInto("operation_index_operations").values(operationRows).returning("ordinal").execute()).map((row) => row.ordinal);
1727
+ for (const chunk of chunkRows(operationRows)) {
1728
+ const insertedOps = await trx.insertInto("operation_index_operations").values(chunk).returning("ordinal").execute();
1729
+ operationOrdinals = operationOrdinals.concat(insertedOps.map((row) => row.ordinal));
1730
+ }
1353
1731
  }
1354
1732
  if (memberships.length > 0) for (const m of memberships) {
1355
1733
  const ordinal = operationOrdinals[m.operationIndex];
@@ -1382,10 +1760,37 @@ var KyselyOperationIndex = class KyselyOperationIndex {
1382
1760
  }
1383
1761
  return operationOrdinals;
1384
1762
  }
1763
+ async getOrdinalsInRange(after, through, limit, signal) {
1764
+ signal?.throwIfAborted();
1765
+ if (through <= after || limit <= 0) return [];
1766
+ return (await this.queryExecutor.selectFrom("operation_index_operations").select("ordinal").where("ordinal", ">", after).where("ordinal", "<=", through).orderBy("ordinal", "asc").limit(limit).execute()).map((row) => Number(row.ordinal));
1767
+ }
1768
+ async getByOrdinals(ordinals, signal) {
1769
+ signal?.throwIfAborted();
1770
+ if (ordinals.length === 0) return [];
1771
+ const results = [];
1772
+ for (let i = 0; i < ordinals.length; i += MAX_BIND_PARAMETERS) {
1773
+ const chunk = ordinals.slice(i, i + MAX_BIND_PARAMETERS);
1774
+ const rows = await this.queryExecutor.selectFrom("operation_index_operations").selectAll().where("ordinal", "in", chunk).orderBy("ordinal", "asc").execute();
1775
+ for (const row of rows) results.push(this.rowToOperationWithContext(row));
1776
+ }
1777
+ results.sort((a, b) => a.context.ordinal - b.context.ordinal);
1778
+ return results;
1779
+ }
1780
+ async getStreamAfter(stream, after, signal) {
1781
+ signal?.throwIfAborted();
1782
+ return (await this.queryExecutor.selectFrom("operation_index_operations").selectAll().where("documentId", "=", stream.documentId).where("branch", "=", stream.branch).where("scope", "=", stream.scope).where("ordinal", ">", after).orderBy("ordinal", "asc").execute()).map((row) => this.rowToOperationWithContext(row));
1783
+ }
1385
1784
  async getGroupReferencers(groupId, signal) {
1386
1785
  if (signal?.aborted) throw new Error("Operation aborted");
1387
1786
  return (await this.queryExecutor.selectFrom("group_references").select("documentId").where("groupId", "=", groupId).orderBy("documentId").execute()).map((row) => row.documentId);
1388
1787
  }
1788
+ async getOrdinalsByOpIds(documentId, scope, branch, opIds, signal) {
1789
+ signal?.throwIfAborted();
1790
+ if (opIds.length === 0) return /* @__PURE__ */ new Map();
1791
+ const rows = await this.queryExecutor.selectFrom("operation_index_operations").select("opId").select((eb) => eb.fn.max("ordinal").as("ordinal")).where("documentId", "=", documentId).where("branch", "=", branch).where("scope", "=", scope).where("opId", "in", opIds).groupBy("opId").execute();
1792
+ return new Map(rows.map((row) => [row.opId, Number(row.ordinal)]));
1793
+ }
1389
1794
  async find(collectionId, cursor, view, paging, signal) {
1390
1795
  if (signal?.aborted) throw new Error("Operation aborted");
1391
1796
  const outerCursor = cursor ?? -1;
@@ -1399,6 +1804,7 @@ var KyselyOperationIndex = class KyselyOperationIndex {
1399
1804
  if (view?.branch) qb = qb.where("oi.branch", "=", view.branch);
1400
1805
  if (view?.scopes && view.scopes.length > 0) qb = qb.where("oi.scope", "in", view.scopes);
1401
1806
  if (view?.excludeSourceRemote) qb = qb.where("oi.sourceRemote", "!=", view.excludeSourceRemote);
1807
+ if (view?.throughOrdinal !== void 0) qb = qb.where("oi.ordinal", "<=", view.throughOrdinal).where("dc.joinedOrdinal", "<=", BigInt(view.throughOrdinal));
1402
1808
  return qb;
1403
1809
  };
1404
1810
  const rows = await buildBranch("joiner").unionAll(buildBranch("newOps")).orderBy("ordinal", "asc").limit(limit + 1).execute();
@@ -1487,6 +1893,17 @@ var KyselyOperationIndex = class KyselyOperationIndex {
1487
1893
  }, signal) : void 0
1488
1894
  };
1489
1895
  }
1896
+ /** Takes the xid before the first ordinal, as the settled watermark needs. */
1897
+ async assignXid(trx) {
1898
+ let fn = xidFunctions.get(this.db);
1899
+ if (fn === void 0) {
1900
+ fn = readSnapshotFunctions(trx).then((fns) => fns.currentXid);
1901
+ xidFunctions.set(this.db, fn);
1902
+ fn.catch(() => xidFunctions.delete(this.db));
1903
+ }
1904
+ const name = await fn;
1905
+ await sql`select ${sql.raw(name)}()`.execute(trx);
1906
+ }
1490
1907
  rowToOperationWithContext(row) {
1491
1908
  return {
1492
1909
  operation: {
@@ -1605,21 +2022,6 @@ var RingBuffer = class {
1605
2022
  }
1606
2023
  };
1607
2024
  //#endregion
1608
- //#region src/cache/write-cache-types.ts
1609
- /**
1610
- * Where a snapshot sits in its stream.
1611
- *
1612
- * - `Head`: the newest revision of the stream when it was stored. Only these
1613
- * can answer a read that asks for the head.
1614
- * - `Historical`: state at an earlier revision. Usable as a starting point to
1615
- * replay forward from, and as an answer to a read for that same revision.
1616
- */
1617
- let SnapshotPosition = /* @__PURE__ */ function(SnapshotPosition) {
1618
- SnapshotPosition["Head"] = "head";
1619
- SnapshotPosition["Historical"] = "historical";
1620
- return SnapshotPosition;
1621
- }({});
1622
- //#endregion
1623
2025
  //#region src/cache/kysely-write-cache.ts
1624
2026
  /**
1625
2027
  * The last operation index a keyframe's document reflects for the scope. A
@@ -1758,13 +2160,13 @@ var KyselyWriteCache = class KyselyWriteCache {
1758
2160
  const snapshots = stream.ringBuffer.getAll();
1759
2161
  if (targetRevision === void 0) {
1760
2162
  const newest = highestRevision(snapshots);
1761
- if (newest?.position === SnapshotPosition.Head) {
2163
+ if (newest?.position === "head") {
1762
2164
  this.lruTracker.touch(streamKey);
1763
2165
  return copyDocument(newest.document);
1764
2166
  }
1765
2167
  if (newest) {
1766
2168
  const document = await this.warmMissRebuild(newest.document, newest.revision, documentId, scope, branch, void 0, signal);
1767
- this.store(documentId, scope, branch, (document.header.revision[scope] ?? 0) - 1, document, SnapshotPosition.Head);
2169
+ this.store(documentId, scope, branch, (document.header.revision[scope] ?? 0) - 1, document, "head");
1768
2170
  this.lruTracker.touch(streamKey);
1769
2171
  return document;
1770
2172
  }
@@ -1777,7 +2179,7 @@ var KyselyWriteCache = class KyselyWriteCache {
1777
2179
  const newestOlder = this.findNearestOlderSnapshot(snapshots, targetRevision);
1778
2180
  if (newestOlder) {
1779
2181
  const document = await this.warmMissRebuild(newestOlder.document, newestOlder.revision, documentId, scope, branch, targetRevision, signal);
1780
- this.store(documentId, scope, branch, targetRevision, document, SnapshotPosition.Historical);
2182
+ this.store(documentId, scope, branch, targetRevision, document, "historical");
1781
2183
  this.lruTracker.touch(streamKey);
1782
2184
  return document;
1783
2185
  }
@@ -1785,7 +2187,7 @@ var KyselyWriteCache = class KyselyWriteCache {
1785
2187
  }
1786
2188
  const document = await this.coldMissRebuild(documentId, scope, branch, targetRevision, signal);
1787
2189
  const revision = targetRevision ?? (document.header.revision[scope] ?? 0) - 1;
1788
- this.store(documentId, scope, branch, revision, document, targetRevision === void 0 ? SnapshotPosition.Head : SnapshotPosition.Historical);
2190
+ this.store(documentId, scope, branch, revision, document, targetRevision === void 0 ? "head" : "historical");
1789
2191
  return document;
1790
2192
  }
1791
2193
  /**
@@ -1820,7 +2222,7 @@ var KyselyWriteCache = class KyselyWriteCache {
1820
2222
  if (run.length === 0) return;
1821
2223
  for (const entry of run.slice(0, -1)) this.persistKeyframe(documentId, scope, branch, entry.revision, entry.document);
1822
2224
  const head = run[run.length - 1];
1823
- this.store(documentId, scope, branch, head.revision, head.document, SnapshotPosition.Head);
2225
+ this.store(documentId, scope, branch, head.revision, head.document, "head");
1824
2226
  }
1825
2227
  store(documentId, scope, branch, revision, document, position) {
1826
2228
  const streamKey = this.makeStreamKey(documentId, scope, branch);
@@ -1924,9 +2326,9 @@ var KyselyWriteCache = class KyselyWriteCache {
1924
2326
  * the last replayed operation are applied at the end. Creation-time 0->N seed
1925
2327
  * upgrades carry the initial state, so they still apply immediately.
1926
2328
  */
1927
- async coldMissRebuild(documentId, scope, branch, targetRevision, signal) {
2329
+ async coldMissRebuild(documentId, scope, branch, targetRevision, signal, fromKeyframe = true) {
1928
2330
  const effectiveTargetRevision = targetRevision || Number.MAX_SAFE_INTEGER;
1929
- const keyframe = await this.findNearestKeyframe(documentId, scope, branch, effectiveTargetRevision, signal);
2331
+ const keyframe = fromKeyframe ? await this.findNearestKeyframe(documentId, scope, branch, effectiveTargetRevision, signal) : void 0;
1930
2332
  const documentScopeBound = scope === "document" ? targetRevision : void 0;
1931
2333
  let document;
1932
2334
  let startRevision;
@@ -2026,10 +2428,10 @@ var KyselyWriteCache = class KyselyWriteCache {
2026
2428
  for (const pending of pendingUpgrades) pending.subsequentDeletes.push(operation.action);
2027
2429
  } else {
2028
2430
  const protocolVersion = baseReducerVersion(document.header);
2029
- document = docModule.reducer(document, operation.action, void 0, {
2431
+ document = withProtocolVersions(docModule.reducer(document, operation.action, void 0, {
2030
2432
  skip: operation.skip,
2031
2433
  protocolVersion
2032
- });
2434
+ }), document.header.protocolVersions);
2033
2435
  }
2034
2436
  }
2035
2437
  }
@@ -2063,6 +2465,7 @@ var KyselyWriteCache = class KyselyWriteCache {
2063
2465
  let cursor = void 0;
2064
2466
  const pageSize = 100;
2065
2467
  let hasMorePages;
2468
+ let needsFullReplay = false;
2066
2469
  do {
2067
2470
  if (signal?.aborted) throw new Error("Operation aborted");
2068
2471
  const paging = {
@@ -2073,26 +2476,31 @@ var KyselyWriteCache = class KyselyWriteCache {
2073
2476
  const result = await this.operationStore.getSince(documentId, scope, branch, startRevision, void 0, paging, signal);
2074
2477
  for (const operation of result.results) {
2075
2478
  if (targetRevision !== void 0 && operation.index > targetRevision) break;
2479
+ if (keyframe && operation.skip > 0) {
2480
+ needsFullReplay = true;
2481
+ break;
2482
+ }
2076
2483
  const moduleVersion = this.resolveModuleVersionForOp(operation.index, operation.timestampUtcMs, scope, validatedUpgrades, finalVersion);
2077
2484
  document = this.applyPendingUpgrades(document, pendingUpgrades, moduleVersion ?? Number.MAX_SAFE_INTEGER);
2078
2485
  if (isDenied(operation)) document = appendWithoutApplying(document, operation, scope);
2079
2486
  else {
2080
2487
  const protocolVersion = baseReducerVersion(document.header);
2081
- document = getModuleCached(moduleVersion).reducer(document, operation.action, void 0, {
2488
+ document = withProtocolVersions(getModuleCached(moduleVersion).reducer(document, operation.action, void 0, {
2082
2489
  skip: operation.skip,
2083
2490
  protocolVersion,
2084
2491
  replayOptions: { operation },
2085
2492
  skipIndexValidation: true
2086
- });
2493
+ }), document.header.protocolVersions);
2087
2494
  }
2088
2495
  }
2089
2496
  const reachedTarget = targetRevision !== void 0 && result.results.some((op) => op.index >= targetRevision);
2090
- hasMorePages = Boolean(result.nextCursor) && !reachedTarget;
2497
+ hasMorePages = Boolean(result.nextCursor) && !reachedTarget && !needsFullReplay;
2091
2498
  if (hasMorePages) cursor = result.nextCursor;
2092
2499
  } catch (err) {
2093
2500
  throw new Error(`Failed to rebuild document ${documentId}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
2094
2501
  }
2095
2502
  } while (hasMorePages);
2503
+ if (needsFullReplay) return this.coldMissRebuild(documentId, scope, branch, targetRevision, signal, false);
2096
2504
  document = this.applyTailPendingUpgrades(document, pendingUpgrades, scope, targetRevision);
2097
2505
  document = await this.stampRevisions(document, documentId, scope, branch, targetRevision, signal);
2098
2506
  if (pendingUpgrades.length > 0) {
@@ -2199,26 +2607,32 @@ var KyselyWriteCache = class KyselyWriteCache {
2199
2607
  if ((await this.operationStore.getSince(documentId, "document", branch, docScopeNextIndex - 1, void 0, void 0, signal)).results.length > 0) return this.coldMissRebuild(documentId, scope, branch, targetRevision, signal);
2200
2608
  const module = this.registry.getModule(documentType, extractModuleVersion(baseDocument));
2201
2609
  let document = copyDocument(baseDocument);
2610
+ let needsFullReplay = false;
2202
2611
  try {
2203
2612
  const pagedResults = await this.operationStore.getSince(documentId, scope, branch, baseRevision, void 0, void 0, signal);
2204
2613
  for (const operation of pagedResults.results) {
2205
2614
  if (signal?.aborted) throw new Error("Operation aborted");
2206
2615
  if (targetRevision !== void 0 && operation.index > targetRevision) break;
2616
+ if (operation.skip > 0) {
2617
+ needsFullReplay = true;
2618
+ break;
2619
+ }
2207
2620
  if (isDenied(operation)) document = appendWithoutApplying(document, operation, scope);
2208
2621
  else {
2209
2622
  const protocolVersion = baseReducerVersion(document.header);
2210
- document = module.reducer(document, operation.action, void 0, {
2623
+ document = withProtocolVersions(module.reducer(document, operation.action, void 0, {
2211
2624
  skip: operation.skip,
2212
2625
  protocolVersion,
2213
2626
  replayOptions: { operation },
2214
2627
  skipIndexValidation: true
2215
- });
2628
+ }), document.header.protocolVersions);
2216
2629
  }
2217
2630
  if (targetRevision !== void 0 && operation.index === targetRevision) break;
2218
2631
  }
2219
2632
  } catch (err) {
2220
2633
  throw new Error(`Failed to rebuild document ${documentId}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
2221
2634
  }
2635
+ if (needsFullReplay) return this.coldMissRebuild(documentId, scope, branch, targetRevision, signal, false);
2222
2636
  const revisions = await this.operationStore.getRevisions(documentId, branch, signal);
2223
2637
  document.header.revision = revisions.revision;
2224
2638
  if (targetRevision !== void 0) document.header.revision = {
@@ -2339,6 +2753,11 @@ function validateFeatureFlags(flags, prerequisites) {
2339
2753
  //#endregion
2340
2754
  //#region src/executor/execution-scope.ts
2341
2755
  var DefaultExecutionScope = class {
2756
+ operationStore;
2757
+ operationIndex;
2758
+ writeCache;
2759
+ documentMetaCache;
2760
+ collectionMembershipCache;
2342
2761
  constructor(operationStore, operationIndex, writeCache, documentMetaCache, collectionMembershipCache) {
2343
2762
  this.operationStore = operationStore;
2344
2763
  this.operationIndex = operationIndex;
@@ -2358,6 +2777,13 @@ var DefaultExecutionScope = class {
2358
2777
  }
2359
2778
  };
2360
2779
  var KyselyExecutionScope = class {
2780
+ db;
2781
+ operationStore;
2782
+ operationIndex;
2783
+ keyframeStore;
2784
+ writeCache;
2785
+ documentMetaCache;
2786
+ collectionMembershipCache;
2361
2787
  constructor(db, operationStore, operationIndex, keyframeStore, writeCache, documentMetaCache, collectionMembershipCache) {
2362
2788
  this.db = db;
2363
2789
  this.operationStore = operationStore;
@@ -2385,7 +2811,7 @@ var KyselyExecutionScope = class {
2385
2811
  };
2386
2812
  //#endregion
2387
2813
  //#region src/utils/reshuffle.ts
2388
- const STRICT_ORDER_ACTION_TYPES = new Set([
2814
+ const STRICT_ORDER_ACTION_TYPES = /* @__PURE__ */ new Set([
2389
2815
  "CREATE_DOCUMENT",
2390
2816
  "DELETE_DOCUMENT",
2391
2817
  "UPGRADE_DOCUMENT",
@@ -2702,58 +3128,36 @@ async function evaluateByPosition(model, target, subject, stores, signal) {
2702
3128
  return operations.map((operation) => reasons.get(operation.id));
2703
3129
  }
2704
3130
  //#endregion
2705
- //#region src/cache/operation-index-types.ts
2706
- const DRIVE_COLLECTION_PREFIX = "drive.";
2707
- /**
2708
- * Identifies the collection a remote synchronizes. Collections are drive-level
2709
- * abstractions (document-drive and reactor-drive), so a collection id is the
2710
- * drive document id plus the branch it scopes to rather than an opaque string.
2711
- *
2712
- * The canonical string form (`drive.${branch}.${driveId}`) is produced only by
2713
- * `key` and parsed only by `fromKey`; that string is the wire and storage
2714
- * representation and is byte-for-byte identical to the legacy
2715
- * `driveCollectionId(branch, driveId)` output, so existing `document_collections`
2716
- * rows and persisted remotes remain valid without migration.
2717
- */
2718
- var DriveCollectionId = class DriveCollectionId {
2719
- constructor(driveId, branch) {
2720
- this.driveId = driveId;
2721
- this.branch = branch;
2722
- }
2723
- static forDrive(driveId, branch = "main") {
2724
- return new DriveCollectionId(driveId, branch);
2725
- }
2726
- /**
2727
- * The single deserializer for the wire/storage form. `branch` may contain
2728
- * dots, while `driveId` is a dot-free document id, so the drive id is the
2729
- * final dot-delimited segment.
2730
- */
2731
- static fromKey(key) {
2732
- if (!key.startsWith(DRIVE_COLLECTION_PREFIX)) throw new Error(`Unsupported collection id: ${key}`);
2733
- const rest = key.slice(6);
2734
- const lastDot = rest.lastIndexOf(".");
2735
- if (lastDot === -1 || lastDot === rest.length - 1) throw new Error(`Malformed drive collection id: ${key}`);
2736
- return new DriveCollectionId(rest.slice(lastDot + 1), rest.slice(0, lastDot));
2737
- }
2738
- get key() {
2739
- return `${DRIVE_COLLECTION_PREFIX}${this.branch}.${this.driveId}`;
2740
- }
2741
- toString() {
2742
- return this.key;
2743
- }
2744
- equals(other) {
2745
- return this.driveId === other.driveId && this.branch === other.branch;
2746
- }
2747
- };
2748
- //#endregion
2749
3131
  //#region src/executor/document-action-handler.ts
2750
3132
  var DocumentActionHandler = class {
2751
- constructor(registry, logger, driveContainerTypes, featureFlags, decisionModel) {
3133
+ registry;
3134
+ logger;
3135
+ driveContainerTypes;
3136
+ featureFlags;
3137
+ decisionModel;
3138
+ protocolSupport;
3139
+ loggedUnregisteredProtocols = /* @__PURE__ */ new Set();
3140
+ constructor(registry, logger, driveContainerTypes, featureFlags, decisionModel, protocolSupport) {
2752
3141
  this.registry = registry;
2753
3142
  this.logger = logger;
2754
3143
  this.driveContainerTypes = driveContainerTypes;
2755
3144
  this.featureFlags = featureFlags;
2756
3145
  this.decisionModel = decisionModel;
3146
+ this.protocolSupport = protocolSupport;
3147
+ }
3148
+ /** Keys this reactor does not register are admitted: it cannot judge them. */
3149
+ unsupportedProtocol(documentId, protocolVersions) {
3150
+ for (const [protocol, version] of Object.entries(protocolVersions ?? {})) {
3151
+ const supported = this.protocolSupport[protocol];
3152
+ if (supported === void 0) {
3153
+ if (!this.loggedUnregisteredProtocols.has(protocol)) {
3154
+ this.loggedUnregisteredProtocols.add(protocol);
3155
+ this.logger.info("Admitting document @documentId with unregistered protocol @protocol @version", documentId, protocol, version);
3156
+ }
3157
+ continue;
3158
+ }
3159
+ if (!supported.includes(version)) return new UnsupportedProtocolVersionError(documentId, protocol, version);
3160
+ }
2757
3161
  }
2758
3162
  /** Whether the write arrives with its evaluation already decided. */
2759
3163
  alreadyEvaluated(executing) {
@@ -2840,7 +3244,7 @@ var DocumentActionHandler = class {
2840
3244
  [job.scope]: [...standing.operations[job.scope] ?? [], operation]
2841
3245
  };
2842
3246
  executing.touchedStreams.add(job.documentId, job.scope, job.branch);
2843
- stores.writeCache.putState(job.documentId, job.scope, job.branch, operation.index, standing, SnapshotPosition.Head);
3247
+ stores.writeCache.putState(job.documentId, job.scope, job.branch, operation.index, standing, "head");
2844
3248
  indexTxn.write([{
2845
3249
  ...operation,
2846
3250
  documentId: job.documentId,
@@ -2852,6 +3256,7 @@ var DocumentActionHandler = class {
2852
3256
  stores.documentMetaCache.putDocumentMeta(job.documentId, job.branch, {
2853
3257
  state: standing.state.document,
2854
3258
  documentType: standing.header.documentType,
3259
+ protocolVersions: standing.header.protocolVersions,
2855
3260
  documentScopeRevision: operation.index + 1
2856
3261
  });
2857
3262
  return buildSuccessResult(job, operation, job.documentId, standing.header.documentType, JSON.stringify({
@@ -2868,6 +3273,9 @@ var DocumentActionHandler = class {
2868
3273
  error: /* @__PURE__ */ new Error(`CREATE_DOCUMENT must be in "document" scope, got "${job.scope}"`),
2869
3274
  duration: Date.now() - startTime
2870
3275
  };
3276
+ const input = action.input;
3277
+ const unsupported = this.unsupportedProtocol(input.documentId, input.protocolVersions);
3278
+ if (unsupported) return buildErrorResult(job, unsupported, startTime);
2871
3279
  const document = createDocumentFromAction(action);
2872
3280
  let operation = createOperation(action, 0, skip, {
2873
3281
  documentId: document.header.id,
@@ -2875,8 +3283,8 @@ var DocumentActionHandler = class {
2875
3283
  branch: job.branch
2876
3284
  });
2877
3285
  const resultingStateObj = {
2878
- header: document.header,
2879
- ...document.state
3286
+ ...document.state,
3287
+ header: document.header
2880
3288
  };
2881
3289
  const resultingState = JSON.stringify(resultingStateObj);
2882
3290
  const writeResult = await this.writeOperationToStore({
@@ -2893,7 +3301,7 @@ var DocumentActionHandler = class {
2893
3301
  [job.scope]: [...document.operations[job.scope] ?? [], operation]
2894
3302
  };
2895
3303
  executing.touchedStreams.add(document.header.id, job.scope, job.branch);
2896
- stores.writeCache.putState(document.header.id, job.scope, job.branch, operation.index, document, SnapshotPosition.Head);
3304
+ stores.writeCache.putState(document.header.id, job.scope, job.branch, operation.index, document, "head");
2897
3305
  indexTxn.write([{
2898
3306
  ...operation,
2899
3307
  documentId: document.header.id,
@@ -2910,6 +3318,7 @@ var DocumentActionHandler = class {
2910
3318
  stores.documentMetaCache.putDocumentMeta(document.header.id, job.branch, {
2911
3319
  state: document.state.document,
2912
3320
  documentType: document.header.documentType,
3321
+ protocolVersions: document.header.protocolVersions,
2913
3322
  documentScopeRevision: 1
2914
3323
  });
2915
3324
  return buildSuccessResult(job, operation, document.header.id, document.header.documentType, resultingState, startTime);
@@ -2953,7 +3362,7 @@ var DocumentActionHandler = class {
2953
3362
  [job.scope]: [...document.operations[job.scope] ?? [], operation]
2954
3363
  };
2955
3364
  executing.touchedStreams.add(documentId, job.scope, job.branch);
2956
- stores.writeCache.putState(documentId, job.scope, job.branch, operation.index, document, SnapshotPosition.Head);
3365
+ stores.writeCache.putState(documentId, job.scope, job.branch, operation.index, document, "head");
2957
3366
  indexTxn.write([{
2958
3367
  ...operation,
2959
3368
  documentId,
@@ -2965,6 +3374,7 @@ var DocumentActionHandler = class {
2965
3374
  stores.documentMetaCache.putDocumentMeta(documentId, job.branch, {
2966
3375
  state: document.state.document,
2967
3376
  documentType: document.header.documentType,
3377
+ protocolVersions: document.header.protocolVersions,
2968
3378
  documentScopeRevision: operation.index + 1
2969
3379
  });
2970
3380
  return buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
@@ -3003,7 +3413,7 @@ var DocumentActionHandler = class {
3003
3413
  } catch (error) {
3004
3414
  return buildErrorResult(job, /* @__PURE__ */ new Error(`Failed to fetch revisions for upgrade: ${error instanceof Error ? error.message : String(error)}`), startTime);
3005
3415
  }
3006
- const revisionScopes = new Set([...Object.keys(input.revision), ...Object.keys(actualRevisions)]);
3416
+ const revisionScopes = /* @__PURE__ */ new Set([...Object.keys(input.revision), ...Object.keys(actualRevisions)]);
3007
3417
  for (const revisionScope of revisionScopes) {
3008
3418
  const snapshot = input.revision[revisionScope] ?? 0;
3009
3419
  const actual = actualRevisions[revisionScope] ?? 0;
@@ -3045,8 +3455,8 @@ var DocumentActionHandler = class {
3045
3455
  branch: job.branch
3046
3456
  });
3047
3457
  const resultingStateObj = {
3048
- header: document.header,
3049
- ...document.state
3458
+ ...document.state,
3459
+ header: document.header
3050
3460
  };
3051
3461
  if (fromVersion > 0) resultingStateObj.__migrated = true;
3052
3462
  const resultingState = JSON.stringify(resultingStateObj);
@@ -3064,7 +3474,7 @@ var DocumentActionHandler = class {
3064
3474
  [job.scope]: [...document.operations[job.scope] ?? [], operation]
3065
3475
  };
3066
3476
  executing.touchedStreams.add(documentId, job.scope, job.branch);
3067
- stores.writeCache.putState(documentId, job.scope, job.branch, operation.index, document, SnapshotPosition.Head);
3477
+ stores.writeCache.putState(documentId, job.scope, job.branch, operation.index, document, "head");
3068
3478
  for (const scope of otherScopes) executing.postCommitInvalidations.push({
3069
3479
  documentId,
3070
3480
  scope,
@@ -3081,6 +3491,7 @@ var DocumentActionHandler = class {
3081
3491
  stores.documentMetaCache.putDocumentMeta(documentId, job.branch, {
3082
3492
  state: document.state.document,
3083
3493
  documentType: document.header.documentType,
3494
+ protocolVersions: document.header.protocolVersions,
3084
3495
  documentScopeRevision: operation.index + 1
3085
3496
  });
3086
3497
  return buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
@@ -3150,7 +3561,7 @@ var DocumentActionHandler = class {
3150
3561
  const resultingState = JSON.stringify(resultingStateObj);
3151
3562
  executing.touchedStreams.add(input.sourceId, job.scope, job.branch);
3152
3563
  executing.touchedStreams.add(input.targetId, job.scope, job.branch);
3153
- stores.writeCache.putState(input.sourceId, job.scope, job.branch, operation.index, sourceDoc, SnapshotPosition.Head);
3564
+ stores.writeCache.putState(input.sourceId, job.scope, job.branch, operation.index, sourceDoc, "head");
3154
3565
  indexTxn.write([{
3155
3566
  ...operation,
3156
3567
  documentId: input.sourceId,
@@ -3169,6 +3580,7 @@ var DocumentActionHandler = class {
3169
3580
  stores.documentMetaCache.putDocumentMeta(input.sourceId, job.branch, {
3170
3581
  state: sourceDoc.state.document,
3171
3582
  documentType: sourceDoc.header.documentType,
3583
+ protocolVersions: sourceDoc.header.protocolVersions,
3172
3584
  documentScopeRevision: operation.index + 1
3173
3585
  });
3174
3586
  return buildSuccessResult(job, operation, input.sourceId, sourceDoc.header.documentType, resultingState, startTime);
@@ -3197,86 +3609,701 @@ var DocumentActionHandler = class {
3197
3609
  }
3198
3610
  };
3199
3611
  //#endregion
3200
- //#region src/executor/signature-verifier.ts
3201
- var SignatureVerifier = class {
3202
- constructor(verifier) {
3203
- this.verifier = verifier;
3204
- }
3205
- async verifyActions(documentId, branch, actions) {
3206
- if (!this.verifier) return;
3207
- for (const action of actions) {
3208
- const signer = action.context?.signer;
3209
- if (!signer) continue;
3210
- if (signer.signatures.length === 0) throw new InvalidSignatureError(documentId, `Action ${action.id} has signer but no signatures`);
3211
- const publicKey = signer.app.key;
3212
- let isValid;
3213
- try {
3214
- const tempOperation = {
3215
- id: deriveOperationId(documentId, action.scope, branch, action.id),
3216
- index: 0,
3217
- timestampUtcMs: action.timestampUtcMs || (/* @__PURE__ */ new Date()).toISOString(),
3218
- hash: "",
3219
- skip: 0,
3220
- action
3221
- };
3222
- isValid = await this.verifier(tempOperation, publicKey);
3223
- } catch (error) {
3224
- const errorMessage = error instanceof Error ? error.message : String(error);
3225
- throw new InvalidSignatureError(documentId, `Action ${action.id} verification failed: ${errorMessage}`);
3226
- }
3227
- if (!isValid) throw new InvalidSignatureError(documentId, `Action ${action.id} signature verification returned false`);
3228
- }
3612
+ //#region src/signer/passthrough-signer.ts
3613
+ /**
3614
+ * A no-op signer that returns empty values for all methods.
3615
+ * Used when signing is not required.
3616
+ */
3617
+ var PassthroughSigner = class {
3618
+ publicKey = {};
3619
+ sign() {
3620
+ return Promise.resolve(/* @__PURE__ */ new Uint8Array(0));
3229
3621
  }
3230
- async verifyOperations(documentId, operations) {
3231
- if (!this.verifier) return;
3232
- for (let i = 0; i < operations.length; i++) {
3233
- const operation = operations[i];
3234
- const signer = operation.action.context?.signer;
3235
- if (!signer) continue;
3236
- if (signer.signatures.length === 0) throw new InvalidSignatureError(documentId, `Operation ${operation.id} at index ${operation.index} has signer but no signatures`);
3237
- const publicKey = signer.app.key;
3238
- let isValid;
3239
- try {
3240
- isValid = await this.verifier(operation, publicKey);
3241
- } catch (error) {
3242
- const errorMessage = error instanceof Error ? error.message : String(error);
3243
- throw new InvalidSignatureError(documentId, `Operation ${operation.id} at index ${operation.index} verification failed: ${errorMessage}`);
3244
- }
3245
- if (!isValid) throw new InvalidSignatureError(documentId, `Operation ${operation.id} at index ${operation.index} signature verification returned false`);
3246
- }
3622
+ verify() {
3623
+ return Promise.resolve();
3624
+ }
3625
+ signAction() {
3626
+ return Promise.resolve([
3627
+ "",
3628
+ "",
3629
+ "",
3630
+ "",
3631
+ ""
3632
+ ]);
3247
3633
  }
3248
3634
  };
3249
3635
  //#endregion
3250
- //#region src/executor/simple-job-executor.ts
3251
- const MAX_SKIP_THRESHOLD = 1e3;
3252
- const ISO_TIMESTAMP_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
3253
- function isValidISOTimestamp(value) {
3254
- if (!ISO_TIMESTAMP_REGEX.test(value)) return false;
3255
- return !isNaN(new Date(value).getTime());
3256
- }
3636
+ //#region src/signer/trust-policy.ts
3257
3637
  /**
3258
- * Carries a failed job out of the execution scope so its transaction rolls
3259
- * back. A scope callback that returns commits, whatever result it returns, so
3260
- * a returned failure would leave the writes the job made before it failed
3261
- * standing. Never escapes executeJob: the failure goes back to being a
3262
- * returned JobResult there, which is what the queue, the worker protocol and
3263
- * every test expect a failed job to look like.
3638
+ * The policy admission asks: the reactor's own key signing as its own user is
3639
+ * accepted, then the host's policy decides, else the default does. `own` is
3640
+ * read on every ask, since its user changes on login.
3264
3641
  */
3265
- var JobRollbackSignal = class extends Error {
3266
- constructor(result) {
3642
+ function admissionTrustPolicy(own, authEnforcement, policy) {
3643
+ return { authorizeSigner(signer, key, documentId) {
3644
+ const identity = actionSignerIdentity(own);
3645
+ if (identity.app.key !== "" && key === identity.app.key) {
3646
+ if (sameUser(actionSignerIdentity(signer).user, identity.user)) return Promise.resolve(true);
3647
+ }
3648
+ if (policy) return policy.authorizeSigner(signer, key, documentId);
3649
+ return Promise.resolve(!authEnforcement);
3650
+ } };
3651
+ }
3652
+ function sameUser(a, b) {
3653
+ return a.address.toLowerCase() === b.address.toLowerCase() && a.networkId === b.networkId && a.chainId === b.chainId;
3654
+ }
3655
+ //#endregion
3656
+ //#region src/signer/did-key.ts
3657
+ const DID_KEY_PREFIX = "did:key:z";
3658
+ const P256_MULTICODEC = [128, 36];
3659
+ const MAX_CACHED_KEYS = 1024;
3660
+ const P = BigInt("0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff");
3661
+ const A = BigInt("0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc");
3662
+ const B = BigInt("0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b");
3663
+ /** A did:key that is not a compressed P-256 public key. */
3664
+ var MalformedDidKeyError = class extends Error {
3665
+ constructor(did, detail) {
3666
+ super(`Malformed did:key ${did}: ${detail}`);
3667
+ this.name = "MalformedDidKeyError";
3668
+ }
3669
+ };
3670
+ const keyCache = /* @__PURE__ */ new Map();
3671
+ /** Cached per process: decompressing the point costs a BigInt modPow. */
3672
+ async function importDidKey(did) {
3673
+ const cached = keyCache.get(did);
3674
+ if (cached) {
3675
+ keyCache.delete(did);
3676
+ keyCache.set(did, cached);
3677
+ return cached;
3678
+ }
3679
+ const raw = decompressP256(compressedPointFromDid(did), did);
3680
+ const key = await crypto.subtle.importKey("raw", raw.buffer, {
3681
+ name: "ECDSA",
3682
+ namedCurve: "P-256"
3683
+ }, false, ["verify"]);
3684
+ keyCache.set(did, key);
3685
+ if (keyCache.size > MAX_CACHED_KEYS) {
3686
+ const oldest = keyCache.keys().next().value;
3687
+ if (oldest !== void 0) keyCache.delete(oldest);
3688
+ }
3689
+ return key;
3690
+ }
3691
+ function compressedPointFromDid(did) {
3692
+ if (!did.startsWith(DID_KEY_PREFIX)) throw new MalformedDidKeyError(did, "not a base58btc did:key");
3693
+ const decoded = base58Decode(did.slice(9));
3694
+ if (!decoded || decoded.length !== 35) throw new MalformedDidKeyError(did, "not a compressed P-256 key");
3695
+ if (decoded[0] !== P256_MULTICODEC[0] || decoded[1] !== P256_MULTICODEC[1]) throw new MalformedDidKeyError(did, "not a P-256 multicodec");
3696
+ return decoded.subarray(2);
3697
+ }
3698
+ function decompressP256(compressed, did) {
3699
+ const prefix = compressed[0];
3700
+ if (prefix !== 2 && prefix !== 3) throw new MalformedDidKeyError(did, `compression prefix ${prefix}`);
3701
+ let x = BigInt(0);
3702
+ for (let i = 1; i < compressed.length; i++) x = x << BigInt(8) | BigInt(compressed[i]);
3703
+ if (x >= P) throw new MalformedDidKeyError(did, "x outside the field");
3704
+ const ySquared = (modPow(x, BigInt(3), P) + A * x + B) % P;
3705
+ let y = modPow(ySquared, (P + BigInt(1)) / BigInt(4), P);
3706
+ if (y * y % P !== ySquared) throw new MalformedDidKeyError(did, "x is not on the curve");
3707
+ if (y % BigInt(2) === BigInt(0) !== (prefix === 2)) y = P - y;
3708
+ const uncompressed = /* @__PURE__ */ new Uint8Array(65);
3709
+ uncompressed[0] = 4;
3710
+ uncompressed.set(bigIntToBytes(x), 1);
3711
+ uncompressed.set(bigIntToBytes(y), 33);
3712
+ return uncompressed;
3713
+ }
3714
+ function modPow(base, exp, mod) {
3715
+ let result = BigInt(1);
3716
+ let b = base % mod;
3717
+ let e = exp;
3718
+ while (e > BigInt(0)) {
3719
+ if (e & BigInt(1)) result = result * b % mod;
3720
+ e >>= BigInt(1);
3721
+ b = b * b % mod;
3722
+ }
3723
+ return result;
3724
+ }
3725
+ function bigIntToBytes(value) {
3726
+ const bytes = /* @__PURE__ */ new Uint8Array(32);
3727
+ let v = value;
3728
+ for (let i = 31; i >= 0; i--) {
3729
+ bytes[i] = Number(v & BigInt(255));
3730
+ v >>= BigInt(8);
3731
+ }
3732
+ return bytes;
3733
+ }
3734
+ //#endregion
3735
+ //#region src/signer/verify-action-signature.ts
3736
+ const RENOWN_HASH_LENGTH = 44;
3737
+ const SHARED_HASH_LENGTH = 28;
3738
+ const HEX_SIGNATURE = /^(0x)?([0-9a-fA-F]{2})+$/;
3739
+ /**
3740
+ * Integrity only: identity binding and the live-id check sit with the caller.
3741
+ * `operation` is the incoming operation at load admission.
3742
+ */
3743
+ async function verifyActionSignature(action, target, path, operation) {
3744
+ const policy = target.policy ?? "legacy";
3745
+ const refusal = policyRefusal(action, policy);
3746
+ if (refusal) return refusal;
3747
+ const shape = signerShape(action.context?.signer);
3748
+ if (shape.kind === "unsigned") return policy === "v2-required" ? refuse("unsigned", "UNSIGNED_REQUIRED", `action ${action.id} is unsigned but ${target.documentId} requires v2 signatures`) : {
3749
+ ok: true,
3750
+ scheme: "unsigned"
3751
+ };
3752
+ if (shape.kind === "malformed") return refuse("legacy-unknown", "MALFORMED_TUPLE", `action ${action.id} ${shape.reason}`);
3753
+ const { signer, tuple } = shape;
3754
+ const scheme = schemeOf(tuple[2]);
3755
+ if (tuple[1] !== signer.app.key) return refuse(scheme, "KEY_MISMATCH", `action ${action.id} tuple key does not match signer.app.key`);
3756
+ if (scheme !== "v2" && policy === "v2-required") return refuse(scheme, "SCHEME_BELOW_POLICY", `action ${action.id} carries a legacy tuple but ${target.documentId} requires v2 signatures`);
3757
+ if (scheme === "legacy-unknown" && path === "mutation") return refuse(scheme, "MALFORMED_TUPLE", `action ${action.id} carries a hash of unknown length ${tuple[2].length}`);
3758
+ if (scheme === "v2") {
3759
+ const refusal = await checkV2(action, tuple, target, operation);
3760
+ if (refusal) return refusal;
3761
+ } else if (path === "mutation") {
3762
+ if (await legacyHash(scheme, action, target) !== tuple[2]) return refuse(scheme, "HASH_MISMATCH", `action ${action.id} does not match the hash its signature covers`);
3763
+ }
3764
+ return verifyEcdsa(tuple, scheme, action.id);
3765
+ }
3766
+ /** Refusals a document's policy makes before any tuple is read. */
3767
+ function policyRefusal(action, policy) {
3768
+ if (action.type !== "CREATE_DOCUMENT") return;
3769
+ const scheme = schemeOfAction(action);
3770
+ const input = action.input;
3771
+ const documentId = input?.documentId ?? "";
3772
+ if (policy === "legacy") return isDerivedDocumentId(documentId) ? refuse(scheme, "ID_MISMATCH", `action ${action.id}: a legacy document cannot take the content-addressed id ${documentId}`) : void 0;
3773
+ const expected = derivedCreateId(input);
3774
+ if (expected === documentId) return;
3775
+ return refuse(scheme, "ID_MISMATCH", expected === void 0 ? `action ${action.id} creates v2-required ${documentId} without the header params its id is derived from` : `action ${action.id} creates v2-required ${documentId}, but its header params derive ${expected}`);
3776
+ }
3777
+ function derivedCreateId(input) {
3778
+ if (!input?.signing || !input.protocolVersions) return;
3779
+ try {
3780
+ return deriveDocumentId({
3781
+ documentType: input.model,
3782
+ createdAtUtcIso: input.signing.createdAtUtcIso,
3783
+ nonce: input.signing.nonce,
3784
+ protocolVersions: input.protocolVersions
3785
+ });
3786
+ } catch {
3787
+ return;
3788
+ }
3789
+ }
3790
+ function schemeOfAction(action) {
3791
+ const shape = signerShape(action.context?.signer);
3792
+ if (shape.kind === "unsigned") return "unsigned";
3793
+ return shape.kind === "signed" ? schemeOf(shape.tuple[2]) : "legacy-unknown";
3794
+ }
3795
+ /** Peers send arbitrary JSON, so nothing past `context` is assumed. */
3796
+ function signerShape(value) {
3797
+ if (value === void 0 || value === null) return { kind: "unsigned" };
3798
+ if (!isRecord(value)) return {
3799
+ kind: "malformed",
3800
+ reason: "has a signer that is not an object"
3801
+ };
3802
+ const { app, user, signatures } = value;
3803
+ if (app !== void 0 && app !== null && !isRecord(app)) return {
3804
+ kind: "malformed",
3805
+ reason: "has a signer.app that is not an object"
3806
+ };
3807
+ const key = isRecord(app) ? app.key : void 0;
3808
+ if (key !== void 0 && key !== null && typeof key !== "string") return {
3809
+ kind: "malformed",
3810
+ reason: "has a signer.app.key that is not a string"
3811
+ };
3812
+ if (!key) return { kind: "unsigned" };
3813
+ if (!isRecord(user) || typeof user.address !== "string") return {
3814
+ kind: "malformed",
3815
+ reason: "has a signer without a user address"
3816
+ };
3817
+ if (!Array.isArray(signatures)) return {
3818
+ kind: "malformed",
3819
+ reason: "has a signer whose signatures are not a list"
3820
+ };
3821
+ const tuple = signatures.at(-1);
3822
+ if (!isTuple(tuple)) return {
3823
+ kind: "malformed",
3824
+ reason: signatures.length === 0 ? "has a signer but no signatures" : "carries a malformed signature tuple"
3825
+ };
3826
+ return {
3827
+ kind: "signed",
3828
+ signer: value,
3829
+ tuple
3830
+ };
3831
+ }
3832
+ function isRecord(value) {
3833
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3834
+ }
3835
+ async function checkV2(action, tuple, target, operation) {
3836
+ const problem = v2TupleProblem(tuple);
3837
+ if (problem) return refuse("v2", "MALFORMED_TUPLE", `action ${action.id}: ${problem}`);
3838
+ if (operation && !sameInstant(operation.timestampUtcMs, action.timestampUtcMs)) return refuse("v2", "TIMESTAMP_MISMATCH", `action ${action.id} is stamped ${action.timestampUtcMs} but its operation ${operation.timestampUtcMs}`);
3839
+ let expected;
3840
+ try {
3841
+ expected = await hashActionV2(action, target, action.context.signer);
3842
+ } catch (error) {
3843
+ return refuse("v2", "HASH_MISMATCH", `action ${action.id} has no v2 hash: ${error instanceof Error ? error.message : String(error)}`);
3844
+ }
3845
+ if (expected !== tuple[2]) return refuse("v2", "HASH_MISMATCH", `action ${action.id} does not match the hash its signature covers`);
3846
+ }
3847
+ function sameInstant(a, b) {
3848
+ const left = Date.parse(a);
3849
+ return !Number.isNaN(left) && left === Date.parse(b);
3850
+ }
3851
+ function schemeOf(hash) {
3852
+ if (isV2ActionHash(hash)) return "v2";
3853
+ if (hash.length === RENOWN_HASH_LENGTH) return "legacy-renown";
3854
+ if (hash.length === SHARED_HASH_LENGTH) return "legacy-shared";
3855
+ return "legacy-unknown";
3856
+ }
3857
+ async function legacyHash(scheme, action, target) {
3858
+ if (scheme === "legacy-renown") {
3859
+ const payload = [
3860
+ action.scope,
3861
+ action.type,
3862
+ JSON.stringify(action.input)
3863
+ ].join("");
3864
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(payload));
3865
+ return bytesToBase64(new Uint8Array(digest));
3866
+ }
3867
+ const signer = action.context.signer;
3868
+ return buildOperationSignatureParams({
3869
+ documentId: target.documentId,
3870
+ signer,
3871
+ action,
3872
+ previousStateHash: ""
3873
+ })[2];
3874
+ }
3875
+ async function verifyEcdsa(tuple, scheme, actionId) {
3876
+ const [timestamp, key, hash, prevStateHash, signatureHex] = tuple;
3877
+ if (!HEX_SIGNATURE.test(signatureHex)) return refuse(scheme, "MALFORMED_TUPLE", `action ${actionId} signature is not hex`);
3878
+ let cryptoKey;
3879
+ try {
3880
+ cryptoKey = await importDidKey(key);
3881
+ } catch (error) {
3882
+ return refuse(scheme, "MALFORMED_TUPLE", `action ${actionId}: ${error instanceof Error ? error.message : String(error)}`);
3883
+ }
3884
+ const message = buildOperationSignatureMessage([
3885
+ timestamp,
3886
+ key,
3887
+ hash,
3888
+ prevStateHash
3889
+ ]);
3890
+ let valid;
3891
+ try {
3892
+ valid = await crypto.subtle.verify({
3893
+ name: "ECDSA",
3894
+ hash: "SHA-256"
3895
+ }, cryptoKey, hexToBytes(signatureHex).buffer, message.buffer);
3896
+ } catch {
3897
+ valid = false;
3898
+ }
3899
+ if (!valid) return refuse(scheme, "BAD_SIGNATURE", `action ${actionId} signature does not verify under its key`);
3900
+ return {
3901
+ ok: true,
3902
+ scheme
3903
+ };
3904
+ }
3905
+ function isTuple(value) {
3906
+ return Array.isArray(value) && value.length === 5 && value.every((element) => typeof element === "string");
3907
+ }
3908
+ function refuse(scheme, code, reason) {
3909
+ return {
3910
+ ok: false,
3911
+ scheme,
3912
+ code,
3913
+ reason
3914
+ };
3915
+ }
3916
+ function hexToBytes(hex) {
3917
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
3918
+ const bytes = new Uint8Array(clean.length / 2);
3919
+ for (let i = 0; i < bytes.length; i++) bytes[i] = parseInt(clean.substring(i * 2, i * 2 + 2), 16);
3920
+ return bytes;
3921
+ }
3922
+ function bytesToBase64(bytes) {
3923
+ let binary = "";
3924
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
3925
+ return btoa(binary);
3926
+ }
3927
+ //#endregion
3928
+ //#region src/executor/synthesized-signing.ts
3929
+ /** Submitted types whose stored action the reducer writes in their place. */
3930
+ const SYNTHESIZING_TYPES = /* @__PURE__ */ new Set(["UNDO", "REDO"]);
3931
+ /**
3932
+ * The id of the action synthesized from `submittedId`. Derived, so the stored
3933
+ * operation answers for the submitted id at the live-id check.
3934
+ */
3935
+ function synthesizedActionId(submittedId) {
3936
+ return hashBrowser(`synthesized:${submittedId}`, "sha1", "hex").slice(0, 32);
3937
+ }
3938
+ /**
3939
+ * Whether `operation` stores an action the reducer made from `submitted`
3940
+ * rather than `submitted` itself or an operation already in `history`.
3941
+ */
3942
+ function isSynthesized(submitted, operation, history) {
3943
+ if (!SYNTHESIZING_TYPES.has(submitted.type)) return false;
3944
+ const id = operation.action.id;
3945
+ if (!id) return true;
3946
+ if (id === submitted.id) return false;
3947
+ return !history.some((existing) => existing.action.id === id);
3948
+ }
3949
+ /**
3950
+ * Signs the synthesized action of `operation` in place as `signer`, under the
3951
+ * id {@link synthesizedActionId} derives from the submitted one. A REDO's
3952
+ * rebuilt action has no timestamp, so it takes the submitted one; the
3953
+ * operation timestamp is set to the action's, which a peer requires of a v2
3954
+ * tuple.
3955
+ */
3956
+ async function signSynthesized(operation, submitted, target, signer, signal) {
3957
+ const synthesized = operation.action;
3958
+ const id = synthesizedActionId(submitted.id);
3959
+ const timestampUtcMs = operation.timestampUtcMs || synthesized.timestampUtcMs || submitted.timestampUtcMs;
3960
+ const action = {
3961
+ id,
3962
+ timestampUtcMs,
3963
+ type: synthesized.type,
3964
+ input: synthesized.input,
3965
+ scope: synthesized.scope
3966
+ };
3967
+ const signature = await signer.signAction(action, {
3968
+ documentId: target.documentId,
3969
+ branch: target.branch
3970
+ }, signal);
3971
+ operation.action = {
3972
+ ...action,
3973
+ context: { signer: {
3974
+ ...actionSignerIdentity(signer),
3975
+ signatures: [signature]
3976
+ } }
3977
+ };
3978
+ operation.id = deriveOperationId(target.documentId, target.scope, target.branch, id);
3979
+ operation.timestampUtcMs = timestampUtcMs;
3980
+ }
3981
+ //#endregion
3982
+ //#region src/executor/signature-admission.ts
3983
+ const DEFAULT_TRUST_TIMEOUT_MS = 1e4;
3984
+ /** Runs once per write, when this reactor first stores it; never on re-appends. */
3985
+ var SignatureAdmission = class {
3986
+ mode;
3987
+ logger;
3988
+ eventBus;
3989
+ policySource;
3990
+ trustPolicy;
3991
+ trustTimeoutMs;
3992
+ constructor(mode, logger, eventBus, policySource = "meta", trust = {
3993
+ authEnforcement: false,
3994
+ timeoutMs: DEFAULT_TRUST_TIMEOUT_MS
3995
+ }) {
3996
+ this.mode = mode;
3997
+ this.logger = logger;
3998
+ this.eventBus = eventBus;
3999
+ this.policySource = policySource;
4000
+ this.trustPolicy = admissionTrustPolicy(trust.signer ?? new PassthroughSigner(), trust.authEnforcement, trust.policy);
4001
+ this.trustTimeoutMs = trust.timeoutMs;
4002
+ }
4003
+ /** The first refusal of a mutation's submitted actions, when enforcing. */
4004
+ async admitMutation(job, stores, signal) {
4005
+ const { operationStore } = stores;
4006
+ const candidates = job.actions.map((action) => candidate(action, mutationStream(action, job)));
4007
+ const live = await this.liveOperationIds(candidates, operationStore, signal);
4008
+ const committed = await this.storedAsSubmitted(candidates, live, isRetry(job), operationStore, signal);
4009
+ await resolvePolicies(candidates, job.actions, (documentId, branch) => storedPolicy(stores, this.policySource, documentId, branch, signal));
4010
+ const submitted = /* @__PURE__ */ new Set();
4011
+ for (const entry of candidates) {
4012
+ if (committed.has(entry)) continue;
4013
+ const verdict = await this.verdict(entry, live, submitted, "mutation", signal);
4014
+ submitted.add(entry.opId);
4015
+ if (verdict.ok) continue;
4016
+ const refusal = this.record(job, entry, verdict, "mutation");
4017
+ if (refusal) return {
4018
+ kind: "refused",
4019
+ error: refusal
4020
+ };
4021
+ }
4022
+ return {
4023
+ kind: "admitted",
4024
+ committed: [...committed.values()]
4025
+ };
4026
+ }
4027
+ /** The incoming operations a load drops, empty unless enforcing. */
4028
+ async admitLoad(job, operations, stores, signal) {
4029
+ const { operationStore } = stores;
4030
+ const stream = {
4031
+ documentId: job.documentId,
4032
+ scope: job.scope,
4033
+ branch: job.branch
4034
+ };
4035
+ const candidates = operations.map((operation) => ({
4036
+ ...candidate(operation.action, stream),
4037
+ operation
4038
+ }));
4039
+ const live = await this.liveOperationIds(candidates, operationStore, signal);
4040
+ const redelivered = await this.redelivered(candidates, live, operationStore, signal);
4041
+ await resolvePolicies(candidates, job.operations.map((operation) => operation.action), (documentId, branch) => storedPolicy(stores, this.policySource, documentId, branch, signal));
4042
+ const dropped = /* @__PURE__ */ new Set();
4043
+ for (let i = 0; i < candidates.length; i++) {
4044
+ const verdict = await this.verdict(candidates[i], live, void 0, "load", signal, redelivered.has(candidates[i]));
4045
+ if (verdict.ok) continue;
4046
+ if (this.record(job, candidates[i], verdict, "load")) dropped.add(operations[i]);
4047
+ }
4048
+ return dropped;
4049
+ }
4050
+ async verdict(entry, live, submitted, path, signal, known = false) {
4051
+ const verdict = await verifyActionSignature(entry.action, {
4052
+ documentId: entry.stream.documentId,
4053
+ branch: entry.stream.branch,
4054
+ policy: entry.policy
4055
+ }, path, entry.operation);
4056
+ if (!verdict.ok) return verdict;
4057
+ if (!known && (isLive(entry, live) || submitted?.has(entry.opId))) return {
4058
+ ok: false,
4059
+ scheme: verdict.scheme,
4060
+ code: "DUPLICATE_ACTION",
4061
+ reason: `action ${entry.action.id} is already in the stream`
4062
+ };
4063
+ if (verdict.scheme === "unsigned") {
4064
+ const address = entry.action.context?.signer?.user?.address;
4065
+ return address ? {
4066
+ ok: false,
4067
+ scheme: "unsigned",
4068
+ code: "UNSIGNED_IDENTITY",
4069
+ reason: `action ${entry.action.id} is unsigned but claims to act as ${address}`
4070
+ } : verdict;
4071
+ }
4072
+ if (known) return verdict;
4073
+ const signer = entry.action.context.signer;
4074
+ if (!await this.authorize(signer, entry.stream.documentId, signal)) return {
4075
+ ok: false,
4076
+ scheme: verdict.scheme,
4077
+ code: "SIGNER_UNAUTHORIZED",
4078
+ reason: `action ${entry.action.id}: key ${signer.app.key} may not sign as ${signer.user.address}`
4079
+ };
4080
+ return verdict;
4081
+ }
4082
+ /** Throws when the policy does, times out or the job is aborted. */
4083
+ async authorize(signer, documentId, signal) {
4084
+ signal?.throwIfAborted();
4085
+ let timer;
4086
+ let onAbort;
4087
+ const stop = new Promise((_, reject) => {
4088
+ timer = setTimeout(() => {
4089
+ reject(/* @__PURE__ */ new Error(`signature trust policy did not answer for ${signer.app.key} within ${this.trustTimeoutMs}ms`));
4090
+ }, this.trustTimeoutMs);
4091
+ if (signal) {
4092
+ onAbort = () => reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason)));
4093
+ signal.addEventListener("abort", onAbort, { once: true });
4094
+ }
4095
+ });
4096
+ try {
4097
+ return await Promise.race([this.trustPolicy.authorizeSigner(signer, signer.app.key, documentId), stop]);
4098
+ } finally {
4099
+ clearTimeout(timer);
4100
+ if (onAbort) signal?.removeEventListener("abort", onAbort);
4101
+ }
4102
+ }
4103
+ /**
4104
+ * The live candidates stored exactly as submitted and still standing: one
4105
+ * an undo or a reshuffle has since superseded is refused, not re-emitted with
4106
+ * the state it once produced. A synthesized operation
4107
+ * holds another action, so it cannot be compared; only a retry, whose first
4108
+ * attempt this job was, takes it as committed.
4109
+ */
4110
+ async storedAsSubmitted(candidates, live, retry, operationStore, signal) {
4111
+ const committed = /* @__PURE__ */ new Map();
4112
+ const toCompare = [];
4113
+ for (const entry of candidates) if (entry.synthesizedOpId && live.has(entry.synthesizedOpId)) {
4114
+ if (retry) committed.set(entry, committedWrite(entry, entry.synthesizedOpId));
4115
+ } else if (live.has(entry.opId)) toCompare.push(entry);
4116
+ for (const [stream, entries] of byStream(toCompare)) {
4117
+ const stored = await operationStore.getOperationsByIds(stream.documentId, stream.scope, stream.branch, entries.map((entry) => entry.opId), signal);
4118
+ const byId = new Map(stored.map((operation) => [operation.id, operation]));
4119
+ const matching = entries.filter((entry) => {
4120
+ const operation = byId.get(entry.opId);
4121
+ return operation && sameContent(operation.action, entry.action);
4122
+ });
4123
+ if (matching.length === 0) continue;
4124
+ const later = await operationStore.getSince(stream.documentId, stream.scope, stream.branch, Math.min(...matching.map((entry) => byId.get(entry.opId).index)), void 0, void 0, signal);
4125
+ for (const entry of matching) if (!isSuperseded(byId.get(entry.opId), later.results)) committed.set(entry, committedWrite(entry, entry.opId));
4126
+ }
4127
+ return committed;
4128
+ }
4129
+ /**
4130
+ * The load candidates stored exactly as they arrive, timestamp included: a
4131
+ * peer re-sending what this stream holds, not a replay of its action.
4132
+ */
4133
+ async redelivered(candidates, live, operationStore, signal) {
4134
+ const found = /* @__PURE__ */ new Set();
4135
+ const toCompare = candidates.filter((entry) => live.has(entry.opId) && !(entry.synthesizedOpId && live.has(entry.synthesizedOpId)));
4136
+ for (const [stream, entries] of byStream(toCompare)) {
4137
+ const stored = await operationStore.getOperationsByIds(stream.documentId, stream.scope, stream.branch, entries.map((entry) => entry.opId), signal);
4138
+ for (const entry of entries) if (stored.some((operation) => operation.id === entry.opId && Date.parse(operation.timestampUtcMs) === Date.parse(entry.operation?.timestampUtcMs ?? "") && sameContent(operation.action, entry.action))) found.add(entry);
4139
+ }
4140
+ return found;
4141
+ }
4142
+ async liveOperationIds(candidates, operationStore, signal) {
4143
+ const live = /* @__PURE__ */ new Set();
4144
+ for (const [stream, entries] of byStream(candidates)) {
4145
+ const found = await operationStore.findOperationIds(stream.documentId, stream.scope, stream.branch, entries.flatMap((entry) => entry.synthesizedOpId ? [entry.opId, entry.synthesizedOpId] : [entry.opId]), signal);
4146
+ for (const opId of found) live.add(opId);
4147
+ }
4148
+ return live;
4149
+ }
4150
+ /** Logs and counts a refusal; returns the error only when enforcing. */
4151
+ record(job, entry, refusal, path) {
4152
+ const enforced = this.mode === "enforce";
4153
+ const event = {
4154
+ jobId: job.id,
4155
+ documentId: entry.stream.documentId,
4156
+ scope: entry.stream.scope,
4157
+ branch: entry.stream.branch,
4158
+ actionId: entry.action.id,
4159
+ code: refusal.code,
4160
+ scheme: refusal.scheme,
4161
+ path,
4162
+ enforced,
4163
+ reason: refusal.reason
4164
+ };
4165
+ this.logger.warn(enforced ? "Signature refused: @Refusal" : "Signature refusal logged, write admitted: @Refusal", event);
4166
+ this.eventBus.emit(ReactorEventTypes.SIGNATURE_REFUSED, event).catch((error) => {
4167
+ this.logger.error("Failed to emit SIGNATURE_REFUSED: @Error", error);
4168
+ });
4169
+ return enforced ? new InvalidSignatureError(entry.stream.documentId, refusal.code, refusal.reason) : void 0;
4170
+ }
4171
+ };
4172
+ /**
4173
+ * A CREATE_DOCUMENT is verified under its own input. Anything else takes the
4174
+ * stored document's policy, or, for a document this job creates, that CREATE's.
4175
+ */
4176
+ async function resolvePolicies(candidates, jobActions, stored) {
4177
+ const resolved = /* @__PURE__ */ new Map();
4178
+ for (const entry of candidates) {
4179
+ if (entry.action.type === "CREATE_DOCUMENT") {
4180
+ entry.policy = createPolicy(entry.action);
4181
+ continue;
4182
+ }
4183
+ const { documentId, branch } = entry.stream;
4184
+ const key = `${documentId}\u0000${branch}`;
4185
+ let policy = resolved.get(key);
4186
+ if (policy === void 0) {
4187
+ policy = await stored(documentId, branch) ?? createdPolicy(jobActions, documentId) ?? "legacy";
4188
+ resolved.set(key, policy);
4189
+ }
4190
+ entry.policy = policy;
4191
+ }
4192
+ }
4193
+ async function storedPolicy(stores, source, documentId, branch, signal) {
4194
+ try {
4195
+ if (source === "write-cache") {
4196
+ const document = await stores.writeCache.getState(documentId, "document", branch, void 0, signal);
4197
+ return signaturePolicyOf(document.header);
4198
+ }
4199
+ const meta = await stores.documentMetaCache.getDocumentMeta(documentId, branch, signal);
4200
+ return signaturePolicyOf(meta.protocolVersions);
4201
+ } catch (error) {
4202
+ if (DocumentNotFoundError.isError(error)) return;
4203
+ throw error;
4204
+ }
4205
+ }
4206
+ function createdPolicy(actions, documentId) {
4207
+ const create = actions.find((action) => action.type === "CREATE_DOCUMENT" && action.input?.documentId === documentId);
4208
+ return create ? createPolicy(create) : void 0;
4209
+ }
4210
+ function createPolicy(action) {
4211
+ return signaturePolicyOf(action.input);
4212
+ }
4213
+ function candidate(action, stream) {
4214
+ const opId = (actionId) => deriveOperationId(stream.documentId, stream.scope, stream.branch, actionId);
4215
+ return {
4216
+ action,
4217
+ stream,
4218
+ opId: opId(action.id),
4219
+ synthesizedOpId: SYNTHESIZING_TYPES.has(action.type) ? opId(synthesizedActionId(action.id)) : void 0
4220
+ };
4221
+ }
4222
+ function isLive(entry, live) {
4223
+ return live.has(entry.opId) || entry.synthesizedOpId !== void 0 && live.has(entry.synthesizedOpId);
4224
+ }
4225
+ function mutationStream(action, job) {
4226
+ return {
4227
+ ...actionSigningTarget(action, job.documentId, job.branch),
4228
+ scope: job.scope
4229
+ };
4230
+ }
4231
+ function byStream(candidates) {
4232
+ const groups = /* @__PURE__ */ new Map();
4233
+ for (const entry of candidates) {
4234
+ const key = `${entry.stream.documentId}\u0000${entry.stream.scope}\u0000${entry.stream.branch}`;
4235
+ const group = groups.get(key) ?? [entry.stream, []];
4236
+ group[1].push(entry);
4237
+ groups.set(key, group);
4238
+ }
4239
+ return [...groups.values()];
4240
+ }
4241
+ function isRetry(job) {
4242
+ return job.errorHistory.length > 0 || (job.retryCount ?? 0) > 0;
4243
+ }
4244
+ function committedWrite(entry, opId) {
4245
+ return {
4246
+ ...entry.stream,
4247
+ actionId: entry.action.id,
4248
+ opId
4249
+ };
4250
+ }
4251
+ function isSuperseded(operation, later) {
4252
+ return later.some((next) => next.index > operation.index && next.skip > 0 && next.index - next.skip <= operation.index);
4253
+ }
4254
+ function sameContent(stored, submitted) {
4255
+ try {
4256
+ return canonicalJson(stored) === canonicalJson(submitted);
4257
+ } catch {
4258
+ return false;
4259
+ }
4260
+ }
4261
+ //#endregion
4262
+ //#region src/executor/simple-job-executor.ts
4263
+ const MAX_SKIP_THRESHOLD = 1e3;
4264
+ const ISO_TIMESTAMP_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
4265
+ function isValidISOTimestamp(value) {
4266
+ if (!ISO_TIMESTAMP_REGEX.test(value)) return false;
4267
+ return !isNaN(new Date(value).getTime());
4268
+ }
4269
+ /**
4270
+ * Carries a failed job out of the execution scope so its transaction rolls
4271
+ * back. A scope callback that returns commits, whatever result it returns, so
4272
+ * a returned failure would leave the writes the job made before it failed
4273
+ * standing. Never escapes executeJob: the failure goes back to being a
4274
+ * returned JobResult there, which is what the queue, the worker protocol and
4275
+ * every test expect a failed job to look like.
4276
+ */
4277
+ var JobRollbackSignal = class extends Error {
4278
+ result;
4279
+ constructor(result) {
3267
4280
  super("job rolled back");
3268
4281
  this.result = result;
3269
4282
  this.name = "JobRollbackSignal";
3270
4283
  }
3271
4284
  };
3272
4285
  var SimpleJobExecutor = class {
4286
+ logger;
4287
+ registry;
4288
+ operationStore;
4289
+ eventBus;
4290
+ writeCache;
4291
+ operationIndex;
4292
+ documentMetaCache;
4293
+ collectionMembershipCache;
4294
+ driveContainerTypes;
3273
4295
  config;
3274
4296
  featureFlags;
3275
4297
  decisionModel;
3276
- signatureVerifierModule;
4298
+ signatureAdmission;
3277
4299
  documentActionHandler;
3278
4300
  executionScope;
3279
- constructor(logger, registry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, driveContainerTypes, config, signatureVerifier, executionScope) {
4301
+ signer;
4302
+ /**
4303
+ * `signer` signs the operations the reducer synthesizes; unsigned if omitted.
4304
+ * `trustPolicy` decides which keys may sign as which users at admission.
4305
+ */
4306
+ constructor(logger, registry, operationStore, eventBus, writeCache, operationIndex, documentMetaCache, collectionMembershipCache, driveContainerTypes, config, executionScope, signer, trustPolicy) {
3280
4307
  this.logger = logger;
3281
4308
  this.registry = registry;
3282
4309
  this.operationStore = operationStore;
@@ -3286,6 +4313,8 @@ var SimpleJobExecutor = class {
3286
4313
  this.documentMetaCache = documentMetaCache;
3287
4314
  this.collectionMembershipCache = collectionMembershipCache;
3288
4315
  this.driveContainerTypes = driveContainerTypes;
4316
+ this.signer = signer ?? new PassthroughSigner();
4317
+ this.featureFlags = resolveFeatureFlags(config.featureFlags);
3289
4318
  this.config = {
3290
4319
  featureFlags: config.featureFlags ?? {},
3291
4320
  maxSkipThreshold: config.maxSkipThreshold ?? MAX_SKIP_THRESHOLD,
@@ -3295,12 +4324,18 @@ var SimpleJobExecutor = class {
3295
4324
  retryBaseDelayMs: config.retryBaseDelayMs ?? 100,
3296
4325
  retryMaxDelayMs: config.retryMaxDelayMs ?? 5e3,
3297
4326
  yieldDeadlineMs: config.yieldDeadlineMs ?? 50,
3298
- batchApplies: config.batchApplies ?? true
4327
+ batchApplies: config.batchApplies ?? true,
4328
+ signatureVerification: config.signatureVerification ?? "enforce",
4329
+ protocolSupport: config.protocolSupport ?? localSupports(PEER_CAPABILITIES, this.featureFlags).protocols
3299
4330
  };
3300
- this.featureFlags = resolveFeatureFlags(config.featureFlags);
3301
4331
  this.decisionModel = selectDecisionModel(this.featureFlags, registry);
3302
- this.signatureVerifierModule = new SignatureVerifier(signatureVerifier);
3303
- this.documentActionHandler = new DocumentActionHandler(registry, logger, driveContainerTypes, this.featureFlags, this.decisionModel);
4332
+ this.signatureAdmission = new SignatureAdmission(this.config.signatureVerification, logger, eventBus, this.featureFlags.documentDecisions ? "write-cache" : "meta", {
4333
+ signer: this.signer,
4334
+ authEnforcement: this.featureFlags.authEnforcement,
4335
+ policy: trustPolicy,
4336
+ timeoutMs: Math.min(DEFAULT_TRUST_TIMEOUT_MS, this.config.jobTimeoutMs / 2)
4337
+ });
4338
+ this.documentActionHandler = new DocumentActionHandler(registry, logger, driveContainerTypes, this.featureFlags, this.decisionModel, this.config.protocolSupport);
3304
4339
  this.executionScope = executionScope ?? new DefaultExecutionScope(operationStore, operationIndex, writeCache, documentMetaCache, collectionMembershipCache);
3305
4340
  }
3306
4341
  /**
@@ -3366,6 +4401,8 @@ var SimpleJobExecutor = class {
3366
4401
  async executeInScope(params) {
3367
4402
  const { job, startTime, stores, signal, touchedStreams, postCommitInvalidations, postCommitMembershipInvalidations } = params;
3368
4403
  let pendingEvent;
4404
+ const unsupported = await this.unsupportedStoredProtocol(job, stores, signal);
4405
+ if (unsupported) return { result: buildErrorResult(job, unsupported, startTime) };
3369
4406
  const indexTxn = stores.operationIndex.start();
3370
4407
  if (job.kind === "load") {
3371
4408
  const loadResult = await this.executeLoadJob({
@@ -3429,10 +4466,29 @@ var SimpleJobExecutor = class {
3429
4466
  pendingEvent
3430
4467
  };
3431
4468
  }
3432
- const positioned = await this.positionByTimestamp(job, stores, signal);
4469
+ let admission;
4470
+ try {
4471
+ admission = await this.signatureAdmission.admitMutation(job, stores, signal);
4472
+ } catch (error) {
4473
+ return { result: buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime) };
4474
+ }
4475
+ if (admission.kind === "refused") return { result: buildErrorResult(job, admission.error, startTime) };
4476
+ let committed;
4477
+ try {
4478
+ committed = await this.reloadCommitted(admission.committed, stores, signal);
4479
+ } catch (error) {
4480
+ return { result: buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime) };
4481
+ }
4482
+ const committedIds = new Set(admission.committed.map((write) => write.actionId));
4483
+ const fresh = committedIds.size === 0 ? job : {
4484
+ ...job,
4485
+ actions: job.actions.filter((action) => !committedIds.has(action.id))
4486
+ };
4487
+ if (fresh.actions.length === 0) return this.committedOutcome(job, committed, [], [], stores, startTime);
4488
+ const positioned = await this.positionByTimestamp(fresh, stores, signal);
3433
4489
  if (positioned.error) return { result: buildErrorResult(job, positioned.error, startTime) };
3434
4490
  const executing = {
3435
- job,
4491
+ job: fresh,
3436
4492
  startTime,
3437
4493
  indexTxn,
3438
4494
  stores,
@@ -3462,13 +4518,33 @@ var SimpleJobExecutor = class {
3462
4518
  } };
3463
4519
  const ordinals = await stores.operationIndex.commit(indexTxn, signal);
3464
4520
  postCommitMembershipInvalidations.push(...indexTxn.getMembershipInvalidations());
3465
- if (actionResult.operationsWithContext.length > 0) {
3466
- for (let i = 0; i < actionResult.operationsWithContext.length; i++) actionResult.operationsWithContext[i].context.ordinal = ordinals[i];
3467
- const collectionMemberships = await this.getCollectionMembershipsForOperations(actionResult.operationsWithContext, stores);
4521
+ for (let i = 0; i < actionResult.operationsWithContext.length; i++) actionResult.operationsWithContext[i].context.ordinal = ordinals[i];
4522
+ return this.committedOutcome(job, committed, actionResult.operationsWithContext, actionResult.generatedOperations, stores, startTime);
4523
+ }
4524
+ /** A stored document at a version this reactor does not run is read-only here. */
4525
+ async unsupportedStoredProtocol(job, stores, signal) {
4526
+ let versions;
4527
+ try {
4528
+ versions = this.featureFlags.documentDecisions && job.kind !== "load" ? (await stores.writeCache.getState(job.documentId, "document", job.branch, void 0, signal)).header.protocolVersions : (await stores.documentMetaCache.getDocumentMeta(job.documentId, job.branch, signal)).protocolVersions;
4529
+ } catch {
4530
+ return;
4531
+ }
4532
+ return this.documentActionHandler.unsupportedProtocol(job.documentId, versions);
4533
+ }
4534
+ /**
4535
+ * A successful mutation's result and the write-ready event it owes, carrying
4536
+ * what earlier attempts committed ahead of what this one wrote.
4537
+ */
4538
+ async committedOutcome(job, reloaded, written, generated, stores, startTime) {
4539
+ const operationsWithContext = [...reloaded, ...written];
4540
+ let pendingEvent;
4541
+ if (operationsWithContext.length > 0) {
4542
+ const collectionMemberships = await this.getCollectionMembershipsForOperations(operationsWithContext, stores);
3468
4543
  pendingEvent = {
3469
4544
  jobId: job.id,
3470
- operations: actionResult.operationsWithContext,
4545
+ operations: operationsWithContext,
3471
4546
  jobMeta: job.meta,
4547
+ submittedActionIds: submittedActionIds(job),
3472
4548
  collectionMemberships
3473
4549
  };
3474
4550
  }
@@ -3476,14 +4552,62 @@ var SimpleJobExecutor = class {
3476
4552
  result: {
3477
4553
  job,
3478
4554
  success: true,
3479
- operations: actionResult.generatedOperations,
3480
- operationsWithContext: actionResult.operationsWithContext,
4555
+ operations: [...reloaded.map((entry) => entry.operation), ...generated],
4556
+ operationsWithContext,
3481
4557
  duration: Date.now() - startTime
3482
4558
  },
3483
4559
  pendingEvent
3484
4560
  };
3485
4561
  }
3486
4562
  /**
4563
+ * The stored operations of writes an earlier attempt committed, as a fresh
4564
+ * write would report them. State is rebuilt from the write cache, and left
4565
+ * out when it cannot be.
4566
+ */
4567
+ async reloadCommitted(writes, stores, signal) {
4568
+ const streams = /* @__PURE__ */ new Map();
4569
+ for (const write of writes) {
4570
+ const key = `${write.documentId}\u0000${write.scope}\u0000${write.branch}`;
4571
+ streams.set(key, [...streams.get(key) ?? [], write]);
4572
+ }
4573
+ const reloaded = [];
4574
+ for (const group of streams.values()) {
4575
+ const { documentId, scope, branch } = group[0];
4576
+ const opIds = [...new Set(group.map((write) => write.opId))];
4577
+ const operations = await stores.operationStore.getOperationsByIds(documentId, scope, branch, opIds, signal);
4578
+ const ordinals = await stores.operationIndex.getOrdinalsByOpIds(documentId, scope, branch, opIds, signal);
4579
+ const { documentType } = await stores.documentMetaCache.getDocumentMeta(documentId, branch, signal);
4580
+ for (const operation of operations.sort((a, b) => a.index - b.index)) reloaded.push({
4581
+ operation,
4582
+ context: {
4583
+ documentId,
4584
+ scope,
4585
+ branch,
4586
+ documentType,
4587
+ resultingState: await this.rebuiltState(stores, {
4588
+ documentId,
4589
+ scope,
4590
+ branch
4591
+ }, operation, signal),
4592
+ ordinal: ordinals.get(operation.id) ?? 0
4593
+ }
4594
+ });
4595
+ }
4596
+ return reloaded;
4597
+ }
4598
+ async rebuiltState(stores, stream, operation, signal) {
4599
+ try {
4600
+ const document = await stores.writeCache.getState(stream.documentId, stream.scope, stream.branch, operation.index, signal);
4601
+ return JSON.stringify({
4602
+ ...document.state,
4603
+ header: document.header
4604
+ });
4605
+ } catch (error) {
4606
+ this.logger.warn("No state for committed operation @OperationId in @Stream: @Error", operation.id, stream, error);
4607
+ return;
4608
+ }
4609
+ }
4610
+ /**
3487
4611
  * Drops the cached state of every stream a job wrote, after its transaction
3488
4612
  * did not commit.
3489
4613
  *
@@ -3512,16 +4636,6 @@ var SimpleJobExecutor = class {
3512
4636
  const actions = writes.map((write) => write.action);
3513
4637
  const generatedOperations = [];
3514
4638
  const operationsWithContext = [];
3515
- try {
3516
- await this.signatureVerifierModule.verifyActions(job.documentId, job.branch, actions);
3517
- } catch (error) {
3518
- return {
3519
- success: false,
3520
- generatedOperations,
3521
- operationsWithContext,
3522
- error: error instanceof Error ? error : new Error(String(error))
3523
- };
3524
- }
3525
4639
  for (const action of actions) if (action.timestampUtcMs && !isValidISOTimestamp(action.timestampUtcMs)) return {
3526
4640
  success: false,
3527
4641
  generatedOperations,
@@ -3619,7 +4733,7 @@ var SimpleJobExecutor = class {
3619
4733
  if (docMeta.state.isDeleted) return buildErrorResult(job, new DocumentDeletedError(job.documentId, docMeta.state.deletedAtUtcIso), startTime);
3620
4734
  documentVersion = docMeta.state.version;
3621
4735
  }
3622
- if (isUndoRedo(action) || action.type === "PRUNE" || skip > 0) stores.writeCache.invalidate(job.documentId, job.scope, job.branch);
4736
+ if (isUndoRedo(action) || skip > 0) stores.writeCache.invalidate(job.documentId, job.scope, job.branch);
3623
4737
  let document;
3624
4738
  if (baseDocument !== void 0) document = baseDocument;
3625
4739
  else try {
@@ -3679,7 +4793,7 @@ var SimpleJobExecutor = class {
3679
4793
  branch: job.branch,
3680
4794
  protocolVersion
3681
4795
  };
3682
- updatedDocument = module.reducer(document, action, void 0, reducerOptions);
4796
+ updatedDocument = withProtocolVersions(module.reducer(document, action, void 0, reducerOptions), document.header.protocolVersions);
3683
4797
  } catch (error) {
3684
4798
  const contextMessage = `Failed to apply action to document:\n Action type: ${action.type}\n Document ID: ${job.documentId}\n Document type: ${document.header.documentType}\n Scope: ${job.scope}\n Original error: ${error instanceof Error ? error.message : String(error)}`;
3685
4799
  const enhancedError = new Error(contextMessage);
@@ -3691,6 +4805,15 @@ var SimpleJobExecutor = class {
3691
4805
  if (operations.length === 0) return buildErrorResult(job, /* @__PURE__ */ new Error("No operation generated from action"), startTime);
3692
4806
  const newOperation = operations[operations.length - 1];
3693
4807
  if (!isUndoRedo(action)) newOperation.skip = skip;
4808
+ if (deniedReason === void 0 && sourceOperation === void 0 && !executing.replayingAcceptedHistory && isSynthesized(action, newOperation, document.operations[scope] ?? [])) try {
4809
+ await signSynthesized(newOperation, action, {
4810
+ documentId: job.documentId,
4811
+ scope,
4812
+ branch: job.branch
4813
+ }, this.signer, signal);
4814
+ } catch (error) {
4815
+ return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
4816
+ }
3694
4817
  const resultingState = JSON.stringify({
3695
4818
  ...updatedDocument.state,
3696
4819
  header: updatedDocument.header
@@ -3791,7 +4914,7 @@ var SimpleJobExecutor = class {
3791
4914
  * apply and its own reasons for it.
3792
4915
  * - A positional or replayed run carries skips and re-appended operations,
3793
4916
  * whose indices are not a simple ascending run from the head.
3794
- * - UNDO, REDO, PRUNE and NOOP-with-skip each invalidate the write cache to
4917
+ * - UNDO, REDO and NOOP-with-skip each invalidate the write cache to
3795
4918
  * force a full-history rebuild, so they cannot be reduced against state
3796
4919
  * threaded from the write before them.
3797
4920
  * - The auth scope decides later writes against the policy earlier ones
@@ -3809,7 +4932,7 @@ var SimpleJobExecutor = class {
3809
4932
  if (scope === "auth" || scope === "document") return false;
3810
4933
  return writes.every((write) => {
3811
4934
  const type = write.action.type;
3812
- return write.skip === 0 && write.deniedReason === void 0 && write.sourceOperation === void 0 && !DOCUMENT_SCOPE_ACTIONS.has(type) && !isUndoRedo(write.action) && type !== "PRUNE" && type !== "NOOP";
4935
+ return write.skip === 0 && write.deniedReason === void 0 && write.sourceOperation === void 0 && !DOCUMENT_SCOPE_ACTIONS.has(type) && !isUndoRedo(write.action) && type !== "NOOP";
3813
4936
  });
3814
4937
  }
3815
4938
  /**
@@ -4107,7 +5230,7 @@ var SimpleJobExecutor = class {
4107
5230
  stores.writeCache.invalidate(job.documentId, scope, job.branch);
4108
5231
  const result = await this.processActions(tail.map((operation, i) => ({
4109
5232
  action: operation.action,
4110
- skip: i === 0 ? retractionSkip(nextIndex, tail[0].index) : 0,
5233
+ skip: i === 0 ? retractionSkip(nextIndex, tail[0].index - tail[0].skip) : 0,
4111
5234
  sourceRemote: "",
4112
5235
  deniedReason: reevaluated[firstChange + i]
4113
5236
  })), {
@@ -4196,36 +5319,32 @@ var SimpleJobExecutor = class {
4196
5319
  error: new InvalidOperationTimestampError(job.documentId, scope, operation.timestampUtcMs, `operation (index: ${operation.index})`),
4197
5320
  duration: Date.now() - startTime
4198
5321
  };
4199
- let minIncomingIndex = Number.POSITIVE_INFINITY;
4200
- let minIncomingTimestamp = job.operations[0]?.timestampUtcMs || "";
4201
- for (const operation of job.operations) {
4202
- minIncomingIndex = Math.min(minIncomingIndex, operation.index);
4203
- const ts = operation.timestampUtcMs || "";
4204
- if (Date.parse(ts) < Date.parse(minIncomingTimestamp)) minIncomingTimestamp = ts;
4205
- }
4206
- let conflictingOps;
4207
- try {
4208
- conflictingOps = (await stores.operationStore.getConflicting(job.documentId, scope, job.branch, minIncomingTimestamp, void 0, signal)).results;
4209
- } catch {
4210
- conflictingOps = [];
4211
- }
4212
- let allOpsFromMinConflictingIndex = conflictingOps;
4213
- if (conflictingOps.length > 0) {
4214
- const minConflictingIndex = Math.min(...conflictingOps.map((op) => op.index));
5322
+ const dropped = /* @__PURE__ */ new Set();
5323
+ const admitted = /* @__PURE__ */ new Set();
5324
+ let selection = await this.selectLoadWrites(job, job.operations, stores, signal);
5325
+ for (;;) {
5326
+ const unadmitted = selection.incomingOpsToApply.filter((operation) => !admitted.has(operation));
5327
+ if (unadmitted.length === 0) break;
5328
+ let refused;
4215
5329
  try {
4216
- allOpsFromMinConflictingIndex = (await stores.operationStore.getSince(job.documentId, scope, job.branch, minConflictingIndex - 1, void 0, void 0, signal)).results;
4217
- } catch {
4218
- allOpsFromMinConflictingIndex = conflictingOps;
5330
+ refused = await this.signatureAdmission.admitLoad(job, unadmitted, stores, signal);
5331
+ } catch (error) {
5332
+ return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
4219
5333
  }
5334
+ for (const operation of unadmitted) if (refused.has(operation)) dropped.add(operation);
5335
+ else admitted.add(operation);
5336
+ if (refused.size === 0) break;
5337
+ const remaining = job.operations.filter((operation) => !dropped.has(operation));
5338
+ if (remaining.length === 0) return {
5339
+ job,
5340
+ success: true,
5341
+ operations: [],
5342
+ operationsWithContext: [],
5343
+ duration: Date.now() - startTime
5344
+ };
5345
+ selection = await this.selectLoadWrites(job, remaining, stores, signal);
4220
5346
  }
4221
- const incomingActionIds = new Set(job.operations.map((op) => op.action.id));
4222
- const nonSupersededOps = conflictingOps.filter((op) => {
4223
- if (op.index < minIncomingIndex && !incomingActionIds.has(op.action.id)) return false;
4224
- for (const laterOp of allOpsFromMinConflictingIndex) if (laterOp.index > op.index && laterOp.skip > 0) {
4225
- if (laterOp.index - laterOp.skip <= op.index) return false;
4226
- }
4227
- return true;
4228
- });
5347
+ const { nonSupersededOps, allOpsFromMinConflictingIndex, incomingOpsToApply } = selection;
4229
5348
  const existingOpsToReshuffle = monotonicAuthStream ? [] : nonSupersededOps.filter((operation) => !isGenesisOperation(operation));
4230
5349
  const actionIdCounts = /* @__PURE__ */ new Map();
4231
5350
  for (const operation of allOpsFromMinConflictingIndex) actionIdCounts.set(operation.action.id, (actionIdCounts.get(operation.action.id) ?? 0) + 1);
@@ -4246,14 +5365,6 @@ var SimpleJobExecutor = class {
4246
5365
  const logicalSkip = latestRevision - minLogicalIndex;
4247
5366
  if (logicalSkip > skipCount) skipCount = logicalSkip;
4248
5367
  }
4249
- const existingActionIds = new Set(nonSupersededOps.map((op) => op.action.id));
4250
- const seenIncomingActionIds = /* @__PURE__ */ new Set();
4251
- const incomingOpsToApply = job.operations.filter((op) => {
4252
- if (existingActionIds.has(op.action.id)) return false;
4253
- if (seenIncomingActionIds.has(op.action.id)) return false;
4254
- seenIncomingActionIds.add(op.action.id);
4255
- return true;
4256
- });
4257
5368
  if (incomingOpsToApply.length === 0) return {
4258
5369
  job,
4259
5370
  success: true,
@@ -4281,7 +5392,8 @@ var SimpleJobExecutor = class {
4281
5392
  ...operation,
4282
5393
  id: operation.id
4283
5394
  })));
4284
- for (const operation of reshuffledOperations) if (operation.action.type === "NOOP" && operation.skip === 0) operation.skip = 1;
5395
+ const incomingIds = new Set(incomingOpsToApply.map((operation) => operation.action.id));
5396
+ for (const operation of reshuffledOperations) if (operation.action.type === "NOOP" && operation.skip === 0 && incomingIds.has(operation.action.id)) operation.skip = 1;
4285
5397
  let deniedReasons;
4286
5398
  if (this.featureFlags.documentDecisions) try {
4287
5399
  deniedReasons = await evaluateByPosition(this.decisionModel, {
@@ -4333,6 +5445,71 @@ var SimpleJobExecutor = class {
4333
5445
  duration: Date.now() - startTime
4334
5446
  };
4335
5447
  }
5448
+ /** The conflicting window a load opens, and which of its operations are new. */
5449
+ async selectLoadWrites(job, operations, stores, signal) {
5450
+ const scope = job.scope;
5451
+ let minIncomingIndex = Number.POSITIVE_INFINITY;
5452
+ let minIncomingTimestamp = operations[0]?.timestampUtcMs || "";
5453
+ for (const operation of operations) {
5454
+ minIncomingIndex = Math.min(minIncomingIndex, operation.index);
5455
+ const ts = operation.timestampUtcMs || "";
5456
+ if (Date.parse(ts) < Date.parse(minIncomingTimestamp)) minIncomingTimestamp = ts;
5457
+ }
5458
+ let conflictingOps;
5459
+ try {
5460
+ conflictingOps = (await stores.operationStore.getConflicting(job.documentId, scope, job.branch, minIncomingTimestamp, void 0, signal)).results;
5461
+ } catch {
5462
+ conflictingOps = [];
5463
+ }
5464
+ let allOpsFromMinConflictingIndex = conflictingOps;
5465
+ if (conflictingOps.length > 0) {
5466
+ const minConflictingIndex = Math.min(...conflictingOps.map((op) => op.index));
5467
+ try {
5468
+ allOpsFromMinConflictingIndex = (await stores.operationStore.getSince(job.documentId, scope, job.branch, minConflictingIndex - 1, void 0, void 0, signal)).results;
5469
+ } catch {
5470
+ allOpsFromMinConflictingIndex = conflictingOps;
5471
+ }
5472
+ }
5473
+ const incomingActionIds = new Set(operations.map((op) => op.action.id));
5474
+ const supersededBy = (op) => {
5475
+ let nearest;
5476
+ for (const laterOp of allOpsFromMinConflictingIndex) if (laterOp.index > op.index && laterOp.skip > 0 && laterOp.index - laterOp.skip <= op.index && (nearest === void 0 || laterOp.index < nearest.index)) nearest = laterOp;
5477
+ return nearest;
5478
+ };
5479
+ const undoneActionIds = /* @__PURE__ */ new Set();
5480
+ let predecessorBound = minIncomingIndex;
5481
+ const rowsByActionId = /* @__PURE__ */ new Map();
5482
+ for (const op of conflictingOps) if (incomingActionIds.has(op.action.id)) rowsByActionId.set(op.action.id, [...rowsByActionId.get(op.action.id) ?? [], op]);
5483
+ const liveOps = /* @__PURE__ */ new Set();
5484
+ for (const op of conflictingOps) if (supersededBy(op) === void 0) liveOps.add(op);
5485
+ for (const [actionId, rows] of rowsByActionId) {
5486
+ if (rows.some((row) => liveOps.has(row))) continue;
5487
+ if (rows.some((row) => supersededBy(row)?.action.type === "NOOP")) undoneActionIds.add(actionId);
5488
+ else predecessorBound = Math.min(predecessorBound, Math.max(...rows.map((row) => row.index)));
5489
+ }
5490
+ const nonSupersededOps = conflictingOps.filter((op) => {
5491
+ if (op.index < predecessorBound && !incomingActionIds.has(op.action.id)) return false;
5492
+ return liveOps.has(op);
5493
+ });
5494
+ let rewoundFrom = Number.POSITIVE_INFINITY;
5495
+ for (const op of nonSupersededOps) if (!isGenesisOperation(op)) rewoundFrom = Math.min(rewoundFrom, op.index - op.skip);
5496
+ const reappended = new Set(nonSupersededOps.map((op) => op.index));
5497
+ for (const op of allOpsFromMinConflictingIndex) if (op.index >= rewoundFrom && !reappended.has(op.index) && supersededBy(op) === void 0) nonSupersededOps.push(op);
5498
+ const existingActionIds = new Set(nonSupersededOps.map((op) => op.action.id));
5499
+ const seenIncomingActionIds = /* @__PURE__ */ new Set();
5500
+ const incomingOpsToApply = operations.filter((op) => {
5501
+ if (existingActionIds.has(op.action.id)) return false;
5502
+ if (undoneActionIds.has(op.action.id)) return false;
5503
+ if (seenIncomingActionIds.has(op.action.id)) return false;
5504
+ seenIncomingActionIds.add(op.action.id);
5505
+ return true;
5506
+ });
5507
+ return {
5508
+ nonSupersededOps,
5509
+ allOpsFromMinConflictingIndex,
5510
+ incomingOpsToApply
5511
+ };
5512
+ }
4336
5513
  accumulateResultOrReturnError(result, generatedOperations, operationsWithContext) {
4337
5514
  if (!result.success) return result;
4338
5515
  if (result.operations && result.operations.length > 0) generatedOperations.push(...result.operations);
@@ -4479,6 +5656,7 @@ var DocumentModelRegistry = class {
4479
5656
  //#endregion
4480
5657
  //#region src/storage/kysely/keyframe-store.ts
4481
5658
  var KyselyKeyframeStore = class KyselyKeyframeStore {
5659
+ db;
4482
5660
  trx;
4483
5661
  constructor(db) {
4484
5662
  this.db = db;
@@ -4563,6 +5741,11 @@ function paginateRows(rows, paging, cursorOf, toItem, refetch) {
4563
5741
  //#endregion
4564
5742
  //#region src/storage/txn.ts
4565
5743
  var AtomicTransaction = class {
5744
+ documentId;
5745
+ documentType;
5746
+ scope;
5747
+ branch;
5748
+ baseRevision;
4566
5749
  operations = [];
4567
5750
  constructor(documentId, documentType, scope, branch, baseRevision) {
4568
5751
  this.documentId = documentId;
@@ -4596,6 +5779,11 @@ var AtomicTransaction = class {
4596
5779
  //#endregion
4597
5780
  //#region src/storage/kysely/store.ts
4598
5781
  var _UniqueConstraintContext = class extends Error {
5782
+ documentId;
5783
+ scope;
5784
+ branch;
5785
+ revision;
5786
+ stagedOps;
4599
5787
  constructor(documentId, scope, branch, revision, stagedOps) {
4600
5788
  super("unique constraint");
4601
5789
  this.documentId = documentId;
@@ -4607,6 +5795,7 @@ var _UniqueConstraintContext = class extends Error {
4607
5795
  }
4608
5796
  };
4609
5797
  var KyselyOperationStore = class KyselyOperationStore {
5798
+ db;
4610
5799
  trx;
4611
5800
  constructor(db) {
4612
5801
  this.db = db;
@@ -4706,12 +5895,13 @@ var KyselyOperationStore = class KyselyOperationStore {
4706
5895
  * otherwise read a snapshot taken before the locks were held.
4707
5896
  */
4708
5897
  async acquireStreamLocks(trx, documentId, scope, branch, condition) {
4709
- const keys = new Set([`${documentId}:${scope}:${branch}`]);
5898
+ const keys = /* @__PURE__ */ new Set([`${documentId}:${scope}:${branch}`]);
4710
5899
  for (const stream of condition.streams) keys.add(`${stream.documentId}:${stream.scope}:${stream.branch}`);
5900
+ const sortedKeys = sql.join([...keys].sort());
4711
5901
  await sql`
4712
5902
  with ordered as materialized (
4713
5903
  select key
4714
- from unnest(array[${sql.join([...keys].sort())}]::text[]) with ordinality as t(key, ord)
5904
+ from unnest(array[${sortedKeys}]::text[]) with ordinality as t(key, ord)
4715
5905
  order by ord
4716
5906
  )
4717
5907
  select pg_advisory_xact_lock(hashtext(key)) from ordered
@@ -4788,6 +5978,14 @@ var KyselyOperationStore = class KyselyOperationStore {
4788
5978
  }
4789
5979
  return false;
4790
5980
  }
5981
+ /**
5982
+ * The paging cursor here encodes the index to resume from (one past the
5983
+ * last row returned), not the last row's own index. This keeps "0" an
5984
+ * unambiguous start-of-stream sentinel even when a page ends at index 0
5985
+ * (e.g. `limit: 1` on a fresh stream), which would otherwise make
5986
+ * `nextCursor` equal the start cursor and loop a caller that walks pages
5987
+ * forever.
5988
+ */
4791
5989
  async getSince(documentId, scope, branch, revision, filter, paging, signal) {
4792
5990
  throwIfAborted(signal);
4793
5991
  let query = this.queryExecutor.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).where("index", ">", revision).orderBy("index", "asc");
@@ -4802,10 +6000,10 @@ var KyselyOperationStore = class KyselyOperationStore {
4802
6000
  }
4803
6001
  if (paging) {
4804
6002
  const cursorValue = Number.parseInt(paging.cursor, 10);
4805
- if (cursorValue > 0) query = query.where("index", ">", cursorValue);
6003
+ if (Number.isFinite(cursorValue) && cursorValue > 0) query = query.where("index", ">=", cursorValue);
4806
6004
  if (paging.limit) query = query.limit(paging.limit + 1);
4807
6005
  }
4808
- return paginateRows(await query.execute(), paging, (row) => row.index, (row) => this.rowToOperation(row), (cursor, limit) => this.getSince(documentId, scope, branch, revision, filter, {
6006
+ return paginateRows(await query.execute(), paging, (row) => row.index + 1, (row) => this.rowToOperation(row), (cursor, limit) => this.getSince(documentId, scope, branch, revision, filter, {
4809
6007
  cursor,
4810
6008
  limit
4811
6009
  }, signal));
@@ -4823,26 +6021,28 @@ var KyselyOperationStore = class KyselyOperationStore {
4823
6021
  limit
4824
6022
  }, signal));
4825
6023
  }
6024
+ /**
6025
+ * The paging cursor here encodes the index to resume from (one past the
6026
+ * last row returned), not the last row's own index, for the same reason as
6027
+ * `getSince`: a page ending at index 0 must not produce a cursor that is
6028
+ * indistinguishable from the start-of-stream sentinel.
6029
+ */
4826
6030
  async getConflicting(documentId, scope, branch, minTimestamp, paging, signal) {
4827
6031
  throwIfAborted(signal);
4828
6032
  let query = this.queryExecutor.selectFrom("Operation").selectAll().where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).where("timestampUtcMs", ">=", new Date(minTimestamp)).orderBy("index", "asc");
4829
6033
  if (paging) {
4830
6034
  const cursorValue = Number.parseInt(paging.cursor, 10);
4831
- if (cursorValue > 0) query = query.where("index", ">", cursorValue);
6035
+ if (Number.isFinite(cursorValue) && cursorValue > 0) query = query.where("index", ">=", cursorValue);
4832
6036
  if (paging.limit) query = query.limit(paging.limit + 1);
4833
6037
  }
4834
- return paginateRows(await query.execute(), paging, (row) => row.index, (row) => this.rowToOperation(row), (cursor, limit) => this.getConflicting(documentId, scope, branch, minTimestamp, {
6038
+ return paginateRows(await query.execute(), paging, (row) => row.index + 1, (row) => this.rowToOperation(row), (cursor, limit) => this.getConflicting(documentId, scope, branch, minTimestamp, {
4835
6039
  cursor,
4836
6040
  limit
4837
6041
  }, signal));
4838
6042
  }
4839
6043
  async getRevisions(documentId, branch, signal) {
4840
6044
  throwIfAborted(signal);
4841
- const scopeRevisions = await this.queryExecutor.selectFrom("Operation as o1").select([
4842
- "o1.scope",
4843
- "o1.index",
4844
- "o1.timestampUtcMs"
4845
- ]).where("o1.documentId", "=", documentId).where("o1.branch", "=", branch).where((eb) => eb("o1.index", "=", eb.selectFrom("Operation as o2").select((eb2) => eb2.fn.max("o2.index").as("maxIndex")).where("o2.documentId", "=", eb.ref("o1.documentId")).where("o2.branch", "=", eb.ref("o1.branch")).where("o2.scope", "=", eb.ref("o1.scope")))).execute();
6045
+ const scopeRevisions = await this.queryExecutor.selectFrom("Operation").select("scope").select((eb) => eb.fn.max("index").as("index")).where("documentId", "=", documentId).where("branch", "=", branch).groupBy("scope").execute();
4846
6046
  const latest = await this.queryExecutor.selectFrom("Operation").select((eb) => eb.fn.max("timestampUtcMs").as("latestTimestamp")).where("documentId", "=", documentId).where("branch", "=", branch).executeTakeFirst();
4847
6047
  const revision = {};
4848
6048
  for (const row of scopeRevisions) revision[row.scope] = row.index + 1;
@@ -4855,6 +6055,20 @@ var KyselyOperationStore = class KyselyOperationStore {
4855
6055
  const latest = await this.queryExecutor.selectFrom("Operation").select((eb) => eb.fn.max("timestampUtcMs").as("latestTimestamp")).where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).executeTakeFirst();
4856
6056
  return latest?.latestTimestamp ? new Date(latest.latestTimestamp).toISOString() : void 0;
4857
6057
  }
6058
+ async findOperationIds(documentId, scope, branch, opIds, signal) {
6059
+ throwIfAborted(signal);
6060
+ if (opIds.length === 0) return /* @__PURE__ */ new Set();
6061
+ const rows = await this.queryExecutor.selectFrom("Operation").select("opId").distinct().where("opId", "in", opIds).where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).execute();
6062
+ return new Set(rows.map((row) => row.opId));
6063
+ }
6064
+ async getOperationsByIds(documentId, scope, branch, opIds, signal) {
6065
+ throwIfAborted(signal);
6066
+ if (opIds.length === 0) return [];
6067
+ const rows = await this.queryExecutor.selectFrom("Operation").selectAll().where("opId", "in", opIds).where("documentId", "=", documentId).where("scope", "=", scope).where("branch", "=", branch).orderBy("index", "asc").execute();
6068
+ const latest = /* @__PURE__ */ new Map();
6069
+ for (const row of rows) latest.set(row.opId, this.rowToOperation(row));
6070
+ return [...latest.values()];
6071
+ }
4858
6072
  rowToOperation(row) {
4859
6073
  return {
4860
6074
  index: row.index,
@@ -4948,8 +6162,8 @@ function createForwardingPoolInstrumentation(name) {
4948
6162
  }
4949
6163
  //#endregion
4950
6164
  //#region src/storage/migrations/001_create_operation_table.ts
4951
- var _001_create_operation_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$18 });
4952
- async function up$18(db) {
6165
+ var _001_create_operation_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$22 });
6166
+ async function up$22(db) {
4953
6167
  await db.schema.createTable("Operation").addColumn("id", "serial", (col) => col.primaryKey()).addColumn("jobId", "text", (col) => col.notNull()).addColumn("opId", "text", (col) => col.notNull()).addColumn("prevOpId", "text", (col) => col.notNull()).addColumn("writeTimestampUtcMs", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("documentId", "text", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("timestampUtcMs", "timestamptz", (col) => col.notNull()).addColumn("index", "integer", (col) => col.notNull()).addColumn("action", "jsonb", (col) => col.notNull()).addColumn("skip", "integer", (col) => col.notNull()).addColumn("error", "text").addColumn("hash", "text", (col) => col.notNull()).addUniqueConstraint("unique_revision", [
4954
6168
  "documentId",
4955
6169
  "scope",
@@ -4974,8 +6188,8 @@ async function up$18(db) {
4974
6188
  }
4975
6189
  //#endregion
4976
6190
  //#region src/storage/migrations/002_create_keyframe_table.ts
4977
- var _002_create_keyframe_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$17 });
4978
- async function up$17(db) {
6191
+ var _002_create_keyframe_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$21 });
6192
+ async function up$21(db) {
4979
6193
  await db.schema.createTable("Keyframe").addColumn("id", "serial", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("revision", "integer", (col) => col.notNull()).addColumn("document", "jsonb", (col) => col.notNull()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addUniqueConstraint("unique_keyframe", [
4980
6194
  "documentId",
4981
6195
  "scope",
@@ -4991,14 +6205,14 @@ async function up$17(db) {
4991
6205
  }
4992
6206
  //#endregion
4993
6207
  //#region src/storage/migrations/003_create_document_table.ts
4994
- var _003_create_document_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$16 });
4995
- async function up$16(db) {
6208
+ var _003_create_document_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$20 });
6209
+ async function up$20(db) {
4996
6210
  await db.schema.createTable("Document").addColumn("id", "text", (col) => col.primaryKey()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
4997
6211
  }
4998
6212
  //#endregion
4999
6213
  //#region src/storage/migrations/004_create_document_relationship_table.ts
5000
- var _004_create_document_relationship_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$15 });
5001
- async function up$15(db) {
6214
+ var _004_create_document_relationship_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$19 });
6215
+ async function up$19(db) {
5002
6216
  await db.schema.createTable("DocumentRelationship").addColumn("id", "text", (col) => col.primaryKey()).addColumn("sourceId", "text", (col) => col.notNull().references("Document.id").onDelete("cascade")).addColumn("targetId", "text", (col) => col.notNull().references("Document.id").onDelete("cascade")).addColumn("relationshipType", "text", (col) => col.notNull()).addColumn("metadata", "jsonb").addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addUniqueConstraint("unique_source_target_type", [
5003
6217
  "sourceId",
5004
6218
  "targetId",
@@ -5010,14 +6224,14 @@ async function up$15(db) {
5010
6224
  }
5011
6225
  //#endregion
5012
6226
  //#region src/storage/migrations/005_create_indexer_state_table.ts
5013
- var _005_create_indexer_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$14 });
5014
- async function up$14(db) {
6227
+ var _005_create_indexer_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$18 });
6228
+ async function up$18(db) {
5015
6229
  await db.schema.createTable("IndexerState").addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()).addColumn("lastOperationId", "integer", (col) => col.notNull()).addColumn("lastOperationTimestamp", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
5016
6230
  }
5017
6231
  //#endregion
5018
6232
  //#region src/storage/migrations/006_create_document_snapshot_table.ts
5019
- var _006_create_document_snapshot_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$13 });
5020
- async function up$13(db) {
6233
+ var _006_create_document_snapshot_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$17 });
6234
+ async function up$17(db) {
5021
6235
  await db.schema.createTable("DocumentSnapshot").addColumn("id", "text", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("slug", "text").addColumn("name", "text").addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("content", "jsonb", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("lastOperationIndex", "integer", (col) => col.notNull()).addColumn("lastOperationHash", "text", (col) => col.notNull()).addColumn("lastUpdatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("snapshotVersion", "integer", (col) => col.notNull().defaultTo(1)).addColumn("identifiers", "jsonb").addColumn("metadata", "jsonb").addColumn("isDeleted", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("deletedAt", "timestamptz").addUniqueConstraint("unique_doc_scope_branch", [
5022
6236
  "documentId",
5023
6237
  "scope",
@@ -5038,8 +6252,8 @@ async function up$13(db) {
5038
6252
  }
5039
6253
  //#endregion
5040
6254
  //#region src/storage/migrations/007_create_slug_mapping_table.ts
5041
- var _007_create_slug_mapping_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$12 });
5042
- async function up$12(db) {
6255
+ var _007_create_slug_mapping_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$16 });
6256
+ async function up$16(db) {
5043
6257
  await db.schema.createTable("SlugMapping").addColumn("slug", "text", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addUniqueConstraint("unique_docid_scope_branch", [
5044
6258
  "documentId",
5045
6259
  "scope",
@@ -5049,14 +6263,14 @@ async function up$12(db) {
5049
6263
  }
5050
6264
  //#endregion
5051
6265
  //#region src/storage/migrations/008_create_view_state_table.ts
5052
- var _008_create_view_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$11 });
5053
- async function up$11(db) {
6266
+ var _008_create_view_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$15 });
6267
+ async function up$15(db) {
5054
6268
  await db.schema.createTable("ViewState").addColumn("readModelId", "text", (col) => col.primaryKey()).addColumn("lastOrdinal", "integer", (col) => col.notNull().defaultTo(0)).addColumn("lastOperationTimestamp", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
5055
6269
  }
5056
6270
  //#endregion
5057
6271
  //#region src/storage/migrations/009_create_operation_index_tables.ts
5058
- var _009_create_operation_index_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$10 });
5059
- async function up$10(db) {
6272
+ var _009_create_operation_index_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$14 });
6273
+ async function up$14(db) {
5060
6274
  await db.schema.createTable("document_collections").addColumn("documentId", "text", (col) => col.notNull()).addColumn("collectionId", "text", (col) => col.notNull()).addColumn("joinedOrdinal", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("leftOrdinal", "bigint").addPrimaryKeyConstraint("document_collections_pkey", ["documentId", "collectionId"]).execute();
5061
6275
  await db.schema.createIndex("idx_document_collections_collectionId").on("document_collections").column("collectionId").execute();
5062
6276
  await db.schema.createIndex("idx_doc_collections_collection_range").on("document_collections").columns(["collectionId", "joinedOrdinal"]).execute();
@@ -5070,8 +6284,8 @@ async function up$10(db) {
5070
6284
  }
5071
6285
  //#endregion
5072
6286
  //#region src/storage/migrations/010_create_sync_tables.ts
5073
- var _010_create_sync_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$9 });
5074
- async function up$9(db) {
6287
+ var _010_create_sync_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$13 });
6288
+ async function up$13(db) {
5075
6289
  await db.schema.createTable("sync_remotes").addColumn("name", "text", (col) => col.primaryKey()).addColumn("collection_id", "text", (col) => col.notNull()).addColumn("channel_type", "text", (col) => col.notNull()).addColumn("channel_id", "text", (col) => col.notNull().defaultTo("")).addColumn("remote_name", "text", (col) => col.notNull().defaultTo("")).addColumn("channel_parameters", "jsonb", (col) => col.notNull().defaultTo(sql`'{}'::jsonb`)).addColumn("filter_document_ids", "jsonb").addColumn("filter_scopes", "jsonb").addColumn("filter_branch", "text", (col) => col.notNull().defaultTo("main")).addColumn("push_state", "text", (col) => col.notNull().defaultTo("idle")).addColumn("push_last_success_utc_ms", "text").addColumn("push_last_failure_utc_ms", "text").addColumn("push_failure_count", "integer", (col) => col.notNull().defaultTo(0)).addColumn("pull_state", "text", (col) => col.notNull().defaultTo("idle")).addColumn("pull_last_success_utc_ms", "text").addColumn("pull_last_failure_utc_ms", "text").addColumn("pull_failure_count", "integer", (col) => col.notNull().defaultTo(0)).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
5076
6290
  await db.schema.createIndex("idx_sync_remotes_collection").on("sync_remotes").column("collection_id").execute();
5077
6291
  await db.schema.createTable("sync_cursors").addColumn("remote_name", "text", (col) => col.primaryKey().references("sync_remotes.name").onDelete("cascade")).addColumn("cursor_ordinal", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_synced_at_utc_ms", "text").addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
@@ -5079,8 +6293,8 @@ async function up$9(db) {
5079
6293
  }
5080
6294
  //#endregion
5081
6295
  //#region src/storage/migrations/011_add_cursor_type_column.ts
5082
- var _011_add_cursor_type_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$8 });
5083
- async function up$8(db) {
6296
+ var _011_add_cursor_type_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$12 });
6297
+ async function up$12(db) {
5084
6298
  await db.deleteFrom("sync_cursors").where("remote_name", "like", "outbox::%").execute();
5085
6299
  await db.deleteFrom("sync_remotes").where("name", "like", "outbox::%").execute();
5086
6300
  await db.schema.dropTable("sync_cursors").execute();
@@ -5089,64 +6303,64 @@ async function up$8(db) {
5089
6303
  }
5090
6304
  //#endregion
5091
6305
  //#region src/storage/migrations/012_add_source_remote_column.ts
5092
- var _012_add_source_remote_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$7 });
5093
- async function up$7(db) {
6306
+ var _012_add_source_remote_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$11 });
6307
+ async function up$11(db) {
5094
6308
  await db.schema.alterTable("operation_index_operations").addColumn("sourceRemote", "text", (col) => col.notNull().defaultTo("")).execute();
5095
6309
  }
5096
6310
  //#endregion
5097
6311
  //#region src/storage/migrations/013_create_sync_dead_letters_table.ts
5098
- var _013_create_sync_dead_letters_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$6 });
5099
- async function up$6(db) {
6312
+ var _013_create_sync_dead_letters_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$10 });
6313
+ async function up$10(db) {
5100
6314
  await db.schema.createTable("sync_dead_letters").addColumn("ordinal", "serial", (col) => col.primaryKey()).addColumn("id", "text", (col) => col.unique().notNull()).addColumn("job_id", "text", (col) => col.notNull()).addColumn("job_dependencies", "jsonb", (col) => col.notNull().defaultTo(sql`'[]'::jsonb`)).addColumn("remote_name", "text", (col) => col.notNull().references("sync_remotes.name").onDelete("cascade")).addColumn("document_id", "text", (col) => col.notNull()).addColumn("scopes", "jsonb", (col) => col.notNull().defaultTo(sql`'[]'::jsonb`)).addColumn("branch", "text", (col) => col.notNull()).addColumn("operations", "jsonb", (col) => col.notNull().defaultTo(sql`'[]'::jsonb`)).addColumn("error_source", "text", (col) => col.notNull()).addColumn("error_message", "text", (col) => col.notNull()).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
5101
6315
  await db.schema.createIndex("idx_sync_dead_letters_remote").on("sync_dead_letters").column("remote_name").execute();
5102
6316
  }
5103
6317
  //#endregion
5104
6318
  //#region src/storage/migrations/014_create_processor_cursor_table.ts
5105
- var _014_create_processor_cursor_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$5 });
5106
- async function up$5(db) {
6319
+ var _014_create_processor_cursor_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$9 });
6320
+ async function up$9(db) {
5107
6321
  await db.schema.createTable("ProcessorCursor").addColumn("processorId", "text", (col) => col.primaryKey()).addColumn("factoryId", "text", (col) => col.notNull()).addColumn("driveId", "text", (col) => col.notNull()).addColumn("processorIndex", "integer", (col) => col.notNull()).addColumn("lastOrdinal", "integer", (col) => col.notNull().defaultTo(sql`0`)).addColumn("status", "text", (col) => col.notNull().defaultTo(sql`'active'`)).addColumn("lastError", "text").addColumn("lastErrorTimestamp", "timestamptz").addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
5108
6322
  }
5109
6323
  //#endregion
5110
6324
  //#region src/storage/migrations/015_add_operation_denied_reason.ts
5111
6325
  var _015_add_operation_denied_reason_exports = /* @__PURE__ */ __exportAll({
5112
- down: () => down$4,
5113
- up: () => up$4
6326
+ down: () => down$8,
6327
+ up: () => up$8
5114
6328
  });
5115
6329
  /**
5116
6330
  * Records why authorization refused an operation. Separate from `error` so a
5117
6331
  * denial is distinguishable from a reducer failure without matching on a
5118
6332
  * message. Null for every operation written before decisions were enforced.
5119
6333
  */
5120
- async function up$4(db) {
6334
+ async function up$8(db) {
5121
6335
  await db.schema.alterTable("Operation").addColumn("deniedReason", "text").execute();
5122
6336
  await db.schema.alterTable("operation_index_operations").addColumn("deniedReason", "text").execute();
5123
6337
  }
5124
- async function down$4(db) {
6338
+ async function down$8(db) {
5125
6339
  await db.schema.alterTable("operation_index_operations").dropColumn("deniedReason").execute();
5126
6340
  await db.schema.alterTable("Operation").dropColumn("deniedReason").execute();
5127
6341
  }
5128
6342
  //#endregion
5129
6343
  //#region src/storage/migrations/016_add_dead_letter_error_type.ts
5130
6344
  var _016_add_dead_letter_error_type_exports = /* @__PURE__ */ __exportAll({
5131
- down: () => down$3,
5132
- up: () => up$3
6345
+ down: () => down$7,
6346
+ up: () => up$7
5133
6347
  });
5134
6348
  /**
5135
6349
  * The classification a dead letter falls into, stored because it decides whether
5136
6350
  * the document stays quarantined and the in-memory error is gone after a restart.
5137
6351
  * Defaulted rather than nullable, so a pre-existing row rehydrates.
5138
6352
  */
5139
- async function up$3(db) {
6353
+ async function up$7(db) {
5140
6354
  await db.schema.alterTable("sync_dead_letters").addColumn("error_type", "text", (col) => col.notNull().defaultTo("UNCLASSIFIED")).execute();
5141
6355
  }
5142
- async function down$3(db) {
6356
+ async function down$7(db) {
5143
6357
  await db.schema.alterTable("sync_dead_letters").dropColumn("error_type").execute();
5144
6358
  }
5145
6359
  //#endregion
5146
6360
  //#region src/storage/migrations/017_create_group_references.ts
5147
6361
  var _017_create_group_references_exports = /* @__PURE__ */ __exportAll({
5148
- down: () => down$2,
5149
- up: () => up$2
6362
+ down: () => down$6,
6363
+ up: () => up$6
5150
6364
  });
5151
6365
  /**
5152
6366
  * One row per (document, group) reference ever discovered from an auth
@@ -5157,18 +6371,18 @@ var _017_create_group_references_exports = /* @__PURE__ */ __exportAll({
5157
6371
  * requires (sync), and by groupId for the documents a group change affects
5158
6372
  * (re-evaluation).
5159
6373
  */
5160
- async function up$2(db) {
6374
+ async function up$6(db) {
5161
6375
  await db.schema.createTable("group_references").addColumn("documentId", "text", (col) => col.notNull()).addColumn("groupId", "text", (col) => col.notNull()).addPrimaryKeyConstraint("group_references_pkey", ["documentId", "groupId"]).execute();
5162
6376
  await db.schema.createIndex("idx_group_references_groupId").on("group_references").column("groupId").execute();
5163
6377
  }
5164
- async function down$2(db) {
6378
+ async function down$6(db) {
5165
6379
  await db.schema.dropTable("group_references").execute();
5166
6380
  }
5167
6381
  //#endregion
5168
6382
  //#region src/storage/migrations/018_add_sync_remote_bound_address.ts
5169
6383
  var _018_add_sync_remote_bound_address_exports = /* @__PURE__ */ __exportAll({
5170
- down: () => down$1,
5171
- up: () => up$1
6384
+ down: () => down$5,
6385
+ up: () => up$5
5172
6386
  });
5173
6387
  /**
5174
6388
  * The address a sync channel is bound to, so a channel created by one subject
@@ -5179,17 +6393,17 @@ var _018_add_sync_remote_bound_address_exports = /* @__PURE__ */ __exportAll({
5179
6393
  * until its first authenticated poll adopts it. A default would claim them all
5180
6394
  * for one address.
5181
6395
  */
5182
- async function up$1(db) {
6396
+ async function up$5(db) {
5183
6397
  await db.schema.alterTable("sync_remotes").addColumn("bound_address", "text").execute();
5184
6398
  }
5185
- async function down$1(db) {
6399
+ async function down$5(db) {
5186
6400
  await db.schema.alterTable("sync_remotes").dropColumn("bound_address").execute();
5187
6401
  }
5188
6402
  //#endregion
5189
6403
  //#region src/storage/migrations/019_require_action_id.ts
5190
6404
  var _019_require_action_id_exports = /* @__PURE__ */ __exportAll({
5191
- down: () => down,
5192
- up: () => up
6405
+ down: () => down$4,
6406
+ up: () => up$4
5193
6407
  });
5194
6408
  /**
5195
6409
  * Makes an operation whose action carries no id physically unstorable.
@@ -5216,7 +6430,7 @@ var _019_require_action_id_exports = /* @__PURE__ */ __exportAll({
5216
6430
  * The empty string is rejected alongside null. It derives the same colliding
5217
6431
  * operation id as an absent id, so admitting it would leave the hole open.
5218
6432
  */
5219
- async function up(db) {
6433
+ async function up$4(db) {
5220
6434
  await db.updateTable("Operation").set({ action: sql`jsonb_set(action, '{id}', to_jsonb(gen_random_uuid()::text))` }).where(sql`jsonb_typeof(action) = 'object' and coalesce(action->>'id', '') = ''`).execute();
5221
6435
  await db.updateTable("operation_index_operations as oio").from("Operation as op").set({ action: sql`jsonb_set(oio.action, '{id}', to_jsonb(op.action->>'id'))` }).whereRef("oio.opId", "=", "op.opId").whereRef("oio.index", "=", "op.index").whereRef("oio.skip", "=", "op.skip").where(sql`jsonb_typeof(oio.action) = 'object' and coalesce(oio.action->>'id', '') = ''`).where(sql`coalesce(op.action->>'id', '') <> ''`).execute();
5222
6436
  await db.updateTable("operation_index_operations").set({ action: sql`jsonb_set(action, '{id}', to_jsonb(gen_random_uuid()::text))` }).where(sql`jsonb_typeof(action) = 'object' and coalesce(action->>'id', '') = ''`).execute();
@@ -5228,11 +6442,74 @@ async function up(db) {
5228
6442
  * their operations are now known by, and reverting them would reintroduce the
5229
6443
  * collision the migration removed.
5230
6444
  */
5231
- async function down(db) {
6445
+ async function down$4(db) {
5232
6446
  await db.schema.alterTable("operation_index_operations").dropConstraint("action_must_have_id").execute();
5233
6447
  await db.schema.alterTable("Operation").dropConstraint("action_must_have_id").execute();
5234
6448
  }
5235
6449
  //#endregion
6450
+ //#region src/storage/migrations/020_add_snapshot_operation_ordinal.ts
6451
+ var _020_add_snapshot_operation_ordinal_exports = /* @__PURE__ */ __exportAll({
6452
+ down: () => down$3,
6453
+ up: () => up$3
6454
+ });
6455
+ /** Orders header row writes across scopes; `lastOperationIndex` is per scope. */
6456
+ async function up$3(db) {
6457
+ await db.schema.alterTable("DocumentSnapshot").addColumn("lastOperationOrdinal", "integer", (col) => col.notNull().defaultTo(0)).execute();
6458
+ }
6459
+ async function down$3(db) {
6460
+ await db.schema.alterTable("DocumentSnapshot").dropColumn("lastOperationOrdinal").execute();
6461
+ }
6462
+ //#endregion
6463
+ //#region src/storage/migrations/021_add_sync_remote_peer.ts
6464
+ var _021_add_sync_remote_peer_exports = /* @__PURE__ */ __exportAll({
6465
+ down: () => down$2,
6466
+ up: () => up$2
6467
+ });
6468
+ /**
6469
+ * The peer's manifest as canonical JSON, and when it was heard. Both null: not
6470
+ * yet heard. A time with no manifest: a silent peer, which gets the baselines.
6471
+ */
6472
+ async function up$2(db) {
6473
+ await db.schema.alterTable("sync_remotes").addColumn("peer_manifest", "text").addColumn("peer_manifest_at_utc_ms", "bigint").execute();
6474
+ }
6475
+ async function down$2(db) {
6476
+ await db.schema.alterTable("sync_remotes").dropColumn("peer_manifest").dropColumn("peer_manifest_at_utc_ms").execute();
6477
+ }
6478
+ //#endregion
6479
+ //#region src/storage/migrations/022_create_sync_holds.ts
6480
+ var _022_create_sync_holds_exports = /* @__PURE__ */ __exportAll({
6481
+ down: () => down$1,
6482
+ up: () => up$1
6483
+ });
6484
+ /** Documents held back from a remote whose peer cannot run their versions. */
6485
+ async function up$1(db) {
6486
+ await db.schema.createTable("sync_holds").addColumn("remote_name", "text", (col) => col.notNull().references("sync_remotes.name").onDelete("cascade")).addColumn("document_id", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("protocol", "text", (col) => col.notNull()).addColumn("version", "integer", (col) => col.notNull()).addColumn("held_at_utc_ms", "bigint", (col) => col.notNull()).addPrimaryKeyConstraint("sync_holds_pkey", [
6487
+ "remote_name",
6488
+ "document_id",
6489
+ "branch"
6490
+ ]).execute();
6491
+ }
6492
+ async function down$1(db) {
6493
+ await db.schema.dropTable("sync_holds").execute();
6494
+ }
6495
+ //#endregion
6496
+ //#region src/storage/migrations/023_index_created_protocol_versions.ts
6497
+ var _023_index_created_protocol_versions_exports = /* @__PURE__ */ __exportAll({
6498
+ down: () => down,
6499
+ up: () => up
6500
+ });
6501
+ /**
6502
+ * One entry per document created with protocolVersions, keyed by a hash so an
6503
+ * oversized header cannot make its creation unstorable. Startup reads the
6504
+ * distinct values without visiting every document.
6505
+ */
6506
+ async function up(db) {
6507
+ await db.schema.createIndex("idx_operation_created_protocol_versions").on("Operation").expression(sql`md5((action->'input'->'protocolVersions')::text)`).where(sql`scope = 'document' and "index" = 0 and (action->'input'->'protocolVersions') is not null`).execute();
6508
+ }
6509
+ async function down(db) {
6510
+ await db.schema.dropIndex("idx_operation_created_protocol_versions").execute();
6511
+ }
6512
+ //#endregion
5236
6513
  //#region src/storage/migrations/migrator.ts
5237
6514
  const REACTOR_SCHEMA = "reactor";
5238
6515
  const migrations = {
@@ -5254,7 +6531,11 @@ const migrations = {
5254
6531
  "016_add_dead_letter_error_type": _016_add_dead_letter_error_type_exports,
5255
6532
  "017_create_group_references": _017_create_group_references_exports,
5256
6533
  "018_add_sync_remote_bound_address": _018_add_sync_remote_bound_address_exports,
5257
- "019_require_action_id": _019_require_action_id_exports
6534
+ "019_require_action_id": _019_require_action_id_exports,
6535
+ "020_add_snapshot_operation_ordinal": _020_add_snapshot_operation_ordinal_exports,
6536
+ "021_add_sync_remote_peer": _021_add_sync_remote_peer_exports,
6537
+ "022_create_sync_holds": _022_create_sync_holds_exports,
6538
+ "023_index_created_protocol_versions": _023_index_created_protocol_versions_exports
5258
6539
  };
5259
6540
  var ProgrammaticMigrationProvider = class {
5260
6541
  getMigrations() {
@@ -5312,8 +6593,148 @@ async function getMigrationStatus(db, schema = REACTOR_SCHEMA) {
5312
6593
  }
5313
6594
  //#endregion
5314
6595
  //#region src/core/drive-container-types.ts
5315
- const DEFAULT_DRIVE_CONTAINER_TYPES = new Set(["powerhouse/document-drive", "powerhouse/reactor-drive"]);
6596
+ const DEFAULT_DRIVE_CONTAINER_TYPES = /* @__PURE__ */ new Set(["powerhouse/document-drive", "powerhouse/reactor-drive"]);
6597
+ //#endregion
6598
+ //#region src/executor/worker/error-info.ts
6599
+ const MAX_STACK_LENGTH = 8192;
6600
+ const MAX_CAUSE_DEPTH = 8;
6601
+ function toErrorInfo(err, depth = 0) {
6602
+ if (depth > MAX_CAUSE_DEPTH) return {
6603
+ name: "Error",
6604
+ message: "cause chain truncated"
6605
+ };
6606
+ if (err instanceof Error) {
6607
+ const info = {
6608
+ name: err.name || "Error",
6609
+ message: err.message
6610
+ };
6611
+ if (typeof err.stack === "string") info.stack = err.stack.length > MAX_STACK_LENGTH ? err.stack.slice(0, MAX_STACK_LENGTH) : err.stack;
6612
+ if (err.cause !== void 0) info.cause = toErrorInfo(err.cause, depth + 1);
6613
+ return info;
6614
+ }
6615
+ if (typeof err === "string") return {
6616
+ name: "Error",
6617
+ message: err
6618
+ };
6619
+ if (err && typeof err === "object") {
6620
+ const record = err;
6621
+ return {
6622
+ name: typeof record.name === "string" ? record.name : "Error",
6623
+ message: typeof record.message === "string" ? record.message : safeStringify(err)
6624
+ };
6625
+ }
6626
+ return {
6627
+ name: "Error",
6628
+ message: safeStringify(err)
6629
+ };
6630
+ }
6631
+ function safeStringify(value) {
6632
+ if (value === null || value === void 0) return String(value);
6633
+ if (typeof value === "string") return value;
6634
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
6635
+ try {
6636
+ return JSON.stringify(value);
6637
+ } catch {
6638
+ return "[unserializable]";
6639
+ }
6640
+ }
6641
+ function fromErrorInfo(info) {
6642
+ const err = new Error(info.message);
6643
+ Object.defineProperty(err, "name", {
6644
+ value: info.name,
6645
+ configurable: true,
6646
+ writable: true
6647
+ });
6648
+ if (info.stack !== void 0) err.stack = info.stack;
6649
+ if (info.cause !== void 0) Object.defineProperty(err, "cause", {
6650
+ value: fromErrorInfo(info.cause),
6651
+ configurable: true,
6652
+ writable: true
6653
+ });
6654
+ return err;
6655
+ }
6656
+ //#endregion
6657
+ //#region src/executor/worker/sanitize.ts
6658
+ const MAX_DEPTH = 8;
6659
+ function errorToInfo(err) {
6660
+ return toErrorInfo(err);
6661
+ }
6662
+ function sanitizeArg(value, depth = 0, seen = /* @__PURE__ */ new WeakSet()) {
6663
+ if (depth > MAX_DEPTH) return "[Truncated]";
6664
+ if (value === null || value === void 0) return null;
6665
+ if (typeof value === "boolean") return value;
6666
+ if (typeof value === "number") return value;
6667
+ if (typeof value === "string") return value;
6668
+ if (typeof value === "bigint") return value.toString();
6669
+ if (typeof value === "function" || typeof value === "symbol") return null;
6670
+ if (value instanceof Error) return toErrorInfo(value);
6671
+ if (value instanceof Date) return value.toISOString();
6672
+ if (value instanceof Map) {
6673
+ const obj = {};
6674
+ for (const [k, v] of value.entries()) obj[String(k)] = sanitizeArg(v, depth + 1, seen);
6675
+ return obj;
6676
+ }
6677
+ if (value instanceof Set) {
6678
+ const arr = [];
6679
+ for (const v of value.values()) arr.push(sanitizeArg(v, depth + 1, seen));
6680
+ return arr;
6681
+ }
6682
+ if (Array.isArray(value)) {
6683
+ if (seen.has(value)) return "[Circular]";
6684
+ seen.add(value);
6685
+ const result = value.map((v) => sanitizeArg(v, depth + 1, seen));
6686
+ seen.delete(value);
6687
+ return result;
6688
+ }
6689
+ if (typeof value === "object") {
6690
+ const proto = Object.getPrototypeOf(value);
6691
+ if (proto !== Object.prototype && proto !== null) return { __nonClonable: value.constructor?.name || "Unknown" };
6692
+ if (seen.has(value)) return "[Circular]";
6693
+ seen.add(value);
6694
+ const result = {};
6695
+ for (const [k, v] of Object.entries(value)) result[k] = sanitizeArg(v, depth + 1, seen);
6696
+ seen.delete(value);
6697
+ return result;
6698
+ }
6699
+ return null;
6700
+ }
6701
+ //#endregion
6702
+ //#region src/executor/worker/forwarding-logger.ts
6703
+ function createForwardingLogger(post, level = "info", tags = []) {
6704
+ function safePost(msg) {
6705
+ try {
6706
+ post(msg);
6707
+ } catch {
6708
+ try {
6709
+ process.stderr.write(`[forwarding-logger] postMessage failed: ${msg.level} ${msg.message}\n`);
6710
+ } catch {}
6711
+ }
6712
+ }
6713
+ function emit(wireLevel, message, args) {
6714
+ const sanitizedArgs = args.map((a) => sanitizeArg(a));
6715
+ safePost({
6716
+ type: "log",
6717
+ level: wireLevel,
6718
+ message,
6719
+ args: tags.length > 0 ? [sanitizeArg({ tags }), ...sanitizedArgs] : sanitizedArgs,
6720
+ timestamp: Date.now()
6721
+ });
6722
+ }
6723
+ return {
6724
+ level,
6725
+ verbose: (message, ...args) => emit("debug", message, args),
6726
+ debug: (message, ...args) => emit("debug", message, args),
6727
+ info: (message, ...args) => emit("info", message, args),
6728
+ warn: (message, ...args) => emit("warn", message, args),
6729
+ error: (message, ...args) => emit("error", message, args),
6730
+ errorHandler: ((...data) => {
6731
+ const first = data[0];
6732
+ emit("error", typeof first === "string" ? first : "", data.slice(1));
6733
+ }),
6734
+ child: (childTags) => createForwardingLogger(post, level, [...tags, ...childTags])
6735
+ };
6736
+ }
5316
6737
  //#endregion
5317
- export { selectDecisionModel as A, RevisionMismatchError as B, InvalidModuleError as C, createEmptyConsistencyToken as D, createConsistencyToken as E, AppendConditionFailedError as F, DocumentNotFoundError as G, AuthTimestampNotMonotonicError as H, DocumentAlreadyExistsError as I, UpgradePreconditionFailedError as J, ExcessiveReshuffleError as K, DocumentExistence as L, authDecisionModel as M, buildDecisionModel as N, targetDocumentId as O, APPEND_CONDITION_FAILED_PREFIX as P, __exportAll as Q, DuplicateOperationError as R, DuplicateModuleError as S, GATED_DOCUMENT_ACTIONS as T, AuthorizationDeniedError as U, AuthEnforcementDisabledError as V, DocumentDeletedError as W, parsePagingOptions as X, matchesScope as Y, throwIfAborted as Z, DocumentMetaCache as _, createForwardingPoolInstrumentation as a, JobExecutorEventTypes as b, KyselyKeyframeStore as c, DriveCollectionId as d, KyselyExecutionScope as f, KyselyOperationIndex as g, KyselyWriteCache as h, runMigrations as i, documentDecisionModel as j, decideAtHead as k, DocumentModelRegistry as l, EventBus as m, REACTOR_SCHEMA as n, instrumentPgPool as o, resolveFeatureFlags as p, InvalidOperationTimestampError as q, getMigrationStatus as r, KyselyOperationStore as s, DEFAULT_DRIVE_CONTAINER_TYPES as t, SimpleJobExecutor as u, CollectionMembershipCache as v, ModuleNotFoundError as w, DuplicateManifestError as x, DEFAULT_DEFERRED_JOB_TTL_MS as y, OptimisticLockError as z };
6738
+ export { OptimisticLockError as $, DEFAULT_DEFERRED_JOB_TTL_MS as A, summarizeSubmittedActions as B, SettledWatermark as C, snapshotFunctionsFor as D, parseSnapshot as E, ModuleNotFoundError as F, authDecisionModel as G, decideAtHead as H, GATED_DOCUMENT_ACTIONS as I, APPEND_CONDITION_FAILED_PREFIX as J, buildDecisionModel as K, createConsistencyToken as L, DuplicateManifestError as M, DuplicateModuleError as N, DocumentMetaCache as O, InvalidModuleError as P, DuplicateOperationError as Q, createEmptyConsistencyToken as R, ProbeSettler as S, describeWaitingSessions as T, selectDecisionModel as U, targetDocumentId as V, documentDecisionModel as W, DocumentAlreadyExistsError as X, AppendConditionFailedError as Y, DocumentExistence as Z, KyselyExecutionScope as _, throwIfAborted as _t, DEFAULT_DRIVE_CONTAINER_TYPES as a, DocumentNotFoundError as at, KyselyWriteCache as b, runMigrations as c, InvalidSignatureError as ct, KyselyOperationStore as d, UnsupportedStoredProtocolError as dt, RevisionMismatchError as et, KyselyKeyframeStore as f, UpgradePreconditionFailedError as ft, PassthroughSigner as g, parsePagingOptions as gt, verifyActionSignature as h, matchesScope as ht, fromErrorInfo as i, DocumentDeletedError as it, JobExecutorEventTypes as j, CollectionMembershipCache as k, createForwardingPoolInstrumentation as l, RelationshipNotFoundError as lt, SimpleJobExecutor as m, ReactorEventTypes as mt, errorToInfo as n, AuthTimestampNotMonotonicError as nt, REACTOR_SCHEMA as o, ExcessiveReshuffleError as ot, DocumentModelRegistry as p, EventBusAggregateError as pt, DriveCollectionId as q, sanitizeArg as r, AuthorizationDeniedError as rt, getMigrationStatus as s, InvalidOperationTimestampError as st, createForwardingLogger as t, AuthEnforcementDisabledError as tt, instrumentPgPool as u, UnsupportedProtocolVersionError as ut, resolveFeatureFlags as v, yieldToMain as vt, createKyselyWatermarkProbe as w, KyselyOperationIndex as x, EventBus as y, __exportAll as yt, submittedActionIds as z };
5318
6739
 
5319
- //# sourceMappingURL=drive-container-types-aO-PASn7.js.map
6740
+ //# sourceMappingURL=forwarding-logger-BkwlvD9W.js.map