@hops-ops/distributed 0.0.0 → 4.0.1

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 (78) hide show
  1. package/README.md +26 -13
  2. package/dist/auth-headers.js +3 -3
  3. package/dist/internal/cache-engine/engine.d.ts +3 -1
  4. package/dist/internal/cache-engine/engine.js +137 -22
  5. package/dist/internal/cache-engine/errors.d.ts +4 -0
  6. package/dist/internal/cache-engine/errors.js +8 -0
  7. package/dist/internal/cache-engine/index.d.ts +2 -2
  8. package/dist/internal/cache-engine/index.js +1 -1
  9. package/dist/internal/cache-engine/types.d.ts +16 -0
  10. package/dist/internal/cache-engine.d.ts +2 -2
  11. package/dist/internal/cache-engine.js +1 -1
  12. package/dist/protocol.d.ts +9 -2
  13. package/dist/protocol.js +35 -7
  14. package/dist/replica/command-runtime/create.js +499 -57
  15. package/dist/replica/command-runtime/index.d.ts +1 -1
  16. package/dist/replica/command-runtime/index.js +1 -1
  17. package/dist/replica/command-runtime/lib/effects.d.ts +22 -9
  18. package/dist/replica/command-runtime/lib/effects.js +118 -20
  19. package/dist/replica/command-runtime/lib/projection.d.ts +2 -2
  20. package/dist/replica/command-runtime/lib/projection.js +14 -6
  21. package/dist/replica/command-runtime/lib/status.js +18 -10
  22. package/dist/replica/command-runtime/lib/transport.js +4 -0
  23. package/dist/replica/command-runtime/lib/util.js +8 -3
  24. package/dist/replica/command-runtime/symbols.d.ts +9 -0
  25. package/dist/replica/command-runtime/symbols.js +9 -0
  26. package/dist/replica/command-runtime/types.d.ts +15 -6
  27. package/dist/replica/command-runtime/types.js +1 -1
  28. package/dist/replica/command-runtime.d.ts +1 -1
  29. package/dist/replica/command-runtime.js +1 -1
  30. package/dist/replica/commands/clone.js +2 -1
  31. package/dist/replica/commands/index.d.ts +1 -0
  32. package/dist/replica/commands/prepare.js +7 -4
  33. package/dist/replica/commands/receipt.js +30 -16
  34. package/dist/replica/commands/types.d.ts +10 -5
  35. package/dist/replica/commands/validate.d.ts +5 -1
  36. package/dist/replica/commands/validate.js +129 -62
  37. package/dist/replica/diagnostics/inspect.js +51 -19
  38. package/dist/replica/diagnostics/types.d.ts +7 -7
  39. package/dist/replica/distributed-replica/helpers.js +22 -8
  40. package/dist/replica/distributed-replica/hydration.js +14 -2
  41. package/dist/replica/distributed-replica/impl-diagnostics.d.ts +1 -1
  42. package/dist/replica/distributed-replica/impl-diagnostics.js +4 -3
  43. package/dist/replica/distributed-replica/impl-hydration-orchestrate.d.ts +1 -1
  44. package/dist/replica/distributed-replica/impl-hydration-orchestrate.js +76 -42
  45. package/dist/replica/distributed-replica/impl-optimistic.d.ts +11 -2
  46. package/dist/replica/distributed-replica/impl-optimistic.js +22 -6
  47. package/dist/replica/distributed-replica/impl-protocol.js +4 -2
  48. package/dist/replica/distributed-replica/impl.d.ts +8 -2
  49. package/dist/replica/distributed-replica/impl.js +22 -5
  50. package/dist/replica/distributed-replica/optimistic.js +31 -3
  51. package/dist/replica/distributed-replica/types.d.ts +1 -0
  52. package/dist/replica/index-maintenance/engine.js +14 -6
  53. package/dist/replica/index.d.ts +4 -0
  54. package/dist/replica/index.js +2 -0
  55. package/dist/replica/materialize.js +25 -0
  56. package/dist/replica/mutation-cache.d.ts +62 -0
  57. package/dist/replica/mutation-cache.js +91 -0
  58. package/dist/replica/operation-binding.js +17 -6
  59. package/dist/replica/persistence/state.js +9 -1
  60. package/dist/replica/projection-delta/canonical.d.ts +6 -0
  61. package/dist/replica/projection-delta/canonical.js +29 -0
  62. package/dist/replica/projection-delta/index.d.ts +5 -0
  63. package/dist/replica/projection-delta/index.js +3 -0
  64. package/dist/replica/projection-delta/resolve.d.ts +8 -0
  65. package/dist/replica/projection-delta/resolve.js +266 -0
  66. package/dist/replica/projection-delta/types.d.ts +415 -0
  67. package/dist/replica/projection-delta/types.js +6 -0
  68. package/dist/replica/projection-delta/validate.d.ts +18 -0
  69. package/dist/replica/projection-delta/validate.js +1718 -0
  70. package/dist/replica/projection-delta/wasm-pure.d.ts +36 -0
  71. package/dist/replica/projection-delta/wasm-pure.js +71 -0
  72. package/dist/replica/query-plan/pagination.js +18 -8
  73. package/dist/replica/types.d.ts +13 -2
  74. package/dist/sveltekit/replica.d.ts +1 -1
  75. package/dist/sveltekit/replica.js +3 -0
  76. package/dist/sveltekit/vite.d.ts +6 -6
  77. package/dist/sveltekit/vite.js +6 -6
  78. package/package.json +3 -3
