@taskforcehq/taskforce 0.3.328 → 0.3.329
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/dist/Taskforce.module.css +233 -34
- package/dist/TaskforceCore.js +237 -230
- package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.js +1 -1
- package/dist/cli.js +34 -0
- package/dist/cliCloudTargetGuard.d.ts +5 -0
- package/dist/cliCloudTargetGuard.js +11 -0
- package/dist/components/features/AnnotatedAttachmentWorkspace.js +24 -8
- package/dist/components/features/TaskSettings.d.ts +12 -1
- package/dist/components/features/TaskSettings.js +147 -40
- package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.d.ts +1 -1
- package/dist/components/features/planning/PlanningModule.js +2 -5
- package/dist/components/features/planning/PlanningTaskOrderList.d.ts +1 -1
- package/dist/components/features/planning/PlanningTaskOrderList.js +17 -6
- package/dist/components/features/planning/planningTaskArrangement.d.ts +1 -0
- package/dist/components/features/planning/planningTaskArrangement.js +5 -5
- package/dist/components/features/taskforceAgentEditorModel.js +3 -1
- package/dist/components/task/TaskCard.d.ts +5 -0
- package/dist/components/task/TaskCard.js +65 -10
- package/dist/components/task/TaskImageReviews.js +14 -5
- package/dist/components/task/TaskKanban.d.ts +6 -1
- package/dist/components/task/TaskKanban.js +152 -6
- package/dist/components/task/TaskList.d.ts +5 -0
- package/dist/components/task/TaskList.js +3 -3
- package/dist/components/ui/PasswordInput.d.ts +6 -0
- package/dist/components/ui/PasswordInput.js +11 -0
- package/dist/components/ui/TopNoticeLayer.js +1 -1
- package/dist/components/views/AppShellHeader.js +32 -11
- package/dist/components/views/PlanComparisonPage.js +12 -0
- package/dist/components/views/StandaloneLayout.js +7 -31
- package/dist/components/views/WidgetView.js +3 -3
- package/dist/components/views/panels/PlanningDrawer.js +5 -12
- package/dist/components/views/standalone/modals/AccountSettingsModal.js +26 -17
- package/dist/config/envSchema.js +12 -0
- package/dist/config/localServiceRuntimeContract.d.ts +13 -0
- package/dist/config/localServiceRuntimeContract.js +135 -0
- package/dist/core/AiProfileService.js +46 -8
- package/dist/core/DeletedTaskLifecycleService.d.ts +1 -0
- package/dist/core/DeletedTaskLifecycleService.js +37 -6
- package/dist/core/TaskAttachmentLinksDurableMutationService.d.ts +3 -2
- package/dist/core/TaskAttachmentLinksDurableMutationService.js +33 -9
- package/dist/core/Taskforce.d.ts +62 -2
- package/dist/core/Taskforce.js +635 -32
- package/dist/core/types.d.ts +6 -0
- package/dist/hooks/sync/useDurableLocalOutboxDrain.d.ts +1 -1
- package/dist/hooks/sync/useDurableTaskCreateMutation.js +1 -1
- package/dist/hooks/sync/useDurableTaskMetadataMutation.d.ts +1 -0
- package/dist/hooks/sync/useDurableTaskMetadataMutation.js +1 -0
- package/dist/hooks/sync/useDurableTaskStatusMutation.d.ts +1 -0
- package/dist/hooks/tasks/taskHttpMutation.js +1 -0
- package/dist/hooks/tasks/useCloudTaskSearch.d.ts +12 -0
- package/dist/hooks/tasks/useCloudTaskSearch.js +71 -0
- package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.d.ts +16 -0
- package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.js +197 -0
- package/dist/hooks/tasks/useTaskEditorNavigation.d.ts +16 -3
- package/dist/hooks/tasks/useTaskEditorNavigation.js +72 -13
- package/dist/hooks/tasks/useTaskFormActions.d.ts +6 -0
- package/dist/hooks/tasks/useTaskFormActions.js +65 -12
- package/dist/hooks/ui/useSettingsModel.d.ts +24 -2
- package/dist/hooks/ui/useSettingsModel.js +13 -2
- package/dist/hooks/ui/useSettingsPersistence.d.ts +12 -1
- package/dist/hooks/ui/useSettingsPersistence.js +74 -47
- package/dist/hooks/useFilterSort.d.ts +7 -3
- package/dist/hooks/useFilterSort.js +15 -5
- package/dist/hooks/useTaskData.d.ts +10 -1
- package/dist/hooks/useTaskData.js +216 -7
- package/dist/hooks/useTaskMutations.d.ts +10 -2
- package/dist/hooks/useTaskMutations.js +223 -43
- package/dist/hooks/useTaskforce.d.ts +37 -9
- package/dist/hooks/useTaskforce.js +349 -39
- package/dist/hooks/useUiNotice.d.ts +4 -0
- package/dist/hooks/useUiNotice.js +4 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +3 -3
- package/dist/mcp/documentAssetRegistrar.js +44 -35
- package/dist/mcp/documentHelpers.d.ts +1 -0
- package/dist/mcp/documentHelpers.js +7 -3
- package/dist/mcp/runtime.js +172 -42
- package/dist/mcp/taskPlanningRegistrar.js +41 -38
- package/dist/mcp/toolCallCompatibility.js +38 -1
- package/dist/mcp/toolCatalog.d.ts +1 -0
- package/dist/migrations/taskSchemaMigrations.js +12 -0
- package/dist/runtime/appGateDefinitions.d.ts +30 -6
- package/dist/runtime/appGateDefinitions.js +6 -6
- package/dist/runtime/appGates.d.ts +12 -13
- package/dist/runtime/appGates.js +29 -13
- package/dist/runtime/nodeAppGates.d.ts +8 -0
- package/dist/runtime/nodeAppGates.js +27 -0
- package/dist/server/cloudCollectionRead.d.ts +1 -1
- package/dist/server/cloudCollectionRead.js +2 -2
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +15 -3
- package/dist/server/localServiceLease.d.ts +3 -0
- package/dist/server/localServiceLease.js +9 -1
- package/dist/server/localWorkspaceSyncServerRuntime.js +10 -3
- package/dist/server/rateLimit.d.ts +1 -0
- package/dist/server/rateLimit.js +4 -0
- package/dist/server/realtimeSyncWs.js +36 -12
- package/dist/server/routes/admin.js +1 -0
- package/dist/server/routes/agentRoles.js +3 -2
- package/dist/server/routes/agentSkills.js +3 -2
- package/dist/server/routes/agents.js +118 -16
- package/dist/server/routes/annotatedAttachments.js +3 -2
- package/dist/server/routes/auth.js +3 -1
- package/dist/server/routes/durableTaskHttpRouters.d.ts +9 -2
- package/dist/server/routes/localService.js +2 -0
- package/dist/server/routes/primitives.d.ts +2 -0
- package/dist/server/routes/syncAuxRoutes.js +5 -2
- package/dist/server/routes/syncPullApplyRoutes.js +23 -4
- package/dist/server/routes/syncV3FeedRoutes.js +18 -16
- package/dist/server/routes/syncV3LocalCaptureRoutes.js +28 -2
- package/dist/server/routes/syncV3LocalFeedApplyRoutes.d.ts +1 -0
- package/dist/server/routes/syncV3LocalFeedApplyRoutes.js +26 -0
- package/dist/server/routes/syncV3OperationRoutes.js +10 -0
- package/dist/server/routes/tasks.js +103 -11
- package/dist/server/routes/workspaces.js +43 -1
- package/dist/server/workspaceDeletionAdmission.d.ts +23 -0
- package/dist/server/workspaceDeletionAdmission.js +50 -0
- package/dist/service/localServiceCli.js +16 -0
- package/dist/service/localServiceClientLifecycle.js +36 -10
- package/dist/services/codexAppServerModelGateway.d.ts +112 -0
- package/dist/services/codexAppServerModelGateway.js +438 -0
- package/dist/services/taskforceAgentMcpBridge.d.ts +7 -1
- package/dist/services/taskforceAgentMcpBridge.js +23 -3
- package/dist/shared/taskforceAgentDefinition.js +7 -1
- package/dist/shared/taskforceAgentModels.d.ts +1 -0
- package/dist/shared/taskforceAgentModels.js +30 -1
- package/dist/storage/postgresAdapter.d.ts +20 -3
- package/dist/storage/postgresAdapter.js +106 -14
- package/dist/storage/postgresWorker.js +13 -4
- package/dist/storage/postgresWorkerProtocol.d.ts +29 -0
- package/dist/storage/postgresWorkerProtocol.js +30 -0
- package/dist/sync/cloudSyncApi.d.ts +4 -1
- package/dist/sync/cloudSyncApi.js +7 -2
- package/dist/sync/contentSyncState.d.ts +1 -1
- package/dist/sync/coordinator/localSyncExecutionPermitStore.d.ts +1 -0
- package/dist/sync/coordinator/localSyncExecutionPermitStore.js +7 -1
- package/dist/sync/coordinator/localWorkspaceSyncRunner.js +6 -2
- package/dist/sync/engine/workspaceSyncEngineBrowserGatewayRemoteServices.js +20 -2
- package/dist/sync/engine/workspaceSyncEngineLocalServices.d.ts +4 -0
- package/dist/sync/engine/workspaceSyncEngineLocalServices.js +11 -0
- package/dist/sync/engine/workspaceSyncEngineRemoteServices.d.ts +11 -0
- package/dist/sync/engine/workspaceSyncEngineRemoteServices.js +2 -1
- package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +31 -4
- package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.d.ts +3 -1
- package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +25 -8
- package/dist/sync/engine/workspaceSyncEngineTransfers.d.ts +1 -1
- package/dist/sync/engine/workspaceSyncEngineTransfers.js +8 -3
- package/dist/sync/syncService.d.ts +2 -0
- package/dist/sync/syncService.js +4 -1
- package/dist/sync/v3/combinedFeedCapability.js +4 -0
- package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.d.ts +2 -0
- package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +11 -1
- package/dist/sync/v3/durableTaskChecklistMutationRouter.d.ts +2 -0
- package/dist/sync/v3/durableTaskChecklistMutationRouter.js +4 -0
- package/dist/sync/v3/durableTaskHttpBulkMutationRouter.d.ts +1 -0
- package/dist/sync/v3/durableTaskHttpBulkMutationRouter.js +30 -8
- package/dist/sync/v3/durableTaskHttpRootMutationRouter.d.ts +5 -3
- package/dist/sync/v3/durableTaskHttpRootMutationRouter.js +27 -2
- package/dist/sync/v3/localTaskAttachmentLinksOutboxService.d.ts +2 -0
- package/dist/sync/v3/localTaskAttachmentLinksOutboxService.js +47 -7
- package/dist/sync/v3/localTaskCreateOutboxService.js +15 -6
- package/dist/sync/v3/localTaskMetadataClient.d.ts +1 -0
- package/dist/sync/v3/localTaskMetadataClient.js +1 -0
- package/dist/sync/v3/localTaskMetadataOutboxHandler.js +5 -1
- package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -0
- package/dist/sync/v3/localTaskMetadataOutboxService.js +7 -0
- package/dist/sync/v3/localTaskRestoreOutboxService.js +14 -6
- package/dist/sync/v3/localTaskStatusClient.d.ts +1 -0
- package/dist/sync/v3/localTaskStatusClient.js +6 -1
- package/dist/sync/v3/localTaskStatusOutboxService.d.ts +2 -0
- package/dist/sync/v3/localTaskStatusOutboxService.js +54 -13
- package/dist/sync/v3/syncDurabilityStore.d.ts +7 -0
- package/dist/sync/v3/syncDurabilityStore.js +31 -11
- package/dist/sync/v3/taskAttachmentLinksMutationService.d.ts +6 -4
- package/dist/sync/v3/taskAttachmentLinksMutationService.js +19 -10
- package/dist/sync/v3/taskChecklistMutationService.d.ts +4 -2
- package/dist/sync/v3/taskChecklistMutationService.js +4 -0
- package/dist/sync/v3/taskCreateMutationService.d.ts +2 -2
- package/dist/sync/v3/taskCreateMutationService.js +7 -4
- package/dist/sync/v3/taskMetadataMutationService.d.ts +3 -0
- package/dist/sync/v3/taskMetadataMutationService.js +4 -0
- package/dist/sync/v3/taskRestoreMutationService.d.ts +2 -2
- package/dist/sync/v3/taskRestoreMutationService.js +7 -4
- package/dist/sync/v3/taskStatusMutationService.d.ts +2 -0
- package/dist/sync/v3/taskStatusMutationService.js +4 -0
- package/dist/sync/v3/workflowAssignmentSync.d.ts +1 -0
- package/dist/sync/v3/workflowAssignmentSync.js +3 -0
- package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.d.ts +1 -1
- package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.js +13 -3
- package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.d.ts +11 -0
- package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.js +27 -10
- package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.d.ts +2 -1
- package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.js +52 -10
- package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +9 -1
- package/dist/sync/v3/workspaceSyncV3CombinedFeedOrchestrator.js +8 -3
- package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +51 -10
- package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +7 -0
- package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +30 -18
- package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +9 -4
- package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +98 -47
- package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.d.ts +10 -0
- package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +58 -21
- package/dist/sync/v3/workspaceSyncV3CombinedWireContract.d.ts +3 -1
- package/dist/sync/v3/workspaceSyncV3CombinedWireContract.js +18 -0
- package/dist/sync/v3/workspaceSyncV3CoverageRegistry.js +6 -0
- package/dist/sync/v3/workspaceSyncV3FeedProfile.d.ts +1 -1
- package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.js +23 -10
- package/dist/sync/v3/workspaceSyncV3NestedTaskApply.d.ts +3 -0
- package/dist/sync/v3/workspaceSyncV3NestedTaskApply.js +64 -13
- package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.d.ts +9 -0
- package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.js +26 -0
- package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +15 -5
- package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +50 -14
- package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.d.ts +2 -1
- package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.js +11 -3
- package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +2 -0
- package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.d.ts +2 -0
- package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.js +19 -7
- package/dist/sync/v3/workspaceSyncV3TaskCommentAudit.js +9 -2
- package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.d.ts +7 -0
- package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.js +37 -0
- package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.d.ts +6 -0
- package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.js +9 -0
- package/dist/sync/workspacePullFeed.js +2 -1
- package/dist/sync/workspaceRepair.d.ts +3 -0
- package/dist/sync/workspaceRepair.js +67 -6
- package/dist/types.d.ts +47 -1
- package/dist/types.js +6 -0
- package/dist/ui/assets/{AiIdentityRosterCard-MZs7lVED.js → AiIdentityRosterCard-BZM4aQnM.js} +1 -1
- package/dist/ui/assets/AiProfilesModule-BbAEWtHH.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-B68Wg-RV.js +3 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-TzYu7pkG.css +1 -0
- package/dist/ui/assets/{AssetTraySortControl-Kf1pelSy.js → AssetTraySortControl-C8Rztyal.js} +1 -1
- package/dist/ui/assets/{ContextAttachmentManager-B8boOs6Q.js → ContextAttachmentManager-Cj3c7X9y.js} +2 -2
- package/dist/ui/assets/{DocumentWorkspace-DXH4XUsB.js → DocumentWorkspace-B5uvzems.js} +1 -1
- package/dist/ui/assets/{EntityActivityTimeline-BtSTq0ON.js → EntityActivityTimeline-BVFvCB5R.js} +1 -1
- package/dist/ui/assets/PlanningModule-B5DWQOGz.js +1 -0
- package/dist/ui/assets/PlansPage-AY4cGWco.js +1 -0
- package/dist/ui/assets/TaskContextUpload-DZxTVesW.js +1 -0
- package/dist/ui/assets/TaskSettings-BeWAuyZj.js +12 -0
- package/dist/ui/assets/{TaskforceAgentsModule-BazdUwxZ.js → TaskforceAgentsModule-BifCYtjR.js} +6 -6
- package/dist/ui/assets/{WorkflowManagerModule-DWFspC-o.js → WorkflowManagerModule-DEr5di_r.js} +1 -1
- package/dist/ui/assets/index-I5zhdtgt.js +7 -0
- package/dist/ui/assets/index-VRFsx3wz.css +1 -0
- package/dist/ui/assets/{vendor-icons-BkFLXavV.js → vendor-icons-D9Lpw-j4.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/taskActivity.d.ts +3 -2
- package/dist/utils/taskActivity.js +19 -2
- package/dist/utils/taskEditorReconciliation.d.ts +40 -0
- package/dist/utils/taskEditorReconciliation.js +217 -0
- package/dist/utils/taskNormalization.d.ts +1 -0
- package/dist/utils/taskNormalization.js +39 -0
- package/dist/utils/taskSearch.js +2 -2
- package/dist/utils/uiNotice.d.ts +4 -0
- package/package.json +16 -4
- package/dist/ui/assets/AiProfilesModule-C3i0LOxe.js +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-CBRZ8rca.css +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-DVZk5Tln.js +0 -3
- package/dist/ui/assets/PlanningModule-BPVyEa-f.js +0 -1
- package/dist/ui/assets/PlansPage-ChqgxALc.js +0 -1
- package/dist/ui/assets/TaskContextUpload-CB3ol0sN.js +0 -1
- package/dist/ui/assets/TaskSettings-g4ECKNOz.js +0 -12
- package/dist/ui/assets/index-BUplSsv_.js +0 -7
- package/dist/ui/assets/index-gz2EXuoK.css +0 -1
|
@@ -6,6 +6,7 @@ import { runWorkspaceSyncV3CombinedProjectionCycle } from '../v3/workspaceSyncV3
|
|
|
6
6
|
import { runWorkspaceSyncV3InitiativeProjectionCycle } from '../v3/workspaceSyncV3FeedOrchestrator.js';
|
|
7
7
|
import { normalizeWorkspaceSyncV2ProjectionExclusions } from '../v3/workspaceSyncV2ProjectionExclusions.js';
|
|
8
8
|
import { canonicalWorkspaceSyncJson } from '../v3/canonicalWorkspaceSyncJson.js';
|
|
9
|
+
import { authoritativeV2ApplyFailureMessage, } from './workspaceSyncEngineLocalServices.js';
|
|
9
10
|
import { combinedAttachmentAssetHydrationId, createWorkspaceSyncV3TransferBindings, } from './workspaceSyncEngineTransfers.js';
|
|
10
11
|
import { createWorkspaceSyncTransferEvidence, } from './workspaceSyncTransferEvidence.js';
|
|
11
12
|
const NOOP_CLOUD_RESOLVER = (path) => path;
|
|
@@ -13,6 +14,7 @@ const RETRY_BASE_DELAY_MS = 1_000;
|
|
|
13
14
|
const RETRY_MAX_DELAY_MS = 5 * 60_000;
|
|
14
15
|
const COMBINED_FEED_COVERED_OUTBOX_BLOCK = 'WORKSPACE_SYNC_V3_COVERED_LOCAL_OPERATIONS';
|
|
15
16
|
const REPAIR_OWNERSHIP_FINGERPRINT_VERSION = 2;
|
|
17
|
+
const NON_APPLYING_ACKNOWLEDGEMENT_CHECKPOINT_INTERVAL = 64;
|
|
16
18
|
class WorkspaceSyncEngineServerRunnerOperationError extends Error {
|
|
17
19
|
code;
|
|
18
20
|
retryAfterMs;
|
|
@@ -513,7 +515,9 @@ export function createWorkspaceSyncEngineServerRunnerOperations(options) {
|
|
|
513
515
|
? 'repair'
|
|
514
516
|
: 'push';
|
|
515
517
|
activeRetryKind = pushRetryKind;
|
|
516
|
-
|
|
518
|
+
if (checkpoint.controlState !== 'pushing') {
|
|
519
|
+
persist({ ...checkpoint, controlState: 'pushing' });
|
|
520
|
+
}
|
|
517
521
|
let payload;
|
|
518
522
|
let signature;
|
|
519
523
|
if (checkpoint.pendingV2Push) {
|
|
@@ -551,11 +555,13 @@ export function createWorkspaceSyncEngineServerRunnerOperations(options) {
|
|
|
551
555
|
persist({ ...checkpoint, pendingV2Push });
|
|
552
556
|
}
|
|
553
557
|
if (checkpoint.remoteHintObservedAt) {
|
|
554
|
-
|
|
558
|
+
checkpoint = {
|
|
555
559
|
...checkpoint,
|
|
556
560
|
consecutivePushesWhilePullDue: Math.min(Number.MAX_SAFE_INTEGER, checkpoint.consecutivePushesWhilePullDue + 1),
|
|
557
|
-
}
|
|
561
|
+
};
|
|
558
562
|
}
|
|
563
|
+
let persistedAcknowledgementCount = checkpoint.pendingV2Push
|
|
564
|
+
?.acknowledgedNonApplyingBatchOperationIdentities.length || 0;
|
|
559
565
|
const result = await runWorkspacePushSyncService({
|
|
560
566
|
resolveCloudAuthUrl: NOOP_CLOUD_RESOLVER,
|
|
561
567
|
workspaceId,
|
|
@@ -583,13 +589,14 @@ export function createWorkspaceSyncEngineServerRunnerOperations(options) {
|
|
|
583
589
|
],
|
|
584
590
|
},
|
|
585
591
|
});
|
|
592
|
+
persistedAcknowledgementCount = acknowledgedOperationIdentities.length;
|
|
586
593
|
},
|
|
587
594
|
onNonApplyingBatchAcknowledged: ({ acknowledgedOperationIdentities }) => {
|
|
588
595
|
const pendingV2Push = checkpoint.pendingV2Push;
|
|
589
596
|
if (!pendingV2Push) {
|
|
590
597
|
throw new WorkspaceSyncEngineServerRunnerOperationError('The frozen V2 push disappeared while recording batch progress.', 'SYNC_RUNNER_PUSH_CHECKPOINT_INVALID');
|
|
591
598
|
}
|
|
592
|
-
|
|
599
|
+
checkpoint = {
|
|
593
600
|
...checkpoint,
|
|
594
601
|
pendingV2Push: {
|
|
595
602
|
...pendingV2Push,
|
|
@@ -597,19 +604,25 @@ export function createWorkspaceSyncEngineServerRunnerOperations(options) {
|
|
|
597
604
|
...acknowledgedOperationIdentities,
|
|
598
605
|
],
|
|
599
606
|
},
|
|
600
|
-
}
|
|
607
|
+
};
|
|
608
|
+
if (acknowledgedOperationIdentities.length
|
|
609
|
+
- persistedAcknowledgementCount
|
|
610
|
+
>= NON_APPLYING_ACKNOWLEDGEMENT_CHECKPOINT_INTERVAL) {
|
|
611
|
+
persist(checkpoint);
|
|
612
|
+
persistedAcknowledgementCount = acknowledgedOperationIdentities.length;
|
|
613
|
+
}
|
|
601
614
|
},
|
|
602
615
|
});
|
|
603
616
|
onResult?.(result);
|
|
604
617
|
if (!result.success) {
|
|
605
618
|
if (result.batchProgress && checkpoint.pendingV2Push) {
|
|
606
|
-
|
|
619
|
+
checkpoint = {
|
|
607
620
|
...checkpoint,
|
|
608
621
|
pendingV2Push: {
|
|
609
622
|
...checkpoint.pendingV2Push,
|
|
610
623
|
lastBatchProgress: structuredClone(result.batchProgress),
|
|
611
624
|
},
|
|
612
|
-
}
|
|
625
|
+
};
|
|
613
626
|
}
|
|
614
627
|
const message = pushFailureMessage(result);
|
|
615
628
|
if (isAuthFailure(result)) {
|
|
@@ -692,6 +705,8 @@ export function createWorkspaceSyncEngineServerRunnerOperations(options) {
|
|
|
692
705
|
? { durableRevision: entry.durableRevision }
|
|
693
706
|
: {}),
|
|
694
707
|
})), {
|
|
708
|
+
repairMode: attemptOptions.repairMode,
|
|
709
|
+
excludedProjections: result.effectiveV2ExcludedProjections,
|
|
695
710
|
...(completionGroup ? { completionGroup } : {}),
|
|
696
711
|
commitServerCheckpoint: () => {
|
|
697
712
|
const persisted = input.persistCheckpoint(nextCheckpoint, {
|
|
@@ -711,7 +726,9 @@ export function createWorkspaceSyncEngineServerRunnerOperations(options) {
|
|
|
711
726
|
}
|
|
712
727
|
if (!applyResult.ok || !commitState.checkpoint) {
|
|
713
728
|
await abandonPushReceipts('WORKSPACE_SYNC_V2_PUSH_AUTHORITATIVE_APPLY_FAILED');
|
|
714
|
-
throw new WorkspaceSyncEngineServerRunnerOperationError(
|
|
729
|
+
throw new WorkspaceSyncEngineServerRunnerOperationError(!applyResult.ok
|
|
730
|
+
? authoritativeV2ApplyFailureMessage(applyResult)
|
|
731
|
+
: 'Authoritative V2 push echo did not commit the server checkpoint.', 'SYNC_RUNNER_PUSH_AUTHORITATIVE_APPLY_FAILED');
|
|
715
732
|
}
|
|
716
733
|
const committedCheckpoint = commitState.checkpoint;
|
|
717
734
|
if (!committedCheckpoint) {
|
|
@@ -3,7 +3,7 @@ import type { WorkspaceSyncPayload } from '../workspaceSyncModel.js';
|
|
|
3
3
|
import type { WorkspaceSyncPhase } from '../workspaceSyncState.js';
|
|
4
4
|
import type { WorkspaceSyncV3CombinedCoverageSelector } from '../v3/workspaceSyncV3CombinedWireContract.js';
|
|
5
5
|
import type { WorkspaceSyncEngineRuntime } from './workspaceSyncEngineRuntime.js';
|
|
6
|
-
import type
|
|
6
|
+
import { type WorkspaceSyncEngineLocalServices } from './workspaceSyncEngineLocalServices.js';
|
|
7
7
|
import type { WorkspaceSyncEngineRemoteServices, WorkspaceSyncEngineRemoteTransferReceipt } from './workspaceSyncEngineRemoteServices.js';
|
|
8
8
|
export type { PendingWorkspacePushReplay } from './workspaceSyncEngineTransferTypes.js';
|
|
9
9
|
type StateSetter<T> = (value: T | ((current: T) => T)) => void;
|
|
@@ -4,6 +4,7 @@ import { resolveWorkspacePullRequestPlan, resolveWorkspacePullSuccessPlan } from
|
|
|
4
4
|
import { runWorkspaceSyncV3InitiativeProjectionCycle } from '../v3/workspaceSyncV3FeedOrchestrator.js';
|
|
5
5
|
import { runWorkspaceSyncV3CombinedProjectionCycle } from '../v3/workspaceSyncV3CombinedFeedOrchestrator.js';
|
|
6
6
|
import { hydrateWorkspaceSyncV3AttachmentAsset } from '../v3/workspaceSyncV3AttachmentAssetHydration.js';
|
|
7
|
+
import { authoritativeV2ApplyFailureMessage, } from './workspaceSyncEngineLocalServices.js';
|
|
7
8
|
import { WORKSPACE_SYNC_V3_APPLY_NOT_COMMITTED } from './workspaceSyncEngineRemoteServices.js';
|
|
8
9
|
export function combinedAttachmentAssetHydrationId(input) {
|
|
9
10
|
if (input.causeCode === 'TASK_ATTACHMENT_ASSET_NOT_READY' && input.causeAssetId) {
|
|
@@ -391,6 +392,10 @@ export function createWorkspaceTransferCoordinator({ cloudAuthConfigured, runtim
|
|
|
391
392
|
}
|
|
392
393
|
let applyResult;
|
|
393
394
|
try {
|
|
395
|
+
const applyOptions = {
|
|
396
|
+
repairMode: replayPayload?.repairMode ?? repairMode,
|
|
397
|
+
excludedProjections: pushResult.effectiveV2ExcludedProjections,
|
|
398
|
+
};
|
|
394
399
|
const authoritativeChanges = appliedTaskUpserts.map((entry) => ({
|
|
395
400
|
op: 'upsert',
|
|
396
401
|
archived: entry.archived === true,
|
|
@@ -398,8 +403,8 @@ export function createWorkspaceTransferCoordinator({ cloudAuthConfigured, runtim
|
|
|
398
403
|
...(entry.durableRevision ? { durableRevision: entry.durableRevision } : {}),
|
|
399
404
|
}));
|
|
400
405
|
applyResult = completionGroup
|
|
401
|
-
? await localServices.applyV2Batch(currentWorkspaceId, authoritativeChanges, { completionGroup })
|
|
402
|
-
: await localServices.applyV2Batch(currentWorkspaceId, authoritativeChanges);
|
|
406
|
+
? await localServices.applyV2Batch(currentWorkspaceId, authoritativeChanges, { ...applyOptions, completionGroup })
|
|
407
|
+
: await localServices.applyV2Batch(currentWorkspaceId, authoritativeChanges, applyOptions);
|
|
403
408
|
}
|
|
404
409
|
catch (error) {
|
|
405
410
|
if (pushTransferReceipts.length > 0) {
|
|
@@ -419,7 +424,7 @@ export function createWorkspaceTransferCoordinator({ cloudAuthConfigured, runtim
|
|
|
419
424
|
errorCode: 'WORKSPACE_SYNC_V2_PUSH_AUTHORITATIVE_APPLY_FAILED',
|
|
420
425
|
});
|
|
421
426
|
}
|
|
422
|
-
throw new Error(
|
|
427
|
+
throw new Error(authoritativeV2ApplyFailureMessage(applyResult));
|
|
423
428
|
}
|
|
424
429
|
for (const entry of appliedTaskUpserts) {
|
|
425
430
|
const taskId = String(entry.task.id || '').trim();
|
|
@@ -77,6 +77,8 @@ export interface WorkspacePushSyncResult {
|
|
|
77
77
|
effectiveDeltaDiagnostics?: WorkspaceSyncDeltaDiagnostics & {
|
|
78
78
|
combinedV3SuppressedChangeCount?: number;
|
|
79
79
|
};
|
|
80
|
+
/** Exact V3-owned projections excluded while preparing this V2 attempt. */
|
|
81
|
+
effectiveV2ExcludedProjections?: readonly WorkspaceSyncV2ProjectionExclusion[];
|
|
80
82
|
deleteCount: number;
|
|
81
83
|
currentTaskIds: Set<string>;
|
|
82
84
|
currentTaskWatermarks?: Map<string, string>;
|
package/dist/sync/syncService.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createWorkspaceSyncEngineV2PushBoundary, parseWorkspaceSyncEngineApplie
|
|
|
5
5
|
import { encryptDocument } from './encryptionService.js';
|
|
6
6
|
import { decryptWorkspaceSyncContentChangeInPlace } from './workspaceSyncContentDecryption.js';
|
|
7
7
|
import { decideWorkspaceContentFetch, fingerprintWorkspaceContentBody, } from './workspaceContentManifest.js';
|
|
8
|
-
import { workspaceSyncV2ProjectionExclusionsEqual, } from './v3/workspaceSyncV2ProjectionExclusions.js';
|
|
8
|
+
import { normalizeWorkspaceSyncV2ProjectionExclusions, workspaceSyncV2ProjectionExclusionsEqual, } from './v3/workspaceSyncV2ProjectionExclusions.js';
|
|
9
9
|
import { WORKSPACE_PUSH_PREPARED_PLAN_VERSION, } from './workspacePushPreparedPlan.js';
|
|
10
10
|
import { canonicalWorkspaceSyncJson } from './v3/canonicalWorkspaceSyncJson.js';
|
|
11
11
|
function asObjectRecord(value) {
|
|
@@ -464,6 +464,7 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
464
464
|
}
|
|
465
465
|
const changeCount = preparedChanges.length;
|
|
466
466
|
const effectiveDeltaDiagnostics = buildEffectiveWorkspacePushDiagnostics(input.payload.deltaDiagnostics, preparedChanges, preparedOwnership);
|
|
467
|
+
const effectiveV2ExcludedProjections = normalizeWorkspaceSyncV2ProjectionExclusions(preparedOwnership.combinedExcludedProjections || []);
|
|
467
468
|
const deleteCount = input.payload.deleteTaskIds.size;
|
|
468
469
|
let requestMs = 0;
|
|
469
470
|
const canElideSteadyStateRequest = changeCount === 0
|
|
@@ -478,6 +479,7 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
478
479
|
requestMs: 0,
|
|
479
480
|
changeCount: 0,
|
|
480
481
|
effectiveDeltaDiagnostics,
|
|
482
|
+
effectiveV2ExcludedProjections,
|
|
481
483
|
deleteCount,
|
|
482
484
|
currentTaskIds: input.payload.currentTaskIds,
|
|
483
485
|
currentTaskWatermarks: input.payload.currentTaskWatermarks ?? new Map(),
|
|
@@ -841,6 +843,7 @@ export async function runWorkspacePushSyncService(input) {
|
|
|
841
843
|
failureDomain: null,
|
|
842
844
|
},
|
|
843
845
|
effectiveDeltaDiagnostics,
|
|
846
|
+
effectiveV2ExcludedProjections,
|
|
844
847
|
deleteCount,
|
|
845
848
|
currentTaskIds: input.payload.currentTaskIds,
|
|
846
849
|
currentTaskWatermarks: input.payload.currentTaskWatermarks ?? new Map(),
|
|
@@ -3,6 +3,7 @@ import { WORKSPACE_SYNC_V3_COMBINED_CAPABILITY_VERSION, WORKSPACE_SYNC_V3_COMBIN
|
|
|
3
3
|
import { WORKSPACE_SYNC_V3_NESTED_COVERAGE_SELECTOR } from './workspaceSyncV3NestedWireContract.js';
|
|
4
4
|
import { WORKSPACE_SYNC_V3_WORKFLOW_COVERAGE_SELECTOR } from './workspaceSyncV3WorkflowWireContract.js';
|
|
5
5
|
import { WORKSPACE_SYNC_V3_PLANNING_ORDER_COVERAGE_SELECTOR } from './workspaceSyncV3PlanningOrderWireContract.js';
|
|
6
|
+
import { WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_SELECTOR } from './workspaceSyncV3TaskCoverWireContract.js';
|
|
6
7
|
export { WORKSPACE_SYNC_V3_COMBINED_CAPABILITY_VERSION } from './workspaceSyncV3CombinedWireContract.js';
|
|
7
8
|
export function resolveRequestedCombinedFeedCoverage(env = process.env) {
|
|
8
9
|
const requestedVersion = String(env.TASKFORCE_SYNC_V3_COMBINED_FEED_COVERAGE_VERSION || '').trim();
|
|
@@ -18,6 +19,9 @@ export function resolveRequestedCombinedFeedCoverage(env = process.env) {
|
|
|
18
19
|
if (requestedVersion === String(WORKSPACE_SYNC_V3_PLANNING_ORDER_COVERAGE_SELECTOR.coverageVersion)) {
|
|
19
20
|
return WORKSPACE_SYNC_V3_PLANNING_ORDER_COVERAGE_SELECTOR;
|
|
20
21
|
}
|
|
22
|
+
if (requestedVersion === String(WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_SELECTOR.coverageVersion)) {
|
|
23
|
+
return WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_SELECTOR;
|
|
24
|
+
}
|
|
21
25
|
throw new Error(`Unsupported TASKFORCE_SYNC_V3_COMBINED_FEED_COVERAGE_VERSION: ${requestedVersion}.`);
|
|
22
26
|
}
|
|
23
27
|
export function resolveCombinedFeedCapability(input) {
|
|
@@ -12,6 +12,7 @@ export declare function resolveTaskAttachmentLinksMutationCapability(input: {
|
|
|
12
12
|
export type DurableTaskAttachmentLinksMutationRouterCore = DurableTaskAttachmentLinksServiceCore & LocalTaskAttachmentLinksOutboxCore & {
|
|
13
13
|
getDatabaseAdapter(): DatabaseAdapter;
|
|
14
14
|
getTask(id: string, workspaceId?: string): TaskItem | null;
|
|
15
|
+
assertTaskDetailRevisionForUpdate(id: string, expected: string, workspaceId?: string): void;
|
|
15
16
|
};
|
|
16
17
|
export declare class DurableTaskAttachmentLinksRouterDisabledError extends Error {
|
|
17
18
|
readonly code = "WORKSPACE_SYNC_V3_TASK_ATTACHMENT_LINKS_ROUTER_DISABLED";
|
|
@@ -41,6 +42,7 @@ export declare function createDurableTaskAttachmentLinksMutationRouter(input: {
|
|
|
41
42
|
taskId: string;
|
|
42
43
|
links: readonly WorkspaceSyncV3TaskAttachmentLink[];
|
|
43
44
|
cardCoverAssetId?: string | null;
|
|
45
|
+
expectedDetailRevision?: string;
|
|
44
46
|
}): {
|
|
45
47
|
state: "pending" | "committed";
|
|
46
48
|
route: "local-outbox" | "cloud-acceptance";
|
|
@@ -140,7 +140,13 @@ export function createDurableTaskAttachmentLinksMutationRouter(input) {
|
|
|
140
140
|
catch {
|
|
141
141
|
throw new DurableTaskAttachmentLinksMutationError('Captured task attachment-link payload is invalid.', 'TASK_ATTACHMENT_LINKS_IDENTITY_CONFLICT', 409);
|
|
142
142
|
}
|
|
143
|
-
|
|
143
|
+
const relationshipIntent = (entries) => entries.map((link) => ({
|
|
144
|
+
assetId: link.assetId,
|
|
145
|
+
role: link.role,
|
|
146
|
+
order: link.order,
|
|
147
|
+
}));
|
|
148
|
+
if (canonicalWorkspaceSyncJson(relationshipIntent(frozen))
|
|
149
|
+
!== canonicalWorkspaceSyncJson(relationshipIntent(links))
|
|
144
150
|
|| String(payload.cardCoverAssetId || '') !== String(cardCoverAssetId || '')) {
|
|
145
151
|
throw new DurableTaskAttachmentLinksMutationError('Operation identity is assigned to different task attachment links.', 'TASK_ATTACHMENT_LINKS_IDENTITY_CONFLICT', 409);
|
|
146
152
|
}
|
|
@@ -204,6 +210,10 @@ export function createDurableTaskAttachmentLinksMutationRouter(input) {
|
|
|
204
210
|
baseEntityRevision: coordinates.entityRevision, baseLifecycleRevision: coordinates.lifecycleRevision,
|
|
205
211
|
now: acceptedAtIso, leaseExpiresAt: new Date(acceptedAt.getTime() + OPERATION_LEASE_MS).toISOString(),
|
|
206
212
|
actorRef, onPostCommitError: input.onPostCommitError,
|
|
213
|
+
expectedDetailRevision: args.expectedDetailRevision,
|
|
214
|
+
assertPrecondition: args.expectedDetailRevision
|
|
215
|
+
? () => input.core.assertTaskDetailRevisionForUpdate(taskId, args.expectedDetailRevision, workspaceId)
|
|
216
|
+
: undefined,
|
|
207
217
|
});
|
|
208
218
|
if (result.state === 'in_progress')
|
|
209
219
|
throw new DurableTaskAttachmentLinksRouterInProgressError(result.retryAt);
|
|
@@ -11,6 +11,7 @@ export declare function resolveTaskChecklistMutationCapability(input: {
|
|
|
11
11
|
export type DurableTaskChecklistMutationRouterCore = DurableTaskChecklistServiceCore & LocalTaskChecklistOutboxCore & {
|
|
12
12
|
getDatabaseAdapter(): DatabaseAdapter;
|
|
13
13
|
getTask(id: string, workspaceId?: string): TaskItem | null;
|
|
14
|
+
assertTaskDetailRevisionForUpdate(id: string, expected: string, workspaceId?: string): void;
|
|
14
15
|
};
|
|
15
16
|
export declare class DurableTaskChecklistRouterDisabledError extends Error {
|
|
16
17
|
readonly code = "WORKSPACE_SYNC_V3_TASK_CHECKLIST_ROUTER_DISABLED";
|
|
@@ -38,6 +39,7 @@ export declare function createDurableTaskChecklistMutationRouter(input: {
|
|
|
38
39
|
replace(args: {
|
|
39
40
|
taskId: string;
|
|
40
41
|
items: readonly Partial<ChecklistItem>[];
|
|
42
|
+
expectedDetailRevision?: string;
|
|
41
43
|
}): {
|
|
42
44
|
state: "pending" | "committed";
|
|
43
45
|
route: "local-outbox" | "cloud-acceptance";
|
|
@@ -170,6 +170,10 @@ export function createDurableTaskChecklistMutationRouter(input) {
|
|
|
170
170
|
baseEntityRevision: coordinates.entityRevision, baseLifecycleRevision: coordinates.lifecycleRevision,
|
|
171
171
|
now: acceptedAtIso, leaseExpiresAt: new Date(acceptedAt.getTime() + OPERATION_LEASE_MS).toISOString(),
|
|
172
172
|
actorRef, onPostCommitError: input.onPostCommitError,
|
|
173
|
+
expectedDetailRevision: args.expectedDetailRevision,
|
|
174
|
+
assertPrecondition: args.expectedDetailRevision
|
|
175
|
+
? () => input.core.assertTaskDetailRevisionForUpdate(taskId, args.expectedDetailRevision, workspaceId)
|
|
176
|
+
: undefined,
|
|
173
177
|
});
|
|
174
178
|
if (result.state === 'in_progress')
|
|
175
179
|
throw new DurableTaskChecklistRouterInProgressError(result.retryAt);
|
|
@@ -51,6 +51,7 @@ export declare function createDurableTaskHttpBulkMutationRouter(input: {
|
|
|
51
51
|
}>, options?: {
|
|
52
52
|
saveSource?: "manual" | "autosave";
|
|
53
53
|
atomicItemFailures?: boolean;
|
|
54
|
+
expectedDetailRevision?: string;
|
|
54
55
|
}): {
|
|
55
56
|
preview: import("../../core/Taskforce.js").BulkUpdateFieldsPreview;
|
|
56
57
|
results: TaskItem[];
|
|
@@ -222,7 +222,12 @@ export function createDurableTaskHttpBulkMutationRouter(input) {
|
|
|
222
222
|
}, { atomicRecovery: true });
|
|
223
223
|
},
|
|
224
224
|
updateFields(updates, options = {}) {
|
|
225
|
-
return input.rootRouter.runBatch('bulk-update-fields', {
|
|
225
|
+
return input.rootRouter.runBatch('bulk-update-fields', {
|
|
226
|
+
updates,
|
|
227
|
+
...(options.expectedDetailRevision
|
|
228
|
+
? { expectedDetailRevision: options.expectedDetailRevision }
|
|
229
|
+
: {}),
|
|
230
|
+
}, (batch) => {
|
|
226
231
|
const preview = input.core.bulkUpdateFieldsPreview(updates, input.workspaceId);
|
|
227
232
|
const results = [];
|
|
228
233
|
const errors = preview.skipped
|
|
@@ -273,14 +278,21 @@ export function createDurableTaskHttpBulkMutationRouter(input) {
|
|
|
273
278
|
&& !(durableAttachmentLinks && key === 'cardCoverAssetId'))))
|
|
274
279
|
: item.fields;
|
|
275
280
|
let updated = input.core.getTask(item.id, input.workspaceId);
|
|
281
|
+
let expectedDetailRevision = options.expectedDetailRevision;
|
|
276
282
|
if (Object.keys(rootFields).length > 0) {
|
|
277
283
|
const child = input.createChildRouter(input.deriveChildOperationId('bulk-update-fields-root', item.index, item.id), () => { });
|
|
278
284
|
updated = child.enabled
|
|
279
|
-
? batch.runChild(() => child.update(item.id, rootFields, options.saveSource || 'manual'))
|
|
280
|
-
:
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
285
|
+
? batch.runChild(() => child.update(item.id, rootFields, options.saveSource || 'manual', undefined, expectedDetailRevision))
|
|
286
|
+
: (() => {
|
|
287
|
+
if (expectedDetailRevision) {
|
|
288
|
+
input.core.assertTaskDetailRevisionForUpdate(item.id, expectedDetailRevision, input.workspaceId);
|
|
289
|
+
}
|
|
290
|
+
return input.core.updateTask(item.id, rootFields, input.workspaceId, {
|
|
291
|
+
actorRef: 'user',
|
|
292
|
+
saveSource: options.saveSource || 'manual',
|
|
293
|
+
});
|
|
294
|
+
})();
|
|
295
|
+
expectedDetailRevision = undefined;
|
|
284
296
|
}
|
|
285
297
|
if (durableChecklist && checklist) {
|
|
286
298
|
const currentTask = input.core.getTask(item.id, input.workspaceId) || updated;
|
|
@@ -301,7 +313,12 @@ export function createDurableTaskHttpBulkMutationRouter(input) {
|
|
|
301
313
|
updatedAt: String(unchanged ? existing.updatedAt : existing ? now : source.updatedAt || createdAt),
|
|
302
314
|
};
|
|
303
315
|
});
|
|
304
|
-
updated = batch.runChild(() => checklist.replace({
|
|
316
|
+
updated = batch.runChild(() => checklist.replace({
|
|
317
|
+
taskId: item.id,
|
|
318
|
+
items: normalizedChecklist,
|
|
319
|
+
expectedDetailRevision,
|
|
320
|
+
}).task);
|
|
321
|
+
expectedDetailRevision = undefined;
|
|
305
322
|
}
|
|
306
323
|
if (durableAttachmentLinks && attachmentLinks) {
|
|
307
324
|
updated = batch.runChild(() => attachmentLinks.replace({
|
|
@@ -312,7 +329,9 @@ export function createDurableTaskHttpBulkMutationRouter(input) {
|
|
|
312
329
|
? null
|
|
313
330
|
: String(item.fields.cardCoverAssetId).trim(),
|
|
314
331
|
} : {}),
|
|
332
|
+
expectedDetailRevision,
|
|
315
333
|
}).task);
|
|
334
|
+
expectedDetailRevision = undefined;
|
|
316
335
|
}
|
|
317
336
|
if (updated)
|
|
318
337
|
results.push(updated);
|
|
@@ -327,7 +346,10 @@ export function createDurableTaskHttpBulkMutationRouter(input) {
|
|
|
327
346
|
}
|
|
328
347
|
batch.deferPostCommitNotification(() => input.core.notifyDurableTaskBatchCommitted(input.workspaceId, results.map((task) => task.id)));
|
|
329
348
|
return { preview, results, errors };
|
|
330
|
-
}, {
|
|
349
|
+
}, {
|
|
350
|
+
atomicRecovery: true,
|
|
351
|
+
allowIndependentNestedOwnership: true,
|
|
352
|
+
});
|
|
331
353
|
},
|
|
332
354
|
};
|
|
333
355
|
}
|
|
@@ -3,7 +3,8 @@ import type { TaskRestoreDestination } from '../../shared/taskRestoreDestination
|
|
|
3
3
|
export declare class DurableTaskHttpRootMutationError extends Error {
|
|
4
4
|
readonly code: string;
|
|
5
5
|
readonly statusCode: number;
|
|
6
|
-
|
|
6
|
+
readonly details?: Record<string, unknown> | undefined;
|
|
7
|
+
constructor(message: string, code: string, statusCode: number, details?: Record<string, unknown> | undefined);
|
|
7
8
|
}
|
|
8
9
|
export declare function resolveTaskHttpRootMutationCapability(input: {
|
|
9
10
|
runtimeMode: 'local' | 'cloud';
|
|
@@ -22,8 +23,9 @@ export declare function createDurableTaskHttpRootMutationRouter(input: {
|
|
|
22
23
|
deferPostCommitNotification?: (notify: () => void) => void;
|
|
23
24
|
}): {
|
|
24
25
|
enabled: boolean;
|
|
26
|
+
hasCommittedOperation(): boolean;
|
|
25
27
|
create(candidate: unknown): TaskItem;
|
|
26
|
-
update(taskIdInput: string, candidate: unknown, saveSource: "manual" | "autosave", activityAction?: "task-updated" | "task-schedule-changed"): TaskItem;
|
|
28
|
+
update(taskIdInput: string, candidate: unknown, saveSource: "manual" | "autosave", activityAction?: "task-updated" | "task-schedule-changed", expectedDetailRevision?: string): TaskItem;
|
|
27
29
|
runBatch<T>(routeInput: string, request: unknown, apply: (context: {
|
|
28
30
|
runChild<R>(mutation: () => R): R;
|
|
29
31
|
deferPostCommitNotification(notify: () => void): void;
|
|
@@ -36,5 +38,5 @@ export declare function createDurableTaskHttpRootMutationRouter(input: {
|
|
|
36
38
|
restore(deletedRecordIdInput: string, restoreDestination?: TaskRestoreDestination): TaskItem;
|
|
37
39
|
delete(taskIdInput: string): DeletedTaskRecord;
|
|
38
40
|
};
|
|
39
|
-
type TaskforceCoreForHttpRootMutation = Pick<import('../../core/Taskforce.js').TaskforceCore, 'getDatabaseAdapter' | 'getTask' | 'listAssignableActors' | 'getDeletedTask' | 'updateTask' | 'hasTaskDeletionMarkerForSync' | 'createTask' | 'listTaskAttachmentLinksForDurableSync' | 'resolveTaskAttachmentLinksForDurableSync' | 'normalizeTaskAttachmentDescriptorsForDurableSync' | 'notifyDurableTaskCreateCommitted' | 'notifyDurableTaskBatchCommitted' | 'notifyDurableTaskMetadataCommitted' | 'notifyDurableTaskLifecycleCommitted' | 'notifyDurableTaskDeleteCommitted' | 'getTaskEventForSync' | 'getEntityEventForSync' | 'lockTaskLifecycleMutationDomain' | 'archiveTask' | 'unarchiveTask' | 'restoreDeletedTask' | 'addTaskStatusEventsForDurableSync' | 'lockTaskRelationshipMutationDomain' | 'listTaskRelationships' | 'getTaskRelationshipForSync' | 'deleteTaskForSync'>;
|
|
41
|
+
type TaskforceCoreForHttpRootMutation = Pick<import('../../core/Taskforce.js').TaskforceCore, 'getDatabaseAdapter' | 'getTask' | 'getTaskDetailRevision' | 'assertTaskDetailRevisionForUpdate' | 'listAssignableActors' | 'getDeletedTask' | 'updateTask' | 'hasTaskDeletionMarkerForSync' | 'createTask' | 'listTaskAttachmentLinksForDurableSync' | 'resolveTaskAttachmentLinksForDurableSync' | 'normalizeTaskAttachmentDescriptorsForDurableSync' | 'notifyDurableTaskCreateCommitted' | 'notifyDurableTaskBatchCommitted' | 'notifyDurableTaskMetadataCommitted' | 'notifyDurableTaskLifecycleCommitted' | 'notifyDurableTaskDeleteCommitted' | 'getTaskEventForSync' | 'getEntityEventForSync' | 'lockTaskLifecycleMutationDomain' | 'archiveTask' | 'unarchiveTask' | 'restoreDeletedTask' | 'addTaskStatusEventsForDurableSync' | 'lockTaskRelationshipMutationDomain' | 'listTaskRelationships' | 'getTaskRelationshipForSync' | 'deleteTaskForSync'>;
|
|
40
42
|
export {};
|
|
@@ -28,10 +28,12 @@ function requiredText(value, label) {
|
|
|
28
28
|
export class DurableTaskHttpRootMutationError extends Error {
|
|
29
29
|
code;
|
|
30
30
|
statusCode;
|
|
31
|
-
|
|
31
|
+
details;
|
|
32
|
+
constructor(message, code, statusCode, details) {
|
|
32
33
|
super(message);
|
|
33
34
|
this.code = code;
|
|
34
35
|
this.statusCode = statusCode;
|
|
36
|
+
this.details = details;
|
|
35
37
|
this.name = 'DurableTaskHttpRootMutationError';
|
|
36
38
|
}
|
|
37
39
|
}
|
|
@@ -120,6 +122,11 @@ export function createDurableTaskHttpRootMutationRouter(input) {
|
|
|
120
122
|
rethrowDurableReplayError(error, 'Committed durable task HTTP response is invalid.');
|
|
121
123
|
}
|
|
122
124
|
};
|
|
125
|
+
const detailRevisionPrecondition = (taskId, expectedDetailRevision) => {
|
|
126
|
+
if (!expectedDetailRevision)
|
|
127
|
+
return undefined;
|
|
128
|
+
return () => input.core.assertTaskDetailRevisionForUpdate(taskId, expectedDetailRevision, input.workspaceId);
|
|
129
|
+
};
|
|
123
130
|
const lifecycle = (taskIdInput, mutationKind, payload) => {
|
|
124
131
|
const taskId = requiredText(taskIdInput, 'Task id');
|
|
125
132
|
const operation = context();
|
|
@@ -144,6 +151,12 @@ export function createDurableTaskHttpRootMutationRouter(input) {
|
|
|
144
151
|
};
|
|
145
152
|
return {
|
|
146
153
|
enabled,
|
|
154
|
+
hasCommittedOperation() {
|
|
155
|
+
const operationId = String(input.operationId || '').trim();
|
|
156
|
+
if (!operationId)
|
|
157
|
+
return false;
|
|
158
|
+
return Boolean(store.readCommittedOperationResponse(operationIdentity(operationId)));
|
|
159
|
+
},
|
|
147
160
|
create(candidate) {
|
|
148
161
|
const source = candidate && typeof candidate === 'object' && !Array.isArray(candidate)
|
|
149
162
|
? candidate
|
|
@@ -183,7 +196,7 @@ export function createDurableTaskHttpRootMutationRouter(input) {
|
|
|
183
196
|
throw new DurableTaskHttpRootMutationError('The durable task mutation is already in progress.', 'WORKSPACE_SYNC_OPERATION_IN_PROGRESS', 409);
|
|
184
197
|
return result.response.authoritative.payload;
|
|
185
198
|
},
|
|
186
|
-
update(taskIdInput, candidate, saveSource, activityAction) {
|
|
199
|
+
update(taskIdInput, candidate, saveSource, activityAction, expectedDetailRevision) {
|
|
187
200
|
const taskId = requiredText(taskIdInput, 'Task id');
|
|
188
201
|
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
|
|
189
202
|
throw new DurableTaskHttpRootMutationError('Task patch must be an object.', 'DURABLE_TASK_PATCH_INVALID', 400);
|
|
@@ -224,6 +237,8 @@ export function createDurableTaskHttpRootMutationRouter(input) {
|
|
|
224
237
|
now: operation.acceptedAtIso,
|
|
225
238
|
leaseExpiresAt: new Date(operation.acceptedAt.getTime() + OPERATION_LEASE_MS).toISOString(),
|
|
226
239
|
actorRef: input.actorRef,
|
|
240
|
+
expectedDetailRevision,
|
|
241
|
+
assertPrecondition: detailRevisionPrecondition(taskId, expectedDetailRevision),
|
|
227
242
|
onPostCommitError: input.onPostCommitError,
|
|
228
243
|
});
|
|
229
244
|
if (result.state === 'in_progress')
|
|
@@ -283,6 +298,8 @@ export function createDurableTaskHttpRootMutationRouter(input) {
|
|
|
283
298
|
actorRef: input.actorRef,
|
|
284
299
|
saveSource,
|
|
285
300
|
activityAction,
|
|
301
|
+
expectedDetailRevision,
|
|
302
|
+
assertPrecondition: detailRevisionPrecondition(taskId, expectedDetailRevision),
|
|
286
303
|
onPostCommitError: input.onPostCommitError,
|
|
287
304
|
});
|
|
288
305
|
if (result.state === 'in_progress')
|
|
@@ -341,6 +358,14 @@ export function createDurableTaskHttpRootMutationRouter(input) {
|
|
|
341
358
|
if (options?.atomicRecovery) {
|
|
342
359
|
const notifications = [];
|
|
343
360
|
const result = input.core.getDatabaseAdapter().transaction(() => {
|
|
361
|
+
// Child durable mutations lock the workspace sequence before
|
|
362
|
+
// touching task rows. Take the same lock first when a batch
|
|
363
|
+
// may combine a legacy root write with durable child owners.
|
|
364
|
+
store.lockWorkspaceSequenceForMutation({
|
|
365
|
+
tenantId: input.tenantId,
|
|
366
|
+
workspaceId: input.workspaceId,
|
|
367
|
+
now: operation.acceptedAtIso,
|
|
368
|
+
});
|
|
344
369
|
const value = apply({
|
|
345
370
|
runChild: (mutation) => input.core.getDatabaseAdapter().transaction(mutation)(),
|
|
346
371
|
deferPostCommitNotification: (notify) => notifications.push(notify),
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { TaskEvent, TaskItem } from '../../core/Taskforce.js';
|
|
2
|
+
import type { DatabaseAdapter } from '../../storage/databaseAdapter.js';
|
|
2
3
|
import type { DurableTaskAttachmentLinksServiceCore } from './taskAttachmentLinksMutationService.js';
|
|
3
4
|
import { WorkspaceSyncDurabilityStore, type WorkspaceSyncOperationIdentity } from './syncDurabilityStore.js';
|
|
4
5
|
import { type WorkspaceSyncV3TaskAttachmentLink } from './workspaceSyncV3NestedTaskCodecs.js';
|
|
5
6
|
export interface LocalTaskAttachmentLinksOutboxCore extends DurableTaskAttachmentLinksServiceCore {
|
|
7
|
+
getDatabaseAdapter(): DatabaseAdapter;
|
|
6
8
|
getWorkspaceAssetDispatchReadiness(assetId: string, workspaceId: string): {
|
|
7
9
|
ready: boolean;
|
|
8
10
|
retryable: boolean;
|
|
@@ -5,7 +5,9 @@ import { buildTaskAttachmentLinksActivityEventId } from './taskAttachmentLinksAc
|
|
|
5
5
|
import { canonicalizeEntityActivityDetailsForSync, serializeTaskActivityForSync, } from './taskActivityJournal.js';
|
|
6
6
|
import { fingerprintWorkspaceSyncPayload, } from './syncDurabilityStore.js';
|
|
7
7
|
import { serializeTaskRootForSync } from './workspaceSyncV3TaskRootProjection.js';
|
|
8
|
-
import {
|
|
8
|
+
import { resolveWorkspaceSyncV3RegisteredCoverage } from './workspaceSyncV3CoverageRegistry.js';
|
|
9
|
+
import { WorkspaceSyncV3CombinedProjectionStateStore } from './workspaceSyncV3CombinedProjectionState.js';
|
|
10
|
+
import { serializeWorkspaceSyncV3TaskAttachmentLinksPayload, serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload, validateWorkspaceSyncV3TaskAttachmentLinksPayload, validateWorkspaceSyncV3TaskAttachmentLinksV2Payload, } from './workspaceSyncV3NestedTaskCodecs.js';
|
|
9
11
|
import { withOriginActorPayload } from './workspaceSyncV3OriginActor.js';
|
|
10
12
|
export class LocalAuthoritativeTaskAttachmentLinksError extends Error {
|
|
11
13
|
code = 'WORKSPACE_SYNC_AUTHORITATIVE_RESPONSE_INVALID';
|
|
@@ -90,6 +92,24 @@ function requireRevision(value, label) {
|
|
|
90
92
|
throw new LocalAuthoritativeTaskAttachmentLinksError(`${label} is invalid.`);
|
|
91
93
|
return text;
|
|
92
94
|
}
|
|
95
|
+
function resolveOwnedAttachmentProjection(input) {
|
|
96
|
+
const state = new WorkspaceSyncV3CombinedProjectionStateStore(input.core.getDatabaseAdapter()).read({
|
|
97
|
+
tenantId: input.identity.tenantId,
|
|
98
|
+
workspaceId: input.identity.workspaceId,
|
|
99
|
+
});
|
|
100
|
+
if (state.owner !== 'v3')
|
|
101
|
+
return null;
|
|
102
|
+
const coverage = resolveWorkspaceSyncV3RegisteredCoverage(state)?.profile;
|
|
103
|
+
if (!coverage)
|
|
104
|
+
throw new LocalAuthoritativeTaskAttachmentLinksError('Owned task attachment-link coverage is not registered.');
|
|
105
|
+
if (coverage.projections.some(({ projectionId }) => projectionId === 'task-attachment-links/full-v2')) {
|
|
106
|
+
return 'task-attachment-links/full-v2';
|
|
107
|
+
}
|
|
108
|
+
if (coverage.projections.some(({ projectionId }) => projectionId === 'task-attachment-links/full-v1')) {
|
|
109
|
+
return 'task-attachment-links/full-v1';
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
93
113
|
export function enqueueLocalTaskAttachmentLinksMutation(input) {
|
|
94
114
|
const requestedLinks = canonicalLinks(input.taskId, input.links);
|
|
95
115
|
let attachments;
|
|
@@ -206,9 +226,20 @@ export function applyAuthoritativeTaskAttachmentLinksAndAck(input) {
|
|
|
206
226
|
throw new LocalAuthoritativeTaskAttachmentLinksError('Authoritative task attachment-link operation ordering is invalid.');
|
|
207
227
|
}
|
|
208
228
|
const rootPayload = serializeTaskRootForSync(root.payload);
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
229
|
+
const rawAttachmentPayload = attachmentLinks.payload;
|
|
230
|
+
const hasAuthoritativeCardCover = Object.prototype.hasOwnProperty.call(rawAttachmentPayload, 'cardCoverAssetId');
|
|
231
|
+
const validatedAttachmentLinksPayload = hasAuthoritativeCardCover
|
|
232
|
+
? validateWorkspaceSyncV3TaskAttachmentLinksV2Payload(rawAttachmentPayload)
|
|
233
|
+
: validateWorkspaceSyncV3TaskAttachmentLinksPayload(rawAttachmentPayload);
|
|
234
|
+
const ownedAttachmentProjection = resolveOwnedAttachmentProjection(input);
|
|
235
|
+
const attachmentLinksPayload = ownedAttachmentProjection === 'task-attachment-links/full-v1'
|
|
236
|
+
? serializeWorkspaceSyncV3TaskAttachmentLinksPayload(validatedAttachmentLinksPayload)
|
|
237
|
+
: validatedAttachmentLinksPayload;
|
|
238
|
+
const authoritativeCardCoverAssetId = hasAuthoritativeCardCover
|
|
239
|
+
&& ownedAttachmentProjection !== 'task-attachment-links/full-v1'
|
|
240
|
+
? validatedAttachmentLinksPayload
|
|
241
|
+
.cardCoverAssetId
|
|
242
|
+
: undefined;
|
|
212
243
|
const activityPayload = serializeTaskActivityForSync(activity);
|
|
213
244
|
const expectedActivityId = buildTaskAttachmentLinksActivityEventId({
|
|
214
245
|
workspaceId: input.identity.workspaceId, clientId: input.identity.clientId,
|
|
@@ -268,6 +299,9 @@ export function applyAuthoritativeTaskAttachmentLinksAndAck(input) {
|
|
|
268
299
|
completedAt: rootPayload.completedAt,
|
|
269
300
|
canceledReason: rootPayload.canceledReason,
|
|
270
301
|
attachments,
|
|
302
|
+
...(authoritativeCardCoverAssetId !== undefined
|
|
303
|
+
? { cardCoverAssetId: authoritativeCardCoverAssetId }
|
|
304
|
+
: {}),
|
|
271
305
|
};
|
|
272
306
|
const updated = input.core.updateTask(root.entityId, rootPatch, input.identity.workspaceId, {
|
|
273
307
|
persistBackup: false, emitRealtime: false, skipTaskEvents: true,
|
|
@@ -282,9 +316,15 @@ export function applyAuthoritativeTaskAttachmentLinksAndAck(input) {
|
|
|
282
316
|
input.core.upsertEntityEventForSync(activity, { allowAuthoritativeReplacement: true });
|
|
283
317
|
const installedLinks = input.core.listTaskAttachmentLinksForDurableSync(updated.id, input.identity.workspaceId);
|
|
284
318
|
if (canonicalWorkspaceSyncJson(serializeTaskRootForSync(updated)) !== canonicalWorkspaceSyncJson(rootPayload)
|
|
285
|
-
|| canonicalWorkspaceSyncJson(
|
|
286
|
-
|
|
287
|
-
|
|
319
|
+
|| canonicalWorkspaceSyncJson(authoritativeCardCoverAssetId !== undefined
|
|
320
|
+
? serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload({
|
|
321
|
+
taskId: updated.id,
|
|
322
|
+
links: installedLinks,
|
|
323
|
+
cardCoverAssetId: updated.cardCoverAssetId ?? null,
|
|
324
|
+
})
|
|
325
|
+
: serializeWorkspaceSyncV3TaskAttachmentLinksPayload({
|
|
326
|
+
taskId: updated.id, links: installedLinks,
|
|
327
|
+
})) !== canonicalWorkspaceSyncJson(attachmentLinksPayload)) {
|
|
288
328
|
throw new LocalAuthoritativeTaskAttachmentLinksError('Installed task attachment-link state does not match authority.');
|
|
289
329
|
}
|
|
290
330
|
return updated;
|