@powerhousedao/reactor 6.2.2-dev.31 → 6.2.2-dev.33

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.
@@ -425,6 +425,16 @@ function buildErrorResult(job, error, startTime) {
425
425
  duration: Date.now() - startTime
426
426
  };
427
427
  }
428
+ /**
429
+ * Whether this operation is part of the document's creation. The create and the
430
+ * upgrade from version zero hold the first two indexes for the life of the
431
+ * document, so a reshuffle has to leave them where they are.
432
+ */
433
+ function isGenesisOperation(operation) {
434
+ if (operation.action.type === "CREATE_DOCUMENT") return true;
435
+ if (operation.action.type !== "UPGRADE_DOCUMENT") return false;
436
+ return operation.action.input.fromVersion === 0;
437
+ }
428
438
  //#endregion
429
439
  //#region src/cache/lru/lru-tracker.ts
430
440
  var LRUNode = class {
@@ -1743,7 +1753,7 @@ function documentDecisionModel(target) {
1743
1753
  scope: "document"
1744
1754
  }
1745
1755
  } },
1746
- judgesScope() {
1756
+ evaluatesScope() {
1747
1757
  return true;
1748
1758
  },
1749
1759
  decide(model) {
@@ -1767,19 +1777,15 @@ function comparePositions(a, b) {
1767
1777
  const aTime = Date.parse(a.operation.timestampUtcMs);
1768
1778
  const bTime = Date.parse(b.operation.timestampUtcMs);
1769
1779
  if (aTime !== bTime) return aTime - bTime;
1780
+ if (a.streamKey === b.streamKey) return a.operation.index - b.operation.index;
1770
1781
  const actionIds = (a.operation.action.id ?? "").localeCompare(b.operation.action.id ?? "");
1771
1782
  if (actionIds !== 0) return actionIds;
1772
1783
  return (a.operation.id ?? "").localeCompare(b.operation.id ?? "");
1773
1784
  }
1774
1785
  /**
1775
- * Merges the read-set streams into one sequence by position. Each stream keeps
1776
- * its stored order; only how far to go is decided by the timestamp, so a stream
1777
- * whose stored order disagrees with its timestamp order still applies in the
1778
- * order it is stored.
1779
- *
1780
- * An operation's position in the result is the bound a decision at that
1781
- * operation reads to: every operation before it has been applied, and it has
1782
- * not.
1786
+ * Merges the read-set streams into one sequence by position. An operation's
1787
+ * place in the result is the bound a decision at that operation reads to: every
1788
+ * operation before it has been applied, and it has not.
1783
1789
  */
1784
1790
  function mergeByPosition(streams) {
1785
1791
  const merged = [];
@@ -1790,8 +1796,9 @@ function mergeByPosition(streams) {
1790
1796
  return merged.sort(comparePositions);
1791
1797
  }
1792
1798
  /**
1793
- * For auth-related reshuffles, we may need to retract previous operations.
1794
- * This function calculates the needed skip value.
1799
+ * The skip that retracts everything from `firstRetractedIndex` up to where the
1800
+ * re-appended operation lands. It spans the indexes rather than counting the
1801
+ * operations, because a stream with a gap in it makes those differ.
1795
1802
  */
1796
1803
  function retractionSkip(nextIndex, firstRetractedIndex) {
1797
1804
  return nextIndex - firstRetractedIndex;
@@ -1799,6 +1806,23 @@ function retractionSkip(nextIndex, firstRetractedIndex) {
1799
1806
  //#endregion
1800
1807
  //#region src/decision/walk.ts
1801
1808
  /**
1809
+ * A single forward pass is only correct while a stream's effective operations
1810
+ * are ordered.
1811
+ */
1812
+ function assertPositionOrder(streamKey, operations) {
1813
+ for (let i = 1; i < operations.length; i++) {
1814
+ const previous = operations[i - 1];
1815
+ const current = operations[i];
1816
+ if (comparePositions({
1817
+ streamKey,
1818
+ operation: previous
1819
+ }, {
1820
+ streamKey,
1821
+ operation: current
1822
+ }) > 0) throw new Error(`Stream ${streamKey} is out of position order: index ${previous.index} at ${previous.timestampUtcMs} precedes index ${current.index} at ${current.timestampUtcMs}`);
1823
+ }
1824
+ }
1825
+ /**
1802
1826
  * Visits every operation in the read-set once, in the order their positions
1803
1827
  * fall, and hands back the state each stream held just before it. That state is
1804
1828
  * what a decision at that operation reads: everything ahead of it has been
@@ -1810,10 +1834,15 @@ function retractionSkip(nextIndex, firstRetractedIndex) {
1810
1834
  * A denied operation is visited but not applied.
1811
1835
  */
1812
1836
  function* walkByPosition(streams) {
1813
- const merged = mergeByPosition(streams.map((stream) => ({
1814
- streamKey: stream.streamKey,
1815
- operations: garbageCollect(sortOperations([...stream.operations]))
1816
- })));
1837
+ const merged = mergeByPosition(streams.map((stream) => {
1838
+ const operations = garbageCollect(sortOperations([...stream.operations]));
1839
+ assertPositionOrder(stream.streamKey, operations);
1840
+ return {
1841
+ streamKey: stream.streamKey,
1842
+ operations
1843
+ };
1844
+ }));
1845
+ const byKey = new Map(streams.map((stream) => [stream.streamKey, stream]));
1817
1846
  const states = new Map(streams.map((stream) => [stream.streamKey, stream.document]));
1818
1847
  for (const { streamKey, operation } of merged) {
1819
1848
  yield {
@@ -1822,16 +1851,16 @@ function* walkByPosition(streams) {
1822
1851
  states: new Map(states)
1823
1852
  };
1824
1853
  if (isDenied(operation)) continue;
1825
- const stream = streams.find((candidate) => candidate.streamKey === streamKey);
1854
+ const stream = byKey.get(streamKey);
1826
1855
  const before = states.get(streamKey);
1827
1856
  if (before === void 0 || stream === void 0) throw new Error(`No state for stream ${streamKey}`);
1828
1857
  states.set(streamKey, stream.apply(before, operation));
1829
1858
  }
1830
1859
  }
1831
1860
  //#endregion
1832
- //#region src/decision/deletion-verdicts.ts
1861
+ //#region src/decision/deletion-evaluation.ts
1833
1862
  const WRITTEN = "written";
1834
- /** Whether a read stream counts this action as one that changes a verdict. */
1863
+ /** Whether a read stream counts this action as one that changes an evaluation. */
1835
1864
  function canRefuseOthers(operation, readSet) {
1836
1865
  return readSet.some((stream) => stream.decidingActions.includes(operation.action.type));
1837
1866
  }
@@ -1849,19 +1878,20 @@ function firstDeleted(candidates) {
1849
1878
  * and leaves the earlier ones alone. A deletion among the ones passed in does
1850
1879
  * the same to those after it.
1851
1880
  */
1852
- async function deletionVerdictsByPosition(documentId, scope, branch, operations, writeCache, operationStore, signal) {
1853
- const definition = documentDecisionModel({
1854
- documentId,
1855
- branch
1856
- });
1881
+ async function evaluateDeletionsByPosition(target, subject, stores, signal) {
1882
+ const { scope, operations } = subject;
1883
+ const { writeCache, operationStore } = stores;
1884
+ const definition = documentDecisionModel(target);
1857
1885
  const readSet = staticReadSet(definition);
1858
- if (!definition.judgesScope(scope)) return operations.map(() => void 0);
1886
+ if (!definition.evaluatesScope(scope)) return operations.map(() => void 0);
1887
+ const evaluating = new Set(operations.map((operation) => operation.id));
1859
1888
  const readStreams = await Promise.all(readSet.map(async (stream) => ({
1860
1889
  stream,
1861
- operations: (await operationStore.getSince(stream.query.documentId, stream.query.scope, stream.query.branch, 0, { actionTypes: stream.decidingActions }, void 0, signal)).results
1890
+ operations: (await operationStore.getSince(stream.query.documentId, stream.query.scope, stream.query.branch, 0, { actionTypes: stream.decidingActions }, void 0, signal)).results.filter((operation) => !evaluating.has(operation.id))
1862
1891
  })));
1863
1892
  const decidingWritten = operations.filter((operation) => canRefuseOthers(operation, readSet));
1864
1893
  if (readStreams.every((read) => read.operations.length === 0) && decidingWritten.length === 0) return operations.map(() => void 0);
1894
+ if (readStreams.length === 0) throw new Error(`Decision model for ${target.documentId} reads no stream whose query is known before it is built`);
1865
1895
  const walked = [];
1866
1896
  for (const read of readStreams) {
1867
1897
  const before = await writeCache.getState(read.stream.query.documentId, read.stream.query.scope, read.stream.query.branch, 0, signal);
@@ -1942,23 +1972,81 @@ var DriveCollectionId = class DriveCollectionId {
1942
1972
  //#endregion
1943
1973
  //#region src/executor/document-action-handler.ts
1944
1974
  var DocumentActionHandler = class {
1945
- constructor(registry, logger, driveContainerTypes) {
1975
+ constructor(registry, logger, driveContainerTypes, featureFlags) {
1946
1976
  this.registry = registry;
1947
1977
  this.logger = logger;
1948
1978
  this.driveContainerTypes = driveContainerTypes;
1979
+ this.featureFlags = featureFlags;
1949
1980
  }
1950
- async execute(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal, verdictAlreadyDecided = false) {
1981
+ /** Whether the write arrives with its evaluation already decided. */
1982
+ alreadyEvaluated(executing) {
1983
+ return this.featureFlags.documentDecisions && executing.replayingAcceptedHistory;
1984
+ }
1985
+ async execute(write, executing) {
1986
+ const { action } = write;
1987
+ if (write.deniedReason !== void 0) return this.writeDenied(write, executing);
1951
1988
  switch (action.type) {
1952
- case "CREATE_DOCUMENT": return this.executeCreate(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal);
1953
- case "DELETE_DOCUMENT": return this.executeDelete(job, action, startTime, indexTxn, stores, sourceRemote, signal, verdictAlreadyDecided);
1954
- case "UPGRADE_DOCUMENT": return this.executeUpgrade(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal, verdictAlreadyDecided);
1955
- case "ADD_RELATIONSHIP": return this.executeAddRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
1956
- case "REMOVE_RELATIONSHIP": return this.executeRemoveRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
1957
- case "UPDATE_RELATIONSHIP": return this.executeUpdateRelationship(job, action, startTime, indexTxn, stores, sourceRemote, signal);
1958
- default: return buildErrorResult(job, /* @__PURE__ */ new Error(`Unknown document action type: ${action.type}`), startTime);
1989
+ case "CREATE_DOCUMENT": return this.executeCreate(write, executing);
1990
+ case "DELETE_DOCUMENT": return this.executeDelete(write, executing);
1991
+ case "UPGRADE_DOCUMENT": return this.executeUpgrade(write, executing);
1992
+ case "ADD_RELATIONSHIP": return this.executeAddRelationship(write, executing);
1993
+ case "REMOVE_RELATIONSHIP": return this.executeRemoveRelationship(write, executing);
1994
+ case "UPDATE_RELATIONSHIP": return this.executeUpdateRelationship(write, executing);
1995
+ default: return buildErrorResult(executing.job, /* @__PURE__ */ new Error(`Unknown document action type: ${action.type}`), executing.startTime);
1996
+ }
1997
+ }
1998
+ /** A refused operation holds a position in the stream but changes nothing. */
1999
+ async writeDenied(write, executing) {
2000
+ const { action, skip, sourceRemote, deniedReason } = write;
2001
+ const { job, startTime, indexTxn, stores, signal } = executing;
2002
+ let document;
2003
+ try {
2004
+ document = await stores.writeCache.getState(job.documentId, job.scope, job.branch, void 0, signal);
2005
+ } catch (error) {
2006
+ return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
1959
2007
  }
2008
+ let operation = createOperation(action, getNextIndexForScope(document, job.scope), skip, {
2009
+ documentId: job.documentId,
2010
+ scope: job.scope,
2011
+ branch: job.branch
2012
+ });
2013
+ operation.deniedReason = deniedReason;
2014
+ operation.hash = hashDocumentStateForScope(document, job.scope);
2015
+ const writeResult = await this.writeOperationToStore({
2016
+ documentId: job.documentId,
2017
+ documentType: document.header.documentType,
2018
+ scope: job.scope,
2019
+ branch: job.branch
2020
+ }, operation, executing);
2021
+ if (!Array.isArray(writeResult)) return writeResult;
2022
+ operation = writeResult[0];
2023
+ updateDocumentRevision(document, job.scope, operation.index);
2024
+ document.operations = {
2025
+ ...document.operations,
2026
+ [job.scope]: [...document.operations[job.scope] ?? [], operation]
2027
+ };
2028
+ stores.writeCache.putState(job.documentId, job.scope, job.branch, operation.index, document, SnapshotPosition.Head);
2029
+ indexTxn.write([{
2030
+ ...operation,
2031
+ documentId: job.documentId,
2032
+ documentType: document.header.documentType,
2033
+ branch: job.branch,
2034
+ scope: job.scope,
2035
+ sourceRemote
2036
+ }]);
2037
+ stores.documentMetaCache.putDocumentMeta(job.documentId, job.branch, {
2038
+ state: document.state.document,
2039
+ documentType: document.header.documentType,
2040
+ documentScopeRevision: operation.index + 1
2041
+ });
2042
+ return buildSuccessResult(job, operation, job.documentId, document.header.documentType, JSON.stringify({
2043
+ header: document.header,
2044
+ document: document.state.document
2045
+ }), startTime);
1960
2046
  }
1961
- async executeCreate(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal) {
2047
+ async executeCreate(write, executing) {
2048
+ const { action, skip, sourceRemote } = write;
2049
+ const { job, startTime, indexTxn, stores, signal } = executing;
1962
2050
  if (job.scope !== "document") return {
1963
2051
  job,
1964
2052
  success: false,
@@ -1976,7 +2064,12 @@ var DocumentActionHandler = class {
1976
2064
  ...document.state
1977
2065
  };
1978
2066
  const resultingState = JSON.stringify(resultingStateObj);
1979
- const writeResult = await this.writeOperationToStore(document.header.id, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
2067
+ const writeResult = await this.writeOperationToStore({
2068
+ documentId: document.header.id,
2069
+ documentType: document.header.documentType,
2070
+ scope: job.scope,
2071
+ branch: job.branch
2072
+ }, operation, executing);
1980
2073
  if (!Array.isArray(writeResult)) return writeResult;
1981
2074
  operation = writeResult[0];
1982
2075
  updateDocumentRevision(document, job.scope, operation.index);
@@ -2005,7 +2098,9 @@ var DocumentActionHandler = class {
2005
2098
  });
2006
2099
  return buildSuccessResult(job, operation, document.header.id, document.header.documentType, resultingState, startTime);
2007
2100
  }
2008
- async executeDelete(job, action, startTime, indexTxn, stores, sourceRemote = "", signal, verdictAlreadyDecided = false) {
2101
+ async executeDelete(write, executing) {
2102
+ const { action, skip, sourceRemote } = write;
2103
+ const { job, startTime, indexTxn, stores, signal } = executing;
2009
2104
  const input = action.input;
2010
2105
  if (!input.documentId) return buildErrorResult(job, /* @__PURE__ */ new Error("DELETE_DOCUMENT action requires a documentId in input"), startTime);
2011
2106
  const documentId = input.documentId;
@@ -2016,8 +2111,8 @@ var DocumentActionHandler = class {
2016
2111
  return buildErrorResult(job, /* @__PURE__ */ new Error(`Failed to fetch document before deletion: ${error instanceof Error ? error.message : String(error)}`), startTime);
2017
2112
  }
2018
2113
  const documentState = document.state.document;
2019
- if (documentState.isDeleted && !verdictAlreadyDecided) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
2020
- let operation = createOperation(action, getNextIndexForScope(document, job.scope), 0, {
2114
+ if (documentState.isDeleted && !this.alreadyEvaluated(executing)) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
2115
+ let operation = createOperation(action, getNextIndexForScope(document, job.scope), skip, {
2021
2116
  documentId,
2022
2117
  scope: job.scope,
2023
2118
  branch: job.branch
@@ -2028,7 +2123,12 @@ var DocumentActionHandler = class {
2028
2123
  document: document.state.document
2029
2124
  };
2030
2125
  const resultingState = JSON.stringify(resultingStateObj);
2031
- const writeResult = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
2126
+ const writeResult = await this.writeOperationToStore({
2127
+ documentId,
2128
+ documentType: document.header.documentType,
2129
+ scope: job.scope,
2130
+ branch: job.branch
2131
+ }, operation, executing);
2032
2132
  if (!Array.isArray(writeResult)) return writeResult;
2033
2133
  operation = writeResult[0];
2034
2134
  updateDocumentRevision(document, job.scope, operation.index);
@@ -2052,7 +2152,9 @@ var DocumentActionHandler = class {
2052
2152
  });
2053
2153
  return buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
2054
2154
  }
2055
- async executeUpgrade(job, action, startTime, indexTxn, stores, skip = 0, sourceRemote = "", signal, verdictAlreadyDecided = false) {
2155
+ async executeUpgrade(write, executing) {
2156
+ const { action, skip, sourceRemote } = write;
2157
+ const { job, startTime, indexTxn, stores, signal } = executing;
2056
2158
  const input = action.input;
2057
2159
  if (!input.documentId) return buildErrorResult(job, /* @__PURE__ */ new Error("UPGRADE_DOCUMENT action requires a documentId in input"), startTime);
2058
2160
  const documentId = input.documentId;
@@ -2065,7 +2167,7 @@ var DocumentActionHandler = class {
2065
2167
  return buildErrorResult(job, /* @__PURE__ */ new Error(`Failed to fetch document for upgrade: ${error instanceof Error ? error.message : String(error)}`), startTime);
2066
2168
  }
2067
2169
  const documentState = document.state.document;
2068
- if (documentState.isDeleted && !verdictAlreadyDecided) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
2170
+ if (documentState.isDeleted && !this.alreadyEvaluated(executing)) return buildErrorResult(job, new DocumentDeletedError(documentId, documentState.deletedAtUtcIso), startTime);
2069
2171
  const nextIndex = getNextIndexForScope(document, job.scope);
2070
2172
  let upgradePath;
2071
2173
  if (fromVersion > 0 && fromVersion < toVersion) try {
@@ -2095,7 +2197,12 @@ var DocumentActionHandler = class {
2095
2197
  ...document.state
2096
2198
  };
2097
2199
  const resultingState = JSON.stringify(resultingStateObj);
2098
- const writeResult = await this.writeOperationToStore(documentId, document.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
2200
+ const writeResult = await this.writeOperationToStore({
2201
+ documentId,
2202
+ documentType: document.header.documentType,
2203
+ scope: job.scope,
2204
+ branch: job.branch
2205
+ }, operation, executing);
2099
2206
  if (!Array.isArray(writeResult)) return writeResult;
2100
2207
  operation = writeResult[0];
2101
2208
  updateDocumentRevision(document, job.scope, operation.index);
@@ -2119,8 +2226,8 @@ var DocumentActionHandler = class {
2119
2226
  });
2120
2227
  return buildSuccessResult(job, operation, documentId, document.header.documentType, resultingState, startTime);
2121
2228
  }
2122
- executeAddRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "", signal) {
2123
- return this.withRelationshipAction("ADD_RELATIONSHIP", job, action, startTime, indexTxn, stores, sourceRemote, signal, (input) => input.sourceId === input.targetId ? /* @__PURE__ */ new Error("ADD_RELATIONSHIP: sourceId and targetId cannot be the same (self-relationships not allowed)") : null, ({ indexTxn: txn, stores: s, sourceDoc, input, job: j }) => {
2229
+ executeAddRelationship(write, executing) {
2230
+ return this.withRelationshipAction("ADD_RELATIONSHIP", write, executing, (input) => input.sourceId === input.targetId ? /* @__PURE__ */ new Error("ADD_RELATIONSHIP: sourceId and targetId cannot be the same (self-relationships not allowed)") : null, ({ indexTxn: txn, stores: s, sourceDoc, input, job: j }) => {
2124
2231
  if (this.driveContainerTypes.has(sourceDoc.header.documentType)) {
2125
2232
  const collectionId = DriveCollectionId.forDrive(input.sourceId, j.branch).key;
2126
2233
  txn.addToCollection(collectionId, input.targetId);
@@ -2128,8 +2235,8 @@ var DocumentActionHandler = class {
2128
2235
  }
2129
2236
  });
2130
2237
  }
2131
- executeRemoveRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "", signal) {
2132
- return this.withRelationshipAction("REMOVE_RELATIONSHIP", job, action, startTime, indexTxn, stores, sourceRemote, signal, null, ({ indexTxn: txn, stores: s, sourceDoc, input, job: j }) => {
2238
+ executeRemoveRelationship(write, executing) {
2239
+ return this.withRelationshipAction("REMOVE_RELATIONSHIP", write, executing, null, ({ indexTxn: txn, stores: s, sourceDoc, input, job: j }) => {
2133
2240
  if (this.driveContainerTypes.has(sourceDoc.header.documentType)) {
2134
2241
  const collectionId = DriveCollectionId.forDrive(input.sourceId, j.branch).key;
2135
2242
  txn.removeFromCollection(collectionId, input.targetId);
@@ -2137,10 +2244,12 @@ var DocumentActionHandler = class {
2137
2244
  }
2138
2245
  });
2139
2246
  }
2140
- executeUpdateRelationship(job, action, startTime, indexTxn, stores, sourceRemote = "", signal) {
2141
- return this.withRelationshipAction("UPDATE_RELATIONSHIP", job, action, startTime, indexTxn, stores, sourceRemote, signal, null, null);
2247
+ executeUpdateRelationship(write, executing) {
2248
+ return this.withRelationshipAction("UPDATE_RELATIONSHIP", write, executing, null, null);
2142
2249
  }
2143
- async withRelationshipAction(actionTypeName, job, action, startTime, indexTxn, stores, sourceRemote, signal, preValidate, postWrite) {
2250
+ async withRelationshipAction(actionTypeName, write, executing, preValidate, postWrite) {
2251
+ const { action, skip, sourceRemote } = write;
2252
+ const { job, startTime, indexTxn, stores, signal } = executing;
2144
2253
  if (job.scope !== "document") return buildErrorResult(job, /* @__PURE__ */ new Error(`${actionTypeName} must be in "document" scope, got "${job.scope}"`), startTime);
2145
2254
  const input = action.input;
2146
2255
  if (!input.sourceId || !input.targetId || !input.relationshipType) return buildErrorResult(job, /* @__PURE__ */ new Error(`${actionTypeName} action requires sourceId, targetId, and relationshipType in input`), startTime);
@@ -2154,12 +2263,17 @@ var DocumentActionHandler = class {
2154
2263
  } catch (error) {
2155
2264
  return buildErrorResult(job, /* @__PURE__ */ new Error(`${actionTypeName}: source document ${input.sourceId} not found: ${error instanceof Error ? error.message : String(error)}`), startTime);
2156
2265
  }
2157
- let operation = createOperation(action, getNextIndexForScope(sourceDoc, job.scope), 0, {
2266
+ let operation = createOperation(action, getNextIndexForScope(sourceDoc, job.scope), skip, {
2158
2267
  documentId: input.sourceId,
2159
2268
  scope: job.scope,
2160
2269
  branch: job.branch
2161
2270
  });
2162
- const writeResult = await this.writeOperationToStore(input.sourceId, sourceDoc.header.documentType, job.scope, job.branch, operation, job, startTime, stores, signal);
2271
+ const writeResult = await this.writeOperationToStore({
2272
+ documentId: input.sourceId,
2273
+ documentType: sourceDoc.header.documentType,
2274
+ scope: job.scope,
2275
+ branch: job.branch
2276
+ }, operation, executing);
2163
2277
  if (!Array.isArray(writeResult)) return writeResult;
2164
2278
  operation = writeResult[0];
2165
2279
  sourceDoc.header.lastModifiedAtUtcIso = operation.timestampUtcMs || (/* @__PURE__ */ new Date()).toISOString();
@@ -2197,7 +2311,9 @@ var DocumentActionHandler = class {
2197
2311
  });
2198
2312
  return buildSuccessResult(job, operation, input.sourceId, sourceDoc.header.documentType, resultingState, startTime);
2199
2313
  }
2200
- async writeOperationToStore(documentId, documentType, scope, branch, operation, job, startTime, stores, signal) {
2314
+ async writeOperationToStore(target, operation, executing) {
2315
+ const { documentId, documentType, scope, branch } = target;
2316
+ const { job, startTime, stores, signal } = executing;
2201
2317
  let storedOperations;
2202
2318
  try {
2203
2319
  storedOperations = await stores.operationStore.apply(documentId, documentType, scope, branch, operation.index, (txn) => {
@@ -2313,7 +2429,7 @@ var SimpleJobExecutor = class {
2313
2429
  };
2314
2430
  this.featureFlags = { documentDecisions: config.featureFlags?.documentDecisions ?? false };
2315
2431
  this.signatureVerifierModule = new SignatureVerifier(signatureVerifier);
2316
- this.documentActionHandler = new DocumentActionHandler(registry, logger, driveContainerTypes);
2432
+ this.documentActionHandler = new DocumentActionHandler(registry, logger, driveContainerTypes, this.featureFlags);
2317
2433
  this.executionScope = executionScope ?? new DefaultExecutionScope(operationStore, operationIndex, writeCache, documentMetaCache, collectionMembershipCache);
2318
2434
  }
2319
2435
  /**
@@ -2329,7 +2445,14 @@ var SimpleJobExecutor = class {
2329
2445
  result = await this.executionScope.run(async (stores) => {
2330
2446
  const indexTxn = stores.operationIndex.start();
2331
2447
  if (job.kind === "load") {
2332
- const loadResult = await this.executeLoadJob(job, startTime, indexTxn, stores, signal);
2448
+ const loadResult = await this.executeLoadJob({
2449
+ job,
2450
+ startTime,
2451
+ indexTxn,
2452
+ stores,
2453
+ signal,
2454
+ replayingAcceptedHistory: true
2455
+ });
2333
2456
  if (loadResult.success && loadResult.operationsWithContext) {
2334
2457
  for (const owc of loadResult.operationsWithContext) touchedCacheEntries.push({
2335
2458
  documentId: owc.context.documentId,
@@ -2348,7 +2471,20 @@ var SimpleJobExecutor = class {
2348
2471
  }
2349
2472
  return loadResult;
2350
2473
  }
2351
- const actionResult = await this.processActions(job, job.actions, startTime, indexTxn, stores, void 0, void 0, "", signal);
2474
+ const executing = {
2475
+ job,
2476
+ startTime,
2477
+ indexTxn,
2478
+ stores,
2479
+ signal,
2480
+ replayingAcceptedHistory: false
2481
+ };
2482
+ const positioned = await this.positionByTimestamp(job, stores, signal);
2483
+ const actionResult = await this.processActions(positioned.actions.map((action, i) => ({
2484
+ action,
2485
+ skip: positioned.skipValues?.[i] ?? 0,
2486
+ sourceRemote: ""
2487
+ })), executing);
2352
2488
  if (!actionResult.success) return {
2353
2489
  job,
2354
2490
  success: false,
@@ -2360,6 +2496,16 @@ var SimpleJobExecutor = class {
2360
2496
  scope: owc.context.scope,
2361
2497
  branch: owc.context.branch
2362
2498
  });
2499
+ const reevaluationError = await this.reevaluateIfCriteriaMet({
2500
+ scope: job.scope,
2501
+ operations: actionResult.generatedOperations
2502
+ }, executing);
2503
+ if (reevaluationError) return {
2504
+ job,
2505
+ success: false,
2506
+ error: reevaluationError,
2507
+ duration: Date.now() - startTime
2508
+ };
2363
2509
  const ordinals = await stores.operationIndex.commit(indexTxn, signal);
2364
2510
  if (actionResult.operationsWithContext.length > 0) {
2365
2511
  for (let i = 0; i < actionResult.operationsWithContext.length; i++) actionResult.operationsWithContext[i].context.ordinal = ordinals[i];
@@ -2395,7 +2541,9 @@ var SimpleJobExecutor = class {
2395
2541
  const documentIds = [...new Set(operations.map((op) => op.context.documentId))];
2396
2542
  return stores.collectionMembershipCache.getCollectionsForDocuments(documentIds);
2397
2543
  }
2398
- async processActions(job, actions, startTime, indexTxn, stores, skipValues, sourceOperations, sourceRemote = "", signal, deniedReasons) {
2544
+ async processActions(writes, executing) {
2545
+ const { job, signal } = executing;
2546
+ const actions = writes.map((write) => write.action);
2399
2547
  const generatedOperations = [];
2400
2548
  const operationsWithContext = [];
2401
2549
  try {
@@ -2415,12 +2563,8 @@ var SimpleJobExecutor = class {
2415
2563
  error: /* @__PURE__ */ new Error(`Invalid timestamp "${action.timestampUtcMs}" on action ${action.type} (id: ${action.id})`)
2416
2564
  };
2417
2565
  let lastYield = performance.now();
2418
- for (let actionIndex = 0; actionIndex < actions.length; actionIndex++) {
2419
- const action = actions[actionIndex];
2420
- const skip = skipValues?.[actionIndex] ?? 0;
2421
- const sourceOperation = sourceOperations?.[actionIndex];
2422
- const deniedReason = deniedReasons?.[actionIndex];
2423
- const result = documentScopeActions.includes(action.type) ? await this.documentActionHandler.execute(job, action, startTime, indexTxn, stores, skip, sourceRemote, signal, this.featureFlags.documentDecisions && job.kind === "load") : await this.executeRegularAction(job, action, startTime, indexTxn, stores, skip, sourceOperation, sourceRemote, signal, deniedReason);
2566
+ for (const write of writes) {
2567
+ const result = documentScopeActions.includes(write.action.type) ? await this.documentActionHandler.execute(write, executing) : await this.executeRegularAction(write, executing);
2424
2568
  const error = this.accumulateResultOrReturnError(result, generatedOperations, operationsWithContext);
2425
2569
  if (error !== null) return {
2426
2570
  success: false,
@@ -2445,11 +2589,13 @@ var SimpleJobExecutor = class {
2445
2589
  operationsWithContext
2446
2590
  };
2447
2591
  }
2448
- async executeRegularAction(job, action, startTime, indexTxn, stores, skip = 0, sourceOperation, sourceRemote = "", signal, deniedReason) {
2592
+ async executeRegularAction(write, executing) {
2593
+ const { action, skip, sourceOperation, sourceRemote, deniedReason } = write;
2594
+ const { job, startTime, indexTxn, stores, signal } = executing;
2449
2595
  let appendCondition;
2450
2596
  let documentVersion;
2451
- const verdictAlreadyDecided = this.featureFlags.documentDecisions && job.kind === "load";
2452
- if (this.featureFlags.documentDecisions && !verdictAlreadyDecided) {
2597
+ const alreadyEvaluated = this.featureFlags.documentDecisions && executing.replayingAcceptedHistory;
2598
+ if (this.featureFlags.documentDecisions && !alreadyEvaluated) {
2453
2599
  const target = {
2454
2600
  documentId: job.documentId,
2455
2601
  branch: job.branch
@@ -2471,7 +2617,7 @@ var SimpleJobExecutor = class {
2471
2617
  }, { scopeState: void 0 }) === "deny") return buildErrorResult(job, new DocumentDeletedError(job.documentId, built.model.document.deletedAtUtcIso), startTime);
2472
2618
  appendCondition = built.appendCondition;
2473
2619
  documentVersion = built.model.document.version;
2474
- } else if (verdictAlreadyDecided) documentVersion = (await stores.writeCache.getState(job.documentId, "document", job.branch, void 0, signal)).state.document.version;
2620
+ } else if (alreadyEvaluated) documentVersion = (await stores.writeCache.getState(job.documentId, "document", job.branch, void 0, signal)).state.document.version;
2475
2621
  else {
2476
2622
  let docMeta;
2477
2623
  try {
@@ -2489,10 +2635,10 @@ var SimpleJobExecutor = class {
2489
2635
  } catch (error) {
2490
2636
  return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
2491
2637
  }
2492
- if (job.kind !== "load") {
2638
+ if (!executing.replayingAcceptedHistory) {
2493
2639
  const subject = {
2494
- address: action.context?.signer?.user.address,
2495
- key: action.context?.signer?.app.key
2640
+ address: write.action.context?.signer?.user.address,
2641
+ key: write.action.context?.signer?.app.key
2496
2642
  };
2497
2643
  if (decide(document.state.auth, subject, {
2498
2644
  verb: "execute",
@@ -2509,18 +2655,25 @@ var SimpleJobExecutor = class {
2509
2655
  }
2510
2656
  let updatedDocument;
2511
2657
  if (deniedReason !== void 0) {
2512
- const denied = createOperation(action, getNextIndexForScope(document, job.scope), skip, {
2658
+ const index = getNextIndexForScope(document, job.scope);
2659
+ const denied = createOperation(action, index, skip, {
2513
2660
  documentId: job.documentId,
2514
2661
  scope: job.scope,
2515
2662
  branch: job.branch
2516
2663
  });
2517
2664
  denied.deniedReason = deniedReason;
2518
- denied.hash = hashDocumentStateForScope(document, job.scope);
2665
+ let standing = document;
2666
+ if (skip > 0) try {
2667
+ standing = await stores.writeCache.getState(job.documentId, job.scope, job.branch, index - skip - 1, signal);
2668
+ } catch (error) {
2669
+ return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
2670
+ }
2671
+ denied.hash = hashDocumentStateForScope(standing, job.scope);
2519
2672
  updatedDocument = {
2520
- ...document,
2673
+ ...standing,
2521
2674
  operations: {
2522
- ...document.operations,
2523
- [job.scope]: [...document.operations[job.scope] ?? [], denied]
2675
+ ...standing.operations,
2676
+ [job.scope]: [...standing.operations[job.scope] ?? [], denied]
2524
2677
  }
2525
2678
  };
2526
2679
  } else try {
@@ -2600,36 +2753,106 @@ var SimpleJobExecutor = class {
2600
2753
  };
2601
2754
  }
2602
2755
  /**
2603
- * If an operation happened that could change a decision model verdict, we
2604
- * need to re-evaluate across the streams that could be affected. Previously
2605
- * approved operations may need to be denied, which will apply new denied
2606
- * operations with appropriate skip.
2756
+ * Orders a write by timestamp. The caller supplies the timestamp, so a write
2757
+ * can belong before operations already stored, and appending it at the tail
2758
+ * would leave the scope out of order. The operations it belongs before are
2759
+ * re-appended alongside it, the way a load reshuffles.
2607
2760
  */
2608
- async reevaluateReadingScopes(job, startTime, indexTxn, stores, signal) {
2761
+ async positionByTimestamp(job, stores, signal) {
2762
+ if (!this.featureFlags.documentDecisions || job.actions.length === 0) return { actions: job.actions };
2763
+ let earliest = job.actions[0].timestampUtcMs;
2764
+ for (const action of job.actions) if (action.timestampUtcMs < earliest) earliest = action.timestampUtcMs;
2765
+ const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch, signal);
2766
+ if (earliest >= revisions.latestTimestamp) return { actions: job.actions };
2767
+ const conflicting = (await stores.operationStore.getConflicting(job.documentId, job.scope, job.branch, earliest, void 0, signal)).results.filter((operation) => !isGenesisOperation(operation));
2768
+ if (conflicting.length === 0) return { actions: job.actions };
2769
+ const nextIndex = revisions.revision[job.scope] ?? 0;
2770
+ let firstConflicting = conflicting[0].index;
2771
+ for (const operation of conflicting) if (operation.index < firstConflicting) firstConflicting = operation.index;
2772
+ const incoming = job.actions.map((action, i) => ({
2773
+ id: action.id,
2774
+ index: nextIndex + i,
2775
+ skip: 0,
2776
+ hash: "",
2777
+ timestampUtcMs: action.timestampUtcMs,
2778
+ action
2779
+ }));
2780
+ const merged = reshuffleByTimestamp({
2781
+ index: nextIndex,
2782
+ skip: retractionSkip(nextIndex, firstConflicting)
2783
+ }, conflicting, incoming);
2784
+ stores.writeCache.invalidate(job.documentId, job.scope, job.branch);
2785
+ return {
2786
+ actions: merged.map((operation) => operation.action),
2787
+ skipValues: merged.map((operation) => operation.skip)
2788
+ };
2789
+ }
2790
+ /**
2791
+ * Re-evaluates the document when a write meets both criteria: it was written
2792
+ * to a stream the model reads, and it is timestamped before an operation
2793
+ * already stored. The caller supplies the timestamp and the reactor does not replace
2794
+ * it, so a mutation job can write such an operation just as a load job can,
2795
+ * which is why both executeJob and executeLoadJob call this.
2796
+ */
2797
+ async reevaluateIfCriteriaMet(criteria, executing) {
2798
+ if (!this.featureFlags.documentDecisions) return;
2799
+ const { job, stores, signal } = executing;
2800
+ if (!staticReadSet(documentDecisionModel({
2801
+ documentId: job.documentId,
2802
+ branch: job.branch
2803
+ })).some((stream) => stream.query.documentId === job.documentId && stream.query.scope === criteria.scope && stream.query.branch === job.branch)) return;
2804
+ const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch, signal);
2805
+ const latest = Date.parse(revisions.latestTimestamp);
2806
+ if (!criteria.operations.some((operation) => Date.parse(operation.timestampUtcMs) < latest)) return;
2807
+ return this.reevaluateDocument(executing);
2808
+ }
2809
+ /**
2810
+ * Re-evaluates every scope the model evaluates. Where an operation's
2811
+ * evaluation differs from what is stored, the tail from that operation is
2812
+ * re-appended, carrying a skip that spans the indices it supersedes.
2813
+ */
2814
+ async reevaluateDocument(executing) {
2815
+ const { job, stores, signal } = executing;
2609
2816
  const definition = documentDecisionModel({
2610
2817
  documentId: job.documentId,
2611
2818
  branch: job.branch
2612
2819
  });
2613
2820
  const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch, signal);
2614
2821
  for (const scope of Object.keys(revisions.revision)) {
2615
- if (!definition.judgesScope(scope)) continue;
2822
+ if (!definition.evaluatesScope(scope)) continue;
2616
2823
  const stored = (await stores.operationStore.getSince(job.documentId, scope, job.branch, -1, void 0, void 0, signal)).results;
2617
2824
  const effective = garbageCollect(sortOperations([...stored]));
2618
2825
  if (effective.length === 0) continue;
2619
- const recomputed = await deletionVerdictsByPosition(job.documentId, scope, job.branch, effective, stores.writeCache, stores.operationStore, signal);
2620
- const firstChange = effective.findIndex((operation, i) => operation.deniedReason !== recomputed[i]);
2826
+ const reevaluated = await evaluateDeletionsByPosition({
2827
+ documentId: job.documentId,
2828
+ branch: job.branch
2829
+ }, {
2830
+ scope,
2831
+ operations: effective
2832
+ }, stores, signal);
2833
+ const firstChange = effective.findIndex((operation, i) => operation.deniedReason !== reevaluated[i]);
2621
2834
  if (firstChange === -1) continue;
2622
2835
  const tail = effective.slice(firstChange);
2623
2836
  const nextIndex = revisions.revision[scope];
2624
2837
  stores.writeCache.invalidate(job.documentId, scope, job.branch);
2625
- const result = await this.processActions({
2626
- ...job,
2627
- scope
2628
- }, tail.map((operation) => operation.action), startTime, indexTxn, stores, tail.map((_, i) => i === 0 ? retractionSkip(nextIndex, tail[0].index) : 0), void 0, "", signal, recomputed.slice(firstChange));
2838
+ const result = await this.processActions(tail.map((operation, i) => ({
2839
+ action: operation.action,
2840
+ skip: i === 0 ? retractionSkip(nextIndex, tail[0].index) : 0,
2841
+ sourceRemote: "",
2842
+ deniedReason: reevaluated[firstChange + i]
2843
+ })), {
2844
+ ...executing,
2845
+ job: {
2846
+ ...job,
2847
+ scope
2848
+ },
2849
+ replayingAcceptedHistory: true
2850
+ });
2629
2851
  if (!result.success) return result.error ?? /* @__PURE__ */ new Error(`Re-evaluation of ${job.documentId} ${scope} failed`);
2630
2852
  }
2631
2853
  }
2632
- async executeLoadJob(job, startTime, indexTxn, stores, signal) {
2854
+ async executeLoadJob(executing) {
2855
+ const { job, startTime, indexTxn, stores, signal } = executing;
2633
2856
  if (job.operations.length === 0) return buildErrorResult(job, /* @__PURE__ */ new Error("Load job must include at least one operation"), startTime);
2634
2857
  let docMeta;
2635
2858
  try {
@@ -2679,7 +2902,7 @@ var SimpleJobExecutor = class {
2679
2902
  }
2680
2903
  return true;
2681
2904
  });
2682
- const existingOpsToReshuffle = nonSupersededOps;
2905
+ const existingOpsToReshuffle = nonSupersededOps.filter((operation) => !isGenesisOperation(operation));
2683
2906
  if (existingOpsToReshuffle.length > this.config.maxSkipThreshold) return {
2684
2907
  job,
2685
2908
  success: false,
@@ -2722,11 +2945,15 @@ var SimpleJobExecutor = class {
2722
2945
  id: operation.id
2723
2946
  })));
2724
2947
  for (const operation of reshuffledOperations) if (operation.action.type === "NOOP") operation.skip = 1;
2725
- const actions = reshuffledOperations.map((operation) => operation.action);
2726
- const skipValues = reshuffledOperations.map((operation) => operation.skip);
2727
2948
  let deniedReasons;
2728
2949
  if (this.featureFlags.documentDecisions) try {
2729
- deniedReasons = await deletionVerdictsByPosition(job.documentId, scope, job.branch, reshuffledOperations, stores.writeCache, stores.operationStore, signal);
2950
+ deniedReasons = await evaluateDeletionsByPosition({
2951
+ documentId: job.documentId,
2952
+ branch: job.branch
2953
+ }, {
2954
+ scope,
2955
+ operations: reshuffledOperations
2956
+ }, stores, signal);
2730
2957
  } catch (error) {
2731
2958
  return {
2732
2959
  job,
@@ -2736,7 +2963,13 @@ var SimpleJobExecutor = class {
2736
2963
  };
2737
2964
  }
2738
2965
  const effectiveSourceRemote = skipCount > 0 ? "" : job.meta.sourceRemote || "";
2739
- const result = await this.processActions(job, actions, startTime, indexTxn, stores, skipValues, reshuffledOperations, effectiveSourceRemote, signal, deniedReasons);
2966
+ const result = await this.processActions(reshuffledOperations.map((operation, i) => ({
2967
+ action: operation.action,
2968
+ skip: operation.skip,
2969
+ sourceOperation: operation,
2970
+ sourceRemote: effectiveSourceRemote,
2971
+ deniedReason: deniedReasons?.[i]
2972
+ })), executing);
2740
2973
  if (!result.success) return {
2741
2974
  job,
2742
2975
  success: false,
@@ -2745,23 +2978,16 @@ var SimpleJobExecutor = class {
2745
2978
  };
2746
2979
  stores.writeCache.invalidate(job.documentId, scope, job.branch);
2747
2980
  if (scope === "document") stores.documentMetaCache.invalidate(job.documentId, job.branch);
2748
- const readsThisStream = staticReadSet(documentDecisionModel({
2749
- documentId: job.documentId,
2750
- branch: job.branch
2751
- })).some((stream) => stream.query.documentId === job.documentId && stream.query.scope === scope && stream.query.branch === job.branch);
2752
- if (this.featureFlags.documentDecisions && readsThisStream) {
2753
- const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch, signal);
2754
- const latest = Date.parse(revisions.latestTimestamp);
2755
- if (result.generatedOperations.some((operation) => Date.parse(operation.timestampUtcMs) < latest)) {
2756
- const error = await this.reevaluateReadingScopes(job, startTime, indexTxn, stores, signal);
2757
- if (error) return {
2758
- job,
2759
- success: false,
2760
- error,
2761
- duration: Date.now() - startTime
2762
- };
2763
- }
2764
- }
2981
+ const reevaluationError = await this.reevaluateIfCriteriaMet({
2982
+ scope,
2983
+ operations: result.generatedOperations
2984
+ }, executing);
2985
+ if (reevaluationError) return {
2986
+ job,
2987
+ success: false,
2988
+ error: reevaluationError,
2989
+ duration: Date.now() - startTime
2990
+ };
2765
2991
  return {
2766
2992
  job,
2767
2993
  success: true,
@@ -3266,16 +3492,12 @@ var KyselyOperationStore = class KyselyOperationStore {
3266
3492
  "o1.index",
3267
3493
  "o1.timestampUtcMs"
3268
3494
  ]).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();
3495
+ const latest = await this.queryExecutor.selectFrom("Operation").select((eb) => eb.fn.max("timestampUtcMs").as("latestTimestamp")).where("documentId", "=", documentId).where("branch", "=", branch).executeTakeFirst();
3269
3496
  const revision = {};
3270
- let latestTimestamp = (/* @__PURE__ */ new Date(0)).toISOString();
3271
- for (const row of scopeRevisions) {
3272
- revision[row.scope] = row.index + 1;
3273
- const timestamp = row.timestampUtcMs.toISOString();
3274
- if (timestamp > latestTimestamp) latestTimestamp = timestamp;
3275
- }
3497
+ for (const row of scopeRevisions) revision[row.scope] = row.index + 1;
3276
3498
  return {
3277
3499
  revision,
3278
- latestTimestamp
3500
+ latestTimestamp: latest?.latestTimestamp ? new Date(latest.latestTimestamp).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString()
3279
3501
  };
3280
3502
  }
3281
3503
  rowToOperation(row) {
@@ -3622,4 +3844,4 @@ const DEFAULT_DRIVE_CONTAINER_TYPES = new Set(["powerhouse/document-drive", "pow
3622
3844
  //#endregion
3623
3845
  export { AuthorizationDeniedError as A, DuplicateOperationError as C, DuplicateModuleError as D, DuplicateManifestError as E, throwIfAborted as F, __exportAll as I, DocumentNotFoundError as M, matchesScope as N, InvalidModuleError as O, parsePagingOptions as P, AppendConditionFailedError as S, RevisionMismatchError as T, DocumentMetaCache as _, createForwardingPoolInstrumentation as a, CollectionMembershipCache as b, KyselyKeyframeStore as c, DriveCollectionId as d, buildDecisionModel as f, KyselyOperationIndex as g, KyselyWriteCache as h, runMigrations as i, DocumentDeletedError as j, ModuleNotFoundError as k, DocumentModelRegistry as l, EventBus as m, REACTOR_SCHEMA as n, instrumentPgPool as o, KyselyExecutionScope as p, getMigrationStatus as r, KyselyOperationStore as s, DEFAULT_DRIVE_CONTAINER_TYPES as t, SimpleJobExecutor as u, createConsistencyToken as v, OptimisticLockError as w, APPEND_CONDITION_FAILED_PREFIX as x, createEmptyConsistencyToken as y };
3624
3846
 
3625
- //# sourceMappingURL=drive-container-types-BNwEHEXP.js.map
3847
+ //# sourceMappingURL=drive-container-types-ZSLCC3lC.js.map