@@ -1,8 +1,185 @@
1
1
  import { parseGraphqlResponseExtensions } from '../../protocol.js';
2
2
  import { matchReplicaTrustedPresetInventory, prepareReplicaCommandWithTrustedPresets, verifyReplicaCommandReceipt } from '../commands.js';
3
- import { applyOptimisticEffects, attachAuthorityAbort, callerProjectedPromise, cloneScope, commandOutput, commandStatusArtifact, commandStatusRequest, commandSurfaceContract, commandTransportRequest, confirmDirectProjection, defineBoundCommand, dispatchPrepared, failTrackedProjection, freezeCommandTree, graphqlCommandRejection, linkAbortSignals, monitorPendingProjection, normalizeInventory, normalizeRetries, pendingProjection, preparedSemanticChanges, requireCommandEnvelope, requireCommandRejectionEnvelope, requireStatusEnvelope, sameScope, settleProjectionFailure, settleProjectionSuccess, settleTrackedProjection, validateStatusProgression, waitForCommandOperation } from './lib/index.js';
3
+ import { applyOptimisticEffects, attachAuthorityAbort, callerProjectedPromise, cloneScope, commandOutput, commandStatusArtifact, commandStatusRequest, commandSurfaceContract, commandTransportRequest, confirmDirectProjection, defineBoundCommand, dispatchPrepared, failTrackedProjection, freezeCommandTree, graphqlCommandRejection, linkAbortSignals, monitorPendingProjection, normalizeInventory, normalizeRetries, pendingProjection, preparedDispatchKeys, preparedSemanticChanges, requireCommandEnvelope, requireCommandRejectionEnvelope, requireStatusEnvelope, sameScope, settleProjectionFailure, settleProjectionSuccess, settleTrackedProjection, validateStatusProgression, waitForCommandOperation } from './lib/index.js';
4
4
  import { ReplicaCommandRuntimeError } from './errors.js';
