@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
|
@@ -4,7 +4,7 @@ import { assertWorkspaceSyncV3OperationBoundary } from './workspaceSyncV3Operati
|
|
|
4
4
|
import { projectWorkspaceSyncV3TaskRootJournalRow } from './workspaceSyncV3TaskRootProjection.js';
|
|
5
5
|
import { projectWorkspaceSyncV3InitiativeJournalRow } from './workspaceSyncV3Feed.js';
|
|
6
6
|
import { projectWorkspaceSyncV3WorkstreamJournalRow } from './workspaceSyncV3WorkstreamProjection.js';
|
|
7
|
-
import { serializeWorkspaceSyncV3EntityEventPayload, validateWorkspaceSyncV3EntityEventPayload, validateWorkspaceSyncV3TaskAttachmentLinksPayload, validateWorkspaceSyncV3TaskChecklistPayload, validateWorkspaceSyncV3TaskCommentAppendPayload, validateWorkspaceSyncV3TaskCommentsPayload, } from './workspaceSyncV3NestedTaskCodecs.js';
|
|
7
|
+
import { serializeWorkspaceSyncV3EntityEventPayload, serializeWorkspaceSyncV3TaskAttachmentLinksPayload, validateWorkspaceSyncV3EntityEventPayload, validateWorkspaceSyncV3TaskAttachmentLinksPayload, validateWorkspaceSyncV3TaskAttachmentLinksV2Payload, validateWorkspaceSyncV3TaskChecklistPayload, validateWorkspaceSyncV3TaskCommentAppendPayload, validateWorkspaceSyncV3TaskCommentsPayload, } from './workspaceSyncV3NestedTaskCodecs.js';
|
|
8
8
|
import { isLegacyEntityEventAdoptionIdentity } from './entityEventLegacyAdoptionContract.js';
|
|
9
9
|
export const WORKSPACE_SYNC_V3_NESTED_TASK_PROJECTIONS = Object.freeze([
|
|
10
10
|
Object.freeze({
|
|
@@ -46,6 +46,15 @@ export const WORKSPACE_SYNC_V3_NESTED_TASK_PROJECTIONS = Object.freeze([
|
|
|
46
46
|
operationAtomic: true,
|
|
47
47
|
}),
|
|
48
48
|
]);
|
|
49
|
+
export const WORKSPACE_SYNC_V3_TASK_ATTACHMENT_LINKS_V2_PROJECTION = Object.freeze({
|
|
50
|
+
projectionId: 'task-attachment-links/full-v2',
|
|
51
|
+
domain: 'task-attachment-links',
|
|
52
|
+
schemaVersion: 2,
|
|
53
|
+
mutationKinds: Object.freeze(['replace']),
|
|
54
|
+
fields: Object.freeze(['taskId', 'links', 'cardCoverAssetId']),
|
|
55
|
+
dependencies: Object.freeze(['task/root-v1']),
|
|
56
|
+
operationAtomic: true,
|
|
57
|
+
});
|
|
49
58
|
const projectionByDomain = new Map(WORKSPACE_SYNC_V3_NESTED_TASK_PROJECTIONS.map((projection) => [projection.domain, projection.projectionId]));
|
|
50
59
|
function sequence(value, label) {
|
|
51
60
|
const text = String(value ?? '').trim();
|
|
@@ -68,7 +77,7 @@ function parsePayload(row) {
|
|
|
68
77
|
throw new Error(`Nested task journal payload is malformed at ${row.sequence}.`);
|
|
69
78
|
}
|
|
70
79
|
}
|
|
71
|
-
function canonicalPayload(row) {
|
|
80
|
+
function canonicalPayload(row, attachmentProjection) {
|
|
72
81
|
const raw = parsePayload(row);
|
|
73
82
|
if (row.domain === 'task-comments') {
|
|
74
83
|
if (row.mutationKind === 'append') {
|
|
@@ -91,7 +100,17 @@ function canonicalPayload(row) {
|
|
|
91
100
|
return { payload, canonicalFingerprint: fingerprintWorkspaceSyncPayload(payload) };
|
|
92
101
|
}
|
|
93
102
|
if (row.domain === 'task-attachment-links' && row.mutationKind === 'replace') {
|
|
94
|
-
const payload =
|
|
103
|
+
const payload = attachmentProjection === 'task-attachment-links/full-v2'
|
|
104
|
+
? validateWorkspaceSyncV3TaskAttachmentLinksV2Payload(raw)
|
|
105
|
+
: (() => {
|
|
106
|
+
try {
|
|
107
|
+
return validateWorkspaceSyncV3TaskAttachmentLinksPayload(raw);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
const v2 = validateWorkspaceSyncV3TaskAttachmentLinksV2Payload(raw);
|
|
111
|
+
return serializeWorkspaceSyncV3TaskAttachmentLinksPayload(v2);
|
|
112
|
+
}
|
|
113
|
+
})();
|
|
95
114
|
if (payload.taskId !== row.entityId)
|
|
96
115
|
throw new Error(`Task attachment-link identity is invalid at ${row.sequence}.`);
|
|
97
116
|
return { payload, canonicalFingerprint: fingerprintWorkspaceSyncPayload(payload) };
|
|
@@ -107,8 +126,10 @@ function canonicalPayload(row) {
|
|
|
107
126
|
function canonicalEntityEventPayload(raw) {
|
|
108
127
|
return validateWorkspaceSyncV3EntityEventPayload(serializeWorkspaceSyncV3EntityEventPayload(raw));
|
|
109
128
|
}
|
|
110
|
-
|
|
111
|
-
const projection =
|
|
129
|
+
function projectNestedTaskJournalRow(row, attachmentProjection) {
|
|
130
|
+
const projection = row.domain === 'task-attachment-links'
|
|
131
|
+
? attachmentProjection
|
|
132
|
+
: projectionByDomain.get(row.domain);
|
|
112
133
|
if (!projection)
|
|
113
134
|
return null;
|
|
114
135
|
if (row.contractVersion !== WORKSPACE_SYNC_CONTRACT_VERSION) {
|
|
@@ -131,7 +152,7 @@ export function projectWorkspaceSyncV3NestedTaskJournalRow(row) {
|
|
|
131
152
|
if (payload.action === 'sync-v3-repair-baseline')
|
|
132
153
|
return null;
|
|
133
154
|
}
|
|
134
|
-
const { payload, canonicalFingerprint } = canonicalPayload(row);
|
|
155
|
+
const { payload, canonicalFingerprint } = canonicalPayload(row, attachmentProjection);
|
|
135
156
|
return {
|
|
136
157
|
sequence: journalSequence,
|
|
137
158
|
projection,
|
|
@@ -146,8 +167,17 @@ export function projectWorkspaceSyncV3NestedTaskJournalRow(row) {
|
|
|
146
167
|
occurredAt,
|
|
147
168
|
};
|
|
148
169
|
}
|
|
170
|
+
export function projectWorkspaceSyncV3NestedTaskJournalRow(row) {
|
|
171
|
+
return projectNestedTaskJournalRow(row, 'task-attachment-links/full-v1');
|
|
172
|
+
}
|
|
173
|
+
export function projectWorkspaceSyncV3NestedTaskV2JournalRow(row) {
|
|
174
|
+
return projectNestedTaskJournalRow(row, 'task-attachment-links/full-v2');
|
|
175
|
+
}
|
|
149
176
|
export function validateWorkspaceSyncV3NestedTaskFeedEntry(entry) {
|
|
150
|
-
const
|
|
177
|
+
const projector = entry.projection === 'task-attachment-links/full-v2'
|
|
178
|
+
? projectWorkspaceSyncV3NestedTaskV2JournalRow
|
|
179
|
+
: projectWorkspaceSyncV3NestedTaskJournalRow;
|
|
180
|
+
const projected = projector({
|
|
151
181
|
sequence: entry.sequence,
|
|
152
182
|
contractVersion: WORKSPACE_SYNC_CONTRACT_VERSION,
|
|
153
183
|
clientId: 'validation-client',
|
|
@@ -181,9 +211,9 @@ function assertNestedDependentOrder(rows) {
|
|
|
181
211
|
throw new Error('Nested task operation dependents are duplicated or out of order.');
|
|
182
212
|
}
|
|
183
213
|
}
|
|
184
|
-
function projectNestedDependentRows(rows) {
|
|
214
|
+
function projectNestedDependentRows(rows, attachmentProjection) {
|
|
185
215
|
return rows.flatMap((row) => {
|
|
186
|
-
const projected =
|
|
216
|
+
const projected = projectNestedTaskJournalRow(row, attachmentProjection);
|
|
187
217
|
if (!projected) {
|
|
188
218
|
const payload = row.domain === 'entity-event'
|
|
189
219
|
? canonicalEntityEventPayload(parsePayload(row))
|
|
@@ -217,7 +247,7 @@ function isSupportedNestedOnlyOperation(rows) {
|
|
|
217
247
|
* root. Apply still requires referenced parents to exist on the receiver; feed
|
|
218
248
|
* only needs to project the nested rows.
|
|
219
249
|
*/
|
|
220
|
-
function projectNestedOnlyOperation(rows) {
|
|
250
|
+
function projectNestedOnlyOperation(rows, attachmentProjection) {
|
|
221
251
|
if (!isSupportedNestedOnlyOperation(rows)) {
|
|
222
252
|
throw new Error('Nested-only task operation is not an allowed repair or reconciliation shape.');
|
|
223
253
|
}
|
|
@@ -229,7 +259,7 @@ function projectNestedOnlyOperation(rows) {
|
|
|
229
259
|
throw new Error('Nested task operation dependent identity does not match its task root.');
|
|
230
260
|
}
|
|
231
261
|
}
|
|
232
|
-
const projected = projectNestedDependentRows(rows);
|
|
262
|
+
const projected = projectNestedDependentRows(rows, attachmentProjection);
|
|
233
263
|
const events = projected
|
|
234
264
|
.filter((entry) => entry.domain === 'entity-event')
|
|
235
265
|
.map((entry) => validateWorkspaceSyncV3EntityEventPayload(entry.payload));
|
|
@@ -256,14 +286,14 @@ function projectNestedOnlyOperation(rows) {
|
|
|
256
286
|
* parent-first operations; repair baselines and comment reconcile may emit
|
|
257
287
|
* nested-only groups that still project when every row is a nested domain.
|
|
258
288
|
*/
|
|
259
|
-
|
|
289
|
+
function projectNestedTaskOperation(input, attachmentProjection) {
|
|
260
290
|
if (!input.rows.some((row) => projectionByDomain.has(row.domain)))
|
|
261
291
|
return null;
|
|
262
292
|
assertWorkspaceSyncV3OperationBoundary(input);
|
|
263
293
|
const [first, ...dependents] = input.rows;
|
|
264
294
|
if (first && projectionByDomain.has(first.domain)
|
|
265
295
|
&& input.rows.every((row) => projectionByDomain.has(row.domain))) {
|
|
266
|
-
return projectNestedOnlyOperation(input.rows);
|
|
296
|
+
return projectNestedOnlyOperation(input.rows, attachmentProjection);
|
|
267
297
|
}
|
|
268
298
|
if (!first || !['task', 'initiative', 'workstream'].includes(first.domain)) {
|
|
269
299
|
throw new Error('Nested task operation must begin with its v3-owned parent root.');
|
|
@@ -272,7 +302,7 @@ export function projectWorkspaceSyncV3NestedTaskOperation(input) {
|
|
|
272
302
|
throw new Error('Nested task operation contains an unsupported dependent row.');
|
|
273
303
|
}
|
|
274
304
|
assertNestedDependentOrder(dependents);
|
|
275
|
-
const projectedDependents = projectNestedDependentRows(dependents);
|
|
305
|
+
const projectedDependents = projectNestedDependentRows(dependents, attachmentProjection);
|
|
276
306
|
const eventRows = projectedDependents.filter((entry) => entry.domain === 'entity-event');
|
|
277
307
|
const rawEventRows = dependents.filter((row) => row.domain === 'entity-event');
|
|
278
308
|
const sentinelOnly = eventRows.length === 0 && rawEventRows.length > 0 && rawEventRows.every((row) => (canonicalEntityEventPayload(parsePayload(row)).action === 'sync-v3-repair-baseline'));
|
|
@@ -327,3 +357,9 @@ export function projectWorkspaceSyncV3NestedTaskOperation(input) {
|
|
|
327
357
|
}
|
|
328
358
|
return [parent, ...projectedDependents];
|
|
329
359
|
}
|
|
360
|
+
export function projectWorkspaceSyncV3NestedTaskOperation(input) {
|
|
361
|
+
return projectNestedTaskOperation(input, 'task-attachment-links/full-v1');
|
|
362
|
+
}
|
|
363
|
+
export function projectWorkspaceSyncV3NestedTaskV2Operation(input) {
|
|
364
|
+
return projectNestedTaskOperation(input, 'task-attachment-links/full-v2');
|
|
365
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { EntityEvent, TaskItem } from '../../types.js';
|
|
|
2
2
|
import type { WorkspaceSyncV3ProjectionDescriptor } from './workspaceSyncV3FeedProfile.js';
|
|
3
3
|
import { type WorkspaceSyncV3TaskAttachmentLink } from './workspaceSyncV3NestedTaskCodecs.js';
|
|
4
4
|
export type WorkspaceSyncV3NestedTaskRepairCandidate = {
|
|
5
|
-
projection: Extract<WorkspaceSyncV3ProjectionDescriptor, 'task-comments/full-v1' | 'task-checklist/full-v1' | 'task-attachment-links/full-v1' | 'entity-event/full-v1'>;
|
|
5
|
+
projection: Extract<WorkspaceSyncV3ProjectionDescriptor, 'task-comments/full-v1' | 'task-checklist/full-v1' | 'task-attachment-links/full-v1' | 'task-attachment-links/full-v2' | 'entity-event/full-v1'>;
|
|
6
6
|
domain: 'task-comments' | 'task-checklist' | 'task-attachment-links' | 'entity-event';
|
|
7
7
|
entityId: string;
|
|
8
8
|
mutationKind: 'replace' | 'append';
|
|
@@ -16,4 +16,5 @@ export declare function buildWorkspaceSyncV3NestedTaskRepairCandidates(input: {
|
|
|
16
16
|
tasks: readonly TaskItem[];
|
|
17
17
|
events: readonly EntityEvent[];
|
|
18
18
|
attachmentLinksByTaskId?: ReadonlyMap<string, readonly WorkspaceSyncV3TaskAttachmentLink[]>;
|
|
19
|
+
attachmentProjection?: 'task-attachment-links/full-v1' | 'task-attachment-links/full-v2';
|
|
19
20
|
}): WorkspaceSyncV3NestedTaskRepairCandidate[];
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { serializeWorkspaceSyncV3EntityEventPayload, serializeWorkspaceSyncV3TaskChecklistPayload, serializeWorkspaceSyncV3TaskCommentsPayload, validateWorkspaceSyncV3TaskAttachmentLinksPayload, } from './workspaceSyncV3NestedTaskCodecs.js';
|
|
1
|
+
import { serializeWorkspaceSyncV3EntityEventPayload, serializeWorkspaceSyncV3TaskChecklistPayload, serializeWorkspaceSyncV3TaskCommentsPayload, validateWorkspaceSyncV3TaskAttachmentLinksPayload, serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload, } from './workspaceSyncV3NestedTaskCodecs.js';
|
|
2
2
|
const rank = new Map([
|
|
3
3
|
['task-comments/full-v1', 0],
|
|
4
4
|
['task-checklist/full-v1', 1],
|
|
5
5
|
['task-attachment-links/full-v1', 2],
|
|
6
|
+
['task-attachment-links/full-v2', 2],
|
|
6
7
|
['entity-event/full-v1', 3],
|
|
7
8
|
]);
|
|
8
9
|
function compareCodeUnits(left, right) {
|
|
@@ -19,6 +20,7 @@ export function buildWorkspaceSyncV3NestedTaskRepairCandidates(input) {
|
|
|
19
20
|
if (!links) {
|
|
20
21
|
throw new Error(`Task attachment-link repair source is missing for ${task.id}.`);
|
|
21
22
|
}
|
|
23
|
+
const attachmentProjection = input.attachmentProjection || 'task-attachment-links/full-v1';
|
|
22
24
|
candidates.push({
|
|
23
25
|
projection: 'task-comments/full-v1', domain: 'task-comments', entityId: task.id, mutationKind: 'replace',
|
|
24
26
|
payload: serializeWorkspaceSyncV3TaskCommentsPayload({ taskId: task.id, comments: task.comments || [] }),
|
|
@@ -26,8 +28,14 @@ export function buildWorkspaceSyncV3NestedTaskRepairCandidates(input) {
|
|
|
26
28
|
projection: 'task-checklist/full-v1', domain: 'task-checklist', entityId: task.id, mutationKind: 'replace',
|
|
27
29
|
payload: serializeWorkspaceSyncV3TaskChecklistPayload({ taskId: task.id, items: task.checklistItems || [] }),
|
|
28
30
|
}, {
|
|
29
|
-
projection:
|
|
30
|
-
payload:
|
|
31
|
+
projection: attachmentProjection, domain: 'task-attachment-links', entityId: task.id, mutationKind: 'replace',
|
|
32
|
+
payload: attachmentProjection === 'task-attachment-links/full-v2'
|
|
33
|
+
? serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload({
|
|
34
|
+
taskId: task.id,
|
|
35
|
+
links,
|
|
36
|
+
cardCoverAssetId: task.cardCoverAssetId ?? null,
|
|
37
|
+
})
|
|
38
|
+
: validateWorkspaceSyncV3TaskAttachmentLinksPayload({ taskId: task.id, links }),
|
|
31
39
|
});
|
|
32
40
|
}
|
|
33
41
|
for (const event of input.events) {
|
|
@@ -30,6 +30,7 @@ type TaskOperationBase<TMutationKind extends string, TPayload> = {
|
|
|
30
30
|
export type WorkspaceSyncV3TaskMetadataEnvelope = WorkspaceSyncV3OperationEnvelopeBase<TaskOperationBase<'metadata-update', {
|
|
31
31
|
patch: Partial<TaskItem>;
|
|
32
32
|
saveSource?: 'manual' | 'autosave';
|
|
33
|
+
expectedDetailRevision?: string;
|
|
33
34
|
originActor?: WorkspaceSyncV3OriginActor;
|
|
34
35
|
}>>;
|
|
35
36
|
export type WorkspaceSyncV3TaskCreateEnvelope = WorkspaceSyncV3OperationEnvelopeBase<TaskOperationBase<'create', {
|
|
@@ -74,6 +75,7 @@ export type WorkspaceSyncV3TaskWorkflowEnvelope = WorkspaceSyncV3OperationEnvelo
|
|
|
74
75
|
export type WorkspaceSyncV3TaskStatusEnvelope = WorkspaceSyncV3OperationEnvelopeBase<TaskOperationBase<'set-status', DurableTaskStatusMutationPayload & {
|
|
75
76
|
workflowAssignment?: WorkflowAssignmentSyncRecord;
|
|
76
77
|
originActor?: WorkspaceSyncV3OriginActor;
|
|
78
|
+
expectedDetailRevision?: string;
|
|
77
79
|
}>>;
|
|
78
80
|
export type WorkspaceSyncV3WorkflowRuntimeEnvelope = WorkspaceSyncV3OperationEnvelopeBase<{
|
|
79
81
|
domain: 'workflow-execution';
|
|
@@ -131,11 +131,13 @@ export interface WorkspaceSyncV3OperatorDiagnosticsSnapshot {
|
|
|
131
131
|
};
|
|
132
132
|
attachmentApply: {
|
|
133
133
|
openObligations: number;
|
|
134
|
+
terminalObligations: number;
|
|
134
135
|
dueObligations: number;
|
|
135
136
|
oldestOpenCreatedAt: string | null;
|
|
136
137
|
nextAttemptAt: string | null;
|
|
137
138
|
attempted: number;
|
|
138
139
|
lastError: string | null;
|
|
140
|
+
terminalReason: string | null;
|
|
139
141
|
};
|
|
140
142
|
runner: {
|
|
141
143
|
available: boolean;
|
|
@@ -216,12 +216,14 @@ export function buildWorkspaceSyncV3OperatorDiagnostics(input) {
|
|
|
216
216
|
WHERE tenant_id = ? AND workspace_id = ? AND resolved_at IS NULL
|
|
217
217
|
`).get(tenantId, workspaceId);
|
|
218
218
|
attachmentApplyRow = input.db.prepare(`
|
|
219
|
-
SELECT
|
|
220
|
-
SUM(CASE WHEN
|
|
221
|
-
|
|
222
|
-
MIN(
|
|
223
|
-
|
|
224
|
-
|
|
219
|
+
SELECT SUM(CASE WHEN terminal_at IS NULL THEN 1 ELSE 0 END) AS count,
|
|
220
|
+
SUM(CASE WHEN terminal_at IS NOT NULL THEN 1 ELSE 0 END) AS terminal,
|
|
221
|
+
SUM(CASE WHEN terminal_at IS NULL AND next_attempt_at <= ? THEN 1 ELSE 0 END) AS due,
|
|
222
|
+
MIN(CASE WHEN terminal_at IS NULL THEN created_at END) AS oldest_created_at,
|
|
223
|
+
MIN(CASE WHEN terminal_at IS NULL THEN next_attempt_at END) AS next_attempt_at,
|
|
224
|
+
COALESCE(SUM(CASE WHEN terminal_at IS NULL THEN attempts ELSE 0 END), 0) AS attempts,
|
|
225
|
+
MAX(CASE WHEN terminal_at IS NULL THEN last_error END) AS last_error,
|
|
226
|
+
MAX(CASE WHEN terminal_at IS NOT NULL THEN terminal_reason END) AS terminal_reason
|
|
225
227
|
FROM workspace_sync_v3_attachment_apply_obligations
|
|
226
228
|
WHERE tenant_id = ? AND workspace_id = ? AND resolved_at IS NULL
|
|
227
229
|
`).get(nowIso, tenantId, workspaceId);
|
|
@@ -255,7 +257,9 @@ export function buildWorkspaceSyncV3OperatorDiagnostics(input) {
|
|
|
255
257
|
const oldestOpenRecoveryAt = String(recoveryRow?.oldest_created_at || '').trim() || null;
|
|
256
258
|
const coordinatorGeneration = coordinatorState ? integer(coordinatorState.coordinator_generation) : null;
|
|
257
259
|
const ownershipMode = coordinatorState ? String(coordinatorState.ownership_mode || '') || null : null;
|
|
258
|
-
const localConverged = baseLocalConverged
|
|
260
|
+
const localConverged = baseLocalConverged
|
|
261
|
+
&& integer(attachmentApplyRow?.count) === 0
|
|
262
|
+
&& integer(attachmentApplyRow?.terminal) === 0;
|
|
259
263
|
const coordinatorFailures = [];
|
|
260
264
|
if (input.runtimeMode === 'local') {
|
|
261
265
|
if (!coordinatorState)
|
|
@@ -279,6 +283,9 @@ export function buildWorkspaceSyncV3OperatorDiagnostics(input) {
|
|
|
279
283
|
if (integer(attachmentApplyRow?.count) > 0) {
|
|
280
284
|
coordinatorFailures.push('open attachment apply obligations remain');
|
|
281
285
|
}
|
|
286
|
+
if (integer(attachmentApplyRow?.terminal) > 0) {
|
|
287
|
+
coordinatorFailures.push('terminal unavailable attachment apply obligations require operator action');
|
|
288
|
+
}
|
|
282
289
|
if (ownershipMode === 'server' && !runnerState) {
|
|
283
290
|
coordinatorFailures.push('server ownership has no durable runner checkpoint');
|
|
284
291
|
}
|
|
@@ -305,6 +312,9 @@ export function buildWorkspaceSyncV3OperatorDiagnostics(input) {
|
|
|
305
312
|
failures.push('recent pull/apply window contains 409 responses');
|
|
306
313
|
if (integer(attachmentApplyRow?.count) > 0)
|
|
307
314
|
failures.push('attachment apply obligations remain open');
|
|
315
|
+
if (integer(attachmentApplyRow?.terminal) > 0) {
|
|
316
|
+
failures.push('terminal unavailable attachment apply obligations require operator action');
|
|
317
|
+
}
|
|
308
318
|
if (cursorAtCycleHighWatermark === null)
|
|
309
319
|
unknowns.push('no committed feed cycle coordinates are available');
|
|
310
320
|
if (!events.length)
|
|
@@ -387,11 +397,13 @@ export function buildWorkspaceSyncV3OperatorDiagnostics(input) {
|
|
|
387
397
|
},
|
|
388
398
|
attachmentApply: {
|
|
389
399
|
openObligations: integer(attachmentApplyRow?.count),
|
|
400
|
+
terminalObligations: integer(attachmentApplyRow?.terminal),
|
|
390
401
|
dueObligations: integer(attachmentApplyRow?.due),
|
|
391
402
|
oldestOpenCreatedAt: String(attachmentApplyRow?.oldest_created_at || '').trim() || null,
|
|
392
403
|
nextAttemptAt: String(attachmentApplyRow?.next_attempt_at || '').trim() || null,
|
|
393
404
|
attempted: integer(attachmentApplyRow?.attempts),
|
|
394
405
|
lastError: String(attachmentApplyRow?.last_error || '').trim() || null,
|
|
406
|
+
terminalReason: String(attachmentApplyRow?.terminal_reason || '').trim() || null,
|
|
395
407
|
},
|
|
396
408
|
runner: {
|
|
397
409
|
available: Boolean(runnerState),
|
|
@@ -36,12 +36,19 @@ export function buildWorkspaceSyncV3TaskCommentAuditPage(input) {
|
|
|
36
36
|
const workspaceId = String(input.workspaceId || '').trim();
|
|
37
37
|
const previousCursor = normalizeCursor(input.cursor);
|
|
38
38
|
const limit = pageSize(input.limit);
|
|
39
|
+
// Task IDs are compared in JavaScript when a page is installed locally.
|
|
40
|
+
// Make the database boundary use the same bytewise ordering so Postgres
|
|
41
|
+
// locale collation cannot place mixed-case IDs in an order the local
|
|
42
|
+
// validator rejects (SQLite already defaults to binary ordering here).
|
|
43
|
+
const taskIdWithCollationSql = input.core.getDatabaseAdapter().provider === 'postgres'
|
|
44
|
+
? 'id COLLATE "C"'
|
|
45
|
+
: 'id COLLATE BINARY';
|
|
39
46
|
const rows = input.core.getDatabaseAdapter().prepare(`
|
|
40
47
|
SELECT id
|
|
41
48
|
FROM tasks
|
|
42
49
|
WHERE tenant_id = ? AND workspace_id = ?
|
|
43
|
-
AND (? = '' OR
|
|
44
|
-
ORDER BY
|
|
50
|
+
AND (? = '' OR ${taskIdWithCollationSql} > ?)
|
|
51
|
+
ORDER BY ${taskIdWithCollationSql}
|
|
45
52
|
LIMIT ?
|
|
46
53
|
`).all(input.core.getTenantId(), workspaceId, previousCursor || '', previousCursor || '', limit + 1);
|
|
47
54
|
const pageRows = rows.slice(0, limit);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WorkspaceSyncV3FeedCoveragePolicy, WorkspaceSyncV3FeedEntry, WorkspaceSyncV3FeedOperationProjector } from './workspaceSyncV3Feed.js';
|
|
2
|
+
import type { WorkspaceSyncV3CoverageProfile } from './workspaceSyncV3FeedProfile.js';
|
|
3
|
+
/** Dark v7 candidate: v4 ownership plus explicit task card-cover selection. */
|
|
4
|
+
export declare const WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_PROFILE: WorkspaceSyncV3CoverageProfile;
|
|
5
|
+
export declare const projectWorkspaceSyncV3TaskCoverCoverageOperation: WorkspaceSyncV3FeedOperationProjector;
|
|
6
|
+
export declare const WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_POLICY: WorkspaceSyncV3FeedCoveragePolicy;
|
|
7
|
+
export declare function validateWorkspaceSyncV3TaskCoverCoverageFeedEntry(entry: WorkspaceSyncV3FeedEntry): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { canonicalWorkspaceSyncJson } from './canonicalWorkspaceSyncJson.js';
|
|
3
|
+
import { projectWorkspaceSyncV3CombinedOperation, validateWorkspaceSyncV3CombinedFeedEntry, } from './workspaceSyncV3CombinedCoverage.js';
|
|
4
|
+
import { WORKSPACE_SYNC_V3_NESTED_COVERAGE_PROFILE } from './workspaceSyncV3NestedCoverage.js';
|
|
5
|
+
import { projectWorkspaceSyncV3NestedTaskV2Operation, validateWorkspaceSyncV3NestedTaskFeedEntry, WORKSPACE_SYNC_V3_TASK_ATTACHMENT_LINKS_V2_PROJECTION, } from './workspaceSyncV3NestedTaskProjection.js';
|
|
6
|
+
const projections = Object.freeze(WORKSPACE_SYNC_V3_NESTED_COVERAGE_PROFILE.projections.map((projection) => (projection.projectionId === 'task-attachment-links/full-v1'
|
|
7
|
+
? WORKSPACE_SYNC_V3_TASK_ATTACHMENT_LINKS_V2_PROJECTION
|
|
8
|
+
: projection)));
|
|
9
|
+
const identity = Object.freeze({
|
|
10
|
+
coverageProfileId: 'task-planning-metadata',
|
|
11
|
+
coverageVersion: 7,
|
|
12
|
+
projections,
|
|
13
|
+
});
|
|
14
|
+
/** Dark v7 candidate: v4 ownership plus explicit task card-cover selection. */
|
|
15
|
+
export const WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_PROFILE = Object.freeze({
|
|
16
|
+
...identity,
|
|
17
|
+
coverageDigest: createHash('sha256').update(canonicalWorkspaceSyncJson(identity)).digest('hex'),
|
|
18
|
+
});
|
|
19
|
+
const NESTED_DOMAINS = new Set(['task-comments', 'task-checklist', 'task-attachment-links', 'entity-event']);
|
|
20
|
+
export const projectWorkspaceSyncV3TaskCoverCoverageOperation = (input) => {
|
|
21
|
+
if (input.rows.some((row) => NESTED_DOMAINS.has(row.domain))) {
|
|
22
|
+
const nested = projectWorkspaceSyncV3NestedTaskV2Operation(input);
|
|
23
|
+
if (!nested)
|
|
24
|
+
throw new Error('Task-cover coverage operation is incomplete.');
|
|
25
|
+
return nested;
|
|
26
|
+
}
|
|
27
|
+
return projectWorkspaceSyncV3CombinedOperation(input);
|
|
28
|
+
};
|
|
29
|
+
export const WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_POLICY = Object.freeze({
|
|
30
|
+
profile: WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_PROFILE,
|
|
31
|
+
projectOperation: projectWorkspaceSyncV3TaskCoverCoverageOperation,
|
|
32
|
+
});
|
|
33
|
+
export function validateWorkspaceSyncV3TaskCoverCoverageFeedEntry(entry) {
|
|
34
|
+
if (NESTED_DOMAINS.has(entry.domain))
|
|
35
|
+
return validateWorkspaceSyncV3NestedTaskFeedEntry(entry);
|
|
36
|
+
return validateWorkspaceSyncV3CombinedFeedEntry(entry);
|
|
37
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_SELECTOR: Readonly<{
|
|
2
|
+
coverageProfileId: "task-planning-metadata";
|
|
3
|
+
coverageVersion: 7;
|
|
4
|
+
coverageDigest: "84b0dee7ac0811234288c1b296357b2ddbcfe82d438ccf90b6bc6e8b5afea0b6";
|
|
5
|
+
}>;
|
|
6
|
+
export declare const WORKSPACE_SYNC_V3_TASK_COVER_EXCLUDED_V2_PROJECTIONS: readonly ("initiative/full-v1" | "taxonomy/full-v1" | "task/root-v1" | "task-relationship/full-v1" | "workstream/full-v1" | "task-comments/full-v1" | "task-checklist/full-v1" | "task-attachment-links/full-v2" | "entity-event/full-v1")[];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { WORKSPACE_SYNC_V3_NESTED_EXCLUDED_V2_PROJECTIONS } from './workspaceSyncV3NestedWireContract.js';
|
|
2
|
+
export const WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_SELECTOR = Object.freeze({
|
|
3
|
+
coverageProfileId: 'task-planning-metadata',
|
|
4
|
+
coverageVersion: 7,
|
|
5
|
+
coverageDigest: '84b0dee7ac0811234288c1b296357b2ddbcfe82d438ccf90b6bc6e8b5afea0b6',
|
|
6
|
+
});
|
|
7
|
+
export const WORKSPACE_SYNC_V3_TASK_COVER_EXCLUDED_V2_PROJECTIONS = Object.freeze(WORKSPACE_SYNC_V3_NESTED_EXCLUDED_V2_PROJECTIONS.map((projection) => (projection === 'task-attachment-links/full-v1'
|
|
8
|
+
? 'task-attachment-links/full-v2'
|
|
9
|
+
: projection)));
|
|
@@ -33,7 +33,8 @@ export async function buildWorkspacePullFeed(input) {
|
|
|
33
33
|
const excludedProjections = new Set(input.excludedProjections || []);
|
|
34
34
|
const commentsV3Owned = excludedProjections.has('task-comments/full-v1');
|
|
35
35
|
const checklistV3Owned = excludedProjections.has('task-checklist/full-v1');
|
|
36
|
-
const attachmentsV3Owned = excludedProjections.has('task-attachment-links/full-v1')
|
|
36
|
+
const attachmentsV3Owned = excludedProjections.has('task-attachment-links/full-v1')
|
|
37
|
+
|| excludedProjections.has('task-attachment-links/full-v2');
|
|
37
38
|
const suppressTaskCarriers = excludedProjections.has('task/root-v1')
|
|
38
39
|
&& commentsV3Owned && checklistV3Owned && attachmentsV3Owned;
|
|
39
40
|
const syncCursor = cursor ? { watermark: cursor.u, id: cursor.id } : null;
|
|
@@ -22,6 +22,7 @@ export interface WorkspaceRepairManifestDocumentEntry {
|
|
|
22
22
|
sha256: string;
|
|
23
23
|
sizeBytes: number;
|
|
24
24
|
existsLocally: boolean;
|
|
25
|
+
contentCheckReliable: boolean;
|
|
25
26
|
source: 'metadata' | 'filesystem';
|
|
26
27
|
publicationState: 'legacy' | 'versioned' | 'deleted' | 'invalid';
|
|
27
28
|
publicationError: string | null;
|
|
@@ -36,6 +37,7 @@ export interface WorkspaceRepairManifestAssetEntry {
|
|
|
36
37
|
sha256: string;
|
|
37
38
|
sizeBytes: number;
|
|
38
39
|
existsLocally: boolean;
|
|
40
|
+
contentCheckReliable: boolean;
|
|
39
41
|
source: 'metadata' | 'filesystem';
|
|
40
42
|
publicationState: 'legacy' | 'versioned' | 'deleted' | 'invalid';
|
|
41
43
|
publicationError: string | null;
|
|
@@ -103,4 +105,5 @@ export declare function scanWorkspaceRepair(input: WorkspaceRepairInput): Worksp
|
|
|
103
105
|
export declare function applyWorkspaceRepair(input: WorkspaceRepairInput & {
|
|
104
106
|
upsertDocumentWatermark: (workspaceId: string, path: string, contentHash: string, updatedAt: string) => void;
|
|
105
107
|
repairMissingAssetLinks?: boolean;
|
|
108
|
+
repairMissingContentFiles?: boolean;
|
|
106
109
|
}): WorkspaceRepairApplyResult;
|
|
@@ -44,6 +44,14 @@ function collectFiles(dirPath, predicate) {
|
|
|
44
44
|
visit(root);
|
|
45
45
|
return output;
|
|
46
46
|
}
|
|
47
|
+
function isStorageRootAccessible(basePath) {
|
|
48
|
+
try {
|
|
49
|
+
return fs.statSync(basePath).isDirectory();
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
47
55
|
function readFileFingerprint(absolutePath) {
|
|
48
56
|
try {
|
|
49
57
|
const stat = fs.statSync(absolutePath);
|
|
@@ -145,6 +153,7 @@ function buildManifestEntriesFromMetadata(tenantId, workspaceId, store, basePath
|
|
|
145
153
|
const assetDeletes = [];
|
|
146
154
|
const protectedDocumentPaths = new Set();
|
|
147
155
|
const protectedAssetPaths = new Set();
|
|
156
|
+
const storageRootAccessible = isStorageRootAccessible(basePath);
|
|
148
157
|
const metadataEntries = store.listAllByWorkspace(workspaceId, { includeDeleted: true });
|
|
149
158
|
for (const entry of metadataEntries) {
|
|
150
159
|
const contentKind = entry.kind === 'document' ? 'document' : 'asset';
|
|
@@ -192,6 +201,7 @@ function buildManifestEntriesFromMetadata(tenantId, workspaceId, store, basePath
|
|
|
192
201
|
? resolveStorageKeyPathInsideRoot(basePath, published.physicalKey)
|
|
193
202
|
: null;
|
|
194
203
|
let existsLocally = false;
|
|
204
|
+
let contentCheckReliable = true;
|
|
195
205
|
if (!supportsPublishedContent) {
|
|
196
206
|
existsLocally = true;
|
|
197
207
|
}
|
|
@@ -200,13 +210,23 @@ function buildManifestEntriesFromMetadata(tenantId, workspaceId, store, basePath
|
|
|
200
210
|
existsLocally = true;
|
|
201
211
|
}
|
|
202
212
|
else if (physicalPath) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
catch {
|
|
213
|
+
if (!storageRootAccessible) {
|
|
214
|
+
// The storage root itself couldn't be verified (e.g. an unmounted volume or
|
|
215
|
+
// transient I/O failure) — every local file would look "missing" in that state,
|
|
216
|
+
// so don't let a whole-root outage masquerade as N individual deletions.
|
|
209
217
|
existsLocally = false;
|
|
218
|
+
contentCheckReliable = false;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
try {
|
|
222
|
+
const stat = fs.statSync(physicalPath);
|
|
223
|
+
existsLocally = stat.isFile()
|
|
224
|
+
&& (published.source !== 'versioned' || stat.size === published.sizeBytes);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
existsLocally = false;
|
|
228
|
+
contentCheckReliable = error?.code === 'ENOENT';
|
|
229
|
+
}
|
|
210
230
|
}
|
|
211
231
|
}
|
|
212
232
|
}
|
|
@@ -231,6 +251,7 @@ function buildManifestEntriesFromMetadata(tenantId, workspaceId, store, basePath
|
|
|
231
251
|
sha256: contentSha256,
|
|
232
252
|
sizeBytes,
|
|
233
253
|
existsLocally,
|
|
254
|
+
contentCheckReliable,
|
|
234
255
|
source: 'metadata',
|
|
235
256
|
publicationState,
|
|
236
257
|
publicationError,
|
|
@@ -249,6 +270,7 @@ function buildManifestEntriesFromMetadata(tenantId, workspaceId, store, basePath
|
|
|
249
270
|
sha256: contentSha256,
|
|
250
271
|
sizeBytes,
|
|
251
272
|
existsLocally,
|
|
273
|
+
contentCheckReliable,
|
|
252
274
|
source: 'metadata',
|
|
253
275
|
publicationState,
|
|
254
276
|
publicationError,
|
|
@@ -307,6 +329,7 @@ export function buildWorkspaceRepairManifest(input) {
|
|
|
307
329
|
sha256: fingerprint.sha256,
|
|
308
330
|
sizeBytes: fingerprint.sizeBytes,
|
|
309
331
|
existsLocally: true,
|
|
332
|
+
contentCheckReliable: true,
|
|
310
333
|
source: 'filesystem',
|
|
311
334
|
publicationState: 'legacy',
|
|
312
335
|
publicationError: null,
|
|
@@ -337,6 +360,7 @@ export function buildWorkspaceRepairManifest(input) {
|
|
|
337
360
|
sha256: fingerprint.sha256,
|
|
338
361
|
sizeBytes: fingerprint.sizeBytes,
|
|
339
362
|
existsLocally: true,
|
|
363
|
+
contentCheckReliable: true,
|
|
340
364
|
source: 'filesystem',
|
|
341
365
|
publicationState: 'legacy',
|
|
342
366
|
publicationError: null,
|
|
@@ -466,6 +490,19 @@ export function scanWorkspaceRepair(input) {
|
|
|
466
490
|
continue;
|
|
467
491
|
}
|
|
468
492
|
if (!document.existsLocally) {
|
|
493
|
+
if (!document.contentCheckReliable) {
|
|
494
|
+
issues.push({
|
|
495
|
+
code: 'invalid_published_document_content',
|
|
496
|
+
entityKind: 'document',
|
|
497
|
+
path: document.path,
|
|
498
|
+
assetId: document.assetId,
|
|
499
|
+
taskId: document.taskId,
|
|
500
|
+
repairable: false,
|
|
501
|
+
recommendedAction: 'inspect-published-content',
|
|
502
|
+
detail: 'Local document file could not be confirmed missing — the storage root was unreachable or the check was inconclusive. Skipped to avoid converting unavailable content into an authoritative deletion.'
|
|
503
|
+
});
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
469
506
|
issues.push({
|
|
470
507
|
code: 'missing_document_file',
|
|
471
508
|
entityKind: 'document',
|
|
@@ -564,6 +601,19 @@ export function scanWorkspaceRepair(input) {
|
|
|
564
601
|
if (!asset.existsLocally) {
|
|
565
602
|
if (protectedAiAvatarAssetPaths.has(asset.path))
|
|
566
603
|
continue;
|
|
604
|
+
if (!asset.contentCheckReliable) {
|
|
605
|
+
issues.push({
|
|
606
|
+
code: 'invalid_published_asset_content',
|
|
607
|
+
entityKind: 'asset',
|
|
608
|
+
path: asset.path,
|
|
609
|
+
assetId: asset.assetId,
|
|
610
|
+
taskId: asset.taskId,
|
|
611
|
+
repairable: false,
|
|
612
|
+
recommendedAction: 'inspect-published-content',
|
|
613
|
+
detail: 'Local asset file could not be confirmed missing — the storage root was unreachable or the check was inconclusive. Skipped to avoid converting unavailable content into an authoritative deletion.'
|
|
614
|
+
});
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
567
617
|
issues.push({
|
|
568
618
|
code: 'missing_asset_file',
|
|
569
619
|
entityKind: 'asset',
|
|
@@ -791,6 +841,17 @@ export function applyWorkspaceRepair(input) {
|
|
|
791
841
|
continue;
|
|
792
842
|
}
|
|
793
843
|
if ((issue.code === 'missing_document_file' || issue.code === 'missing_asset_file') && issue.assetId) {
|
|
844
|
+
if (input.repairMissingContentFiles !== true) {
|
|
845
|
+
actionsApplied.push({
|
|
846
|
+
code: issue.code,
|
|
847
|
+
path: issue.path,
|
|
848
|
+
assetId: issue.assetId,
|
|
849
|
+
action: issue.recommendedAction,
|
|
850
|
+
status: 'skipped',
|
|
851
|
+
detail: 'Missing content file repair requires explicit admin confirmation, since a single failed existence check can also mean the content is temporarily unavailable rather than deleted.'
|
|
852
|
+
});
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
794
855
|
const updated = input.workspaceAssetStore.markDeleted(issue.assetId, workspaceId);
|
|
795
856
|
actionsApplied.push({
|
|
796
857
|
code: issue.code,
|