querysub 0.403.0 → 0.404.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/.cursorrules +2 -0
  2. package/bin/audit-imports.js +4 -0
  3. package/package.json +7 -4
  4. package/spec.txt +77 -0
  5. package/src/-a-archives/archiveCache.ts +9 -4
  6. package/src/-a-archives/archivesBackBlaze.ts +1039 -1039
  7. package/src/-a-auth/certs.ts +0 -12
  8. package/src/-c-identity/IdentityController.ts +12 -3
  9. package/src/-f-node-discovery/NodeDiscovery.ts +32 -26
  10. package/src/-g-core-values/NodeCapabilities.ts +12 -2
  11. package/src/0-path-value-core/AuthorityLookup.ts +239 -0
  12. package/src/0-path-value-core/LockWatcher2.ts +150 -0
  13. package/src/0-path-value-core/PathRouter.ts +535 -0
  14. package/src/0-path-value-core/PathRouterRouteOverride.ts +72 -0
  15. package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +65 -0
  16. package/src/0-path-value-core/PathValueCommitter.ts +222 -488
  17. package/src/0-path-value-core/PathValueController.ts +277 -239
  18. package/src/0-path-value-core/PathWatcher.ts +534 -0
  19. package/src/0-path-value-core/ShardPrefixes.ts +31 -0
  20. package/src/0-path-value-core/ValidStateComputer.ts +303 -0
  21. package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +1 -1
  22. package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +80 -44
  23. package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +13 -16
  24. package/src/0-path-value-core/auditLogs.ts +2 -0
  25. package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +97 -0
  26. package/src/0-path-value-core/pathValueArchives.ts +490 -492
  27. package/src/0-path-value-core/pathValueCore.ts +195 -1496
  28. package/src/0-path-value-core/startupAuthority.ts +74 -0
  29. package/src/1-path-client/RemoteWatcher.ts +90 -82
  30. package/src/1-path-client/pathValueClientWatcher.ts +808 -815
  31. package/src/2-proxy/PathValueProxyWatcher.ts +10 -8
  32. package/src/2-proxy/archiveMoveHarness.ts +182 -214
  33. package/src/2-proxy/garbageCollection.ts +9 -8
  34. package/src/2-proxy/schema2.ts +21 -1
  35. package/src/3-path-functions/PathFunctionHelpers.ts +206 -180
  36. package/src/3-path-functions/PathFunctionRunner.ts +943 -766
  37. package/src/3-path-functions/PathFunctionRunnerMain.ts +5 -3
  38. package/src/3-path-functions/pathFunctionLoader.ts +2 -2
  39. package/src/3-path-functions/syncSchema.ts +592 -521
  40. package/src/4-deploy/deployFunctions.ts +19 -4
  41. package/src/4-deploy/deployGetFunctionsInner.ts +8 -2
  42. package/src/4-deploy/deployMain.ts +51 -68
  43. package/src/4-deploy/edgeClientWatcher.tsx +1 -1
  44. package/src/4-deploy/edgeNodes.ts +2 -2
  45. package/src/4-dom/qreact.tsx +2 -4
  46. package/src/4-dom/qreactTest.tsx +7 -13
  47. package/src/4-querysub/Querysub.ts +21 -8
  48. package/src/4-querysub/QuerysubController.ts +45 -29
  49. package/src/4-querysub/permissions.ts +2 -2
  50. package/src/4-querysub/querysubPrediction.ts +80 -70
  51. package/src/4-querysub/schemaHelpers.ts +5 -1
  52. package/src/5-diagnostics/GenericFormat.tsx +14 -9
  53. package/src/archiveapps/archiveGCEntry.tsx +9 -2
  54. package/src/archiveapps/archiveJoinEntry.ts +87 -84
  55. package/src/archiveapps/archiveMergeEntry.tsx +2 -0
  56. package/src/bits.ts +19 -0
  57. package/src/config.ts +21 -3
  58. package/src/config2.ts +23 -48
  59. package/src/deployManager/components/DeployPage.tsx +7 -3
  60. package/src/deployManager/machineSchema.ts +4 -1
  61. package/src/diagnostics/ActionsHistory.ts +3 -8
  62. package/src/diagnostics/AuditLogPage.tsx +2 -3
  63. package/src/diagnostics/FunctionCallInfo.tsx +141 -0
  64. package/src/diagnostics/FunctionCallInfoState.ts +162 -0
  65. package/src/diagnostics/MachineThreadInfo.tsx +1 -1
  66. package/src/diagnostics/NodeViewer.tsx +37 -48
  67. package/src/diagnostics/SyncTestPage.tsx +241 -0
  68. package/src/diagnostics/auditImportViolations.ts +185 -0
  69. package/src/diagnostics/listenOnDebugger.ts +3 -3
  70. package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +10 -4
  71. package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +2 -2
  72. package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +24 -22
  73. package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +1 -1
  74. package/src/diagnostics/logs/diskLogGlobalContext.ts +1 -0
  75. package/src/diagnostics/logs/errorNotifications2/logWatcher.ts +1 -3
  76. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryEditor.tsx +34 -16
  77. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryReadMode.tsx +4 -6
  78. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleInstanceTableView.tsx +36 -5
  79. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePage.tsx +19 -5
  80. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleRenderer.tsx +15 -7
  81. package/src/diagnostics/logs/lifeCycleAnalysis/NestedLifeCycleInfo.tsx +28 -106
  82. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleMatching.ts +2 -0
  83. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleMisc.ts +0 -0
  84. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleSearch.tsx +18 -7
  85. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +3 -0
  86. package/src/diagnostics/managementPages.tsx +10 -3
  87. package/src/diagnostics/misc-pages/ArchiveViewer.tsx +20 -26
  88. package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +6 -4
  89. package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +2 -2
  90. package/src/diagnostics/misc-pages/LocalWatchViewer.tsx +7 -9
  91. package/src/diagnostics/misc-pages/SnapshotViewer.tsx +23 -12
  92. package/src/diagnostics/misc-pages/archiveViewerShared.tsx +1 -1
  93. package/src/diagnostics/pathAuditer.ts +486 -0
  94. package/src/diagnostics/pathAuditerCallback.ts +20 -0
  95. package/src/diagnostics/watchdog.ts +8 -1
  96. package/src/library-components/URLParam.ts +1 -1
  97. package/src/misc/hash.ts +1 -0
  98. package/src/path.ts +21 -7
  99. package/src/server.ts +54 -47
  100. package/src/user-implementation/loginEmail.tsx +1 -1
  101. package/tempnotes.txt +67 -0
  102. package/test.ts +288 -95
  103. package/src/0-path-value-core/NodePathAuthorities.ts +0 -1057
  104. package/src/0-path-value-core/PathController.ts +0 -1
  105. package/src/5-diagnostics/diskValueAudit.ts +0 -218
  106. package/src/5-diagnostics/memoryValueAudit.ts +0 -438
  107. package/src/archiveapps/lockTest.ts +0 -127