5
- import { replicaCommandAuthority, replicaCommandProjectedLifecycle, replicaResultObservation } from './symbols.js';
5
+ import { replicaCommandAuthority, replicaCommandProjectionDelta, replicaCommandProjectedLifecycle, replicaCommandReadRecord, replicaResultObservation } from './symbols.js';
6
+ import { operationsFromProjectionDelta, validateProjectionMetadataAuthority } from '../projection-delta/index.js';
7
+ function assertActualProjectionCapabilities(contract, delta) {
8
+ for (let ordinal = 0; ordinal < delta.occurrences.length; ordinal += 1) {
9
+ const references = new Set();
10
+ for (const item of [...delta.operations, ...delta.recoveries]) {
11
+ if (item.occurrence_ordinal !== ordinal)
12
+ continue;
13
+ for (const reference of item.projection_refs)
14
+ references.add(reference);
15
+ }
16
+ let selectedEvent;
17
+ for (const reference of references) {
18
+ const operations = delta.operations.filter((item) => item.occurrence_ordinal === ordinal &&
19
+ item.projection_refs.includes(reference));
20
+ const recoveries = delta.recoveries.filter((item) => item.occurrence_ordinal === ordinal &&
21
+ item.projection_refs.includes(reference));
22
+ const candidates = contract.capabilities.arms.filter((arm) => arm.projection_ref === reference &&
23
+ operations.every(({ mutation }) => capabilityAllowsMutation(arm, mutation)) &&
24
+ recoveries.every(({ target }) => capabilityAllowsRecovery(arm, target)));
25
+ if (candidates.length !== 1) {
26
+ throw new Error('actual projection delta does not identify one generated event arm');
27
+ }
28
+ const event = candidates[0].event;
29
+ if (selectedEvent !== undefined &&
30
+ (event.id !== selectedEvent.id ||
31
+ event.name !== selectedEvent.name ||
32
+ event.version !== selectedEvent.version)) {
33
+ throw new Error('actual projection refs disagree on their generated event arm');
34
+ }
35
+ selectedEvent = event;
36
+ }
37
+ }
38
+ }
39
+ function capabilityAllowsMutation(arm, mutation) {
40
+ switch (mutation.op) {
41
+ case 'upsert': {
42
+ const capabilities = recordCapabilities(arm, mutation.scope);
43
+ const replace = capabilities[0]?.replace;
44
+ return (capabilities.some(({ upsert }) => upsert) &&
45
+ replace !== undefined &&
46
+ capabilities.every((capability) => sameStrings(capability.replace, replace)) &&
47
+ sameStrings(mutation.replace, replace) &&
48
+ sameStrings(mutation.fields.map(({ field }) => field), replace) &&
49
+ sameStrings(unionNames(capabilities.flatMap(({ fields }) => fields)), replace));
50
+ }
51
+ case 'patch': {
52
+ const capabilities = recordCapabilities(arm, mutation.scope);
53
+ const allowed = new Set(capabilities
54
+ .filter(({ patch }) => patch)
55
+ .flatMap(({ fields }) => fields));
56
+ return (allowed.size !== 0 &&
57
+ [...mutation.set.map(({ field }) => field), ...mutation.unset].every((field) => allowed.has(field)));
58
+ }
59
+ case 'delete':
60
+ return recordCapabilities(arm, mutation.scope).some(({ delete: allowed }) => allowed);
61
+ case 'link':
62
+ case 'unlink':
63
+ return arm.mutations.some((capability) => capability.kind === 'relationship' &&
64
+ capability.relationship === mutation.relationship &&
65
+ (mutation.op === 'link' ? capability.link : capability.unlink) &&
66
+ capabilityAllowsScope(arm, mutation.source, capability.source_model, capability.source_key) &&
67
+ capabilityAllowsScope(arm, mutation.target, capability.target_model, capability.target_key));
68
+ case 'invalidate_model':
69
+ return (capabilityAllowsPartition(arm, mutation.partition) &&
70
+ arm.mutations.some((capability) => capability.kind === 'model' &&
71
+ capability.model === mutation.model));
72
+ case 'invalidate_relationship':
73
+ return arm.mutations.some((capability) => capability.kind === 'relationship' &&
74
+ capability.relationship === mutation.relationship &&
75
+ capabilityAllowsScope(arm, mutation.source, capability.source_model, capability.source_key));
76
+ }
77
+ }
78
+ function capabilityAllowsRecovery(arm, target) {
79
+ switch (target.kind) {
80
+ case 'record':
81
+ return recordCapabilities(arm, target.scope).length !== 0;
82
+ case 'relationship':
83
+ return arm.mutations.some((capability) => capability.kind === 'relationship' &&
84
+ capability.relationship === target.relationship &&
85
+ capabilityAllowsScope(arm, target.source, capability.source_model, capability.source_key));
86
+ case 'model':
87
+ return (capabilityAllowsPartition(arm, target.partition) &&
88
+ arm.mutations.some((capability) => capability.kind === 'model' &&
89
+ capability.model === target.model));
90
+ }
91
+ }
92
+ function recordCapabilities(arm, scope) {
93
+ if (!capabilityAllowsPartition(arm, scope.partition))
94
+ return [];
95
+ return arm.mutations.filter((capability) => capability.kind === 'record' &&
96
+ capability.model === scope.model &&
97
+ sameStrings(capability.key, scope.key.map(({ field }) => field)));
98
+ }
99
+ function capabilityAllowsScope(arm, scope, model, key) {
100
+ return (capabilityAllowsPartition(arm, scope.partition) &&
101
+ scope.model === model &&
102
+ sameStrings(key, scope.key.map(({ field }) => field)));
103
+ }
104
+ function capabilityAllowsPartition(arm, partition) {
105
+ return arm.partition.kind === 'unit'
106
+ ? partition?.kind === 'unit'
107
+ : partition === undefined || partition.kind === 'opaque';
108
+ }
109
+ function unionNames(values) {
110
+ return Object.freeze([...new Set(values)].sort(compareCodeUnits));
111
+ }
112
+ function compareCodeUnits(left, right) {
113
+ return left < right ? -1 : left > right ? 1 : 0;
114
+ }
115
+ function sameStrings(left, right) {
116
+ return (left.length === right.length &&
117
+ left.every((value, index) => value === right[index]));
118
+ }
119
+ function actualProjectionRevalidation(base, delta) {
120
+ const models = new Set(base.models);
121
+ const relationships = new Map(base.relationships.map((relationship) => [
122
+ JSON.stringify([
123
+ relationship.sourceModel,
124
+ relationship.field,
125
+ relationship.targetModel
126
+ ]),
127
+ relationship
128
+ ]));
129
+ const addRelationship = (sourceModel, field, targetModel) => {
130
+ const relationship = Object.freeze({ sourceModel, field, targetModel });
131
+ relationships.set(JSON.stringify([sourceModel, field, targetModel]), relationship);
132
+ };
133
+ for (const { mutation } of delta.operations) {
134
+ switch (mutation.op) {
135
+ case 'upsert':
136
+ case 'patch':
137
+ case 'delete':
138
+ models.add(mutation.scope.model);
139
+ break;
140
+ case 'link':
141
+ case 'unlink':
142
+ models.add(mutation.source.model);
143
+ models.add(mutation.target.model);
144
+ addRelationship(mutation.source.model, mutation.relationship, mutation.target.model);
145
+ break;
146
+ case 'invalidate_model':
147
+ models.add(mutation.model);
148
+ break;
149
+ case 'invalidate_relationship':
150
+ models.add(mutation.source.model);
151
+ break;
152
+ }
153
+ }
154
+ for (const { target } of delta.recoveries) {
155
+ switch (target.kind) {
156
+ case 'record':
157
+ models.add(target.scope.model);
158
+ break;
159
+ case 'relationship':
160
+ models.add(target.source.model);
161
+ break;
162
+ case 'model':
163
+ models.add(target.model);
164
+ break;
165
+ }
166
+ }
167
+ return Object.freeze({
168
+ version: 1,
169
+ required: true,
170
+ dependencies: Object.freeze([...base.dependencies]),
171
+ models: Object.freeze([...models].sort(compareCodeUnits)),
172
+ relationships: Object.freeze([...relationships.values()].sort((left, right) => compareCodeUnits(JSON.stringify([
173
+ left.sourceModel,
174
+ left.field,
175
+ left.targetModel
176
+ ]), JSON.stringify([
177
+ right.sourceModel,
178
+ right.field,
179
+ right.targetModel
180
+ ]))))
181
+ });
182
+ }
6
183
  export function createReplicaCommandRuntime(replica, transport, entries, options = {}) {
7
184
  const inventory = normalizeInventory(entries);
8
185
  if (options.diagnostics !== undefined) {
@@ -26,14 +203,145 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
26
203
  throw new TypeError('generated command status artifact requires transport.status');
27
204
  }
28
205
  if (replicaRevalidate === undefined &&
29
- inventory.some(({ artifact }) => artifact.revalidation.required)) {
30
- throw new TypeError('generated command revalidation plan requires replica.revalidate');
206
+ inventory.some(({ artifact }) => artifact.projection !== undefined ||
207
+ artifact.revalidation.required)) {
208
+ throw new TypeError('generated modeled projection or required revalidation plan requires replica.revalidate');
31
209
  }
32
210
  const registration = replica[replicaCommandAuthority]?.(contract);
33
211
  const pending = new Map();
212
+ const projectionReplays = new Map();
213
+ const projectionRevalidations = new Map();
214
+ const dispatchTails = new Map();
34
215
  const unmanagedLayers = new Set();
35
216
  const runtimeAbort = new AbortController();
36
217
  let disposed = false;
218
+ const reserveDispatch = (prepared) => {
219
+ const keys = preparedDispatchKeys(prepared);
220
+ if (keys.length === 0) {
221
+ return Object.freeze({
222
+ wait: Promise.resolve(),
223
+ release() { }
224
+ });
225
+ }
226
+ const predecessors = keys.flatMap((key) => {
227
+ const predecessor = dispatchTails.get(key);
228
+ return predecessor === undefined ? [] : [predecessor];
229
+ });
230
+ let resolve;
231
+ const tail = new Promise((settled) => {
232
+ resolve = settled;
233
+ });
234
+ for (const key of keys)
235
+ dispatchTails.set(key, tail);
236
+ let released = false;
237
+ return Object.freeze({
238
+ wait: Promise.all(predecessors).then(() => undefined),
239
+ release() {
240
+ if (released)
241
+ return;
242
+ released = true;
243
+ for (const key of keys) {
244
+ if (dispatchTails.get(key) === tail)
245
+ dispatchTails.delete(key);
246
+ }
247
+ resolve();
248
+ }
249
+ });
250
+ };
251
+ const validateActualProjection = (prepared, metadata, authority) => {
252
+ if (prepared.projection === undefined) {
253
+ if (metadata.projection !== undefined || metadata.expects.length !== 0) {
254
+ throw new Error('unmodeled command returned a projection delta');
255
+ }
256
+ return Object.freeze({ requiresRevalidation: false });
257
+ }
258
+ const actual = metadata.projection;
259
+ if (actual === undefined) {
260
+ throw new Error('modeled command omitted its projection delta');
261
+ }
262
+ const canonical = validateProjectionMetadataAuthority(actual, prepared.projection.contract, {
263
+ surface: prepared.transport.protocol.surface,
264
+ schemaHash: prepared.transport.protocol.schemaHash,
265
+ protocolHash: prepared.transport.protocol.protocolHash,
266
+ authorizationGeneration: authority.scope.authorizationGeneration,
267
+ cacheScope: authority.scope.cacheScope,
268
+ causationId: metadata.causationId
269
+ });
270
+ const expected = actual.obligations.map((obligation) => ({
271
+ projection: actual.delta.projections[obligation.projectionRef].program_id,
272
+ model: obligation.model,
273
+ scopeToken: obligation.scopeToken
274
+ }));
275
+ if (expected.length !== metadata.expects.length ||
276
+ expected.some((item, index) => {
277
+ const candidate = metadata.expects[index];
278
+ return (candidate === undefined ||
279
+ candidate.projection !== item.projection ||
280
+ candidate.model !== item.model ||
281
+ candidate.scopeToken !== item.scopeToken);
282
+ })) {
283
+ throw new Error('projection obligations do not match command expectations');
284
+ }
285
+ const previous = projectionReplays.get(prepared.commandId);
286
+ if (previous !== undefined) {
287
+ if (previous !== canonical) {
288
+ throw new Error('projection delta changed during command replay');
289
+ }
290
+ return Object.freeze({
291
+ requiresRevalidation: actual.revalidate || actual.obligations.length === 0
292
+ });
293
+ }
294
+ assertActualProjectionCapabilities(prepared.projection.contract, actual.delta);
295
+ const operations = operationsFromProjectionDelta(actual.delta.operations);
296
+ const seam = replica[replicaCommandProjectionDelta];
297
+ if (seam === undefined) {
298
+ throw new Error('replica does not implement projection delta replacement');
299
+ }
300
+ return Object.freeze({
301
+ canonical,
302
+ operations,
303
+ revalidation: actual.revalidate || actual.obligations.length === 0
304
+ ? actualProjectionRevalidation(prepared.revalidation, actual.delta)
305
+ : undefined,
306
+ requiresRevalidation: actual.revalidate || actual.obligations.length === 0
307
+ });
308
+ };
309
+ const commitActualProjection = (prepared, validated) => {
310
+ if (validated.canonical === undefined ||
311
+ validated.operations === undefined) {
312
+ return validated.requiresRevalidation;
313
+ }
314
+ const seam = replica[replicaCommandProjectionDelta];
315
+ const actualPrepared = Object.freeze({
316
+ ...prepared,
317
+ optimistic: Object.freeze({
318
+ version: 1,
319
+ operations: validated.operations,
320
+ fallback: 'revalidate'
321
+ })
322
+ });
323
+ seam.call(replica, prepared.commandId, (writer) => applyOptimisticEffects(writer, validated.operations), preparedSemanticChanges(actualPrepared));
324
+ projectionReplays.set(prepared.commandId, validated.canonical);
325
+ if (validated.revalidation !== undefined) {
326
+ projectionRevalidations.set(prepared.commandId, validated.revalidation);
327
+ }
328
+ return validated.requiresRevalidation;
329
+ };
330
+ const applyActualProjection = (prepared, metadata, authority) => commitActualProjection(prepared, validateActualProjection(prepared, metadata, authority));
331
+ const validateProjectionForState = (prepared, metadata, authority) => {
332
+ switch (metadata.state) {
333
+ case 'succeeded':
334
+ case 'succeeded_pending_projection':
335
+ case 'atomic':
336
+ return validateActualProjection(prepared, metadata, authority);
337
+ case 'in_progress':
338
+ case 'rejected':
339
+ case 'projection_failed':
340
+ case 'expired':
341
+ case 'unknown':
342
+ return Object.freeze({ requiresRevalidation: false });
343
+ }
344
+ };
37
345
  const rejectUnmanagedLayer = (commandId) => {
38
346
  unmanagedLayers.delete(commandId);
39
347
  return replica.rejectOptimisticLayer(commandId);
@@ -87,7 +395,8 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
87
395
  runtimeAbort.signal
88
396
  ]);
