@taskforcehq/taskforce 0.3.330 → 0.3.333
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.
- package/README.md +7 -2
- package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.d.ts +2 -2
- package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.js +2 -2
- package/dist/components/features/planning/PlanningModule.js +15 -3
- package/dist/components/features/planning/planningMetadata.d.ts +10 -0
- package/dist/components/features/planning/planningMetadata.js +8 -0
- package/dist/components/views/StandaloneLayout.js +35 -1
- package/dist/components/views/panels/PlanningDrawer.d.ts +2 -1
- package/dist/components/views/panels/PlanningDrawer.js +12 -8
- package/dist/config/envSchema.js +7 -0
- package/dist/core/McpTokenService.d.ts +2 -0
- package/dist/core/McpTokenService.js +13 -5
- package/dist/core/Taskforce.d.ts +11 -3
- package/dist/core/Taskforce.js +33 -3
- package/dist/core/types.d.ts +1 -0
- package/dist/documentReviews/DocumentReviewCommentStore.d.ts +2 -0
- package/dist/documentReviews/DocumentReviewCommentStore.js +28 -9
- package/dist/mcp/httpProtocolRouting.d.ts +1 -0
- package/dist/mcp/httpProtocolRouting.js +8 -5
- package/dist/mcp/runtime.js +24 -5
- package/dist/mcp/taskPlanningRegistrar.js +2 -2
- package/dist/mcp/toolProfiles.js +1 -0
- package/dist/mcp/toolRegistry.js +8 -8
- package/dist/server/buildInfo.d.ts +1 -0
- package/dist/server/buildInfo.js +5 -1
- package/dist/server/cloudMcpCapacityTelemetry.d.ts +68 -0
- package/dist/server/cloudMcpCapacityTelemetry.js +221 -0
- package/dist/server/localWorkspaceSyncServerRuntime.js +13 -5
- package/dist/server/routes/admin.js +14 -2
- package/dist/server/routes/localService.js +1 -0
- package/dist/server/routes/shared.d.ts +3 -0
- package/dist/server/routes/shared.js +280 -240
- package/dist/server/routes/syncPushRoutes.d.ts +3 -0
- package/dist/server/routes/syncPushRoutes.js +8 -2
- package/dist/service/localServiceCli.d.ts +2 -2
- package/dist/service/localServiceCli.js +4 -3
- package/dist/services/executorPhaseAIssuerCore.d.ts +13 -0
- package/dist/services/executorPhaseAIssuerCore.js +107 -0
- package/dist/services/taskforceAgentMcpBridge.d.ts +1 -1
- package/dist/services/taskforceAgentMcpBridge.js +35 -5
- package/dist/shared/executorPhaseASchemaValidation.js +1 -1
- package/dist/storage/postgresAdapter.js +3 -1
- package/dist/storage/postgresRequestDiagnostics.d.ts +9 -1
- package/dist/storage/postgresRequestDiagnostics.js +15 -2
- package/dist/storage/postgresWorker.js +13 -3
- package/dist/storage/postgresWorkerProtocol.d.ts +6 -0
- package/dist/storage/workflowStore.d.ts +2 -0
- package/dist/storage/workflowStore.js +39 -2
- package/dist/sync/cloudSyncApi.js +4 -2
- package/dist/sync/collaborationSyncState.js +5 -0
- package/dist/sync/coordinator/localWorkspaceSyncRunner.d.ts +3 -1
- package/dist/sync/coordinator/localWorkspaceSyncRunner.js +18 -0
- package/dist/sync/coordinator/localWorkspaceSyncRunnerState.d.ts +1 -0
- package/dist/sync/coordinator/localWorkspaceSyncRunnerState.js +7 -6
- package/dist/sync/engine/workspaceSyncEngineBrowserGatewayRemoteServices.js +2 -2
- package/dist/sync/engine/workspaceSyncEngineLocalApply.d.ts +1 -0
- package/dist/sync/engine/workspaceSyncEnginePushCompletion.d.ts +6 -0
- package/dist/sync/engine/workspaceSyncEnginePushCompletion.js +34 -1
- package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +2 -2
- package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +4 -1
- package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.d.ts +1 -0
- package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.js +55 -10
- package/dist/sync/syncApplyHandlers.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.js +72 -15
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +5 -2
- package/dist/sync/v3/durableWorkflowRuntimeMutationRouter.d.ts +7 -7
- package/dist/sync/v3/durableWorkflowRuntimeMutationRouter.js +50 -13
- package/dist/sync/v3/localOutboxRecoveryPreparationService.js +8 -2
- package/dist/sync/v3/localTaskCreateOutboxService.d.ts +3 -1
- package/dist/sync/v3/localTaskCreateOutboxService.js +6 -1
- package/dist/sync/v3/localTaskDeleteOutboxService.js +1 -0
- package/dist/sync/v3/localTaskLifecycleOutboxService.d.ts +2 -1
- package/dist/sync/v3/localTaskLifecycleOutboxService.js +5 -1
- package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -1
- package/dist/sync/v3/localTaskMetadataOutboxService.js +6 -2
- package/dist/sync/v3/localTaskStatusOutboxService.d.ts +2 -1
- package/dist/sync/v3/localTaskStatusOutboxService.js +20 -5
- package/dist/sync/v3/syncDurabilityStore.d.ts +2 -1
- package/dist/sync/v3/syncDurabilityStore.js +34 -6
- package/dist/sync/v3/workflowRuntimeMutationService.d.ts +5 -0
- package/dist/sync/v3/workflowRuntimeMutationService.js +124 -71
- package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +25 -3
- package/dist/sync/v3/workspaceSyncV3Feed.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3Feed.js +6 -1
- package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +2 -0
- package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +49 -18
- package/dist/sync/v3/workspaceSyncV3WorkflowRuntimeApply.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3WorkflowRuntimeApply.js +50 -5
- package/dist/ui/assets/{AiIdentityRosterCard-OcSz1PkK.js → AiIdentityRosterCard-Cfvb9Nuv.js} +1 -1
- package/dist/ui/assets/{AiProfilesModule-ByhYW5Xi.js → AiProfilesModule-0oR70eFG.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BTM6nIg_.js → AnnotatedAttachmentWorkspace-C33iH_r_.js} +1 -1
- package/dist/ui/assets/{AssetTraySortControl-DabEG8L4.js → AssetTraySortControl-BAqxUtke.js} +1 -1
- package/dist/ui/assets/{ContextAttachmentManager-Coa8yVGM.js → ContextAttachmentManager-BudIbOR9.js} +2 -2
- package/dist/ui/assets/{DocumentWorkspace-BB0qhdix.js → DocumentWorkspace-A8dFQyId.js} +1 -1
- package/dist/ui/assets/{EntityActivityTimeline-CZ8x4UJE.js → EntityActivityTimeline-g7vtdsx1.js} +1 -1
- package/dist/ui/assets/PlanningModule-D-DeQs4F.js +1 -0
- package/dist/ui/assets/{PlansPage-D86pZSSl.js → PlansPage-BWmfGi0Z.js} +1 -1
- package/dist/ui/assets/{TaskContextUpload-BVIkU9yT.js → TaskContextUpload-BA_lQmS9.js} +1 -1
- package/dist/ui/assets/{TaskSettings-Cv1mNhv_.js → TaskSettings-DAlH4YIP.js} +1 -1
- package/dist/ui/assets/{TaskforceAgentsModule-gmH-PD2q.js → TaskforceAgentsModule-DmeCf6pJ.js} +2 -2
- package/dist/ui/assets/{WorkflowManagerModule-DbfnHJ-f.js → WorkflowManagerModule-p08eDOaa.js} +1 -1
- package/dist/ui/assets/index-GHJgcW3j.js +7 -0
- package/dist/ui/index.html +1 -1
- package/package.json +3 -1
- package/dist/ui/assets/PlanningModule-pRPjR7v5.js +0 -1
- package/dist/ui/assets/index-CTsDBaef.js +0 -7
|
@@ -19,10 +19,15 @@ export interface WorkspaceSyncEngineAppliedTaskUpsert {
|
|
|
19
19
|
}
|
|
20
20
|
export declare function workspaceSyncEngineExpectedTaskEchoIds(changes: readonly WorkspaceSyncChange[]): string[];
|
|
21
21
|
export declare function parseWorkspaceSyncEngineAppliedTaskUpserts(value: unknown, expectedTaskIds: readonly string[]): WorkspaceSyncEngineAppliedTaskUpsert[];
|
|
22
|
+
export declare function parseWorkspaceSyncEnginePushTaskSettlement(value: unknown, expectedTaskIds: readonly string[]): {
|
|
23
|
+
appliedTaskUpserts: WorkspaceSyncEngineAppliedTaskUpsert[];
|
|
24
|
+
settledTaskDeleteIds: string[];
|
|
25
|
+
};
|
|
22
26
|
export declare function createWorkspaceSyncEngineV2SettlementIdentityProjection(input: {
|
|
23
27
|
operationIdentity: string;
|
|
24
28
|
changes: readonly WorkspaceSyncChange[];
|
|
25
29
|
appliedTaskUpserts: readonly WorkspaceSyncEngineAppliedTaskUpsert[];
|
|
30
|
+
settledTaskDeleteIds?: readonly string[];
|
|
26
31
|
excludedProjections?: readonly WorkspaceSyncV2ProjectionExclusion[];
|
|
27
32
|
}): {
|
|
28
33
|
version: 1;
|
|
@@ -31,4 +36,5 @@ export declare function createWorkspaceSyncEngineV2SettlementIdentityProjection(
|
|
|
31
36
|
archived: boolean;
|
|
32
37
|
task: Record<string, unknown>;
|
|
33
38
|
}>;
|
|
39
|
+
settledTaskDeleteIds?: string[];
|
|
34
40
|
};
|
|
@@ -86,6 +86,28 @@ export function parseWorkspaceSyncEngineAppliedTaskUpserts(value, expectedTaskId
|
|
|
86
86
|
}
|
|
87
87
|
return normalized;
|
|
88
88
|
}
|
|
89
|
+
export function parseWorkspaceSyncEnginePushTaskSettlement(value, expectedTaskIds) {
|
|
90
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
91
|
+
throw new TypeError('The V2 push authoritative echo response is invalid.');
|
|
92
|
+
}
|
|
93
|
+
const source = value;
|
|
94
|
+
const settledTaskDeleteIds = source.settledTaskDeleteIds === undefined
|
|
95
|
+
? []
|
|
96
|
+
: source.settledTaskDeleteIds;
|
|
97
|
+
if (!Array.isArray(settledTaskDeleteIds)
|
|
98
|
+
|| settledTaskDeleteIds.some((taskId) => (typeof taskId !== 'string'
|
|
99
|
+
|| taskId.trim().length === 0
|
|
100
|
+
|| taskId !== taskId.trim()))
|
|
101
|
+
|| new Set(settledTaskDeleteIds).size !== settledTaskDeleteIds.length
|
|
102
|
+
|| settledTaskDeleteIds.some((taskId) => !expectedTaskIds.includes(taskId))) {
|
|
103
|
+
throw new TypeError('The V2 push terminal task settlement identities are invalid.');
|
|
104
|
+
}
|
|
105
|
+
const settledIds = new Set(settledTaskDeleteIds);
|
|
106
|
+
return {
|
|
107
|
+
appliedTaskUpserts: parseWorkspaceSyncEngineAppliedTaskUpserts(value, expectedTaskIds.filter((taskId) => !settledIds.has(taskId))),
|
|
108
|
+
settledTaskDeleteIds: [...settledTaskDeleteIds],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
89
111
|
function taskSettlementProjection(task, preserveReferenceNumber) {
|
|
90
112
|
const { updatedAt: _updatedAt, ...stableTask } = structuredClone(task);
|
|
91
113
|
if (!preserveReferenceNumber)
|
|
@@ -136,7 +158,17 @@ export function createWorkspaceSyncEngineV2SettlementIdentityProjection(input) {
|
|
|
136
158
|
if (!operationIdentity || operationIdentity !== input.operationIdentity) {
|
|
137
159
|
throw new TypeError('The V2 push settlement operation identity is invalid.');
|
|
138
160
|
}
|
|
139
|
-
const
|
|
161
|
+
const allExpectedTaskUpserts = input.changes.filter((change) => change.op === 'upsert');
|
|
162
|
+
const expectedTaskIds = allExpectedTaskUpserts.map((change) => String(change.task.id || '').trim());
|
|
163
|
+
const settledTaskDeleteIds = [...(input.settledTaskDeleteIds || [])];
|
|
164
|
+
if ((settledTaskDeleteIds.length > 0
|
|
165
|
+
&& !input.excludedProjections?.includes('task/root-v1'))
|
|
166
|
+
|| settledTaskDeleteIds.some((taskId) => !expectedTaskIds.includes(taskId))
|
|
167
|
+
|| new Set(settledTaskDeleteIds).size !== settledTaskDeleteIds.length) {
|
|
168
|
+
throw new TypeError('The V2 push terminal task settlement identities are invalid.');
|
|
169
|
+
}
|
|
170
|
+
const settledIds = new Set(settledTaskDeleteIds);
|
|
171
|
+
const expectedTaskUpserts = allExpectedTaskUpserts.filter((change) => !settledIds.has(String(change.task.id || '').trim()));
|
|
140
172
|
if (expectedTaskUpserts.length !== input.appliedTaskUpserts.length) {
|
|
141
173
|
throw new TypeError('The V2 push authoritative settlement projection is incomplete.');
|
|
142
174
|
}
|
|
@@ -167,5 +199,6 @@ export function createWorkspaceSyncEngineV2SettlementIdentityProjection(input) {
|
|
|
167
199
|
version: 1,
|
|
168
200
|
operationIdentity,
|
|
169
201
|
taskUpserts: expectedProjection,
|
|
202
|
+
...(settledTaskDeleteIds.length > 0 ? { settledTaskDeleteIds } : {}),
|
|
170
203
|
};
|
|
171
204
|
}
|
|
@@ -2,7 +2,7 @@ import { workspaceSyncGatewayOperationClassFor, workspaceSyncGatewayTimeoutMs, }
|
|
|
2
2
|
import { canonicalWorkspaceSyncJson } from '../v3/canonicalWorkspaceSyncJson.js';
|
|
3
3
|
import { WORKSPACE_SYNC_V3_COMBINED_COVERAGE_SELECTOR, resolveWorkspaceSyncV3CombinedCoverageForExclusions, } from '../v3/workspaceSyncV3CombinedWireContract.js';
|
|
4
4
|
import { WORKSPACE_SYNC_ENGINE_TRANSFER_COMPLETION_VERSION, } from './workspaceSyncEngineTransferCompletion.js';
|
|
5
|
-
import {
|
|
5
|
+
import { parseWorkspaceSyncEnginePushTaskSettlement, parseWorkspaceSyncEngineV2PushBoundary, workspaceSyncEngineExpectedTaskEchoIds, } from './workspaceSyncEnginePushCompletion.js';
|
|
6
6
|
const PERMIT_DEADLINE_SLACK_MS = 5_000;
|
|
7
7
|
function isPlainObject(value) {
|
|
8
8
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -311,7 +311,7 @@ export function createServerWorkspaceSyncEngineRemoteServices(options) {
|
|
|
311
311
|
requireAcceptedJsonObject: true,
|
|
312
312
|
validateAcceptedData: (value) => {
|
|
313
313
|
try {
|
|
314
|
-
|
|
314
|
+
parseWorkspaceSyncEnginePushTaskSettlement(value, expectedTaskEchoIds);
|
|
315
315
|
return true;
|
|
316
316
|
}
|
|
317
317
|
catch {
|
|
@@ -623,7 +623,7 @@ export function createWorkspaceSyncEngineServerRunnerOperations(options) {
|
|
|
623
623
|
acknowledgedOperationIdentities.length;
|
|
624
624
|
}
|
|
625
625
|
},
|
|
626
|
-
onAcceptedBatchSettled: async ({ operationIdentity, settlementIdentityFingerprint, appliedTaskUpserts, transferReceipt, pushBoundary, excludedProjections, }) => {
|
|
626
|
+
onAcceptedBatchSettled: async ({ operationIdentity, settlementIdentityFingerprint, appliedTaskUpserts, settledTaskDeleteIds, transferReceipt, pushBoundary, excludedProjections, }) => {
|
|
627
627
|
const pendingV2Push = checkpoint.pendingV2Push;
|
|
628
628
|
if (!pendingV2Push
|
|
629
629
|
|| pendingV2Push.settlementVersion !== 1
|
|
@@ -661,6 +661,9 @@ export function createWorkspaceSyncEngineServerRunnerOperations(options) {
|
|
|
661
661
|
operationIdentity,
|
|
662
662
|
settlementIdentityFingerprint,
|
|
663
663
|
appliedTaskUpserts: structuredClone(appliedTaskUpserts),
|
|
664
|
+
...(settledTaskDeleteIds.length > 0 ? {
|
|
665
|
+
settledTaskDeleteIds: [...settledTaskDeleteIds],
|
|
666
|
+
} : {}),
|
|
664
667
|
},
|
|
665
668
|
],
|
|
666
669
|
},
|
|
@@ -17,6 +17,7 @@ export interface WorkspaceSyncV2DispatchOptions {
|
|
|
17
17
|
coexistenceDirection?: TaskMetadataCoexistenceDirection;
|
|
18
18
|
assertedActiveV3TaskMetadataIds?: ReadonlySet<string>;
|
|
19
19
|
assertedActiveV3TaskDeleteIds?: ReadonlySet<string>;
|
|
20
|
+
candidateTerminalV3TaskDeleteIds?: ReadonlySet<string>;
|
|
20
21
|
assertedActiveV3TaskLifecycleIds?: ReadonlySet<string>;
|
|
21
22
|
assertedActiveV3TaskReopenAssigneeIds?: ReadonlySet<string>;
|
|
22
23
|
assertedActiveV3Taxonomy?: boolean;
|
|
@@ -156,11 +156,40 @@ export function createWorkspaceSyncV2ChangeDispatcher(dependencies) {
|
|
|
156
156
|
throw createSyncRouteError('Cloud pull contained content from an excluded v2 projection.', { excludedProjections: Array.from(v2ExcludedProjections) }, { code: 'SYNC_V2_EXCLUDED_PROJECTION_PRESENT', statusCode: 409 });
|
|
157
157
|
}
|
|
158
158
|
const taskMutationsForApply = prepareWorkspaceSyncV2TaskCarriers(applyModel.taskMutations, v2ExcludedProjections, (taskId) => core.getTask(taskId, workspaceId));
|
|
159
|
-
if (v2ExcludedProjections.has('task/root-v1')
|
|
159
|
+
if (v2ExcludedProjections.has('task/root-v1')
|
|
160
|
+
&& !(options.coexistenceDirection === 'cloud-push'
|
|
161
|
+
&& options.candidateTerminalV3TaskDeleteIds?.size)) {
|
|
160
162
|
assertWorkspaceSyncV2TaskCarrierRootsExist(taskMutationsForApply, (taskId) => Boolean(core.getTask(taskId, workspaceId)));
|
|
161
163
|
}
|
|
162
164
|
return { applyModel, taskMutationsForApply, v2ExcludedProjections };
|
|
163
165
|
};
|
|
166
|
+
const resolveTaskCarrierSettlement = (workspaceId, taskMutations, ownership, options) => {
|
|
167
|
+
const candidates = options.coexistenceDirection === 'cloud-push'
|
|
168
|
+
? options.candidateTerminalV3TaskDeleteIds
|
|
169
|
+
: undefined;
|
|
170
|
+
if (!candidates?.size) {
|
|
171
|
+
return { taskMutationsForSettlement: taskMutations, settledTaskDeleteIds: [] };
|
|
172
|
+
}
|
|
173
|
+
const terminalOwnership = new Set(ownership
|
|
174
|
+
.filter((entry) => entry.activeDeleteOperation === true || entry.deleteOwned === true)
|
|
175
|
+
.map((entry) => entry.taskId));
|
|
176
|
+
const settledTaskDeleteIds = candidates
|
|
177
|
+
? taskMutations.upserts
|
|
178
|
+
.map((mutation) => mutation.task.id)
|
|
179
|
+
.filter((taskId) => (candidates.has(taskId)
|
|
180
|
+
&& terminalOwnership.has(taskId)
|
|
181
|
+
&& !core.getTask(taskId, workspaceId)))
|
|
182
|
+
: [];
|
|
183
|
+
const settledIds = new Set(settledTaskDeleteIds);
|
|
184
|
+
const taskMutationsForSettlement = settledIds.size > 0
|
|
185
|
+
? {
|
|
186
|
+
...taskMutations,
|
|
187
|
+
upserts: taskMutations.upserts.filter((mutation) => !settledIds.has(mutation.task.id)),
|
|
188
|
+
}
|
|
189
|
+
: taskMutations;
|
|
190
|
+
assertWorkspaceSyncV2TaskCarrierRootsExist(taskMutationsForSettlement, (taskId) => Boolean(core.getTask(taskId, workspaceId)));
|
|
191
|
+
return { taskMutationsForSettlement, settledTaskDeleteIds };
|
|
192
|
+
};
|
|
164
193
|
const prepareTaskSnapshotForDispatch = (workspaceId, taskMutationsForApply, v2ExcludedProjections, options) => {
|
|
165
194
|
const prepared = prepareTaskSyncSnapshotApply({
|
|
166
195
|
core,
|
|
@@ -317,6 +346,7 @@ export function createWorkspaceSyncV2ChangeDispatcher(dependencies) {
|
|
|
317
346
|
taskforceAgentPushActorUserId: String(options.actorUserId || '').trim() || null,
|
|
318
347
|
taskforceAgentPushAccessPreflighted: true,
|
|
319
348
|
} : {}),
|
|
349
|
+
coexistenceDirection: options.coexistenceDirection,
|
|
320
350
|
recordSyncDiagnostic,
|
|
321
351
|
createSyncRouteError,
|
|
322
352
|
});
|
|
@@ -334,14 +364,15 @@ export function createWorkspaceSyncV2ChangeDispatcher(dependencies) {
|
|
|
334
364
|
if (options.coexistenceDirection === 'cloud-push'
|
|
335
365
|
&& taskMutationsForApply.upserts.some((mutation) => mutation.durableRevision))
|
|
336
366
|
throw new Error('Task durable revisions are only accepted from the cloud pull feed.');
|
|
367
|
+
const { taskMutationsForSettlement, settledTaskDeleteIds, } = resolveTaskCarrierSettlement(workspaceId, taskMutationsForApply, ownership, options);
|
|
337
368
|
const durablePull = options.coexistenceDirection === 'local-pull'
|
|
338
369
|
? prepareDurableTaskPullMutations({
|
|
339
370
|
store: durabilityStore,
|
|
340
371
|
tenantId: core.getTenantId(),
|
|
341
372
|
workspaceId,
|
|
342
|
-
taskMutations:
|
|
373
|
+
taskMutations: taskMutationsForSettlement,
|
|
343
374
|
})
|
|
344
|
-
: { taskMutations:
|
|
375
|
+
: { taskMutations: taskMutationsForSettlement, pendingRevisions: [] };
|
|
345
376
|
const prepared = prepareTaskSyncSnapshotApply({
|
|
346
377
|
core,
|
|
347
378
|
workspaceId,
|
|
@@ -360,7 +391,7 @@ export function createWorkspaceSyncV2ChangeDispatcher(dependencies) {
|
|
|
360
391
|
ownership: durabilityStore.listTaskTaxonomyProjectionOwnership({
|
|
361
392
|
tenantId: core.getTenantId(),
|
|
362
393
|
workspaceId,
|
|
363
|
-
taskIds:
|
|
394
|
+
taskIds: taskMutationsForSettlement.upserts.map((mutation) => mutation.task.id),
|
|
364
395
|
}),
|
|
365
396
|
}),
|
|
366
397
|
v3OwnedTaskDeleteIds: resolveV3OwnedTaskDeleteIds({
|
|
@@ -395,7 +426,7 @@ export function createWorkspaceSyncV2ChangeDispatcher(dependencies) {
|
|
|
395
426
|
revisions: durablePull.pendingRevisions,
|
|
396
427
|
now: revisionNow,
|
|
397
428
|
});
|
|
398
|
-
return { prepared, result };
|
|
429
|
+
return { prepared, result, settledTaskDeleteIds };
|
|
399
430
|
},
|
|
400
431
|
});
|
|
401
432
|
})()
|
|
@@ -406,6 +437,7 @@ export function createWorkspaceSyncV2ChangeDispatcher(dependencies) {
|
|
|
406
437
|
workspaceId,
|
|
407
438
|
prepared: preparedTaskSnapshot,
|
|
408
439
|
}),
|
|
440
|
+
settledTaskDeleteIds: [],
|
|
409
441
|
};
|
|
410
442
|
const result = finalPreparedTaskSnapshot.result;
|
|
411
443
|
const committedTaskIds = finalPreparedTaskSnapshot.result.changedTaskIds || [];
|
|
@@ -479,7 +511,14 @@ export function createWorkspaceSyncV2ChangeDispatcher(dependencies) {
|
|
|
479
511
|
recordSyncDiagnostic,
|
|
480
512
|
createSyncRouteError,
|
|
481
513
|
});
|
|
482
|
-
return {
|
|
514
|
+
return {
|
|
515
|
+
result,
|
|
516
|
+
deleteCount,
|
|
517
|
+
emittedEventIds: Array.from(emittedEventIds),
|
|
518
|
+
...(finalPreparedTaskSnapshot.settledTaskDeleteIds.length > 0 ? {
|
|
519
|
+
settledTaskDeleteIds: finalPreparedTaskSnapshot.settledTaskDeleteIds,
|
|
520
|
+
} : {}),
|
|
521
|
+
};
|
|
483
522
|
}
|
|
484
523
|
finally {
|
|
485
524
|
unsubscribeRealtime();
|
|
@@ -777,6 +816,7 @@ export function createWorkspaceSyncV2ChangeDispatcher(dependencies) {
|
|
|
777
816
|
taskforceAgentPushActorUserId: String(options.actorUserId || '').trim() || null,
|
|
778
817
|
taskforceAgentPushAccessPreflighted: true,
|
|
779
818
|
} : {}),
|
|
819
|
+
coexistenceDirection: options.coexistenceDirection,
|
|
780
820
|
recordSyncDiagnostic,
|
|
781
821
|
createSyncRouteError,
|
|
782
822
|
});
|
|
@@ -794,14 +834,15 @@ export function createWorkspaceSyncV2ChangeDispatcher(dependencies) {
|
|
|
794
834
|
if (options.coexistenceDirection === 'cloud-push'
|
|
795
835
|
&& taskMutationsForApply.upserts.some((mutation) => mutation.durableRevision))
|
|
796
836
|
throw new Error('Task durable revisions are only accepted from the cloud pull feed.');
|
|
837
|
+
const { taskMutationsForSettlement, settledTaskDeleteIds, } = resolveTaskCarrierSettlement(workspaceId, taskMutationsForApply, ownership, options);
|
|
797
838
|
const durablePull = options.coexistenceDirection === 'local-pull'
|
|
798
839
|
? prepareDurableTaskPullMutations({
|
|
799
840
|
store: durabilityStore,
|
|
800
841
|
tenantId: core.getTenantId(),
|
|
801
842
|
workspaceId,
|
|
802
|
-
taskMutations:
|
|
843
|
+
taskMutations: taskMutationsForSettlement,
|
|
803
844
|
})
|
|
804
|
-
: { taskMutations:
|
|
845
|
+
: { taskMutations: taskMutationsForSettlement, pendingRevisions: [] };
|
|
805
846
|
const prepared = prepareTaskSyncSnapshotApply({
|
|
806
847
|
core,
|
|
807
848
|
workspaceId,
|
|
@@ -820,7 +861,7 @@ export function createWorkspaceSyncV2ChangeDispatcher(dependencies) {
|
|
|
820
861
|
ownership: durabilityStore.listTaskTaxonomyProjectionOwnership({
|
|
821
862
|
tenantId: core.getTenantId(),
|
|
822
863
|
workspaceId,
|
|
823
|
-
taskIds:
|
|
864
|
+
taskIds: taskMutationsForSettlement.upserts.map((mutation) => mutation.task.id),
|
|
824
865
|
}),
|
|
825
866
|
}),
|
|
826
867
|
v3OwnedTaskDeleteIds: resolveV3OwnedTaskDeleteIds({
|
|
@@ -855,7 +896,7 @@ export function createWorkspaceSyncV2ChangeDispatcher(dependencies) {
|
|
|
855
896
|
revisions: durablePull.pendingRevisions,
|
|
856
897
|
now: revisionNow,
|
|
857
898
|
});
|
|
858
|
-
return { prepared, result };
|
|
899
|
+
return { prepared, result, settledTaskDeleteIds };
|
|
859
900
|
},
|
|
860
901
|
});
|
|
861
902
|
})()
|
|
@@ -867,6 +908,7 @@ export function createWorkspaceSyncV2ChangeDispatcher(dependencies) {
|
|
|
867
908
|
prepared: preparedTaskSnapshot,
|
|
868
909
|
deferSideEffects: true,
|
|
869
910
|
}),
|
|
911
|
+
settledTaskDeleteIds: [],
|
|
870
912
|
};
|
|
871
913
|
const protectedTaskCount = finalPreparedTaskSnapshot.prepared.metadataPreservedTaskIds.length;
|
|
872
914
|
const protectedLifecycleTaskCount = finalPreparedTaskSnapshot.prepared.lifecyclePreservedTaskIds.length;
|
|
@@ -932,6 +974,9 @@ export function createWorkspaceSyncV2ChangeDispatcher(dependencies) {
|
|
|
932
974
|
result: finalPreparedTaskSnapshot.result,
|
|
933
975
|
deleteCount,
|
|
934
976
|
emittedEventIds: [],
|
|
977
|
+
...(finalPreparedTaskSnapshot.settledTaskDeleteIds.length > 0 ? {
|
|
978
|
+
settledTaskDeleteIds: finalPreparedTaskSnapshot.settledTaskDeleteIds,
|
|
979
|
+
} : {}),
|
|
935
980
|
};
|
|
936
981
|
const beforeCommitResult = options.transactionHooks?.beforeCommit?.(result);
|
|
937
982
|
assertSynchronousTransactionResult(beforeCommitResult);
|
|
@@ -257,6 +257,7 @@ export declare function applyCollaborationSyncChanges(input: {
|
|
|
257
257
|
incomingAgentConversations?: Map<string, WorkspaceSyncAgentConversationSnapshot>;
|
|
258
258
|
taskforceAgentPushActorUserId?: string | null;
|
|
259
259
|
taskforceAgentPushAccessPreflighted?: boolean;
|
|
260
|
+
coexistenceDirection?: 'local-pull' | 'cloud-push';
|
|
260
261
|
recordSyncDiagnostic: RecordSyncDiagnostic;
|
|
261
262
|
createSyncRouteError: CreateSyncRouteError;
|
|
262
263
|
}): void;
|
|
@@ -1053,7 +1053,7 @@ export async function applyContentSyncChanges(input) {
|
|
|
1053
1053
|
}
|
|
1054
1054
|
}
|
|
1055
1055
|
export function applyCollaborationSyncChanges(input) {
|
|
1056
|
-
const { core, workspaceId, incomingDocumentReviewSessions, incomingDocumentReviewSessionDeletes, incomingDocumentReviewComments, incomingDocumentReviewCommentDeletes, incomingAnnotatedAttachmentSessions, incomingAnnotatedAttachmentSessionDeletes, incomingTaskforceAgents = new Map(), incomingAgentRoles = new Map(), incomingTaskforceAgentSkills = new Map(), incomingAgentConversations = new Map(), taskforceAgentPushActorUserId, taskforceAgentPushAccessPreflighted = false, recordSyncDiagnostic, createSyncRouteError, } = input;
|
|
1056
|
+
const { core, workspaceId, incomingDocumentReviewSessions, incomingDocumentReviewSessionDeletes, incomingDocumentReviewComments, incomingDocumentReviewCommentDeletes, incomingAnnotatedAttachmentSessions, incomingAnnotatedAttachmentSessionDeletes, incomingTaskforceAgents = new Map(), incomingAgentRoles = new Map(), incomingTaskforceAgentSkills = new Map(), incomingAgentConversations = new Map(), taskforceAgentPushActorUserId, taskforceAgentPushAccessPreflighted = false, coexistenceDirection, recordSyncDiagnostic, createSyncRouteError, } = input;
|
|
1057
1057
|
if (taskforceAgentPushActorUserId !== undefined && !taskforceAgentPushAccessPreflighted) {
|
|
1058
1058
|
assertTaskforceAgentSyncPushAccess({
|
|
1059
1059
|
agents: incomingTaskforceAgents.values(),
|
|
@@ -1141,6 +1141,48 @@ export function applyCollaborationSyncChanges(input) {
|
|
|
1141
1141
|
.digest('hex')
|
|
1142
1142
|
: '';
|
|
1143
1143
|
const metadataCompare = resolveSyncConflictOrdering(comment.updatedAt, String(existing?.updatedAt || '').trim(), incomingTieBreak, existingTieBreak);
|
|
1144
|
+
const parentCommentConflict = Boolean(existing
|
|
1145
|
+
&& comment.parentCommentId !== undefined
|
|
1146
|
+
&& comment.parentCommentId !== existing.parentCommentId);
|
|
1147
|
+
const nonThreadFieldsMatch = Boolean(existing
|
|
1148
|
+
&& comment.sessionId === existing.sessionId
|
|
1149
|
+
&& comment.body === existing.body
|
|
1150
|
+
&& JSON.stringify(comment.anchor ?? null) === JSON.stringify(existing.anchor ?? null)
|
|
1151
|
+
&& comment.order === existing.order
|
|
1152
|
+
&& (comment.authorActorId ?? null) === (existing.authorActorId ?? null)
|
|
1153
|
+
&& (comment.updatedByActorId ?? null) === (existing.updatedByActorId ?? null)
|
|
1154
|
+
&& (comment.deletedByActorId ?? null) === (existing.deletedByActorId ?? null)
|
|
1155
|
+
&& comment.createdAt === existing.createdAt
|
|
1156
|
+
&& comment.updatedAt === existing.updatedAt
|
|
1157
|
+
&& (comment.deletedAt ?? null) === (existing.deletedAt ?? null));
|
|
1158
|
+
const incomingIsLegacyProjectedRoot = Boolean(nonThreadFieldsMatch
|
|
1159
|
+
&& comment.parentCommentId === null
|
|
1160
|
+
&& comment.threadStatus === 'open'
|
|
1161
|
+
&& comment.threadStatusUpdatedAt === comment.createdAt
|
|
1162
|
+
&& !comment.resolvedAt
|
|
1163
|
+
&& !comment.resolvedByActorId);
|
|
1164
|
+
const existingIsLegacyProjectedRoot = Boolean(nonThreadFieldsMatch
|
|
1165
|
+
&& existing
|
|
1166
|
+
&& existing.parentCommentId === null
|
|
1167
|
+
&& existing.threadStatus === 'open'
|
|
1168
|
+
&& existing.threadStatusUpdatedAt === existing.createdAt
|
|
1169
|
+
&& !existing.resolvedAt
|
|
1170
|
+
&& !existing.resolvedByActorId);
|
|
1171
|
+
const preserveLocalReplyParent = parentCommentConflict
|
|
1172
|
+
&& coexistenceDirection === 'local-pull'
|
|
1173
|
+
&& Boolean(existing?.parentCommentId)
|
|
1174
|
+
&& incomingIsLegacyProjectedRoot;
|
|
1175
|
+
const reconcileAuthoritativeParent = parentCommentConflict
|
|
1176
|
+
&& coexistenceDirection === 'local-pull'
|
|
1177
|
+
&& !preserveLocalReplyParent;
|
|
1178
|
+
const reconcileLegacyCloudRoot = parentCommentConflict
|
|
1179
|
+
&& coexistenceDirection === 'cloud-push'
|
|
1180
|
+
&& existingIsLegacyProjectedRoot
|
|
1181
|
+
&& Boolean(comment.parentCommentId);
|
|
1182
|
+
const applyIncomingMetadata = metadataCompare >= 0
|
|
1183
|
+
&& !(parentCommentConflict
|
|
1184
|
+
&& coexistenceDirection === 'cloud-push'
|
|
1185
|
+
&& !reconcileLegacyCloudRoot);
|
|
1144
1186
|
const incomingLifecycleTieBreak = createHash('sha256')
|
|
1145
1187
|
.update(JSON.stringify({
|
|
1146
1188
|
threadStatus: comment.threadStatus,
|
|
@@ -1160,28 +1202,43 @@ export function applyCollaborationSyncChanges(input) {
|
|
|
1160
1202
|
const lifecycleCompare = comment.threadStatusUpdatedAt === undefined
|
|
1161
1203
|
? -1
|
|
1162
1204
|
: resolveSyncConflictOrdering(String(comment.threadStatusUpdatedAt || '').trim(), String(existing?.threadStatusUpdatedAt || '').trim(), incomingLifecycleTieBreak, existingLifecycleTieBreak);
|
|
1163
|
-
if (metadataCompare < 0
|
|
1205
|
+
if (metadataCompare < 0
|
|
1206
|
+
&& lifecycleCompare < 0
|
|
1207
|
+
&& !reconcileAuthoritativeParent
|
|
1208
|
+
&& !reconcileLegacyCloudRoot)
|
|
1164
1209
|
continue;
|
|
1165
|
-
|
|
1210
|
+
const commentForSync = {
|
|
1166
1211
|
id: comment.id,
|
|
1167
1212
|
tenantId: core.getTenantId(),
|
|
1168
1213
|
workspaceId,
|
|
1169
|
-
sessionId:
|
|
1170
|
-
body:
|
|
1171
|
-
anchor: (
|
|
1172
|
-
order:
|
|
1173
|
-
authorActorId:
|
|
1174
|
-
updatedByActorId:
|
|
1175
|
-
deletedByActorId:
|
|
1176
|
-
parentCommentId:
|
|
1214
|
+
sessionId: applyIncomingMetadata ? comment.sessionId : existing.sessionId,
|
|
1215
|
+
body: applyIncomingMetadata ? comment.body : existing.body,
|
|
1216
|
+
anchor: (applyIncomingMetadata ? comment.anchor : existing.anchor),
|
|
1217
|
+
order: applyIncomingMetadata ? comment.order : existing.order,
|
|
1218
|
+
authorActorId: applyIncomingMetadata ? comment.authorActorId : existing.authorActorId,
|
|
1219
|
+
updatedByActorId: applyIncomingMetadata ? comment.updatedByActorId : existing.updatedByActorId,
|
|
1220
|
+
deletedByActorId: applyIncomingMetadata ? comment.deletedByActorId : existing.deletedByActorId,
|
|
1221
|
+
parentCommentId: preserveLocalReplyParent
|
|
1222
|
+
? existing.parentCommentId
|
|
1223
|
+
: reconcileAuthoritativeParent || reconcileLegacyCloudRoot || applyIncomingMetadata
|
|
1224
|
+
? comment.parentCommentId
|
|
1225
|
+
: existing.parentCommentId,
|
|
1177
1226
|
threadStatus: lifecycleCompare >= 0 ? comment.threadStatus : undefined,
|
|
1178
1227
|
threadStatusUpdatedAt: lifecycleCompare >= 0 ? comment.threadStatusUpdatedAt : undefined,
|
|
1179
1228
|
resolvedAt: lifecycleCompare >= 0 ? comment.resolvedAt : undefined,
|
|
1180
1229
|
resolvedByActorId: lifecycleCompare >= 0 ? comment.resolvedByActorId : undefined,
|
|
1181
|
-
createdAt:
|
|
1182
|
-
updatedAt:
|
|
1183
|
-
deletedAt:
|
|
1184
|
-
}
|
|
1230
|
+
createdAt: applyIncomingMetadata ? comment.createdAt : existing.createdAt,
|
|
1231
|
+
updatedAt: applyIncomingMetadata ? comment.updatedAt : existing.updatedAt,
|
|
1232
|
+
deletedAt: applyIncomingMetadata ? comment.deletedAt : existing.deletedAt,
|
|
1233
|
+
};
|
|
1234
|
+
if (reconcileAuthoritativeParent || reconcileLegacyCloudRoot) {
|
|
1235
|
+
core.upsertDocumentReviewCommentForSync(commentForSync, {
|
|
1236
|
+
allowAuthoritativeThreadReconciliation: true,
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
else {
|
|
1240
|
+
core.upsertDocumentReviewCommentForSync(commentForSync);
|
|
1241
|
+
}
|
|
1185
1242
|
if (comment.parentCommentId) {
|
|
1186
1243
|
const rootExists = core.listDocumentReviewCommentsForSync({
|
|
1187
1244
|
workspaceId,
|
|
@@ -72,6 +72,7 @@ export interface WorkspacePushSyncInput {
|
|
|
72
72
|
operationIdentity: string;
|
|
73
73
|
settlementIdentityFingerprint: string;
|
|
74
74
|
appliedTaskUpserts: WorkspacePushSyncResult['appliedTaskUpserts'];
|
|
75
|
+
settledTaskDeleteIds: string[];
|
|
75
76
|
transferReceipt: WorkspaceSyncEngineRemoteTransferReceipt;
|
|
76
77
|
pushBoundary: WorkspaceSyncEngineV2PushBoundary;
|
|
77
78
|
excludedProjections?: readonly WorkspaceSyncV2ProjectionExclusion[];
|
package/dist/sync/syncService.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { applyLocalWorkspaceChanges, getLocalWorkspaceContentPullObligations, recordLocalWorkspaceContentPullObligationFailures, getLocalWorkspaceContentManifest, pullUserSettings, pushUserSettings, prepareWorkspacePushChanges, } from './cloudSyncApi.js';
|
|
2
2
|
import { workspaceSyncContentPullObligationId, } from './engine/workspaceSyncContentPullObligationStore.js';
|
|
3
3
|
import { createBrowserWorkspaceSyncEngineRemoteServices, } from './engine/workspaceSyncEngineRemoteServices.js';
|
|
4
|
-
import { createWorkspaceSyncEngineV2SettlementIdentityProjection, createWorkspaceSyncEngineV2PushBoundary,
|
|
4
|
+
import { createWorkspaceSyncEngineV2SettlementIdentityProjection, createWorkspaceSyncEngineV2PushBoundary, parseWorkspaceSyncEnginePushTaskSettlement, workspaceSyncEngineExpectedTaskEchoIds, } from './engine/workspaceSyncEnginePushCompletion.js';
|
|
5
5
|
import { encryptDocument } from './encryptionService.js';
|
|
6
6
|
import { decryptWorkspaceSyncContentChangeInPlace } from './workspaceSyncContentDecryption.js';
|
|
7
7
|
import { decideWorkspaceContentFetch, fingerprintWorkspaceContentBody, } from './workspaceContentManifest.js';
|
|
@@ -835,7 +835,8 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
835
835
|
emittedEventIds.add(eventId);
|
|
836
836
|
}
|
|
837
837
|
}
|
|
838
|
-
const
|
|
838
|
+
const taskSettlement = parseWorkspaceSyncEnginePushTaskSettlement(pushData, expectedTaskEchoIds);
|
|
839
|
+
const normalizedTaskUpserts = taskSettlement.appliedTaskUpserts;
|
|
839
840
|
appliedTaskUpserts.push(...normalizedTaskUpserts);
|
|
840
841
|
if (input.onAcceptedBatchSettled) {
|
|
841
842
|
if (!pushResult.transferReceipt) {
|
|
@@ -847,9 +848,11 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
847
848
|
operationIdentity: idempotencyKey,
|
|
848
849
|
changes: plainBatchChanges,
|
|
849
850
|
appliedTaskUpserts: normalizedTaskUpserts,
|
|
851
|
+
settledTaskDeleteIds: taskSettlement.settledTaskDeleteIds,
|
|
850
852
|
excludedProjections: effectiveV2ExcludedProjections,
|
|
851
853
|
})),
|
|
852
854
|
appliedTaskUpserts: normalizedTaskUpserts,
|
|
855
|
+
settledTaskDeleteIds: taskSettlement.settledTaskDeleteIds,
|
|
853
856
|
transferReceipt: pushResult.transferReceipt,
|
|
854
857
|
pushBoundary,
|
|
855
858
|
excludedProjections: effectiveV2ExcludedProjections,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { WorkflowExecutionAction, WorkflowStepCompletionActorType } from '../../core/WorkflowExecutionService.js';
|
|
2
|
-
import { type WorkflowExecutionStatus } from '../../storage/workflowStore.js';
|
|
2
|
+
import { type WorkflowExecutionEventRecord, type WorkflowExecutionStatus } from '../../storage/workflowStore.js';
|
|
3
3
|
import { type LocalWorkflowRuntimeOutboxCore } from './localWorkflowRuntimeOutboxService.js';
|
|
4
4
|
import { type DurableWorkflowRuntimeServiceCore } from './workflowRuntimeMutationService.js';
|
|
5
5
|
export type DurableWorkflowRuntimeSurface = 'http' | 'mcp';
|
|
@@ -53,11 +53,11 @@ export declare function createDurableWorkflowRuntimeMutationRouter(input: {
|
|
|
53
53
|
details?: Record<string, unknown>;
|
|
54
54
|
}): {
|
|
55
55
|
state: "pending" | "committed";
|
|
56
|
-
route: "local-outbox" | "cloud-acceptance";
|
|
56
|
+
route: "local-outbox" | "cloud-acceptance" | "local-projection-replay";
|
|
57
57
|
operationId: string;
|
|
58
58
|
execution: import("../../storage/workflowStore.js").WorkflowExecutionRecord;
|
|
59
|
-
events:
|
|
60
|
-
event:
|
|
59
|
+
events: WorkflowExecutionEventRecord[];
|
|
60
|
+
event: WorkflowExecutionEventRecord | null;
|
|
61
61
|
completedStep: import("../../storage/workflowStore.js").WorkflowStepRecord | null;
|
|
62
62
|
nextStep: import("../../storage/workflowStore.js").WorkflowStepRecord | null;
|
|
63
63
|
taskAssignee: string | null;
|
|
@@ -72,11 +72,11 @@ export declare function createDurableWorkflowRuntimeMutationRouter(input: {
|
|
|
72
72
|
evidenceCommentId?: string;
|
|
73
73
|
}): {
|
|
74
74
|
state: "pending" | "committed";
|
|
75
|
-
route: "local-outbox" | "cloud-acceptance";
|
|
75
|
+
route: "local-outbox" | "cloud-acceptance" | "local-projection-replay";
|
|
76
76
|
operationId: string;
|
|
77
77
|
execution: import("../../storage/workflowStore.js").WorkflowExecutionRecord;
|
|
78
|
-
events:
|
|
79
|
-
event:
|
|
78
|
+
events: WorkflowExecutionEventRecord[];
|
|
79
|
+
event: WorkflowExecutionEventRecord | null;
|
|
80
80
|
completedStep: import("../../storage/workflowStore.js").WorkflowStepRecord | null;
|
|
81
81
|
nextStep: import("../../storage/workflowStore.js").WorkflowStepRecord | null;
|
|
82
82
|
taskAssignee: string | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WorkflowStore } from '../../storage/workflowStore.js';
|
|
1
|
+
import { WorkflowStore, } from '../../storage/workflowStore.js';
|
|
2
2
|
import { LocalSyncIdentityStore } from './localSyncIdentityStore.js';
|
|
3
3
|
import { canonicalWorkspaceSyncJson } from './canonicalWorkspaceSyncJson.js';
|
|
4
4
|
import { enqueueLocalWorkflowRuntimeMutation, } from './localWorkflowRuntimeOutboxService.js';
|
|
@@ -110,6 +110,31 @@ function taskTarget(input) {
|
|
|
110
110
|
baseLifecycleRevision: coordinates.lifecycleRevision,
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
|
+
function matchCompletedStepReplay(input) {
|
|
114
|
+
const completedEvent = input.events.find((event) => event.idempotencyKey === `${input.operationId}:step-completed`);
|
|
115
|
+
const terminalEvent = input.events.find((event) => (event.idempotencyKey === `${input.operationId}:step-started`
|
|
116
|
+
|| event.idempotencyKey === `${input.operationId}:execution-completed`));
|
|
117
|
+
if (!completedEvent && !terminalEvent)
|
|
118
|
+
return 'absent';
|
|
119
|
+
if (!completedEvent
|
|
120
|
+
|| !terminalEvent
|
|
121
|
+
|| completedEvent.eventType !== 'step-completed'
|
|
122
|
+
|| (terminalEvent.eventType !== 'step-started'
|
|
123
|
+
&& terminalEvent.eventType !== 'execution-completed')
|
|
124
|
+
|| completedEvent.actorId !== input.actorId
|
|
125
|
+
|| terminalEvent.actorId !== input.actorId
|
|
126
|
+
|| (input.expectedStepId !== undefined && completedEvent.stepId !== input.expectedStepId)) {
|
|
127
|
+
return 'conflict';
|
|
128
|
+
}
|
|
129
|
+
const evidence = completedEvent.details.evidence;
|
|
130
|
+
const replayCommentId = evidence
|
|
131
|
+
&& typeof evidence === 'object'
|
|
132
|
+
&& !Array.isArray(evidence)
|
|
133
|
+
&& evidence.type === 'comment'
|
|
134
|
+
? String(evidence.commentId || '')
|
|
135
|
+
: '';
|
|
136
|
+
return (input.evidenceCommentId || '') === replayCommentId ? 'exact' : 'conflict';
|
|
137
|
+
}
|
|
113
138
|
function buildResult(input) {
|
|
114
139
|
const workflowStore = new WorkflowStore(input.core.getDatabaseAdapter(), input.core.getTenantId());
|
|
115
140
|
const assignmentSnapshot = workflowStore.getAssignmentForExecutionSync(input.workspaceId, input.executionId);
|
|
@@ -120,7 +145,8 @@ function buildResult(input) {
|
|
|
120
145
|
const completedStep = completedEvent?.stepId
|
|
121
146
|
? assignmentSnapshot.steps.find((step) => step.id === completedEvent.stepId) || null
|
|
122
147
|
: null;
|
|
123
|
-
const nextStep = assignmentSnapshot.execution.
|
|
148
|
+
const nextStep = assignmentSnapshot.execution.status === 'running'
|
|
149
|
+
&& assignmentSnapshot.execution.currentStepId
|
|
124
150
|
? assignmentSnapshot.steps.find((step) => step.id === assignmentSnapshot.execution.currentStepId) || null
|
|
125
151
|
: null;
|
|
126
152
|
const taskSnapshot = input.core.getTask(assignmentSnapshot.execution.taskId, input.workspaceId);
|
|
@@ -338,18 +364,15 @@ export function createDurableWorkflowRuntimeMutationRouter(input) {
|
|
|
338
364
|
if (!snapshot)
|
|
339
365
|
throw new DurableWorkflowRuntimeExecutionNotFoundError();
|
|
340
366
|
const committed = readCommittedCloudResponse();
|
|
367
|
+
const replayMatch = matchCompletedStepReplay({
|
|
368
|
+
events: snapshot.events,
|
|
369
|
+
operationId,
|
|
370
|
+
actorId,
|
|
371
|
+
expectedStepId: args.expectedStepId,
|
|
372
|
+
evidenceCommentId: args.evidenceCommentId,
|
|
373
|
+
});
|
|
341
374
|
if (committed) {
|
|
342
|
-
|
|
343
|
-
const terminalEvent = snapshot.events.find((event) => (event.idempotencyKey === `${operationId}:step-started`
|
|
344
|
-
|| event.idempotencyKey === `${operationId}:execution-completed`));
|
|
345
|
-
if (!completedEvent
|
|
346
|
-
|| !terminalEvent
|
|
347
|
-
|| completedEvent.actorId !== actorId
|
|
348
|
-
|| terminalEvent.actorId !== actorId
|
|
349
|
-
|| (args.expectedStepId !== undefined && completedEvent.stepId !== args.expectedStepId)
|
|
350
|
-
|| (args.evidenceCommentId !== undefined
|
|
351
|
-
&& completedEvent.details.evidence?.commentId
|
|
352
|
-
!== args.evidenceCommentId)) {
|
|
375
|
+
if (replayMatch !== 'exact') {
|
|
353
376
|
throw new Error('Operation identity is assigned to a different workflow step command.');
|
|
354
377
|
}
|
|
355
378
|
return buildResult({
|
|
@@ -363,6 +386,20 @@ export function createDurableWorkflowRuntimeMutationRouter(input) {
|
|
|
363
386
|
replayed: true,
|
|
364
387
|
});
|
|
365
388
|
}
|
|
389
|
+
if (!existing && replayMatch !== 'absent') {
|
|
390
|
+
if (replayMatch === 'conflict') {
|
|
391
|
+
throw new Error('Operation identity is assigned to a different workflow step command.');
|
|
392
|
+
}
|
|
393
|
+
return buildResult({
|
|
394
|
+
core: input.core,
|
|
395
|
+
workspaceId,
|
|
396
|
+
executionId: args.executionId,
|
|
397
|
+
operationId,
|
|
398
|
+
state: 'committed',
|
|
399
|
+
route: 'local-projection-replay',
|
|
400
|
+
replayed: true,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
366
403
|
const expectedStepId = args.expectedStepId || snapshot.execution.currentStepId;
|
|
367
404
|
if (!expectedStepId)
|
|
368
405
|
throw new Error('Workflow execution current step is unavailable.');
|
|
@@ -43,11 +43,14 @@ function appendEligibleBlockedChains(input) {
|
|
|
43
43
|
workspaceId: input.workspaceId,
|
|
44
44
|
})) {
|
|
45
45
|
try {
|
|
46
|
+
const blocked = input.store.readOutboxOperation(identity);
|
|
46
47
|
input.store.supersedeBlockedOutboxChain({
|
|
47
48
|
identity,
|
|
48
49
|
recoveryBatchId: input.recovery.recoveryBatchId,
|
|
49
50
|
now: input.now,
|
|
50
|
-
replayPolicy: '
|
|
51
|
+
replayPolicy: blocked?.lastErrorCode === 'WORKFLOW_RUNTIME_SEMANTIC_REPLAY_REQUIRED'
|
|
52
|
+
? 'authoritative-workflow-runtime'
|
|
53
|
+
: 'task-root',
|
|
51
54
|
appendToExistingRecovery: true,
|
|
52
55
|
});
|
|
53
56
|
}
|
|
@@ -104,11 +107,14 @@ export function prepareLocalOutboxRecovery(input) {
|
|
|
104
107
|
for (const root of blockedRoots) {
|
|
105
108
|
const candidateBatchId = input.createRecoveryBatchId();
|
|
106
109
|
try {
|
|
110
|
+
const blocked = input.store.readOutboxOperation(root);
|
|
107
111
|
input.store.supersedeBlockedOutboxChain({
|
|
108
112
|
identity: root,
|
|
109
113
|
recoveryBatchId: candidateBatchId,
|
|
110
114
|
now: input.now,
|
|
111
|
-
replayPolicy: '
|
|
115
|
+
replayPolicy: blocked?.lastErrorCode === 'WORKFLOW_RUNTIME_SEMANTIC_REPLAY_REQUIRED'
|
|
116
|
+
? 'authoritative-workflow-runtime'
|
|
117
|
+
: 'task-root',
|
|
112
118
|
});
|
|
113
119
|
recoveryBatchId = candidateBatchId;
|
|
114
120
|
break;
|