@@ -1,41 +1,42 @@
1
1
  import { SocketFunction } from "socket-function/SocketFunction";
2
2
  import { delay, batchFunction } from "socket-function/src/batching";
3
- import { markArrayAsSplitable } from "socket-function/src/fixLargeNetworkCalls";
4
3
  import { isNode, timeInSecond } from "socket-function/src/misc";
5
- import { measureBlock, measureFnc, registerMeasureInfo } from "socket-function/src/profiling/measure";
4
+ import { measureBlock, measureFnc } from "socket-function/src/profiling/measure";
6
5
  import { isTrustedByNode } from "../-d-trust/NetworkTrust2";
7
- import { isOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
8
- import { pathValueSerializer } from "../-h-path-value-serialize/PathValueSerializer";
6
+ import { areNodeIdsEqual, isOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
9
7
  import { ActionsHistory } from "../diagnostics/ActionsHistory";
10
8
  import { errorToUndefined, logErrors, timeoutToUndefined } from "../errors";
11
9
  import { getPathFromStr } from "../path";
12
- import { nodePathAuthority, pathValueAuthority2 } from "./NodePathAuthorities";
13
- import { PathValueController } from "./PathValueController";
14
- import { PathValue, MAX_ACCEPTED_CHANGE_AGE, lockWatcher, authorityStorage, writeValidStorage, WriteState, lockToCallback, pathWatcher, MAX_CHANGE_AGE, debugPathValue, debugPathValuePath, compareTime, epochTime, isOurPrediction, timeHash } from "./pathValueCore";
10
+ import { PathValueControllerBase } from "./PathValueController";
11
+ import { PathRouter } from "./PathRouter";
12
+ import { PathValue, MAX_ACCEPTED_CHANGE_AGE, WriteState, debugPathValuePath, compareTime, epochTime } from "./pathValueCore";
13
+ import { validStateComputer } from "./ValidStateComputer";
15
14
  import debugbreak from "debugbreak";
16
15
  import { red } from "socket-function/src/formatting/logColors";
17
- import { registerDynamicResource } from "../diagnostics/trackResources";
18
- import { StatsValue, addToStats, addToStatsValue, createStatsValue, getStatsTop } from "socket-function/src/profiling/stats";
19
- import { formatStats } from "socket-function/src/profiling/statsFormat";
20
- import { formatNumber, formatTime } from "socket-function/src/formatting/format";
21
16
  import { isClient } from "../config2";
22
- import { remoteWatcher } from "../1-path-client/RemoteWatcher";
23
17
  import { auditLog, isDebugLogEnabled } from "./auditLogs";
24
- import { logDisk } from "../diagnostics/logs/diskLogger";
25
-
26
- /*
27
- - commitWrites <= creating writes
28
- - ingestValues <= forwardWrites
29
- - ingestValidStates <= forwarded valid states
30
- = loop
31
- - setValidState
32
- - gather PathValue changes
33
- - getValidStateChangedTriggers
34
- - calculateInitialValidState
35
- - REMOTE.ingestValidStates => forwarded valid states
36
- - pathWatcher.triggerValuesChanged => forwardWrites + local clients
37
- - broadcastValues
38
- */
18
+ import { authorityLookup } from "./AuthorityLookup";
19
+ setImmediate(() => import("../1-path-client/RemoteWatcher"));
20
+ setImmediate(() => import("../4-querysub/Querysub"));
21
+
22
+ const MAX_SEND_TRY_COUNT = 3;
23
+
24
+ export type BatchValues = {
25
+ pathValues: PathValue[],
26
+ parentsSynced?: string[];
27
+ sourceNodeId: string;
28
+ initialTrigger?: "initialTrigger";
29
+ authoritySync?: boolean;
30
+ };
31
+
32
+ export type RemoteValueAndValidState = {
33
+ sourceNodeId: string;
34
+ pathValues: PathValue[];
35
+ validStates: WriteState[];
36
+ initialTriggers: { values: Set<string>; parentPaths: Set<string> };
37
+ // Means it's from an authority path sync
38
+ authoritySyncPaths?: Set<string>;
39
+ };
39
40
 
40
41
  class PathValueCommitter {
41
42
  private pendingCommits = new Set<Promise<unknown>>();
@@ -95,7 +96,7 @@ class PathValueCommitter {
95
96
  if (predictWrites) {
96
97
  pathValuesToIngest = values;
97
98
  } else {
98
- pathValuesToIngest = values.filter(pathValue => pathValueAuthority2.isSelfAuthority(pathValue.path));
99
+ pathValuesToIngest = values.filter(pathValue => PathRouter.isSelfAuthority(pathValue.path));
99
100
  }
100
101
 
101
102
  // NOTE: Error out early on the client, so we get better errors AND so we can avoid even ingesting the values
@@ -107,54 +108,110 @@ class PathValueCommitter {
107
108
  // (It could even populate on the first domain, so it would always populate on the first write
108
109
  // on the current domain).
109
110
  if (!isNode()) {
110
- let remoteWrites = values.filter(pathValue => !pathValueAuthority2.isSelfAuthority(pathValue.path));
111
+ let remoteWrites = values.filter(pathValue => !PathRouter.isSelfAuthority(pathValue.path));
111
112
  if (remoteWrites.length > 0) {
112
113
  throw new Error(`Cannot commit writes to paths that are not local to this node. Paths: ${remoteWrites.map(x => x.path).join(", ")}`);
113
114
  }
114
115
  }
115
116
 
116
117
  // NOTE: This function will just ignore writes we aren't an authority on, so we can just give it everything.
117
- this.ingestValues(pathValuesToIngest, undefined, undefined);
118
+ validStateComputer.ingestValuesAndValidStates({
119
+ pathValues: pathValuesToIngest,
120
+ parentSyncs: [],
121
+ initialTriggers: { values: new Set(), parentPaths: new Set() },
122
+ });
123
+
124
+ let remoteValues = values.filter(x => !PathRouter.isSelfAuthority(x.path));
118
125
 
119
- // If all values are local, then there is nothing to broadcast
120
- if (values.some(x => !pathValueAuthority2.isLocalPath(x.path))) {
121
- this.addCommitPromise(this.broadcastValues(new Set(values)));
126
+ if (remoteValues.length > 0) {
127
+ this.addCommitPromise(this.broadcastValues({ values: new Set(remoteValues) }));
128
+ }
129
+
130
+ // If we're the authority on it, we should still share it with the other authorities.
131
+ let stillShareValues = values.filter(x => !PathRouter.isLocalPath(x.path) && PathRouter.isSelfAuthority(x.path));
132
+ if (stillShareValues.length > 0) {
133
+ this.addCommitPromise(PathValueControllerBase.authorityShareValues({ pathValues: stillShareValues }));
122
134
  }
123
135
  }
124
136
 
125
137
  private broadcastValues = batchFunction(
126
138
  { delay: 10, throttleWindow: 500, noMeasure: true },
127
- async function internal_forwardWrites(valuesBatched: Set<PathValue>[]) {
128
- let values = new Set(valuesBatched.flatMap(x => Array.from(x)));
139
+ async function internal_forwardWrites(valuesBatched: {
140
+ values: Set<PathValue>;
141
+ tryCount?: number;
142
+ }[]) {
143
+ let values = new Set(valuesBatched.flatMap(x => Array.from(x.values)));
144
+ let tryCountPerValue = new Map<PathValue, number>();
145
+ for (let list of valuesBatched) {
146
+ for (let value of list.values) {
147
+ if (!list.tryCount) continue;
148
+ tryCountPerValue.set(value, list.tryCount);
149
+ }
150
+ }
151
+ if (tryCountPerValue.size > 0) {
152
+ console.info(`Syncing all authorities to ensure we have the latest values, due to failed writes.`);
153
+ await authorityLookup.syncAllNow();
154
+ }
155
+
129
156
 
130
157
  let valuesPerOtherAuthority = new Map<string, PathValue[]>();
131
158
  for (let pathValue of values) {
132
159
  if (isDebugLogEnabled()) {
133
- auditLog("CREATE VALUE", { path: pathValue.path, time: pathValue.time.time });
160
+ // let valueStr: string | undefined;
161
+ // if (typeof pathValue.value === "boolean" || typeof pathValue.value === "number") {
162
+ // valueStr = pathValue.value.toString();
163
+ // } else if (typeof pathValue.value === "string") {
164
+ // valueStr = pathValue.value.slice(0, 100);
165
+ // if (valueStr.length < pathValue.value.length) {
166
+ // valueStr += `[+${pathValue.value.length - valueStr.length}]`;
167
+ // }
168
+ // } else if (Buffer.isBuffer(pathValue.value)) {
169
+ // valueStr = `Buffer(${pathValue.value.length})`;
170
+ // } else if (pathValue.value === null) {
171
+ // valueStr = "{null}";
172
+ // } else {
173
+ // valueStr = `{${typeof pathValue.value}}`;
174
+ // }
175
+ auditLog("CREATE VALUE", {
176
+ path: pathValue.path,
177
+ timeId: pathValue.time.time,
178
+ source: pathValue.source,
179
+ transparent: pathValue.isTransparent,
180
+ // value: valueStr,
181
+ event: pathValue.event,
182
+ // NOTE: I think it'd be too expensive to log all the locks. There's one thing to log every time we read a value or create a value, but to log all of the dependencies for each write path would be O(locksPerWrite * 2 * writes), which is just too much, and could easily result in ten thousand logs per write. I think it will be fine because if there's a rejection, then that will tell us the time ID that the rejected value was reading, Which we can infer to know at least one of the locks, as in the most important lock that that right had.
183
+ });
134
184
  }
135
185
 
136
- let otherAuthorities = await pathValueAuthority2.getWriteNodes(pathValue.path);
137
- otherAuthorities = otherAuthorities.filter(x => !isOwnNodeId(x));
138
- if (otherAuthorities.length === 0 && !pathValueAuthority2.isSelfAuthority(pathValue.path)) {
139
- throw new Error(`There are no authorities for path ${pathValue.path}. The write will be lost.`);
186
+ let otherAuthorities = PathRouter.getAllAuthorities(pathValue.path);
187
+ otherAuthorities = otherAuthorities.filter(x => !isOwnNodeId(x.nodeId));
188
+ if (otherAuthorities.length === 0) {
189
+ validStateComputer.ingestValuesAndValidStates({
190
+ pathValues: [{
191
+ ...pathValue,
192
+ valid: false,
193
+ }],
194
+ parentSyncs: [],
195
+ initialTriggers: { values: new Set(), parentPaths: new Set() },
196
+ });
197
+ PathRouter.getAllAuthorities(pathValue.path);
198
+ console.error(`There are no authorities for path ${pathValue.path}. The write will be lost.`, {
199
+ path: pathValue.path,
200
+ timeId: pathValue.time.time,
201
+ source: pathValue.source,
202
+ otherAuthorities,
203
+ });
140
204
  }
141
205
  for (let otherAuthority of otherAuthorities) {
142
- let values = valuesPerOtherAuthority.get(otherAuthority);
206
+ let values = valuesPerOtherAuthority.get(otherAuthority.nodeId);
143
207
  if (!values) {
144
208
  values = [];
145
- valuesPerOtherAuthority.set(otherAuthority, values);
209
+ valuesPerOtherAuthority.set(otherAuthority.nodeId, values);
146
210
  }
147
211
  values.push(pathValue);
148
212
  }
149
213
  }
150
214
 
151
- // for (let [authorityId, values] of valuesPerOtherAuthority) {
152
- // console.log(`Sending ${values.length} values to ${authorityId} (${pathValueAuthority2.getAuthorityPaths(authorityId)?.map(x => pathValueAuthority2.getArchiveDirectory(x)).join(" | ")})`);
153
- // // for (let value of values) {
154
- // // console.log(" " + value.path);
155
- // // }
156
- // }
157
-
158
215
  // Don't send to bad nodes for 60 seconds
159
216
  const nodeIgnoreTime = Date.now() - 1000 * 60;
160
217
  let promises = Array.from(valuesPerOtherAuthority.entries()).map(async ([otherAuthority, values]) => {
@@ -174,39 +231,58 @@ class PathValueCommitter {
174
231
  // to be on a trusted node (which should always be a server), so... either our network went
175
232
  // down, or all nodes went down. Either way, it likely won't fix itself quickly, and so these
176
233
  // writes are going to too old and therefore rejected by the time a server is up anyways.
177
- markArrayAsSplitable(values);
178
- const { Querysub } = await import("../4-querysub/Querysub");
179
- let serializedValues = await pathValueSerializer.serialize(values, { compress: Querysub.COMPRESS_NETWORK });
180
- console.info("Send PathValues to server", { valueCount: values.length, targetId: otherAuthority, });
181
- let forwardPromise = PathValueController.nodes[otherAuthority].forwardWrites(
182
- serializedValues,
183
- undefined,
184
- "watchLocks"
185
- );
186
- pathValueCommitter.addCommitPromise(forwardPromise);
234
+
235
+ let forwardPromise = PathValueControllerBase.createValues({
236
+ nodeId: otherAuthority,
237
+ pathValues: values,
238
+ });
187
239
  logErrors(forwardPromise);
188
- // NOTE: Usually we don't need watch the locks for our values because upon
189
- // forwarding them the server will automatically watch them. However...
190
- // if the call errors out, we can't know if we are watching them, so
191
- // we have to explicitly find a server to tell us if our writes go through
192
- // (we can't even just drop our writes, as we don't differentiate predicted
193
- // and real writes, so we can't tell if it got through and was sent back,
194
- // or if it was never committed!)
195
- forwardPromise.catch(() => {
196
-
197
- // TODO: For the first few seconds, look for new writers and retry. However,
198
- // after the write is too old, just log the error to the user, as
199
- // at that point even if a writer appears the write will be too stale.
200
-
201
- // Watch the readLocks, so we know if the values are valid or not (this
202
- // code has retry logic).
203
- // NOTE: Technically if this talks to another authority, and then the original authority
204
- // comes back, we might be syncing valid states from multiple authorities, and as
205
- // they aren't timestamped, we could receive an older value later. Which should
206
- // correct itself, but... we would also be subscribed to multiple authorities,
207
- // for the same path, which can result in thrashing. But... this probably won't happen...
208
- lockWatcher.watchValueLocks(values);
240
+ void forwardPromise.then(x => {
241
+ if (x === "refused") {
242
+ values = values.map(value => {
243
+ console.info(`Rejecting past value due to initial sync: ${debugPathValuePath(value)}`, {
244
+ path: value.path,
245
+ timeId: value.time.time,
246
+ });
247
+ return {
248
+ ...value,
249
+ valid: false,
250
+ };
251
+ });
252
+
253
+ validStateComputer.ingestValuesAndValidStates({
254
+ pathValues: values,
255
+ parentSyncs: [],
256
+ initialTriggers: { values: new Set(), parentPaths: new Set() },
257
+ });
258
+ }
209
259
  });
260
+ void forwardPromise.catch(async error => {
261
+ let byTryCount = new Map<number, PathValue[]>();
262
+ for (let value of values) {
263
+ let tryCount = tryCountPerValue.get(value) ?? 0;
264
+ let arr = byTryCount.get(tryCount) ?? [];
265
+ arr.push(value);
266
+ byTryCount.set(tryCount, arr);
267
+ }
268
+ for (let [tryCount, values] of byTryCount.entries()) {
269
+ tryCount++;
270
+ if (tryCount > MAX_SEND_TRY_COUNT) {
271
+ console.error(`Failed to send values after ${MAX_SEND_TRY_COUNT} tries. Giving up.`, {
272
+ error: error.message,
273
+ otherAuthority,
274
+ count: values.length,
275
+ });
276
+ continue;
277
+ }
278
+ void pathValueCommitter.broadcastValues({
279
+ values: new Set(values),
280
+ tryCount: tryCount,
281
+ });
282
+ }
283
+ });
284
+
285
+ pathValueCommitter.addCommitPromise(forwardPromise);
210
286
  });
211
287
  // await, so "waitForValuesToCommit" works correctly
212
288
  await Promise.all(promises.map(x => timeoutToUndefined(timeInSecond * 30, errorToUndefined(x))));
@@ -214,47 +290,33 @@ class PathValueCommitter {
214
290
  );
215
291
 
216
292
 
217
- private getExistingWatchRemoteNodeId = (path: string): string | undefined => undefined;
218
- public setGetRemoteWatchNodeId(fnc: (path: string) => string | undefined) {
219
- this.getExistingWatchRemoteNodeId = fnc;
220
- }
293
+ public ingestRemoteValuesAndValidStates = batchFunction(
294
+ { delay: 16, throttleWindow: 1000, name: "ingestRemoteValuesAndValidStates", noMeasure: true },
295
+ async (batched: RemoteValueAndValidState[]) => {
296
+ const { remoteWatcher } = await import("../1-path-client/RemoteWatcher");
221
297
 
222
- /** When ingesting remote values we batch a bit. Otherwise a stream of values will often result in WAY too many
223
- * clientside evaluations. The network will already add a lot of delay, so this shouldn't be noticeable.
224
- * - AND, with the throttleWindow, we generally won't even be waiting anyways.
225
- * - The delay is 16ms, as we won't render to the screen faster than that anyways, so handling updates
226
- * at a rate faster than that will just pointlessly add lag.
227
- */
228
- public ingestRemoteValues = batchFunction(
229
- { delay: 16, throttleWindow: 1000, name: "ingestRemoteValues", noMeasure: true },
230
- async (batched: {
231
- pathValues: PathValue[],
232
- parentsSynced?: string[];
233
- sourceNodeId: string;
234
- initialTrigger?: "initialTrigger";
235
- }[]) => {
236
- let initialTriggers = { values: new Set<string>(), parentPaths: new Set<string>() };
237
- // Check received values to make sure the authority for them is the same as the sourceNodeId
238
- // (OR that WE are the authority). If not... it means we received values after changing the watcher,
239
- // which requires us to ignore the other values
240
- // (Clients get most of their values from the same server, so they shouldn't require this check)
241
- const self = this;
298
+ // NOTE: We need to ignore values if they're not who we're watching. That way, if we change the watcher, it's smooth and we don't partial data that might clobber with the data from the authority we are really using.
242
299
  if (!isClient()) {
243
300
  measureBlock(function ignoreUnrequestedValues() {
244
301
  for (let batch of batched) {
302
+ function isWrongAuthority(path: string) {
303
+ let watchingAuthorityId = remoteWatcher.getExistingWatchRemoteNodeId(path);
304
+ // If we AREN'T watching it... it's actually fine, we can receive any values.
305
+ // When we start watching, those values will get clobbered.
306
+ if (watchingAuthorityId === undefined) return false;
307
+ return !areNodeIdsEqual(watchingAuthorityId, batch.sourceNodeId);
308
+ }
245
309
  batch.pathValues = batch.pathValues.filter(value => {
310
+ // Authorities watch other authorities using path watches, And so one path can come from many authorities, so we just have to accept it no matter what, if it's from that type of source.
311
+ if (batch.authoritySyncPaths?.has(value.path)) return true;
312
+ // NOTE: See the definition for lock count for why this check isn't checking all the possible cases. Essentially, locks is often empty, and that's intentional. However, the reverse should never be true, locks should never have values when lockCount is 0.
246
313
  if (value.lockCount === 0 && value.locks.length > 0) {
247
314
  console.error(red(`Ignoring value with invalid lockCount. Was ${value.lockCount}, but we have ${value.locks.length} locks. locks are optional, but lockCount isn't. We should never have locks without having lockCount set. ${debugPathValuePath(value)}`));
248
315
  return false;
249
316
  }
250
317
 
251
- // If we are the authority, accept it (we are implicitly watching all of our own paths)
252
- if (nodePathAuthority.isSelfAuthority(value.path)) return true;
253
- let watchingAuthorityId = self.getExistingWatchRemoteNodeId(value.path);
254
- // If we AREN'T watching it... it's actually fine, we can receive any values.
255
- // When we start watching, those values will get clobbered.
256
- if (watchingAuthorityId === undefined) return true;
257
- if (watchingAuthorityId === batch.sourceNodeId) return true;
318
+ if (PathRouter.isSelfAuthority(value.path)) return true;
319
+ if (isWrongAuthority(value.path)) return false;
258
320
  // epochTimes are just indicators that the value has no value, and so are safe to sync
259
321
  // from any source. They won't cause future conflicts, because any other value overrides them.
260
322
  // - This might not be required?
@@ -264,402 +326,74 @@ class PathValueCommitter {
264
326
 
265
327
  // Also warn, because... if we get a lot of these, there might be a bug.
266
328
  // A few when we change watches is possible, but it should be rare.
329
+ let watchingAuthorityId = remoteWatcher.getExistingWatchRemoteNodeId(value.path);
267
330
  auditLog("IGNORING VALUE FROM DIFFERENT AUTHORITY", { path: value.path, watchingAuthorityId, receivedFromAuthority: batch.sourceNodeId });
268
- console.warn(`Ignoring a value that was received from a different authority than the one we are watching. Path: ${value.path}, Watching Authority (should be source): ${watchingAuthorityId}, Actual Source Authority: ${batch.sourceNodeId}`);
269
331
  return false;
270
332
  });
271
- }
272
- });
273
- }
274
-
275
- let specialInitialParentCausedRejections: WriteState[] = [];
276
-
277
- // The first time we receive value for a watch from a specific node, we need to reject future
278
- // missing values (unless we are the authority). Otherwise bad rejected values can stick around,
279
- // because they are newer, and our new source doesn't know we have them.
280
- measureBlock(function resetOnInitialTrigger() {
281
- let receivedValues = new Map<string, Set<string>>();
282
- for (let batch of batched) {
283
- for (let pathValue of batch.pathValues) {
284
- let path = pathValue.path;
285
- let hash = timeHash(pathValue.time);
286
- let paths = receivedValues.get(path);
287
- if (!paths) {
288
- paths = new Set();
289
- receivedValues.set(path, paths);
290
- }
291
- paths.add(hash);
292
- }
293
- }
294
333
 
295
- // TODO: Ugh... rejections on initialSync might the wrong solution. I think we have to just remove the values,
296
- // even though this makes debugging harder, in order to prevent the rejections from sticking around.
297
-
298
- for (let batch of batched) {
299
- if (!batch.initialTrigger) continue;
300
- for (let pathValue of batch.pathValues) {
301
- initialTriggers.values.add(pathValue.path);
302
- auditLog("INITIAL SYNC", { path: pathValue.path, time: pathValue.time.time, sourceNodeId: batch.sourceNodeId });
303
- // We shouldn't receive the initialTrigger flag if we are the authority anyways
304
- if (nodePathAuthority.isSelfAuthority(pathValue.path)) continue;
305
- let history = authorityStorage.getValuePlusHistory(pathValue.path);
306
- for (let value of history) {
307
- // Definitely don't reset our predictions. We will reset those ourselves
308
- if (isOurPrediction(value)) continue;
309
- // Only reset values after us, otherwise we might clear valid history that
310
- // we JUST received.
311
- if (compareTime(value.time, pathValue.time) <= 0) continue;
312
-
313
- // If we just received it, accept it.
314
- // - Otherwise we might reject a valid older value, and then when the newer value is rejected
315
- // the server won't tell us about the older value again, and so we will think no values
316
- // are accepted, when the older value IS accepted.
317
- // BUG: In theory couldn't we receive an outdated value, then reconnect, then receive the correct
318
- // value, and have them all batched together? In which case, we shouldn't accept the value.
319
- // I'm not really sure how to fix this though. We kind of need to just handle adding the values
320
- // and the rejections in order, instead of batching them, but that is much slower, and the race
321
- // condition will likely be extremely rare (and only happen on disconnections anyways).
322
- if (receivedValues.get(pathValue.path)?.has(timeHash(pathValue.time))) continue;
323
-
324
- specialInitialParentCausedRejections.push({
325
- path: value.path,
326
- time: value.time,
327
- isValid: false,
328
- isTransparent: !!value.isTransparent,
329
- reason: "future missing on resync (initial trigger)",
330
- });
334
+ for (let value of batch.initialTriggers.values) {
335
+ if (isWrongAuthority(value)) {
336
+ batch.initialTriggers.values.delete(value);
337
+ }
331
338
  }
332
- }
333
- // IF the parent is synced, but we don't receive a path value, then invalidate the
334
- // old PathValue (as this means it is invalid, but the remote doesn't know to
335
- // tell us it is invalid, as we only watched the parent, not the specific path).
336
- // - If the values become unrejected in the future, we will receive them again, which
337
- // will cause their valid state to be changed to accepted.
338
- // - Also, if another batch sets the value, it will override our valid state write,
339
- // as we are writing this in a special array will is always handled first.
340
- if (batch.parentsSynced) {
341
- for (let parentPath of batch.parentsSynced) {
342
- auditLog("INITIAL PARENT SYNC", { path: parentPath, sourceNodeId: batch.sourceNodeId });
343
- let allChildPaths = authorityStorage.getPathsFromParent(parentPath) || [];
344
- let childChecker = remoteWatcher.getChildPatchWatchChecker({
345
- parentPath,
346
- nodeId: batch.sourceNodeId
347
- });
348
- for (let childPath of allChildPaths) {
349
- if (!childChecker.isWatchedByNodeId(childPath)) continue;
350
- // We shouldn't receive the initialTrigger flag if we are the authority anyways.
351
- if (nodePathAuthority.isSelfAuthority(childPath)) continue;
352
- initialTriggers.parentPaths.add(childPath);
353
-
354
- // NOTE: On initial trigger, we send all child paths, so they should be
355
- // in the values list, if they exist. AND if we receive them after,
356
- // they will clobber this rejection
357
- // - See PathWatcher.watchPath, where when !noInitialTrigger (aka, initialTrigger),
358
- // we get all paths via authorityStorage.getPathsFromParent, and send their values.
359
- if (initialTriggers.values.has(childPath)) continue;
360
-
361
- let values = authorityStorage.getValuePlusHistory(childPath);
362
- if (values.length === 0) continue;
363
-
364
- values = values.filter(value => {
365
- // DO NOT reject epoch values. For missing values, we still need a signal, and the parent
366
- // sync won't send this, so we need to NOT reject them.
367
- if (compareTime(value.time, epochTime) === 0) {
368
- return false;
369
- }
370
- return true;
371
- });
372
- if (values.length === 0) continue;
373
-
374
- console.log(self.getExistingWatchRemoteNodeId(childPath));
375
- for (let value of values) {
376
- specialInitialParentCausedRejections.push({
377
- path: value.path,
378
- time: value.time,
379
- isValid: false,
380
- isTransparent: !!value.isTransparent,
381
- reason: "child missing (initial trigger)",
382
- });
383
- }
339
+ for (let parentPath of batch.initialTriggers.parentPaths) {
340
+ if (isWrongAuthority(parentPath)) {
341
+ batch.initialTriggers.parentPaths.delete(parentPath);
384
342
  }
385
343
  }
386
344
  }
387
- }
388
- });
389
-
390
- let pathValues = batched.map(x => x.pathValues).flat();
391
- let parentsSynced = batched.map(x => x.parentsSynced ?? []).flat();
392
- let parentSyncedSources = new Map<string, string[]>();
393
- for (let batch of batched) {
394
- if (!batch.parentsSynced) continue;
395
- for (let parentSynced of batch.parentsSynced) {
396
- let sources = parentSyncedSources.get(parentSynced);
397
- if (!sources) {
398
- sources = [];
399
- parentSyncedSources.set(parentSynced, sources);
400
- }
401
- sources.push(batch.sourceNodeId);
402
- }
403
- }
404
-
405
-
406
- // Ignore outdated PathValues if we are the authority.
407
- // - This CAN leave our servers in a bad state. However...
408
- // 1) We will resync with the disk eventually (in a few hours), fixing the state
409
- // (IF some servers accepted the values, and other ignored them).
410
- // 1.1) We should also audit between PathValueServers, which should fix the state
411
- // MUCH faster.
412
- // 2) If we DON'T ignore these values, we might end up rejecting an archived value,
413
- // which breaks a lot of values, and which cannot be automatically recovered from.
414
- measureBlock(function discardInvalidGenesisValues() {
415
- let maxAge = Date.now() - MAX_ACCEPTED_CHANGE_AGE;
416
- // NOTE: We could add less to future age here, but it's fine. This is the time
417
- // a services (not a client, as services validate clients) can write in the future,
418
- // which will prevent writes on that path until after that time has passed.
419
- let maxFutureAge = Date.now() + MAX_ACCEPTED_CHANGE_AGE;
420
- pathValues = pathValues.filter(value => {
421
- if (!nodePathAuthority.isSelfAuthority(value.path)) return true;
422
- if (value.time.time < maxAge) {
423
- auditLog("DISCARD INVALID GENESIS VALUE", { path: value.path, time: value.time.time });
424
- console.error(red(`Ignoring a value that is ${formatTime(Date.now() - value.time.time)} pass change time, which would break assumptions made by archives / locks about change stability. ${debugPathValuePath(value)}`));
425
- return false;
426
- }
427
- if (value.time.time > maxFutureAge) {
428
- auditLog("DISCARD INVALID FUTURE VALUE", { path: value.path, time: value.time.time });
429
- console.error(red(`Ignoring a value that is ${formatTime(value.time.time - Date.now())} in the future, which would break writes to that path until that time passes. ${debugPathValuePath(value)}`));
430
- return false;
431
- }
432
- return true;
433
345
  });
434
- });
435
-
436
- // NOTE: Values MUST be synchronously ingested, otherwise our "initialTrigger" resetting will break things.
437
- this.ingestValues(pathValues, parentsSynced, parentSyncedSources, initialTriggers, specialInitialParentCausedRejections);
438
- // Wait a microtick to allow triggered watchers to finish.
439
- await Promise.resolve();
440
- if (!isNode()) {
441
- // Wait a frame, to allow painting to happen. (requestAnimationFrame waits until before paint,
442
- // so we have to wait twice).
443
- await new Promise(resolve => requestAnimationFrame(resolve));
444
- await new Promise(resolve => requestAnimationFrame(resolve));
445
346
  }
446
- }
447
- );
448
-
449
- @measureFnc
450
- public ingestValues(
451
- pathValues: PathValue[],
452
- parentsSynced: string[] | undefined,
453
- parentSyncedSources: Map<string, string[]> | undefined,
454
- initialTriggers?: { values: Set<string>; parentPaths: Set<string> },
455
- specialInitialParentCausedRejections?: WriteState[],
456
- ): void {
457
- if (pathValues.length === 0 && !parentsSynced?.length) return;
458
-
459
- let now = Date.now();
460
347
 
461
- // NOTE: specialInitialParentCausedRejections is a bit dangerous, because it can reject golden values.
462
- // This means we have to ingest it immediately, otherwise authorityStorage might gc path values
463
- // (that are before a golden value), when rejections might remove that golden value.
464
- if (specialInitialParentCausedRejections?.length) {
465
- this.ingestValidStates(specialInitialParentCausedRejections);
466
- }
467
-
468
- authorityStorage.ingestValues(pathValues, parentsSynced, parentSyncedSources);
469
-
470
- // NOTE: This doesn't seem to be where we lag, so we aren't tracking it anymore.
471
- for (let value of pathValues) {
472
- let lag = now - value.time.time;
473
- trackLag(now, lag);
474
- }
475
-
476
- // NOTE: Also triggers rejections of watched paths
477
- this.ingestValidStates([], parentsSynced, undefined, pathValues, initialTriggers);
478
- }
479
-
480
- // NOTE: Technically, because we trigger all remotes any time a value changes, even
481
- // if it was changed by a remote, this could result in excessive notifications. However,
482
- // the only watchers are ourself, and remotes that consider us an authority.
483
- // AND, if we are an authority, we aren't asking any remotes for valid states. So...
484
- // it works out, with authorities receiving few/none external triggerWatchersForValidStateChange calls,
485
- // and non-authorities having few/no remote watchers.
486
- @measureFnc
487
- public ingestValidStates(
488
- remoteLocksChanged: WriteState[],
489
- parentsSynced?: string[],
490
- recomputeValidState?: "recomputeValidState",
491
- alsoPathValues?: PathValue[],
492
- initialTriggers?: { values: Set<string>; parentPaths: Set<string> }
493
- ) {
494
- if (remoteLocksChanged.length === 0 && !parentsSynced?.length && !alsoPathValues?.length) return;
495
-
496
- let now = Date.now();
497
-
498
- let valuesChanged = new Set<PathValue>();
499
-
500
- if (alsoPathValues) {
501
- let complexNewValues: PathValue[] = [];
502
- // TODO: We can avoid values being updated in the updateValidStatesFromCache call,
503
- // and then also being used in setWriteValidState. This might save some time.
504
- // It is rare though, as valid states are mostly only for function predictions,
505
- // and usually arrive after the ValuePaths? Maybe...
506
- writeValidStorage.updateValidStatesFromCache(alsoPathValues);
507
- for (let pathValue of alsoPathValues) {
508
- // All the values are assumed to have changed
509
- valuesChanged.add(pathValue);
510
- if (pathValue.locks.length > 0) {
511
- complexNewValues.push(pathValue);
512
- continue;
513
- }
514
- let testValidState: WriteState = {
515
- path: pathValue.path,
516
- isValid: !!pathValue.valid,
517
- time: pathValue.time,
518
- isTransparent: pathValue.isTransparent || pathValue.canGCValue || false,
519
- };
520
- let callbacks = lockToCallback.getValidStateChangedTriggers(testValidState);
521
- if (callbacks.some(x => typeof x !== "string")) {
522
- complexNewValues.push(pathValue);
523
- continue;
524
- }
525
- // If we get here, it is a simple value, with nothing watching it, and no possibility of
526
- // being rejected, so just mark it as valid.
527
- writeValidStorage.setWriteValidState(testValidState);
528
- }
529
- if (complexNewValues.length > 0) {
530
- let newValidStates = writeValidStorage.computeValidStates(complexNewValues, now);
531
- for (let change of newValidStates) {
532
- remoteLocksChanged.push(change);
533
- }
534
- }
535
- }
536
348
 
537
- {
538
- let allWrites = new Set<WriteState>();
539
- let pendingWriteChanges = new Set<WriteState>(remoteLocksChanged);
540
- while (pendingWriteChanges.size > 0) {
541
- let curWriteChanges = pendingWriteChanges;
542
- pendingWriteChanges = new Set();
543
-
544
- let triggeredSelfChanges = new Set<PathValue[]>();
545
-
546
- for (let changedWrite of curWriteChanges) {
547
- // NOTE: Predicted values are ingested, causing them to have a valid state. SO,
548
- // if they are invalidated, a change will occur here (via the invalid value being forwarded,
549
- // which is convert to triggering a valid state).
550
- let changed = true;
551
-
552
- // If we are recomputing it, don't use the changedWrite value, and assume it has changed,
553
- // recomputing it always.
554
- if (!recomputeValidState) {
555
- changed = writeValidStorage.setWriteValidState(changedWrite);
349
+ // path => sourceNodeId
350
+ let parentSyncs = new Map<string, string>();
351
+
352
+ // We need to do a bit of work to properly clear path values that are from old initial triggers. As if we receive two initial triggers, they need to clobber each other. And if we collapse it, we lose that information. So we have to do that here.
353
+ let finalResults = new Map<string, {
354
+ path: string;
355
+ pathValues: PathValue[];
356
+ initialTrigger: boolean;
357
+ batchIndex: number;
358
+ }>();
359
+
360
+ for (let batchIndex = 0; batchIndex < batched.length; batchIndex++) {
361
+ let batch = batched[batchIndex];
362
+ for (let pathValue of batch.pathValues) {
363
+ let initialTrigger = batch.initialTriggers.values.has(pathValue.path);
364
+ let results = finalResults.get(pathValue.path);
365
+ if (!results) {
366
+ results = {
367
+ path: pathValue.path,
368
+ pathValues: [],
369
+ initialTrigger: false,
370
+ batchIndex: -1,
371
+ };
372
+ finalResults.set(pathValue.path, results);
556
373
  }
557
- if (changed) {
558
- // NOTE: If the value is invalid, the writer will get both the invalid value, AND a valid state change.
559
- // This is unintentional, due to the fact that most watchers aren't watching for valid state changes,
560
- // BUT, the writer might not be watching the path! But, if the writer is watching the path...
561
- // we send both values, because at this don't know if it is the writer not not.
562
- // - We can tell via looking to see if it is a valid state watcher, but... that happens in a different
563
- // function, and... optimizing the reject path shouldn't matter, as rejections should rarely
564
- // happen anyways...
565
- let valueChanged = authorityStorage.getValueAtTime(changedWrite.path, changedWrite.time);
566
- if (valueChanged) {
567
- valuesChanged.add(valueChanged);
568
- }
569
- // Have to send the previous value, as the caller might not have it (as we might start
570
- // rejecting everything, result in the latest valid state being WAY in the past, to the point
571
- // where it is impossible for any caller to have the value synced!
572
- if (!changedWrite.isValid) {
573
- let prevValue = authorityStorage.getValueBeforeTime(changedWrite.path, changedWrite.time);
574
- if (prevValue) {
575
- valuesChanged.add(prevValue);
576
- }
577
- }
578
- }
579
-
580
- allWrites.add(changedWrite);
581
-
582
- let callbacks = lockToCallback.getValidStateChangedTriggers(changedWrite);
583
- for (let valuesWatching of callbacks) {
584
- if (typeof valuesWatching !== "string") {
585
- triggeredSelfChanges.add(valuesWatching);
586
- }
587
- }
588
- }
589
-
590
- // TODO: I'm not sure if we need to sort these before updating them?
591
- // If we experience rejections and then undoing of rejections... we might need to...
592
- let flatTriggeredChanges = Array.from(new Set(Array.from(triggeredSelfChanges).flat()));
593
- let changes = writeValidStorage.computeValidStates(flatTriggeredChanges, now, "alreadyWatching");
594
- for (let change of changes) {
595
- pendingWriteChanges.add(change);
596
- }
597
- // Anything triggered needs to be recomputed
598
- recomputeValidState = "recomputeValidState";
599
- }
600
-
601
- // Handle valid state watches (mostly other authorities)
602
- let validChangesPerNode = new Map<string, WriteState[]>();
603
- for (let write of allWrites) {
604
- let callbacks = lockToCallback.getValidStateChangedTriggers(write);
605
- for (let valuesWatching of callbacks) {
606
- if (typeof valuesWatching !== "string") continue;
607
- let nodeId = valuesWatching;
608
- let changes = validChangesPerNode.get(nodeId);
609
- if (!changes) {
610
- changes = [];
611
- validChangesPerNode.set(nodeId, changes);
374
+ let isContinuedBatch = results.batchIndex === batchIndex;
375
+ results.batchIndex = batchIndex;
376
+ if (initialTrigger && !isContinuedBatch) {
377
+ results.pathValues = [];
378
+ results.initialTrigger = true;
612
379
  }
613
- changes.push(write);
380
+ results.pathValues.push(pathValue);
614
381
  }
615
- }
616
- // NOTE: Valid watchers are for lock watching of other authorities. The others nodes don't
617
- // want our values, and cannot properly ingest them (as they wouldn't be able to recursively resolve
618
- // them, they would be too large, etc, etc).
619
- for (let [nodeId, validStates] of validChangesPerNode) {
620
- if (validStates.length > 0) {
621
- logErrors(PathValueController.nodes[nodeId].onValidChange(validStates));
382
+ for (let parentPath of batch.initialTriggers.parentPaths) {
383
+ parentSyncs.set(parentPath, batch.sourceNodeId);
622
384
  }
623
385
  }
624
- }
625
386
 
626
- // Path watchers are for clients (ex FunctionRunner, browser, server scripts, etc),
627
- // which might be on other machines.
628
- pathWatcher.triggerValuesChanged(valuesChanged, parentsSynced, initialTriggers);
629
- }
630
- }
387
+ let parentPaths = new Set(parentSyncs.keys());
388
+ let initialValues = new Set(Array.from(finalResults.values()).filter(x => x.initialTrigger).map(x => x.path));
631
389
 
632
- let lagWindowSize = 2000;
633
- let lastLagInfo = {
634
- value: createStatsValue(),
635
- startTime: Date.now(),
636
- };
637
- let currentLagInfo = {
638
- value: createStatsValue(),
639
- startTime: Date.now(),
640
- };
641
- export function getReceiveLag() {
642
- let stats = lastLagInfo.value;
643
- if (stats.sum === 0) return undefined;
644
- let top = getStatsTop(stats);
645
- if (!top.topHeavy) {
646
- return `${formatTime(stats.sum / stats.count)} TCP LAG`;
647
- }
648
- let bottomValue = stats.sum - top.value;
649
- let bottomCount = stats.count - top.count;
650
- return `${formatTime(top.value / top.count)} * ${formatNumber(top.count)} + ${formatTime(bottomValue / bottomCount)} * ${formatNumber(bottomCount)} LAG`;
651
- }
652
- registerMeasureInfo(getReceiveLag);
653
- function trackLag(now: number, lag: number) {
654
- if (now - currentLagInfo.startTime > lagWindowSize) {
655
- lastLagInfo = currentLagInfo;
656
- currentLagInfo = {
657
- value: createStatsValue(),
658
- startTime: now,
659
- };
660
- }
661
- addToStatsValue(currentLagInfo.value, lag);
390
+ validStateComputer.ingestValuesAndValidStates({
391
+ pathValues: Array.from(finalResults.values()).map(x => x.pathValues).flat(),
392
+ parentSyncs: Array.from(parentSyncs.entries()).map(([parentPath, sourceNodeId]) => ({ parentPath, sourceNodeId })),
393
+ initialTriggers: { values: initialValues, parentPaths: parentPaths },
394
+ });
395
+ },
396
+ );
662
397
  }
663
398
 
664
-
665
399
  export const pathValueCommitter = new PathValueCommitter();