89
397
  try {
90
- await waitForCommandOperation(replicaRevalidate(prepared.revalidation), revalidationSignals.signal);
398
+ await waitForCommandOperation(replicaRevalidate(projectionRevalidations.get(prepared.commandId) ??
399
+ prepared.revalidation), revalidationSignals.signal);
91
400
  return true;
92
401
  }
93
402
  catch (error) {
@@ -119,6 +428,29 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
119
428
  })
120
429
  .catch(() => undefined);
121
430
  };
431
+ const revalidateDispositionAndRetireInBackground = (controller) => {
432
+ void revalidate(controller.prepared, controller.authority.signal)
433
+ .then((refreshed) => {
434
+ if (refreshed &&
435
+ !controller.settled &&
436
+ stillCurrent(controller.authority) &&
437
+ pending.get(controller.commandId) === controller) {
438
+ retireLayerAfterAuthoritativeRevalidation(controller.commandId);
439
+ settleProjectionSuccess(controller);
440
+ pending.delete(controller.commandId);
441
+ }
442
+ })
443
+ .catch(() => undefined);
444
+ };
445
+ const commitStatusProgression = (tracker, status) => {
446
+ tracker.state = status.state;
447
+ if (status.metadata !== undefined) {
448
+ tracker.metadata = status.metadata;
449
+ if (tracker.pending !== undefined) {
450
+ tracker.pending.metadata = status.metadata;
451
+ }
452
+ }
453
+ };
122
454
  const statusReader = (prepared, authority, tracker) => {
123
455
  const read = async () => {
124
456
  if (disposed) {
@@ -155,21 +487,40 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
155
487
  throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_SCOPE_INVALIDATED', { commandId: prepared.commandId });
156
488
  }
157
489
  let status;
490
+ let statusRequiresRevalidation = false;
158
491
  try {
159
492
  status = requireStatusEnvelope(result, statusArtifact, prepared, authority, contract);
160
493
  validateStatusProgression(tracker.state, tracker.metadata, status);
494
+ if (status.metadata !== undefined) {
495
+ if (tracker.pending !== undefined &&
496
+ status.metadata.causationId !==
497
+ tracker.pending.causationId) {
498
+ throw new Error('command status changed pending causation identity');
499
+ }
500
+ if (status.metadata.projectionDisposition === 'revalidate') {
501
+ /*
502
+ * This is current-envelope authority to refetch, never
503
+ * authority to validate or apply the omitted old-scope
504
+ * delta.
505
+ */
506
+ statusRequiresRevalidation = true;
507
+ }
508
+ else if (prepared.consistency !== 'atomic') {
509
+ // Atomic rows are confirmed on the mutation response,
510
+ // not via async projection-delta status envelopes.
511
+ const validated = validateProjectionForState(prepared, status.metadata, authority);
512
+ statusRequiresRevalidation = commitActualProjection(prepared, validated);
513
+ }
514
+ }
161
515
  }
162
516
  catch (error) {
517
+ const failure = new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', { commandId: prepared.commandId, cause: error });
518
+ rejectUnmanagedLayer(prepared.commandId);
163
519
  revalidateInBackground(prepared, authority);
164
- throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', { commandId: prepared.commandId, cause: error });
165
- }
166
- tracker.state = status.state;
167
- if (status.metadata !== undefined) {
168
- tracker.metadata = status.metadata;
169
- if (tracker.pending !== undefined) {
170
- tracker.pending.metadata = status.metadata;
171
- }
520
+ failTrackedProjection(tracker, pending, failure);
521
+ throw failure;
172
522
  }
523
+ commitStatusProgression(tracker, status);
173
524
  switch (status.state) {
174
525
  case 'rejected':
175
526
  rejectUnmanagedLayer(prepared.commandId);
@@ -187,10 +538,56 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
187
538
  state: status.state
188
539
  }));
