@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
|
@@ -16,7 +16,9 @@ export interface LocalTaskCreateOutboxCore {
|
|
|
16
16
|
allowV3OwnedCreate?: boolean;
|
|
17
17
|
preferExplicitAttachmentAssetId?: boolean;
|
|
18
18
|
}): TaskItem;
|
|
19
|
-
installDurableTaskCreateAuthoritativeForSync(task: TaskItem, workspaceId: string
|
|
19
|
+
installDurableTaskCreateAuthoritativeForSync(task: TaskItem, workspaceId: string, options?: {
|
|
20
|
+
preserveDeletedSuccessor?: boolean;
|
|
21
|
+
}): TaskItem;
|
|
20
22
|
upsertEntityEventForSync(event: EntityEvent, options?: {
|
|
21
23
|
allowAuthoritativeReplacement?: boolean;
|
|
22
24
|
}): unknown;
|
|
@@ -356,10 +356,15 @@ export function applyAuthoritativeTaskCreateAndAck(input) {
|
|
|
356
356
|
canonicalFingerprint: fingerprintWorkspaceSyncPayload(canonicalDependentActivities[index]),
|
|
357
357
|
}))],
|
|
358
358
|
applyAuthoritative: () => {
|
|
359
|
+
const hasDeletedSuccessor = input.store.listActiveOutboxSuccessors(input.identity).some((successor) => (successor.domain === 'task'
|
|
360
|
+
&& successor.entityId === task.id
|
|
361
|
+
&& successor.mutationKind === 'delete'));
|
|
359
362
|
const updated = input.core.installDurableTaskCreateAuthoritativeForSync({
|
|
360
363
|
...task,
|
|
361
364
|
localReferenceNumber: null,
|
|
362
|
-
}, input.identity.workspaceId
|
|
365
|
+
}, input.identity.workspaceId, {
|
|
366
|
+
preserveDeletedSuccessor: hasDeletedSuccessor,
|
|
367
|
+
});
|
|
363
368
|
const installedWithAttachments = canonicalAttachmentProjection
|
|
364
369
|
? input.core.updateTask(task.id, {
|
|
365
370
|
attachments: input.core.resolveTaskAttachmentLinksForDurableSync(canonicalAttachmentProjection.links, input.identity.workspaceId, { adoptAuthoritativeReferenceNumber: true }),
|
|
@@ -99,6 +99,7 @@ export function enqueueLocalTaskDeleteMutation(input) {
|
|
|
99
99
|
entityId: taskId,
|
|
100
100
|
mutationKind: 'delete',
|
|
101
101
|
revisionSource: 'current',
|
|
102
|
+
queuedTaskSuccessor: true,
|
|
102
103
|
ownership: { taskMetadata: true, taskLifecycle: true },
|
|
103
104
|
payload: { relationships },
|
|
104
105
|
now: input.now,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TaskEvent, TaskItem } from '../../core/Taskforce.js';
|
|
1
|
+
import type { DeletedTaskRecord, TaskEvent, TaskItem } from '../../core/Taskforce.js';
|
|
2
2
|
import { type DurableTaskLifecycleMutationCore, type DurableTaskLifecycleMutationKind } from '../../core/TaskDurableLifecycleMutationService.js';
|
|
3
3
|
import { type DurableTaskWorkflowMutationCore, type DurableTaskWorkflowMutationKind } from '../../core/TaskDurableWorkflowMutationService.js';
|
|
4
4
|
import { WorkspaceSyncDurabilityStore, type WorkspaceSyncOperationIdentity, type WorkspaceSyncOutboxRecord } from './syncDurabilityStore.js';
|
|
@@ -16,6 +16,7 @@ export declare function normalizeLocalTaskLifecyclePayload(mutationKind: LocalTa
|
|
|
16
16
|
};
|
|
17
17
|
};
|
|
18
18
|
export interface LocalTaskLifecycleOutboxCore extends DurableTaskLifecycleMutationCore, DurableTaskWorkflowMutationCore {
|
|
19
|
+
getDeletedTask(id: string, workspaceId?: string): DeletedTaskRecord | null;
|
|
19
20
|
installDurableTaskLifecycleAuthoritativeForSync(task: TaskItem, mutationKind: LocalTaskLifecycleMutationKind, workspaceId: string): TaskItem;
|
|
20
21
|
upsertEntityEventForSync(event: TaskEvent, options?: {
|
|
21
22
|
allowAuthoritativeReplacement?: boolean;
|
|
@@ -276,9 +276,13 @@ export function applyAuthoritativeTaskLifecycleAndAck(input) {
|
|
|
276
276
|
canonicalFingerprint: fingerprintWorkspaceSyncPayload(serializeTaskActivityForSync(activity)),
|
|
277
277
|
}] : [])],
|
|
278
278
|
applyAuthoritative: () => {
|
|
279
|
-
const
|
|
279
|
+
const successors = input.store.listActiveOutboxSuccessors(input.identity);
|
|
280
|
+
const hasSuccessor = successors.length > 0;
|
|
280
281
|
const updated = hasSuccessor
|
|
281
282
|
? input.core.getTask(task.id, input.identity.workspaceId)
|
|
283
|
+
|| (successors.some((successor) => successor.mutationKind === 'delete')
|
|
284
|
+
? input.core.getDeletedTask(task.id, input.identity.workspaceId)?.taskSnapshot
|
|
285
|
+
: null)
|
|
282
286
|
: input.core.installDurableTaskLifecycleAuthoritativeForSync(task, input.mutationKind, input.identity.workspaceId);
|
|
283
287
|
if (!updated)
|
|
284
288
|
throw new LocalAuthoritativeTaskLifecycleError('Local task lifecycle projection is missing.');
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { EntityEvent, TaskItem } from '../../core/Taskforce.js';
|
|
1
|
+
import type { DeletedTaskRecord, EntityEvent, TaskItem } from '../../core/Taskforce.js';
|
|
2
2
|
import { WorkspaceSyncDurabilityStore, type WorkspaceSyncOperationIdentity } from './syncDurabilityStore.js';
|
|
3
3
|
export interface LocalTaskMetadataOutboxCore {
|
|
4
4
|
getTask(id: string, workspaceId?: string): TaskItem | null | undefined;
|
|
5
|
+
getDeletedTask(id: string, workspaceId?: string): DeletedTaskRecord | null;
|
|
5
6
|
updateTask(id: string, updates: Partial<TaskItem>, workspaceId?: string, options?: {
|
|
6
7
|
persistBackup?: boolean;
|
|
7
8
|
emitRealtime?: boolean;
|
|
@@ -196,8 +196,12 @@ export function applyAuthoritativeTaskMetadataAndAck(input) {
|
|
|
196
196
|
for (const dependentActivity of canonicalDependentActivities) {
|
|
197
197
|
input.core.upsertEntityEventForSync(dependentActivity, { allowAuthoritativeReplacement: true });
|
|
198
198
|
}
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
const successors = input.store.listActiveOutboxSuccessors(input.identity);
|
|
200
|
+
if (successors.length > 0) {
|
|
201
|
+
const provisional = input.core.getTask(taskId, input.identity.workspaceId)
|
|
202
|
+
|| (successors.some((successor) => successor.mutationKind === 'delete')
|
|
203
|
+
? input.core.getDeletedTask(taskId, input.identity.workspaceId)?.taskSnapshot
|
|
204
|
+
: null);
|
|
201
205
|
if (!provisional)
|
|
202
206
|
throw new DurableTaskMetadataMutationError('Task not found.', 'TASK_NOT_FOUND', 404);
|
|
203
207
|
return provisional;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { EntityEvent, TaskEvent, TaskItem } from '../../core/Taskforce.js';
|
|
1
|
+
import type { DeletedTaskRecord, EntityEvent, TaskEvent, TaskItem } from '../../core/Taskforce.js';
|
|
2
2
|
import { type DurableTaskStatusMutationCore, type DurableTaskStatusMutationPayload } from '../../core/TaskDurableStatusMutationService.js';
|
|
3
3
|
import { WorkspaceSyncDurabilityStore, type WorkspaceSyncOperationIdentity, type WorkspaceSyncOutboxRecord } from './syncDurabilityStore.js';
|
|
4
4
|
import type { DatabaseAdapter } from '../../storage/databaseAdapter.js';
|
|
5
5
|
export interface LocalTaskStatusOutboxCore extends DurableTaskStatusMutationCore {
|
|
6
|
+
getDeletedTask(id: string, workspaceId?: string): DeletedTaskRecord | null;
|
|
6
7
|
assertTaskDetailRevisionForUpdate?(id: string, expectedDetailRevision: string, workspaceId?: string): void;
|
|
7
8
|
getDatabaseAdapter(): DatabaseAdapter;
|
|
8
9
|
getTenantId(): string;
|
|
@@ -347,7 +347,11 @@ export function applyAuthoritativeTaskStatusAndAck(input) {
|
|
|
347
347
|
if (!validLifecycle || authoritative.metadataOwned !== expectsMetadataOwnership) {
|
|
348
348
|
throw new LocalAuthoritativeTaskStatusError('Authoritative task status state does not match the persisted request.');
|
|
349
349
|
}
|
|
350
|
-
const
|
|
350
|
+
const activeSuccessors = input.store.listActiveOutboxSuccessors(input.identity);
|
|
351
|
+
const localCanonical = input.core.getTask(task.id, input.identity.workspaceId)
|
|
352
|
+
|| (activeSuccessors.some((successor) => successor.mutationKind === 'delete')
|
|
353
|
+
? input.core.getDeletedTask(task.id, input.identity.workspaceId)?.taskSnapshot
|
|
354
|
+
: null);
|
|
351
355
|
if (!localCanonical)
|
|
352
356
|
throw new LocalAuthoritativeTaskStatusError('Local task status projection is missing.');
|
|
353
357
|
let canonicalMetadataIntent = null;
|
|
@@ -581,14 +585,20 @@ export function applyAuthoritativeTaskStatusAndAck(input) {
|
|
|
581
585
|
expectedTaskId: task.id,
|
|
582
586
|
});
|
|
583
587
|
}
|
|
584
|
-
const successors =
|
|
588
|
+
const successors = activeSuccessors;
|
|
585
589
|
const hasSuccessor = successors.length > 0;
|
|
586
|
-
|
|
590
|
+
const activeTask = hasSuccessor
|
|
587
591
|
? input.core.getTask(task.id, input.identity.workspaceId)
|
|
592
|
+
: null;
|
|
593
|
+
const deletedSnapshot = !activeTask && successors.some((successor) => successor.mutationKind === 'delete')
|
|
594
|
+
? input.core.getDeletedTask(task.id, input.identity.workspaceId)?.taskSnapshot
|
|
595
|
+
: null;
|
|
596
|
+
let updated = hasSuccessor
|
|
597
|
+
? activeTask || deletedSnapshot
|
|
588
598
|
: input.core.installDurableTaskStatusAuthoritativeForSync(task, payload, installAssignee, input.identity.workspaceId);
|
|
589
599
|
if (!updated)
|
|
590
600
|
throw new LocalAuthoritativeTaskStatusError('Local task status projection is missing.');
|
|
591
|
-
if (reviewHandoff && reviewCommentsPayload && reviewCommentActivity) {
|
|
601
|
+
if (reviewHandoff && reviewCommentsPayload && reviewCommentActivity && !deletedSnapshot) {
|
|
592
602
|
const localComments = serializeWorkspaceSyncV3TaskCommentsPayload({
|
|
593
603
|
taskId: updated.id,
|
|
594
604
|
comments: updated.comments || [],
|
|
@@ -612,7 +622,12 @@ export function applyAuthoritativeTaskStatusAndAck(input) {
|
|
|
612
622
|
input.core.upsertEntityEventForSync(reviewCommentActivity, { allowAuthoritativeReplacement: true });
|
|
613
623
|
activityReconciled = true;
|
|
614
624
|
}
|
|
615
|
-
if (
|
|
625
|
+
else if (reviewHandoff && reviewCommentActivity && deletedSnapshot) {
|
|
626
|
+
input.core.upsertEntityEventForSync(reviewCommentActivity, { allowAuthoritativeReplacement: true });
|
|
627
|
+
activityReconciled = true;
|
|
628
|
+
}
|
|
629
|
+
if (!deletedSnapshot
|
|
630
|
+
&& hasSuccessor
|
|
616
631
|
&& installAssignee
|
|
617
632
|
&& !successors.some((successor) => successor.ownsTaskAssignee)
|
|
618
633
|
&& normalizeAssignee(updated.assignee) !== normalizeAssignee(task.assignee)) {
|
|
@@ -90,6 +90,7 @@ export type WorkspaceSyncOutboxRecord = WorkspaceSyncOperationIdentity & {
|
|
|
90
90
|
initiativeMetadataRevisionRebaseCount?: number;
|
|
91
91
|
leaseOwner: string | null;
|
|
92
92
|
leaseExpiresAt: string | null;
|
|
93
|
+
lastErrorCode: string | null;
|
|
93
94
|
};
|
|
94
95
|
export type WorkspaceSyncOutboxOperationLookup = WorkspaceSyncOperationIdentity & {
|
|
95
96
|
status: string;
|
|
@@ -397,7 +398,7 @@ export declare class WorkspaceSyncDurabilityStore {
|
|
|
397
398
|
identity: WorkspaceSyncOperationIdentity;
|
|
398
399
|
recoveryBatchId: string;
|
|
399
400
|
now: string;
|
|
400
|
-
replayPolicy?: 'task-root';
|
|
401
|
+
replayPolicy?: 'task-root' | 'authoritative-workflow-runtime';
|
|
401
402
|
appendToExistingRecovery?: boolean;
|
|
402
403
|
}): WorkspaceSyncOutboxSupersession;
|
|
403
404
|
recordOutboxRecoveryBaseline(input: {
|
|
@@ -27,6 +27,7 @@ const TASK_AGGREGATE_SUCCESSOR_MUTATIONS = new Map([
|
|
|
27
27
|
const TASK_ROOT_SUCCESSOR_MUTATIONS = new Set([
|
|
28
28
|
'metadata-update', 'set-status', 'archive', 'unarchive', 'complete', 'reopen',
|
|
29
29
|
]);
|
|
30
|
+
const TASK_TERMINAL_SUCCESSOR_MUTATIONS = new Set(['delete']);
|
|
30
31
|
const TASK_CREATE_RECOVERY_ERROR_CODES = new Set([
|
|
31
32
|
'TASK_CREATE_ID_CONFLICT',
|
|
32
33
|
'WORKSPACE_SYNC_REVISION_CONFLICT',
|
|
@@ -795,7 +796,8 @@ export class WorkspaceSyncDurabilityStore {
|
|
|
795
796
|
payload_json, payload_fingerprint, canonical_intent_json, status, attempt_count,
|
|
796
797
|
initial_not_found_attempt_count,
|
|
797
798
|
CAST(enqueue_sequence AS TEXT) AS enqueue_sequence,
|
|
798
|
-
rebase_before_first_attempt, lease_owner, lease_expires_at
|
|
799
|
+
rebase_before_first_attempt, lease_owner, lease_expires_at,
|
|
800
|
+
last_error_code
|
|
799
801
|
FROM workspace_sync_outbox
|
|
800
802
|
WHERE tenant_id = ? AND workspace_id = ? AND client_id = ? AND operation_id = ?
|
|
801
803
|
`).get(...operationParams(identity));
|
|
@@ -1343,6 +1345,17 @@ export class WorkspaceSyncDurabilityStore {
|
|
|
1343
1345
|
}
|
|
1344
1346
|
}
|
|
1345
1347
|
}
|
|
1348
|
+
else if (input.replayPolicy === 'authoritative-workflow-runtime') {
|
|
1349
|
+
if (selected.length !== 1) {
|
|
1350
|
+
throw new Error('Workflow runtime recovery has intersecting intent that requires semantic history reconciliation.');
|
|
1351
|
+
}
|
|
1352
|
+
const candidate = selected[0];
|
|
1353
|
+
if (candidate.domain !== 'workflow-execution'
|
|
1354
|
+
|| candidate.mutation_kind !== 'complete-step'
|
|
1355
|
+
|| candidate.last_error_code !== 'WORKFLOW_RUNTIME_SEMANTIC_REPLAY_REQUIRED') {
|
|
1356
|
+
throw new Error('Workflow runtime recovery requires an exact authoritative semantic replay conflict.');
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1346
1359
|
this.db.prepare(`
|
|
1347
1360
|
INSERT INTO workspace_sync_outbox_recoveries (
|
|
1348
1361
|
tenant_id, workspace_id, recovery_batch_id,
|
|
@@ -1611,6 +1624,11 @@ export class WorkspaceSyncDurabilityStore {
|
|
|
1611
1624
|
}
|
|
1612
1625
|
}
|
|
1613
1626
|
const sources = recoverySources.filter((source) => {
|
|
1627
|
+
if (source.domain === 'workflow-execution'
|
|
1628
|
+
&& source.mutation_kind === 'complete-step'
|
|
1629
|
+
&& source.last_error_code === 'WORKFLOW_RUNTIME_SEMANTIC_REPLAY_REQUIRED') {
|
|
1630
|
+
return false;
|
|
1631
|
+
}
|
|
1614
1632
|
if (source.domain === 'task-comments' && source.mutation_kind === 'append') {
|
|
1615
1633
|
return !this.isTaskCommentAppendRecoveryIntentSatisfied(tenantId, workspaceId, source);
|
|
1616
1634
|
}
|
|
@@ -2591,6 +2609,12 @@ export class WorkspaceSyncDurabilityStore {
|
|
|
2591
2609
|
const queueableTaskRoot = domain === 'task'
|
|
2592
2610
|
&& TASK_ROOT_SUCCESSOR_MUTATIONS.has(mutationKind)
|
|
2593
2611
|
&& (additionalTargets.length === 0 || queueableTaskReviewHandoff);
|
|
2612
|
+
const queueableTaskDelete = domain === 'task'
|
|
2613
|
+
&& TASK_TERMINAL_SUCCESSOR_MUTATIONS.has(mutationKind)
|
|
2614
|
+
&& additionalTargets.length === 0
|
|
2615
|
+
&& ownsTaskMetadata
|
|
2616
|
+
&& ownsTaskLifecycle
|
|
2617
|
+
&& !ownsTaskAssignee;
|
|
2594
2618
|
const queueableTaskAggregate = Boolean(TASK_AGGREGATE_SUCCESSOR_MUTATIONS.get(domain)?.has(mutationKind))
|
|
2595
2619
|
&& ((domain === 'task-comments' && mutationKind === 'reconcile' && additionalTargets.length === 0)
|
|
2596
2620
|
|| (additionalTargets.length === 1
|
|
@@ -2601,7 +2625,8 @@ export class WorkspaceSyncDurabilityStore {
|
|
|
2601
2625
|
&& !taskAggregateRootTarget.ownsTaskLifecycle
|
|
2602
2626
|
&& !taskAggregateRootTarget.ownsTaskAssignee))
|
|
2603
2627
|
&& !ownsTaskMetadata && !ownsTaskLifecycle && !ownsTaskAssignee;
|
|
2604
|
-
if (queuedTaskSuccessor && (input.revisionSource !== 'current'
|
|
2628
|
+
if (queuedTaskSuccessor && (input.revisionSource !== 'current'
|
|
2629
|
+
|| (!queueableTaskRoot && !queueableTaskDelete && !queueableTaskAggregate))) {
|
|
2605
2630
|
throw new Error('Queued task successors require a recognized task root or aggregate operation.');
|
|
2606
2631
|
}
|
|
2607
2632
|
const additionalTargetKeys = new Set();
|
|
@@ -2813,11 +2838,13 @@ export class WorkspaceSyncDurabilityStore {
|
|
|
2813
2838
|
const isTaskRoot = activeDomain === 'task'
|
|
2814
2839
|
&& row.entity_id === entityId
|
|
2815
2840
|
&& (TASK_ROOT_SUCCESSOR_MUTATIONS.has(activeMutationKind)
|
|
2816
|
-
|| (
|
|
2841
|
+
|| (activeMutationKind === 'create'
|
|
2842
|
+
&& Number(row.owns_task_metadata) === 0
|
|
2843
|
+
&& Number(row.owns_task_lifecycle) === 0
|
|
2844
|
+
&& Number(row.owns_task_assignee) === 0))
|
|
2817
2845
|
&& Number(row.target_count) === 1;
|
|
2818
|
-
return isTaskRoot
|
|
2819
|
-
||
|
|
2820
|
-
|| isCompatibleWorkflowRuntimeAggregate(row);
|
|
2846
|
+
return isTaskRoot || (!queueableTaskDelete && (isCompatibleTaskAggregate(row)
|
|
2847
|
+
|| isCompatibleWorkflowRuntimeAggregate(row)));
|
|
2821
2848
|
});
|
|
2822
2849
|
const compatibleWorkflowRuntimeSuccessor = queuedWorkflowRuntimeSuccessor
|
|
2823
2850
|
&& active.every((row) => (row.domain === 'workflow-execution'
|
|
@@ -4914,6 +4941,7 @@ export class WorkspaceSyncDurabilityStore {
|
|
|
4914
4941
|
initiativeMetadataRevisionRebaseCount: Number(row.initiative_metadata_revision_rebase_count || 0),
|
|
4915
4942
|
leaseOwner: row.lease_owner == null ? null : String(row.lease_owner),
|
|
4916
4943
|
leaseExpiresAt: row.lease_expires_at == null ? null : String(row.lease_expires_at),
|
|
4944
|
+
lastErrorCode: row.last_error_code == null ? null : String(row.last_error_code),
|
|
4917
4945
|
};
|
|
4918
4946
|
}
|
|
4919
4947
|
}
|
|
@@ -53,6 +53,11 @@ export type PreparedWorkflowRuntimeMutation = {
|
|
|
53
53
|
terminalEventId: string;
|
|
54
54
|
taskActivityId: string;
|
|
55
55
|
};
|
|
56
|
+
export declare class WorkflowRuntimeSemanticReplayError extends Error {
|
|
57
|
+
readonly code = "WORKFLOW_RUNTIME_SEMANTIC_REPLAY_REQUIRED";
|
|
58
|
+
readonly statusCode = 409;
|
|
59
|
+
constructor();
|
|
60
|
+
}
|
|
56
61
|
export declare function prepareWorkflowRuntimeMutation(input: {
|
|
57
62
|
workflowStore: WorkflowStore;
|
|
58
63
|
workspaceId: string;
|
|
@@ -6,11 +6,42 @@ import { buildTaskActivityJournalEntry, serializeTaskActivityForSync, } from './
|
|
|
6
6
|
import { fingerprintWorkspaceSyncPayload, WORKSPACE_SYNC_CONTRACT_VERSION, } from './syncDurabilityStore.js';
|
|
7
7
|
import { buildWorkflowRuntimeEventId, buildWorkflowRuntimeTaskActivityId, } from './workflowRuntimeOperationIdentity.js';
|
|
8
8
|
import { fingerprintWorkflowRuntimePayload, serializeWorkflowExecutionEventSyncPayload, serializeWorkflowExecutionRootSyncPayload, validateWorkflowRuntimeAuthoritativeResult, validateWorkflowRuntimeMutationCommand, } from './workflowRuntimeSync.js';
|
|
9
|
+
export class WorkflowRuntimeSemanticReplayError extends Error {
|
|
10
|
+
code = 'WORKFLOW_RUNTIME_SEMANTIC_REPLAY_REQUIRED';
|
|
11
|
+
statusCode = 409;
|
|
12
|
+
constructor() {
|
|
13
|
+
super('This workflow command was already completed by another synchronized runtime.');
|
|
14
|
+
this.name = 'WorkflowRuntimeSemanticReplayError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function hasEquivalentCompletedStep(input) {
|
|
18
|
+
if (input.command.kind !== 'complete-step')
|
|
19
|
+
return false;
|
|
20
|
+
const events = input.workflowStore.listExecutionEvents(input.workspaceId, input.command.executionId);
|
|
21
|
+
const completed = events.find((event) => (event.idempotencyKey === `${input.command.idempotencyKey}:step-completed`));
|
|
22
|
+
const terminal = events.find((event) => (event.idempotencyKey === `${input.command.idempotencyKey}:step-started`
|
|
23
|
+
|| event.idempotencyKey === `${input.command.idempotencyKey}:execution-completed`));
|
|
24
|
+
if (!completed || !terminal)
|
|
25
|
+
return false;
|
|
26
|
+
const evidence = completed.details.evidence;
|
|
27
|
+
const evidenceCommentId = evidence
|
|
28
|
+
&& typeof evidence === 'object'
|
|
29
|
+
&& !Array.isArray(evidence)
|
|
30
|
+
&& evidence.type === 'comment'
|
|
31
|
+
? String(evidence.commentId || '')
|
|
32
|
+
: '';
|
|
33
|
+
return completed.actorId === input.command.actorId
|
|
34
|
+
&& terminal.actorId === input.command.actorId
|
|
35
|
+
&& completed.stepId === input.command.expectedStepId
|
|
36
|
+
&& evidenceCommentId === (input.command.evidenceCommentId || '');
|
|
37
|
+
}
|
|
9
38
|
export function prepareWorkflowRuntimeMutation(input) {
|
|
10
39
|
const before = input.workflowStore.getExecutionStateSnapshot(input.workspaceId, input.command.executionId);
|
|
11
40
|
if (!before)
|
|
12
41
|
throw new Error('Workflow execution not found.');
|
|
13
42
|
if (before.execution.status !== input.command.expectedStatus) {
|
|
43
|
+
if (hasEquivalentCompletedStep(input))
|
|
44
|
+
throw new WorkflowRuntimeSemanticReplayError();
|
|
14
45
|
throw new Error('Workflow execution has advanced beyond the expected status.');
|
|
15
46
|
}
|
|
16
47
|
if (input.command.kind === 'complete-step' && input.command.taskTarget.taskId !== before.execution.taskId) {
|
|
@@ -191,13 +222,25 @@ export function runDurableWorkflowRuntimeMutation(input) {
|
|
|
191
222
|
response: JSON.parse(reservation.responseJson),
|
|
192
223
|
};
|
|
193
224
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
225
|
+
let prepared;
|
|
226
|
+
try {
|
|
227
|
+
prepared = prepareWorkflowRuntimeMutation({
|
|
228
|
+
workflowStore,
|
|
229
|
+
workspaceId: input.workspaceId,
|
|
230
|
+
clientId: input.clientId,
|
|
231
|
+
operationId: input.operationId,
|
|
232
|
+
command,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
input.store.releaseFailedOperationReservation({
|
|
237
|
+
identity,
|
|
238
|
+
requestFingerprint,
|
|
239
|
+
leaseOwner: reservation.leaseOwner,
|
|
240
|
+
now: input.now,
|
|
241
|
+
});
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
201
244
|
const { before, willReassignTask, taskActivityId, eventIds } = prepared;
|
|
202
245
|
const readTaskActivity = (result) => {
|
|
203
246
|
const activity = result.taskActivity || input.core.getTaskEventForSync({
|
|
@@ -262,73 +305,83 @@ export function runDurableWorkflowRuntimeMutation(input) {
|
|
|
262
305
|
},
|
|
263
306
|
})),
|
|
264
307
|
];
|
|
265
|
-
const accepted =
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
308
|
+
const accepted = (() => {
|
|
309
|
+
try {
|
|
310
|
+
return input.store.acceptReservedOperationBatch({
|
|
311
|
+
identity,
|
|
312
|
+
requestFingerprint,
|
|
313
|
+
leaseOwner: reservation.leaseOwner,
|
|
314
|
+
now: input.now,
|
|
315
|
+
entries,
|
|
316
|
+
applyMutation: () => applyPreparedWorkflowRuntimeMutation({
|
|
317
|
+
core: input.core,
|
|
318
|
+
workflowStore,
|
|
319
|
+
workspaceId: input.workspaceId,
|
|
320
|
+
command,
|
|
321
|
+
prepared,
|
|
322
|
+
now: input.now,
|
|
323
|
+
}),
|
|
324
|
+
buildResponse: (result, revisions) => {
|
|
325
|
+
const executionPayload = serializeWorkflowExecutionRootSyncPayload(result.executionResult.execution);
|
|
326
|
+
const eventPayloads = result.executionResult.events.map(serializeWorkflowExecutionEventSyncPayload);
|
|
327
|
+
const projectionRevisions = [
|
|
328
|
+
{
|
|
329
|
+
projection: 'workflow-execution/root-v1',
|
|
330
|
+
...revisionFor(revisions, 'workflow-execution', command.executionId),
|
|
331
|
+
canonicalFingerprint: fingerprintWorkflowRuntimePayload(executionPayload),
|
|
332
|
+
},
|
|
333
|
+
...result.newEvents.map((event) => {
|
|
334
|
+
const payload = serializeWorkflowExecutionEventSyncPayload(event);
|
|
335
|
+
return {
|
|
336
|
+
projection: 'workflow-execution-event/full-v1',
|
|
337
|
+
...revisionFor(revisions, 'workflow-execution-event', event.id),
|
|
338
|
+
canonicalFingerprint: fingerprintWorkflowRuntimePayload(payload),
|
|
339
|
+
};
|
|
340
|
+
}),
|
|
341
|
+
...(result.task ? [{
|
|
342
|
+
projection: 'task/root-v1',
|
|
343
|
+
...revisionFor(revisions, 'task', result.task.id),
|
|
344
|
+
canonicalFingerprint: fingerprintWorkspaceSyncPayload(serializeTaskForSync(result.task)),
|
|
345
|
+
}, {
|
|
346
|
+
projection: 'entity-event/full-v1',
|
|
347
|
+
...revisionFor(revisions, 'entity-event', readTaskActivity(result).id),
|
|
348
|
+
canonicalFingerprint: fingerprintWorkspaceSyncPayload(serializeTaskActivityForSync(readTaskActivity(result))),
|
|
349
|
+
}] : []),
|
|
350
|
+
].sort((left, right) => BigInt(left.sequence) < BigInt(right.sequence) ? -1 : 1);
|
|
351
|
+
const authoritative = validateWorkflowRuntimeAuthoritativeResult({
|
|
352
|
+
execution: executionPayload,
|
|
353
|
+
events: eventPayloads,
|
|
354
|
+
acceptedEventIds: result.newEvents.map((event) => event.id),
|
|
355
|
+
...(result.task ? {
|
|
356
|
+
task: {
|
|
357
|
+
payload: serializeTaskForSync(result.task),
|
|
358
|
+
activity: serializeTaskActivityForSync(readTaskActivity(result)),
|
|
359
|
+
},
|
|
360
|
+
} : {}),
|
|
361
|
+
revisions: projectionRevisions,
|
|
362
|
+
replayed: result.executionResult.replayed,
|
|
363
|
+
});
|
|
364
|
+
const executionRevision = revisionFor(revisions, 'workflow-execution', command.executionId);
|
|
290
365
|
return {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
366
|
+
success: true,
|
|
367
|
+
contractVersion: WORKSPACE_SYNC_CONTRACT_VERSION,
|
|
368
|
+
workspaceId: input.workspaceId,
|
|
369
|
+
clientId: input.clientId,
|
|
370
|
+
operationId: input.operationId,
|
|
371
|
+
acceptedSequence: executionRevision.sequence,
|
|
372
|
+
operationHighWatermark: revisions.at(-1)?.sequence || executionRevision.sequence,
|
|
373
|
+
authoritative,
|
|
294
374
|
};
|
|
295
|
-
}
|
|
296
|
-
...(result.task ? [{
|
|
297
|
-
projection: 'task/root-v1',
|
|
298
|
-
...revisionFor(revisions, 'task', result.task.id),
|
|
299
|
-
canonicalFingerprint: fingerprintWorkspaceSyncPayload(serializeTaskForSync(result.task)),
|
|
300
|
-
}, {
|
|
301
|
-
projection: 'entity-event/full-v1',
|
|
302
|
-
...revisionFor(revisions, 'entity-event', readTaskActivity(result).id),
|
|
303
|
-
canonicalFingerprint: fingerprintWorkspaceSyncPayload(serializeTaskActivityForSync(readTaskActivity(result))),
|
|
304
|
-
}] : []),
|
|
305
|
-
].sort((left, right) => BigInt(left.sequence) < BigInt(right.sequence) ? -1 : 1);
|
|
306
|
-
const authoritative = validateWorkflowRuntimeAuthoritativeResult({
|
|
307
|
-
execution: executionPayload,
|
|
308
|
-
events: eventPayloads,
|
|
309
|
-
acceptedEventIds: result.newEvents.map((event) => event.id),
|
|
310
|
-
...(result.task ? {
|
|
311
|
-
task: {
|
|
312
|
-
payload: serializeTaskForSync(result.task),
|
|
313
|
-
activity: serializeTaskActivityForSync(readTaskActivity(result)),
|
|
314
|
-
},
|
|
315
|
-
} : {}),
|
|
316
|
-
revisions: projectionRevisions,
|
|
317
|
-
replayed: result.executionResult.replayed,
|
|
375
|
+
},
|
|
318
376
|
});
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
operationHighWatermark: revisions.at(-1)?.sequence || executionRevision.sequence,
|
|
328
|
-
authoritative,
|
|
329
|
-
};
|
|
330
|
-
},
|
|
331
|
-
});
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
if (hasEquivalentCompletedStep({ workflowStore, workspaceId: input.workspaceId, command })) {
|
|
380
|
+
throw new WorkflowRuntimeSemanticReplayError();
|
|
381
|
+
}
|
|
382
|
+
throw error;
|
|
383
|
+
}
|
|
384
|
+
})();
|
|
332
385
|
const notify = (callback) => {
|
|
333
386
|
if (!callback)
|
|
334
387
|
return;
|
|
@@ -13,7 +13,7 @@ import { enqueueLocalTaskCommentReconciliation } from './localTaskCommentOutboxS
|
|
|
13
13
|
import { buildWorkspaceSyncV3TaskCommentReconcileProvenance, indexWorkspaceSyncV3TaskCommentProvenanceEvidence, } from './taskCommentReconcileProvenance.js';
|
|
14
14
|
import { serializeTaskRootForSync, } from './workspaceSyncV3TaskRootProjection.js';
|
|
15
15
|
import { prepareWorkspaceSyncV3WorkflowRuntimeApply } from './workspaceSyncV3WorkflowRuntimeApply.js';
|
|
16
|
-
import { serializeWorkflowExecutionEventSyncPayload, serializeWorkflowExecutionRootSyncPayload, serializeWorkflowPublishedDefinitionSyncPayload, } from './workflowRuntimeSync.js';
|
|
16
|
+
import { serializeWorkflowExecutionEventSyncPayload, serializeWorkflowExecutionRootSyncPayload, serializeWorkflowPublishedDefinitionSyncPayload, validateWorkflowExecutionEventSyncPayload, } from './workflowRuntimeSync.js';
|
|
17
17
|
import { serializeWorkstreamTaskOrderForSync } from './workstreamTaskOrderSync.js';
|
|
18
18
|
import { validateWorkspaceSyncV3WorkstreamTaskOrderFeedEntry } from './workspaceSyncV3WorkstreamTaskOrderProjection.js';
|
|
19
19
|
function combinedRepairBaselineMarker(page) {
|
|
@@ -198,6 +198,9 @@ function applyEntries(input) {
|
|
|
198
198
|
store: new WorkflowStore(input.core.getDatabaseAdapter(), input.tenantId),
|
|
199
199
|
workspaceId: input.workspaceId,
|
|
200
200
|
entries: input.entries,
|
|
201
|
+
replaceAuthoritativeHistoryExecutionIds: input.allowAuthoritativeRevisionReset
|
|
202
|
+
? input.authoritativeWorkflowRecoveryExecutionIds
|
|
203
|
+
: undefined,
|
|
201
204
|
});
|
|
202
205
|
return store.runWorkspaceDomainCoexistenceTransaction({
|
|
203
206
|
tenantId: input.tenantId,
|
|
@@ -1011,7 +1014,7 @@ export function applyWorkspaceSyncV3CombinedRepairPage(input) {
|
|
|
1011
1014
|
: [];
|
|
1012
1015
|
const selectRecoveryEntries = (entries) => {
|
|
1013
1016
|
const byTarget = new Map(entries.map((entry) => [`${entry.domain}\0${entry.entityId}`, entry]));
|
|
1014
|
-
|
|
1017
|
+
const selected = recoveryTargets.map((target) => {
|
|
1015
1018
|
const entry = byTarget.get(`${target.domain}\0${target.entityId}`);
|
|
1016
1019
|
if (!entry) {
|
|
1017
1020
|
if (!target.required)
|
|
@@ -1023,12 +1026,28 @@ export function applyWorkspaceSyncV3CombinedRepairPage(input) {
|
|
|
1023
1026
|
|| (target.domain === 'task-attachment-links'
|
|
1024
1027
|
&& (entry.projection === 'task-attachment-links/full-v1'
|
|
1025
1028
|
|| entry.projection === 'task-attachment-links/full-v2'))
|
|
1026
|
-
|| (target.domain === 'entity-event' && entry.projection === 'entity-event/full-v1')
|
|
1029
|
+
|| (target.domain === 'entity-event' && entry.projection === 'entity-event/full-v1')
|
|
1030
|
+
|| (target.domain === 'workflow-execution'
|
|
1031
|
+
&& entry.projection === 'workflow-execution/root-v1'));
|
|
1027
1032
|
if (!supportedTarget) {
|
|
1028
1033
|
throw new Error('Targeted workspace sync recovery does not support this projection.');
|
|
1029
1034
|
}
|
|
1030
1035
|
return entry;
|
|
1031
1036
|
}).filter((entry) => entry !== null);
|
|
1037
|
+
const workflowExecutionIds = new Set(selected
|
|
1038
|
+
.filter((entry) => entry.projection === 'workflow-execution/root-v1')
|
|
1039
|
+
.map((entry) => entry.entityId));
|
|
1040
|
+
if (workflowExecutionIds.size > 0) {
|
|
1041
|
+
for (const entry of entries) {
|
|
1042
|
+
if (entry.projection !== 'workflow-execution-event/full-v1')
|
|
1043
|
+
continue;
|
|
1044
|
+
const payload = validateWorkflowExecutionEventSyncPayload(entry.payload);
|
|
1045
|
+
if (!workflowExecutionIds.has(payload.event.workflowExecutionId))
|
|
1046
|
+
continue;
|
|
1047
|
+
selected.push(entry);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
return selected;
|
|
1032
1051
|
};
|
|
1033
1052
|
const projections = coveredProjections(input.page);
|
|
1034
1053
|
let notices = notifications();
|
|
@@ -1056,6 +1075,9 @@ export function applyWorkspaceSyncV3CombinedRepairPage(input) {
|
|
|
1056
1075
|
authoritativeEntityEventRecoveryIds: new Set(recoveryTargets
|
|
1057
1076
|
.filter((target) => target.domain === 'entity-event')
|
|
1058
1077
|
.map((target) => target.entityId)),
|
|
1078
|
+
authoritativeWorkflowRecoveryExecutionIds: new Set(recoveryTargets
|
|
1079
|
+
.filter((target) => target.domain === 'workflow-execution')
|
|
1080
|
+
.map((target) => target.entityId)),
|
|
1059
1081
|
} : {}),
|
|
1060
1082
|
});
|
|
1061
1083
|
},
|
|
@@ -34,6 +34,7 @@ export interface WorkspaceSyncV3FeedProjectionInput {
|
|
|
34
34
|
rows: readonly WorkspaceSyncJournalRow[];
|
|
35
35
|
cycleHighWatermark: string;
|
|
36
36
|
followingRow?: WorkspaceSyncJournalRow | null;
|
|
37
|
+
workspaceId: string;
|
|
37
38
|
}
|
|
38
39
|
export type WorkspaceSyncV3FeedOperationProjector = (input: WorkspaceSyncV3FeedProjectionInput) => WorkspaceSyncV3FeedEntry[];
|
|
39
40
|
export interface WorkspaceSyncV3FeedCoveragePolicy {
|
|
@@ -207,7 +207,12 @@ export function buildWorkspaceSyncV3Feed(input) {
|
|
|
207
207
|
throw new WorkspaceSyncV3FeedOperationTooLargeError(`Workspace sync operation ${clientId}/${operationId} exceeds ${MAX_OPERATION_ROWS} journal rows.`);
|
|
208
208
|
}
|
|
209
209
|
const groupRows = rows.slice(offset, end);
|
|
210
|
-
const groupEntries = projectOperation({
|
|
210
|
+
const groupEntries = projectOperation({
|
|
211
|
+
rows: groupRows,
|
|
212
|
+
cycleHighWatermark: String(high),
|
|
213
|
+
followingRow: rows[end] || null,
|
|
214
|
+
workspaceId: input.workspaceId,
|
|
215
|
+
});
|
|
211
216
|
if (entries.length + groupEntries.length > pageSize) {
|
|
212
217
|
if (!entries.length)
|
|
213
218
|
throw new WorkspaceSyncV3FeedOperationTooLargeError(`Covered operation ${clientId}/${operationId} exceeds the feed entry limit.`);
|
|
@@ -49,9 +49,11 @@ export declare function projectWorkspaceSyncV3NestedTaskOperation(input: {
|
|
|
49
49
|
rows: readonly WorkspaceSyncJournalRow[];
|
|
50
50
|
cycleHighWatermark: string;
|
|
51
51
|
followingRow?: WorkspaceSyncJournalRow | null;
|
|
52
|
+
workspaceId: string;
|
|
52
53
|
}): WorkspaceSyncV3FeedEntry[] | null;
|
|
53
54
|
export declare function projectWorkspaceSyncV3NestedTaskV2Operation(input: {
|
|
54
55
|
rows: readonly WorkspaceSyncJournalRow[];
|
|
55
56
|
cycleHighWatermark: string;
|
|
56
57
|
followingRow?: WorkspaceSyncJournalRow | null;
|
|
58
|
+
workspaceId: string;
|
|
57
59
|
}): WorkspaceSyncV3FeedEntry[] | null;
|