189
540
  break;
190
- case 'accepted':
191
- case 'accepted_pending_projection':
192
- case 'projected': {
541
+ case 'succeeded':
542
+ case 'succeeded_pending_projection':
543
+ case 'atomic': {
193
544
  const metadata = status.metadata;
545
+ if (metadata.projectionDisposition === 'revalidate') {
546
+ if (metadata.state === 'succeeded_pending_projection') {
547
+ /*
548
+ * Historical evidence is still pending. Refresh
549
+ * canonical current-scope queries opportunistically,
550
+ * but retain optimism and keep polling until the
551
+ * server reports a terminal state.
552
+ */
553
+ revalidateInBackground(prepared, authority);
554
+ break;
555
+ }
556
+ let refreshed = false;
557
+ try {
558
+ refreshed = await revalidate(prepared, authority.signal);
559
+ }
560
+ catch (error) {
561
+ if (disposed) {
562
+ throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_DISPOSED', {
563
+ commandId: prepared.commandId,
564
+ cause: error
565
+ });
566
+ }
567
+ if (authority.signal?.aborted ||
568
+ !stillCurrent(authority)) {
569
+ throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_SCOPE_INVALIDATED', {
570
+ commandId: prepared.commandId,
571
+ cause: error
572
+ });
573
+ }
574
+ // The retained monitor will retry this exact
575
+ // current-scope revalidation on the next status read.
576
+ }
577
+ if (refreshed && stillCurrent(authority)) {
578
+ const controller = tracker.pending;
579
+ if (controller !== undefined &&
580
+ !controller.settled &&
581
+ pending.get(prepared.commandId) === controller) {
582
+ retireLayerAfterAuthoritativeRevalidation(prepared.commandId);
583
+ settleTrackedProjection(tracker, pending);
584
+ }
585
+ else if (unmanagedLayers.has(prepared.commandId)) {
586
+ retireLayerAfterAuthoritativeRevalidation(prepared.commandId);
587
+ }
588
+ }
589
+ break;
590
+ }
194
591
  const remainsPending = replica.markOptimisticLayerAccepted(prepared.commandId, metadata);
195
592
  if (!remainsPending) {
196
593
  unmanagedLayers.delete(prepared.commandId);
@@ -198,14 +595,15 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
198
595
  settleTrackedProjection(tracker, pending);
199
596
  }
200
597
  }
201
- else if (metadata.state === 'projected' ||
202
- (metadata.state === 'accepted' &&
203
- prepared.confirmations === undefined &&
204
- prepared.revalidation.required)) {
598
+ else if (metadata.state === 'atomic' ||
599
+ (metadata.state === 'succeeded' &&
600
+ metadata.expects.length === 0 &&
601
+ (prepared.revalidation.required ||
602
+ statusRequiresRevalidation))) {
205
603
  /*
206
604
  * Status is causal truth, but it carries no canonical
207
605
  * query payload. `projected` fences asynchronous
208
- * projection; `accepted` is terminal when the generated
606
+ * projection; `succeeded` is terminal when the generated
209
607
  * contract has no confirmations. In both cases, only a
210
608
  * query started after that fence may retire optimism.
211
609
  */
@@ -295,7 +693,11 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
295
693
  : 'REPLICA_COMMAND_SCOPE_INVALIDATED', { commandId: prepared.commandId, cause: request.signal.reason });
296
694
  }
297
695
  try {
298
- replica.createOptimisticLayer(prepared.commandId, (writer) => applyOptimisticEffects(writer, prepared.optimistic.operations), semanticChanges);
696
+ const pureHost = {
697
+ readRecord: replica[replicaCommandReadRecord]?.bind(replica),
698
+ pureFunctions: options.pureFunctions
699
+ };
700
+ replica.createOptimisticLayer(prepared.commandId, (writer) => applyOptimisticEffects(writer, prepared.optimistic.operations, pureHost), semanticChanges);
299
701
  }
300
702
  catch (error) {
301
703
  requestSignals.dispose();
@@ -326,9 +728,11 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
326
728
  }
327
729
  return undefined;
328
730
  };
731
+ const dispatchReservation = reserveDispatch(prepared);
329
732
  let result;
330
733
  let dispatchAttempted = false;
331
734
  try {
735
+ await dispatchReservation.wait;
332
736
  result = await dispatchPrepared(transport, request, transportRetries, () => {
333
737
  dispatchAttempted = true;
334
738
  });
@@ -357,6 +761,7 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
357
761
  });
358
762
  }
359
763
  finally {
764
+ dispatchReservation.release();
360
765
  requestSignals.dispose();
361
766
  }
362
767
  if (!stillCurrent(authority)) {
@@ -397,8 +802,6 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
397
802
  });
398
803
  }
399
804
  const metadata = distributed.command;
400
- statusTracker.state = metadata.state;
401
- statusTracker.metadata = metadata;
402
805
  if (metadata.state === 'rejected') {
403
806
  rejectUnmanagedLayer(prepared.commandId);
404
807
  throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_REJECTED', {
@@ -412,6 +815,8 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
412
815
  throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROJECTION_FAILED', { commandId: prepared.commandId, state: metadata.state });
413
816
  }
414
817
  if (metadata.state === 'unknown' || metadata.state === 'in_progress') {
818
+ statusTracker.state = metadata.state;
819
+ statusTracker.metadata = metadata;
415
820
  throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_OUTCOME_PENDING', {
416
821
  commandId: prepared.commandId,
417
822
  state: metadata.state,
@@ -424,42 +829,51 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
424
829
  throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROJECTION_FAILED', { commandId: prepared.commandId, state: metadata.state });
425
830
  }
426
831
  if ((result.errors?.length ?? 0) > 0) {
427
- let retainedRecovery;
428
- if (prepared.confirmations?.kind === 'finite') {
429
- replica.markOptimisticLayerAccepted(prepared.commandId, metadata);
430
- retainedRecovery = recoveryForRetainedLayer();
431
- }
432
- else {
433
- rejectUnmanagedLayer(prepared.commandId);
434
- }
832
+ rejectUnmanagedLayer(prepared.commandId);
435
833
  revalidateInBackground(prepared, authority);
436
- throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', { commandId: prepared.commandId, recovery: retainedRecovery });
834
+ throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', { commandId: prepared.commandId });
437
835
  }
438
836
  let output;
439
837
  try {
440
838
  output = commandOutput(artifact, result.data, prepared.transport.mutationField);
441
839
  }
442
840
  catch (error) {
443
- let retainedRecovery;
444
- if (prepared.consistency === 'projected' ||
445
- prepared.confirmations?.kind !== 'finite') {
446
- rejectUnmanagedLayer(prepared.commandId);
447
- }
448
- else {
449
- replica.markOptimisticLayerAccepted(prepared.commandId, metadata);
450
- retainedRecovery = recoveryForRetainedLayer();
451
- }
841
+ rejectUnmanagedLayer(prepared.commandId);
452
842
  revalidateInBackground(prepared, authority);
453
843
  throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', {
454
844
  commandId: prepared.commandId,
455
845
  cause: error,
456
- recovery: retainedRecovery
846
+ ...(statusArtifact === undefined ? {} : { recovery })
457
847
  });
458
848
  }
849
+ /*
850
+ * Ship contract: Atomic seals from the atomic GraphQL row +
851
+ * direct `records` (confirmDirectProjection). Eventual applies
852
+ * projection-delta when present. Both strategies start from the same
853
+ * compiler-derived event→mutation preview — different response proof by
854
+ * design.
855
+ */
856
+ let actualRequiresRevalidation = false;
857
+ if (prepared.consistency !== 'atomic') {
858
+ try {
859
+ actualRequiresRevalidation = applyActualProjection(prepared, metadata, authority);
860
+ }
861
+ catch (error) {
862
+ rejectUnmanagedLayer(prepared.commandId);
863
+ revalidateInBackground(prepared, authority);
864
+ throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', {
865
+ commandId: prepared.commandId,
866
+ cause: error,
867
+ ...(statusArtifact === undefined ? {} : { recovery })
868
+ });
869
+ }
870
+ }
459
871
  let projected;
460
872
  let projectionLifecycle;
461
- if (prepared.consistency === 'projected') {
462
- if (metadata.state !== 'projected') {
873
+ if (prepared.consistency === 'atomic') {
874
+ if (metadata.state !== 'atomic') {
875
+ statusTracker.state = metadata.state;
876
+ statusTracker.metadata = metadata;
463
877
  throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_OUTCOME_PENDING', {
464
878
  commandId: prepared.commandId,
465
879
  state: metadata.state,
@@ -473,19 +887,27 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
473
887
  catch (error) {
474
888
  rejectUnmanagedLayer(prepared.commandId);
475
889
  revalidateInBackground(prepared, authority);
476
- throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', { commandId: prepared.commandId, cause: error });
890
+ throw new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', {
891
+ commandId: prepared.commandId,
892
+ cause: error,
893
+ ...(statusArtifact === undefined ? {} : { recovery })
894
+ });
477
895
  }
896
+ statusTracker.state = metadata.state;
897
+ statusTracker.metadata = metadata;
478
898
  projected = Promise.resolve(Object.freeze({
479
899
  commandId: prepared.commandId,
480
- state: 'projected',
900
+ state: 'atomic',
481
901
  result: output,
482
902
  metadata
483
903
  }));
484
904
  }
485
905
  else {
906
+ statusTracker.state = metadata.state;
907
+ statusTracker.metadata = metadata;
486
908
  const remainsPending = replica.markOptimisticLayerAccepted(prepared.commandId, metadata);
487
- if (prepared.confirmations?.kind === 'finite') {
488
- const controller = pendingProjection(authority, metadata, prepared);
909
+ if (metadata.expects.length > 0) {
910
+ const controller = pendingProjection(authority, metadata, prepared, statusTracker);
489
911
  statusTracker.pending = controller;
490
912
  if (!remainsPending) {
491
913
  unmanagedLayers.delete(prepared.commandId);
@@ -511,7 +933,7 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
511
933
  projected = callerProjectedPromise(controller, callOptions.signal);
512
934
  }
513
935
  }
514
- if (prepared.revalidation.required) {
936
+ if (prepared.revalidation.required || actualRequiresRevalidation) {
515
937
  revalidateAndConfirmUnmanagedInBackground(prepared, authority);
516
938
  }
517
939
  const receiptValue = {
@@ -532,7 +954,7 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
532
954
  }
533
955
  const receipt = Object.freeze(receiptValue);
534
956
  try {
535
- await callOptions.onAccepted?.(receipt);
957
+ await callOptions.onSucceeded?.(receipt);
536
958
  }
537
959
  catch (error) {
538
960
  options.onBackgroundError?.(error);
@@ -567,6 +989,8 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
567
989
  continue;
568
990
  }
569
991
  if (distributed.schemaHash !== controller.authority.scope.schemaHash ||
992
+ distributed.authorizationGeneration !==
993
+ controller.authority.scope.authorizationGeneration ||
570
994
  distributed.cacheScope !== controller.authority.scope.cacheScope) {
571
995
  continue;
572
996
  }
@@ -574,19 +998,28 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
574
998
  if (command?.commandId === commandId) {
575
999
  try {
576
1000
  verifyReplicaCommandReceipt(controller.prepared, command);
1001
+ if (command.causationId !== controller.causationId) {
1002
+ throw new Error('live command changed pending causation identity');
1003
+ }
1004
+ const status = Object.freeze({
1005
+ commandId,
1006
+ state: command.state,
1007
+ metadata: command
1008
+ });
1009
+ validateStatusProgression(controller.tracker.state, controller.tracker.metadata, status);
1010
+ if (command.projectionDisposition !== 'revalidate') {
1011
+ const validated = validateProjectionForState(controller.prepared, command, controller.authority);
1012
+ commitActualProjection(controller.prepared, validated);
1013
+ }
1014
+ commitStatusProgression(controller.tracker, status);
577
1015
  }
578
1016
  catch (error) {
1017
+ replica.rejectOptimisticLayer(commandId);
579
1018
  revalidateInBackground(controller.prepared, controller.authority);
580
1019
  settleProjectionFailure(controller, new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', { commandId, cause: error }));
581
1020
  pending.delete(commandId);
582
1021
  continue;
583
1022
  }
584
- if (command.causationId !== controller.causationId) {
585
- settleProjectionFailure(controller, new ReplicaCommandRuntimeError('REPLICA_COMMAND_PROTOCOL_INVALID', { commandId }));
586
- pending.delete(commandId);
587
- continue;
588
- }
589
- controller.metadata = command;
590
1023
  if (command.state === 'projection_failed') {
591
1024
  replica.rejectOptimisticLayer(commandId);
592
1025
  revalidateInBackground(controller.prepared, controller.authority);
@@ -604,6 +1037,15 @@ export function createReplicaCommandRuntime(replica, transport, entries, options
604
1037
  pending.delete(commandId);
605
1038
  continue;
606
1039
  }
1040
+ if (command.projectionDisposition === 'revalidate') {
1041
+ if (command.state === 'succeeded_pending_projection') {
1042
+ revalidateInBackground(controller.prepared, controller.authority);
1043
+ }
1044
+ else {
1045
+ revalidateDispositionAndRetireInBackground(controller);
1046
+ }
1047
+ continue;
1048
+ }
607
1049
  }
608
1050
  /*
609
1051
  * DistributedReplica is the authority on whether this frame's