@taskforcehq/taskforce 0.3.327 → 0.3.328
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 +24 -53
- package/dist/components/activity/EntityActivityTimeline.js +15 -5
- package/dist/components/context/AttachmentPreviewHost.js +1 -1
- package/dist/components/context/ContextAttachmentManager.d.ts +3 -0
- package/dist/components/context/ContextAttachmentManager.js +116 -20
- package/dist/components/features/planning/PlanningEntitySummary.d.ts +3 -1
- package/dist/components/features/planning/PlanningEntitySummary.js +3 -2
- package/dist/components/features/planning/PlanningLinkPicker.d.ts +3 -1
- package/dist/components/features/planning/PlanningLinkPicker.js +5 -3
- package/dist/components/features/planning/PlanningModule.js +26 -9
- package/dist/components/features/planning/PlanningVisualIdentity.d.ts +14 -0
- package/dist/components/features/planning/PlanningVisualIdentity.js +106 -0
- package/dist/components/features/planning/planningWorkspaceModel.d.ts +11 -0
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +2 -2
- package/dist/components/task/TaskCard.js +37 -4
- package/dist/components/task/TaskContextUpload.d.ts +3 -0
- package/dist/components/task/TaskContextUpload.js +2 -2
- package/dist/components/task/TaskForm.d.ts +4 -0
- package/dist/components/task/TaskForm.js +32 -7
- package/dist/components/task/TaskImageReviews.js +16 -16
- package/dist/components/task/TaskStatusActions.js +11 -2
- package/dist/components/task/TaskWorkstreamPicker.js +2 -1
- package/dist/components/ui/DocumentTypeIcon.d.ts +7 -0
- package/dist/components/ui/DocumentTypeIcon.js +8 -0
- package/dist/components/ui/TaxonomyDropdown.d.ts +9 -1
- package/dist/components/ui/TaxonomyDropdown.js +30 -3
- package/dist/components/views/StandaloneLayout.js +62 -9
- package/dist/components/views/WidgetView.js +8 -5
- package/dist/components/views/panels/PlanningDrawer.d.ts +7 -1
- package/dist/components/views/panels/PlanningDrawer.js +48 -44
- package/dist/config/envSchema.js +1 -0
- package/dist/config/localServiceCloudTarget.d.ts +10 -0
- package/dist/config/localServiceCloudTarget.js +76 -0
- package/dist/core/AiProfileService.d.ts +1 -0
- package/dist/core/AiProfileService.js +2 -1
- package/dist/core/AttachmentLinkService.js +7 -4
- package/dist/core/DeletedTaskLifecycleService.d.ts +4 -0
- package/dist/core/DeletedTaskLifecycleService.js +21 -0
- package/dist/core/McpTokenService.d.ts +4 -0
- package/dist/core/McpTokenService.js +52 -0
- package/dist/core/TaskAttachmentLinksDurableMutationService.d.ts +5 -0
- package/dist/core/TaskAttachmentLinksDurableMutationService.js +20 -11
- package/dist/core/Taskforce.d.ts +63 -1
- package/dist/core/Taskforce.js +558 -73
- package/dist/core/types.d.ts +2 -1
- package/dist/documentReviews/DocumentReviewCommentStore.d.ts +11 -0
- package/dist/documentReviews/DocumentReviewCommentStore.js +35 -2
- package/dist/documentReviews/service.d.ts +4 -0
- package/dist/documentReviews/service.js +141 -46
- package/dist/hooks/tasks/useTaskFormActions.d.ts +3 -0
- package/dist/hooks/tasks/useTaskFormActions.js +33 -7
- package/dist/hooks/useTaskforce.d.ts +59 -50
- package/dist/hooks/useTaskforce.js +103 -1
- package/dist/mcp/canonicalAssetHelpers.d.ts +13 -0
- package/dist/mcp/canonicalAssetHelpers.js +39 -5
- package/dist/mcp/collaborationRegistrar.js +15 -13
- package/dist/mcp/documentAssetRegistrar.js +16 -9
- package/dist/mcp/localServiceProxy.js +4 -1
- package/dist/mcp/runtime.d.ts +16 -0
- package/dist/mcp/runtime.js +566 -171
- package/dist/mcp/taskAttachmentCommandAdapter.d.ts +2 -0
- package/dist/mcp/taskAttachmentCommandAdapter.js +14 -8
- package/dist/mcp/taskPlanningRegistrar.js +42 -34
- package/dist/mcp/toolRegistry.js +2 -2
- package/dist/migrations/taskSchemaMigrations.js +35 -0
- package/dist/runtime/appGateDefinitions.d.ts +2 -2
- package/dist/runtime/appGateDefinitions.js +2 -2
- package/dist/server/appAccess.d.ts +12 -0
- package/dist/server/appAccess.js +21 -0
- package/dist/server/authorizedWorkspaceAccess.d.ts +12 -0
- package/dist/server/authorizedWorkspaceAccess.js +40 -0
- package/dist/server/cloudCollectionRead.d.ts +25 -0
- package/dist/server/cloudCollectionRead.js +120 -0
- package/dist/server/cloudCollectionReadPool.d.ts +52 -0
- package/dist/server/cloudCollectionReadPool.js +140 -0
- package/dist/server/cloudCollectionReadWorker.d.ts +1 -0
- package/dist/server/cloudCollectionReadWorker.js +36 -0
- package/dist/server/index.d.ts +3 -11
- package/dist/server/index.js +53 -40
- package/dist/server/localServiceLease.d.ts +3 -0
- package/dist/server/localServiceLease.js +9 -1
- package/dist/server/routes/documents.d.ts +5 -2
- package/dist/server/routes/documents.js +160 -13
- package/dist/server/routes/durableTaskHttpRouters.d.ts +2 -0
- package/dist/server/routes/durableTaskHttpRouters.js +2 -2
- package/dist/server/routes/localService.js +2 -0
- package/dist/server/routes/primitives.d.ts +2 -0
- package/dist/server/routes/shared.d.ts +24 -0
- package/dist/server/routes/shared.js +114 -18
- package/dist/server/routes/sync.js +22 -0
- package/dist/server/routes/syncV3FeedRoutes.js +4 -2
- package/dist/server/routes/syncV3LocalCaptureRoutes.js +20 -2
- package/dist/server/routes/syncV3OperationRoutes.js +17 -2
- package/dist/server/routes/tasks.js +199 -89
- package/dist/server/routes.js +35 -7
- package/dist/service/localServiceCli.js +36 -25
- package/dist/service/localServiceClientLifecycle.js +67 -6
- package/dist/shared/planningIdentity.d.ts +20 -0
- package/dist/shared/planningIdentity.js +79 -0
- package/dist/storage/postgresAdapter.js +4 -0
- package/dist/storage/postgresRequestDiagnostics.d.ts +8 -0
- package/dist/storage/postgresRequestDiagnostics.js +24 -0
- package/dist/storage/workspaceAssetStore.d.ts +32 -0
- package/dist/storage/workspaceAssetStore.js +70 -0
- package/dist/sync/coordinator/localSyncExecutionPermitStore.d.ts +16 -0
- package/dist/sync/coordinator/localSyncExecutionPermitStore.js +70 -22
- package/dist/sync/coordinator/workspaceSyncTransportGateway.js +4 -2
- package/dist/sync/engine/workspaceSyncEngineRemoteServices.d.ts +5 -0
- package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.d.ts +1 -1
- package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +15 -0
- package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +26 -3
- package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.js +2 -0
- package/dist/sync/syncApplyHandlers.d.ts +3 -1
- package/dist/sync/syncApplyHandlers.js +29 -0
- package/dist/sync/syncService.js +9 -0
- package/dist/sync/taskSyncPayload.d.ts +1 -0
- package/dist/sync/taskSyncPayload.js +6 -0
- package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.d.ts +1 -0
- package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +21 -3
- package/dist/sync/v3/durableTaskHttpBulkMutationRouter.js +17 -4
- package/dist/sync/v3/localInitiativeOutboxService.js +4 -2
- package/dist/sync/v3/localOutboxDispatcher.js +5 -1
- package/dist/sync/v3/localTaskAttachmentLinksOutboxHandler.js +12 -2
- package/dist/sync/v3/localTaskAttachmentLinksOutboxService.d.ts +1 -0
- package/dist/sync/v3/localTaskAttachmentLinksOutboxService.js +15 -3
- package/dist/sync/v3/localWorkstreamOutboxService.js +4 -2
- package/dist/sync/v3/syncDurabilityStore.d.ts +8 -0
- package/dist/sync/v3/syncDurabilityStore.js +171 -33
- package/dist/sync/v3/taskAttachmentLinksMutationService.d.ts +1 -0
- package/dist/sync/v3/taskAttachmentLinksMutationService.js +18 -2
- package/dist/sync/v3/taskMetadataCoexistence.js +4 -0
- package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.js +1 -9
- package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +1 -0
- package/dist/sync/v3/workspaceSyncV3CombinedFeedOrchestrator.js +7 -1
- package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +15 -3
- package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +4 -0
- package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +29 -6
- package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +29 -7
- package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +62 -7
- package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3RepairSqlBatching.d.ts +2 -0
- package/dist/sync/v3/workspaceSyncV3RepairSqlBatching.js +10 -0
- package/dist/sync/v3/workspaceSyncV3TaskRootProjection.d.ts +1 -1
- package/dist/sync/v3/workspaceSyncV3TaskRootProjection.js +1 -1
- package/dist/sync/workspacePullFeed.d.ts +6 -1
- package/dist/sync/workspacePullFeed.js +172 -161
- package/dist/sync/workspaceSyncModel.d.ts +11 -0
- package/dist/sync/workspaceSyncModel.js +27 -2
- package/dist/types.d.ts +8 -0
- package/dist/ui/assets/{AiIdentityRosterCard-DB1BLO_j.js → AiIdentityRosterCard-MZs7lVED.js} +1 -1
- package/dist/ui/assets/{AiProfilesModule-DPxQeF9N.js → AiProfilesModule-C3i0LOxe.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-DHNYaNg6.js → AnnotatedAttachmentWorkspace-DVZk5Tln.js} +1 -1
- package/dist/ui/assets/AssetTraySortControl-Kf1pelSy.js +1 -0
- package/dist/ui/assets/ContextAttachmentManager-B8boOs6Q.js +3 -0
- package/dist/ui/assets/{DocumentWorkspace-BC7sy5TG.js → DocumentWorkspace-DXH4XUsB.js} +4 -4
- package/dist/ui/assets/EntityActivityTimeline-BtSTq0ON.js +1 -0
- package/dist/ui/assets/PlanningModule-BPVyEa-f.js +1 -0
- package/dist/ui/assets/PlanningModule-CoIR9hbV.css +1 -0
- package/dist/ui/assets/{PlansPage-BVH_NfgT.js → PlansPage-ChqgxALc.js} +1 -1
- package/dist/ui/assets/TaskContextUpload-CB3ol0sN.js +1 -0
- package/dist/ui/assets/TaskContextUpload-qmBeUw3u.css +1 -0
- package/dist/ui/assets/{TaskSettings-xVsxSa0g.js → TaskSettings-g4ECKNOz.js} +1 -1
- package/dist/ui/assets/{TaskforceAgentsModule-CkAxW19K.js → TaskforceAgentsModule-BazdUwxZ.js} +1 -1
- package/dist/ui/assets/WorkflowManagerModule-DWFspC-o.js +1 -0
- package/dist/ui/assets/index-BUplSsv_.js +7 -0
- package/dist/ui/assets/index-gz2EXuoK.css +1 -0
- package/dist/ui/assets/{vendor-icons-B8ZNsH1g.js → vendor-icons-BkFLXavV.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/constants.d.ts +2 -2
- package/dist/utils/constants.js +1 -1
- package/dist/utils/contextAttachmentUpload.d.ts +2 -0
- package/dist/utils/contextAttachmentUpload.js +7 -0
- package/dist/utils/contextFiles.d.ts +1 -1
- package/dist/utils/contextFiles.js +4 -4
- package/package.json +6 -1
- package/dist/ui/assets/AssetTraySortControl-DphbyHVc.js +0 -1
- package/dist/ui/assets/ContextAttachmentManager-CMxSzHqc.js +0 -3
- package/dist/ui/assets/EntityActivityTimeline-pJ2ZjZ-k.js +0 -1
- package/dist/ui/assets/PlanningModule-CeTqXMMz.css +0 -1
- package/dist/ui/assets/PlanningModule-t5GbvVR9.js +0 -1
- package/dist/ui/assets/TaskContextUpload-Ds4LnDzV.css +0 -1
- package/dist/ui/assets/TaskContextUpload-bl4fOOUz.js +0 -1
- package/dist/ui/assets/WorkflowManagerModule-BKqIGVnF.js +0 -1
- package/dist/ui/assets/index-BzJlVnNS.css +0 -1
- package/dist/ui/assets/index-Cx7vZJHL.js +0 -7
|
@@ -57,7 +57,7 @@ import { resolveBuildInfo } from '../buildInfo.js';
|
|
|
57
57
|
import { resolveRealtimeSyncFlag } from '../../sync/realtimeFeature.js';
|
|
58
58
|
import { parseJsonBody } from './primitives.js';
|
|
59
59
|
import { resolveRequestAccess } from '../auth.js';
|
|
60
|
-
import { getRouteTimingNow, logSlowBootstrapRoute, requestBootstrapTraceId, requestHeader, requestRuntimeMode,
|
|
60
|
+
import { currentPerformanceTraceDiagnostics, getRouteTimingNow, logSlowBootstrapRoute, performanceTraceServerTimingMetrics, requestBootstrapTraceId, requestHeader, requestHumanActorRef, requestRuntimeMode, respondWithPerformanceTraceJson, resolveErrorStatusCode, runWithPerformanceTrace, serializeJsonWithTiming, setPerformanceTraceHeaders, setServerTimingHeader } from './shared.js';
|
|
61
61
|
import { listBuiltInCustomTaxonomyLibraryPacks } from '../../shared/customTaxonomyLibrary.js';
|
|
62
62
|
import { getBuiltInTaxonomyLibraryPack, listBuiltInTaxonomyLibraryPacks } from '../../shared/taxonomyLibrary.js';
|
|
63
63
|
import { shouldUseProvisionalTaskReferences } from '../../utils/taskReferences.js';
|
|
@@ -177,7 +177,7 @@ export function registerTaskRoutes(deps) {
|
|
|
177
177
|
surface: 'http',
|
|
178
178
|
tenantId: core.getTenantId(),
|
|
179
179
|
workspaceId,
|
|
180
|
-
actorRef:
|
|
180
|
+
actorRef: requestHumanActorRef(req),
|
|
181
181
|
cloudClientId: 'http-cloud',
|
|
182
182
|
...(operationId ? { createOperationId: () => operationId } : {}),
|
|
183
183
|
});
|
|
@@ -192,7 +192,7 @@ export function registerTaskRoutes(deps) {
|
|
|
192
192
|
surface: 'http',
|
|
193
193
|
tenantId: core.getTenantId(),
|
|
194
194
|
workspaceId,
|
|
195
|
-
actorRef:
|
|
195
|
+
actorRef: requestHumanActorRef(req),
|
|
196
196
|
cloudClientId: 'http-cloud',
|
|
197
197
|
...(operationId ? { createOperationId: () => operationId } : {}),
|
|
198
198
|
});
|
|
@@ -465,9 +465,13 @@ export function registerTaskRoutes(deps) {
|
|
|
465
465
|
if (!ensureAppAccess(req, res))
|
|
466
466
|
return;
|
|
467
467
|
const workspaceId = requestWorkspaceId(req);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
468
|
+
respondWithPerformanceTraceJson({
|
|
469
|
+
req,
|
|
470
|
+
res,
|
|
471
|
+
route: '/api/taskforce/tasks',
|
|
472
|
+
workspaceId,
|
|
473
|
+
load: () => core.listTasks(workspaceId),
|
|
474
|
+
});
|
|
471
475
|
});
|
|
472
476
|
addRoute('GET', '/api/taskforce/task-relationships', async (req, res) => {
|
|
473
477
|
if (!ensureAppAccess(req, res))
|
|
@@ -480,9 +484,13 @@ export function registerTaskRoutes(deps) {
|
|
|
480
484
|
if (!ensureAppAccess(req, res))
|
|
481
485
|
return;
|
|
482
486
|
const workspaceId = requestWorkspaceId(req);
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
487
|
+
respondWithPerformanceTraceJson({
|
|
488
|
+
req,
|
|
489
|
+
res,
|
|
490
|
+
route: '/api/taskforce/deleted',
|
|
491
|
+
workspaceId,
|
|
492
|
+
load: () => core.listDeletedTasks(workspaceId),
|
|
493
|
+
});
|
|
486
494
|
};
|
|
487
495
|
// GET /api/taskforce/deleted - List deleted tasks
|
|
488
496
|
addRoute('GET', '/api/taskforce/deleted', handleListDeletedTasks);
|
|
@@ -589,7 +597,7 @@ export function registerTaskRoutes(deps) {
|
|
|
589
597
|
const durableRouter = createTaskHttpRootRouter(req, workspaceId);
|
|
590
598
|
const task = durableRouter.enabled ? durableRouter.create(taskInput) : core.createTask(taskInput, {
|
|
591
599
|
workspaceId,
|
|
592
|
-
actorRef:
|
|
600
|
+
actorRef: requestHumanActorRef(req),
|
|
593
601
|
provisionalLocalReference: shouldUseProvisionalTaskReferences(requestRuntimeMode(req))
|
|
594
602
|
});
|
|
595
603
|
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
@@ -630,13 +638,21 @@ export function registerTaskRoutes(deps) {
|
|
|
630
638
|
if (hasAttachments && !Array.isArray(updates.attachments)) {
|
|
631
639
|
throw new TaskforceRuleError('attachments must be an array.', 400, 'TASK_ATTACHMENT_LINKS_INVALID');
|
|
632
640
|
}
|
|
641
|
+
const hasCardCover = Object.prototype.hasOwnProperty.call(updates || {}, 'cardCoverAssetId');
|
|
642
|
+
if (hasCardCover && updates.cardCoverAssetId !== null
|
|
643
|
+
&& (typeof updates.cardCoverAssetId !== 'string' || !updates.cardCoverAssetId.trim())) {
|
|
644
|
+
throw new TaskforceRuleError('cardCoverAssetId must be a non-empty string or null.', 400, 'TASK_CARD_COVER_INVALID');
|
|
645
|
+
}
|
|
633
646
|
const checklistDurable = hasChecklist && durableRouters.checklist.enabled;
|
|
634
|
-
const attachmentLinksDurable = hasAttachments && durableRouters.attachmentLinks.enabled;
|
|
647
|
+
const attachmentLinksDurable = (hasAttachments || hasCardCover) && durableRouters.attachmentLinks.enabled;
|
|
635
648
|
const rawChecklistItems = hasChecklist ? updates.checklistItems : [];
|
|
636
|
-
const rawAttachments = hasAttachments
|
|
649
|
+
const rawAttachments = hasAttachments
|
|
650
|
+
? updates.attachments
|
|
651
|
+
: (core.getTask(params.id, workspaceId)?.attachments || []);
|
|
637
652
|
const remainingUpdates = checklistDurable || attachmentLinksDurable
|
|
638
653
|
? Object.fromEntries(Object.entries(updates).filter(([key]) => (!(checklistDurable && key === 'checklistItems')
|
|
639
|
-
&& !(attachmentLinksDurable && key === 'attachments')
|
|
654
|
+
&& !(attachmentLinksDurable && key === 'attachments')
|
|
655
|
+
&& !(attachmentLinksDurable && key === 'cardCoverAssetId'))))
|
|
640
656
|
: updates;
|
|
641
657
|
let updated;
|
|
642
658
|
const durableTargetCount = Number(checklistDurable)
|
|
@@ -657,7 +673,7 @@ export function registerTaskRoutes(deps) {
|
|
|
657
673
|
? (durableRouter.enabled
|
|
658
674
|
? durableRouter.update(params.id, remainingUpdates, saveSource)
|
|
659
675
|
: core.updateTask(params.id, remainingUpdates, workspaceId, {
|
|
660
|
-
actorRef:
|
|
676
|
+
actorRef: requestHumanActorRef(req),
|
|
661
677
|
saveSource,
|
|
662
678
|
}))
|
|
663
679
|
: core.getTask(params.id, workspaceId);
|
|
@@ -679,7 +695,15 @@ export function registerTaskRoutes(deps) {
|
|
|
679
695
|
core,
|
|
680
696
|
workspaceId,
|
|
681
697
|
});
|
|
682
|
-
updated = attachmentLinksRouter.replace({
|
|
698
|
+
updated = attachmentLinksRouter.replace({
|
|
699
|
+
taskId: params.id,
|
|
700
|
+
links,
|
|
701
|
+
...(hasCardCover ? {
|
|
702
|
+
cardCoverAssetId: updates.cardCoverAssetId === null
|
|
703
|
+
? null
|
|
704
|
+
: String(updates.cardCoverAssetId).trim(),
|
|
705
|
+
} : {}),
|
|
706
|
+
}).task;
|
|
683
707
|
}
|
|
684
708
|
}
|
|
685
709
|
if (!updated) {
|
|
@@ -766,43 +790,53 @@ export function registerTaskRoutes(deps) {
|
|
|
766
790
|
addRoute('GET', '/api/taskforce/planning/bootstrap', async (req, res) => {
|
|
767
791
|
if (!ensureAppAccess(req, res))
|
|
768
792
|
return;
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
793
|
+
runWithPerformanceTrace(req, () => {
|
|
794
|
+
const startedAt = getRouteTimingNow();
|
|
795
|
+
const workspaceId = requestWorkspaceId(req);
|
|
796
|
+
const traceId = requestBootstrapTraceId(req);
|
|
797
|
+
const diagnostics = {};
|
|
798
|
+
const data = core.listPlanningBootstrap(workspaceId, diagnostics);
|
|
799
|
+
const serializeStartedAt = getRouteTimingNow();
|
|
800
|
+
const serialized = serializeJsonWithTiming(data, serializeStartedAt);
|
|
801
|
+
const totalDurationMs = Math.max(0, getRouteTimingNow() - startedAt);
|
|
802
|
+
const details = diagnostics;
|
|
803
|
+
const performanceDiagnostics = currentPerformanceTraceDiagnostics();
|
|
804
|
+
setServerTimingHeader(res, [
|
|
805
|
+
{ name: 'planning-bootstrap', durationMs: totalDurationMs },
|
|
806
|
+
{ name: 'planning-bootstrap-initiatives', durationMs: details.initiativeLoadMs ?? 0 },
|
|
807
|
+
{ name: 'planning-bootstrap-workstreams', durationMs: details.workstreamLoadMs ?? 0 },
|
|
808
|
+
{ name: 'planning-bootstrap-tasks', durationMs: details.taskSummaryQueryMs ?? 0 },
|
|
809
|
+
{ name: 'planning-bootstrap-build', durationMs: details.taskSummaryBuildMs ?? 0 },
|
|
810
|
+
{ name: 'planning-bootstrap-json', durationMs: serialized.durationMs },
|
|
811
|
+
...performanceTraceServerTimingMetrics(performanceDiagnostics),
|
|
812
|
+
]);
|
|
813
|
+
setPerformanceTraceHeaders(res, performanceDiagnostics, serialized.byteLength);
|
|
814
|
+
logSlowBootstrapRoute({
|
|
815
|
+
route: '/api/taskforce/planning/bootstrap',
|
|
816
|
+
workspaceId,
|
|
817
|
+
traceId,
|
|
818
|
+
durationMs: totalDurationMs,
|
|
819
|
+
details: {
|
|
820
|
+
initiativeCount: details.initiativeCount ?? 0,
|
|
821
|
+
workstreamCount: details.workstreamCount ?? 0,
|
|
822
|
+
workstreamLinkedTaskCount: details.workstreamLinkedTaskCount ?? 0,
|
|
823
|
+
workstreamTaskSummaryCount: details.workstreamTaskSummaryCount ?? 0,
|
|
824
|
+
responseBytes: serialized.byteLength,
|
|
825
|
+
initiativeLoadMs: details.initiativeLoadMs ?? 0,
|
|
826
|
+
workstreamLoadMs: details.workstreamLoadMs ?? 0,
|
|
827
|
+
taskSummaryQueryMs: details.taskSummaryQueryMs ?? 0,
|
|
828
|
+
taskSummaryBuildMs: details.taskSummaryBuildMs ?? 0,
|
|
829
|
+
jsonSerializeMs: serialized.durationMs,
|
|
830
|
+
...(performanceDiagnostics ? {
|
|
831
|
+
postgresQueryCount: performanceDiagnostics.postgresQueryCount,
|
|
832
|
+
postgresWorkerCallCount: performanceDiagnostics.postgresWorkerCallCount,
|
|
833
|
+
postgresWorkerWaitMs: Math.round(performanceDiagnostics.postgresWorkerWaitMs),
|
|
834
|
+
} : {}),
|
|
835
|
+
},
|
|
836
|
+
});
|
|
837
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
838
|
+
res.end(serialized.body);
|
|
803
839
|
});
|
|
804
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
805
|
-
res.end(serialized.body);
|
|
806
840
|
});
|
|
807
841
|
// POST /api/taskforce/initiative - Create initiative
|
|
808
842
|
addRoute('POST', '/api/taskforce/initiative', async (req, res) => {
|
|
@@ -812,7 +846,7 @@ export function registerTaskRoutes(deps) {
|
|
|
812
846
|
try {
|
|
813
847
|
const workspaceId = requestWorkspaceId(req);
|
|
814
848
|
const router = createInitiativeRouter(req, workspaceId);
|
|
815
|
-
const allowed = new Set(['id', 'title', 'description', 'ownerId', 'order', 'attachments', 'createdAt']);
|
|
849
|
+
const allowed = new Set(['id', 'title', 'description', 'ownerId', 'order', 'attachments', 'createdAt', 'icon', 'color']);
|
|
816
850
|
const unsupported = Object.keys(body || {}).filter((key) => !allowed.has(key));
|
|
817
851
|
if (router.enabled && unsupported.length > 0) {
|
|
818
852
|
const error = new Error(`Unsupported initiative fields: ${unsupported.sort().join(', ')}`);
|
|
@@ -826,16 +860,28 @@ export function registerTaskRoutes(deps) {
|
|
|
826
860
|
error.statusCode = 400;
|
|
827
861
|
throw error;
|
|
828
862
|
}
|
|
829
|
-
const initiative =
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
863
|
+
const initiative = core.getDatabaseAdapter().transaction(() => {
|
|
864
|
+
const created = router.enabled
|
|
865
|
+
? initiativeResult(router.create(String(body?.id || '').trim() || `initiative-${randomUUID()}`, {
|
|
866
|
+
title: body?.title,
|
|
867
|
+
description: body?.description ?? null,
|
|
868
|
+
ownerId: body?.ownerId ?? null,
|
|
869
|
+
order: body?.order ?? null,
|
|
870
|
+
attachments: serializeCanonicalPlanningAttachmentLinks(body?.attachments ?? []),
|
|
871
|
+
createdAt: String(body?.createdAt || '').trim() || new Date().toISOString(),
|
|
872
|
+
}))
|
|
873
|
+
: core.createInitiative(body || {}, workspaceId, { actorRef: requestHumanActorRef(req) });
|
|
874
|
+
if (!created)
|
|
875
|
+
throw new Error('Durable initiative capture did not return a provisional initiative.');
|
|
876
|
+
const hasIdentity = Object.prototype.hasOwnProperty.call(body || {}, 'icon')
|
|
877
|
+
|| Object.prototype.hasOwnProperty.call(body || {}, 'color');
|
|
878
|
+
return hasIdentity
|
|
879
|
+
? core.updatePlanningEntityIdentity('initiative', created.id, {
|
|
880
|
+
...(Object.prototype.hasOwnProperty.call(body || {}, 'icon') ? { icon: body.icon } : {}),
|
|
881
|
+
...(Object.prototype.hasOwnProperty.call(body || {}, 'color') ? { color: body.color } : {}),
|
|
882
|
+
}, workspaceId)
|
|
883
|
+
: created;
|
|
884
|
+
})();
|
|
839
885
|
if (!initiative)
|
|
840
886
|
throw new Error('Durable initiative capture did not return a provisional initiative.');
|
|
841
887
|
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
@@ -883,7 +929,7 @@ export function registerTaskRoutes(deps) {
|
|
|
883
929
|
};
|
|
884
930
|
const updated = router.enabled
|
|
885
931
|
? initiativeResult(router.updateMetadata(params.id, durablePatch))
|
|
886
|
-
: core.updateInitiative(params.id, body || {}, workspaceId, { actorRef:
|
|
932
|
+
: core.updateInitiative(params.id, body || {}, workspaceId, { actorRef: requestHumanActorRef(req) });
|
|
887
933
|
if (!updated)
|
|
888
934
|
throw new Error('Durable initiative capture did not return a provisional initiative.');
|
|
889
935
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -893,6 +939,30 @@ export function registerTaskRoutes(deps) {
|
|
|
893
939
|
writeInitiativeMutationError(res, error, 'Failed to update initiative');
|
|
894
940
|
}
|
|
895
941
|
});
|
|
942
|
+
addRoute('PATCH', '/api/taskforce/initiative/:id/identity', async (req, res, params) => {
|
|
943
|
+
if (!ensureAppAccess(req, res))
|
|
944
|
+
return;
|
|
945
|
+
const body = await parseJsonBody(req);
|
|
946
|
+
try {
|
|
947
|
+
const workspaceId = requestWorkspaceId(req);
|
|
948
|
+
const source = body && typeof body === 'object' && !Array.isArray(body) ? body : {};
|
|
949
|
+
const keys = Object.keys(source);
|
|
950
|
+
if (keys.length === 0 || keys.some((key) => key !== 'icon' && key !== 'color')) {
|
|
951
|
+
throw new TaskforceRuleError('Planning identity update must contain only icon or color.', 400, 'PLANNING_IDENTITY_INVALID');
|
|
952
|
+
}
|
|
953
|
+
const updated = core.updatePlanningEntityIdentity('initiative', params.id, source, workspaceId);
|
|
954
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
955
|
+
res.end(JSON.stringify(updated));
|
|
956
|
+
}
|
|
957
|
+
catch (error) {
|
|
958
|
+
const statusCode = resolveErrorStatusCode(error, 400);
|
|
959
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
960
|
+
res.end(JSON.stringify({
|
|
961
|
+
error: error instanceof Error ? error.message : 'Failed to update initiative identity',
|
|
962
|
+
code: error instanceof TaskforceRuleError ? error.code : undefined,
|
|
963
|
+
}));
|
|
964
|
+
}
|
|
965
|
+
});
|
|
896
966
|
addRoute('POST', '/api/taskforce/initiative/:id/archive', async (req, res, params) => {
|
|
897
967
|
if (!ensureAppAccess(req, res))
|
|
898
968
|
return;
|
|
@@ -901,7 +971,7 @@ export function registerTaskRoutes(deps) {
|
|
|
901
971
|
const router = createInitiativeRouter(req, workspaceId);
|
|
902
972
|
const archived = router.enabled
|
|
903
973
|
? initiativeResult(router.archive(params.id))
|
|
904
|
-
: core.archiveInitiative(params.id, workspaceId, { actorRef:
|
|
974
|
+
: core.archiveInitiative(params.id, workspaceId, { actorRef: requestHumanActorRef(req) });
|
|
905
975
|
if (!archived)
|
|
906
976
|
throw new Error('Durable initiative capture did not return a provisional initiative.');
|
|
907
977
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -919,7 +989,7 @@ export function registerTaskRoutes(deps) {
|
|
|
919
989
|
const router = createInitiativeRouter(req, workspaceId);
|
|
920
990
|
const restored = router.enabled
|
|
921
991
|
? initiativeResult(router.unarchive(params.id))
|
|
922
|
-
: core.unarchiveInitiative(params.id, workspaceId, { actorRef:
|
|
992
|
+
: core.unarchiveInitiative(params.id, workspaceId, { actorRef: requestHumanActorRef(req) });
|
|
923
993
|
if (!restored)
|
|
924
994
|
throw new Error('Durable initiative capture did not return a provisional initiative.');
|
|
925
995
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -946,7 +1016,7 @@ export function registerTaskRoutes(deps) {
|
|
|
946
1016
|
try {
|
|
947
1017
|
const workspaceId = requestWorkspaceId(req);
|
|
948
1018
|
const router = createWorkstreamRouter(req, workspaceId);
|
|
949
|
-
const allowed = new Set(['id', 'initiativeId', 'title', 'description', 'ownerId', 'order', 'attachments', 'createdAt']);
|
|
1019
|
+
const allowed = new Set(['id', 'initiativeId', 'title', 'description', 'ownerId', 'order', 'attachments', 'createdAt', 'icon', 'color']);
|
|
950
1020
|
const unsupported = Object.keys(body || {}).filter((key) => !allowed.has(key));
|
|
951
1021
|
if (router.enabled && unsupported.length > 0) {
|
|
952
1022
|
const error = new Error(`Unsupported workstream fields: ${unsupported.sort().join(', ')}`);
|
|
@@ -960,17 +1030,29 @@ export function registerTaskRoutes(deps) {
|
|
|
960
1030
|
error.statusCode = 400;
|
|
961
1031
|
throw error;
|
|
962
1032
|
}
|
|
963
|
-
const workstream =
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1033
|
+
const workstream = core.getDatabaseAdapter().transaction(() => {
|
|
1034
|
+
const created = router.enabled
|
|
1035
|
+
? workstreamResult(router.create(String(body?.id || '').trim(), {
|
|
1036
|
+
initiativeId: body?.initiativeId ?? null,
|
|
1037
|
+
title: body?.title,
|
|
1038
|
+
description: body?.description ?? null,
|
|
1039
|
+
ownerId: body?.ownerId ?? null,
|
|
1040
|
+
order: body?.order ?? null,
|
|
1041
|
+
attachments: serializeCanonicalPlanningAttachmentLinks(body?.attachments ?? []),
|
|
1042
|
+
createdAt: String(body?.createdAt || '').trim(),
|
|
1043
|
+
}))
|
|
1044
|
+
: core.createWorkstream(body || {}, workspaceId, { actorRef: requestHumanActorRef(req) });
|
|
1045
|
+
if (!created)
|
|
1046
|
+
throw new Error('Durable workstream capture did not return a provisional workstream.');
|
|
1047
|
+
const hasIdentity = Object.prototype.hasOwnProperty.call(body || {}, 'icon')
|
|
1048
|
+
|| Object.prototype.hasOwnProperty.call(body || {}, 'color');
|
|
1049
|
+
return hasIdentity
|
|
1050
|
+
? core.updatePlanningEntityIdentity('workstream', created.id, {
|
|
1051
|
+
...(Object.prototype.hasOwnProperty.call(body || {}, 'icon') ? { icon: body.icon } : {}),
|
|
1052
|
+
...(Object.prototype.hasOwnProperty.call(body || {}, 'color') ? { color: body.color } : {}),
|
|
1053
|
+
}, workspaceId)
|
|
1054
|
+
: created;
|
|
1055
|
+
})();
|
|
974
1056
|
if (!workstream)
|
|
975
1057
|
throw new Error('Durable workstream capture did not return a provisional workstream.');
|
|
976
1058
|
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
@@ -1018,7 +1100,7 @@ export function registerTaskRoutes(deps) {
|
|
|
1018
1100
|
};
|
|
1019
1101
|
const updated = router.enabled
|
|
1020
1102
|
? workstreamResult(router.updateMetadata(params.id, durablePatch))
|
|
1021
|
-
: core.updateWorkstream(params.id, body || {}, workspaceId, { actorRef:
|
|
1103
|
+
: core.updateWorkstream(params.id, body || {}, workspaceId, { actorRef: requestHumanActorRef(req) });
|
|
1022
1104
|
if (!updated)
|
|
1023
1105
|
throw new Error('Durable workstream capture did not return a provisional workstream.');
|
|
1024
1106
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -1028,6 +1110,30 @@ export function registerTaskRoutes(deps) {
|
|
|
1028
1110
|
writeWorkstreamMutationError(res, error, 'Failed to update workstream');
|
|
1029
1111
|
}
|
|
1030
1112
|
});
|
|
1113
|
+
addRoute('PATCH', '/api/taskforce/workstream/:id/identity', async (req, res, params) => {
|
|
1114
|
+
if (!ensureAppAccess(req, res))
|
|
1115
|
+
return;
|
|
1116
|
+
const body = await parseJsonBody(req);
|
|
1117
|
+
try {
|
|
1118
|
+
const workspaceId = requestWorkspaceId(req);
|
|
1119
|
+
const source = body && typeof body === 'object' && !Array.isArray(body) ? body : {};
|
|
1120
|
+
const keys = Object.keys(source);
|
|
1121
|
+
if (keys.length === 0 || keys.some((key) => key !== 'icon' && key !== 'color')) {
|
|
1122
|
+
throw new TaskforceRuleError('Planning identity update must contain only icon or color.', 400, 'PLANNING_IDENTITY_INVALID');
|
|
1123
|
+
}
|
|
1124
|
+
const updated = core.updatePlanningEntityIdentity('workstream', params.id, source, workspaceId);
|
|
1125
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1126
|
+
res.end(JSON.stringify(updated));
|
|
1127
|
+
}
|
|
1128
|
+
catch (error) {
|
|
1129
|
+
const statusCode = resolveErrorStatusCode(error, 400);
|
|
1130
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
1131
|
+
res.end(JSON.stringify({
|
|
1132
|
+
error: error instanceof Error ? error.message : 'Failed to update workstream identity',
|
|
1133
|
+
code: error instanceof TaskforceRuleError ? error.code : undefined,
|
|
1134
|
+
}));
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1031
1137
|
addRoute('POST', '/api/taskforce/workstream/:id/archive', async (req, res, params) => {
|
|
1032
1138
|
if (!ensureAppAccess(req, res))
|
|
1033
1139
|
return;
|
|
@@ -1036,7 +1142,7 @@ export function registerTaskRoutes(deps) {
|
|
|
1036
1142
|
const router = createWorkstreamRouter(req, workspaceId);
|
|
1037
1143
|
const archived = router.enabled
|
|
1038
1144
|
? workstreamResult(router.archive(params.id))
|
|
1039
|
-
: core.archiveWorkstream(params.id, workspaceId, { actorRef:
|
|
1145
|
+
: core.archiveWorkstream(params.id, workspaceId, { actorRef: requestHumanActorRef(req) });
|
|
1040
1146
|
if (!archived)
|
|
1041
1147
|
throw new Error('Durable workstream capture did not return a provisional workstream.');
|
|
1042
1148
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -1054,7 +1160,7 @@ export function registerTaskRoutes(deps) {
|
|
|
1054
1160
|
const router = createWorkstreamRouter(req, workspaceId);
|
|
1055
1161
|
const restored = router.enabled
|
|
1056
1162
|
? workstreamResult(router.unarchive(params.id))
|
|
1057
|
-
: core.unarchiveWorkstream(params.id, workspaceId, { actorRef:
|
|
1163
|
+
: core.unarchiveWorkstream(params.id, workspaceId, { actorRef: requestHumanActorRef(req) });
|
|
1058
1164
|
if (!restored)
|
|
1059
1165
|
throw new Error('Durable workstream capture did not return a provisional workstream.');
|
|
1060
1166
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -1180,9 +1286,13 @@ export function registerTaskRoutes(deps) {
|
|
|
1180
1286
|
if (!ensureAppAccess(req, res))
|
|
1181
1287
|
return;
|
|
1182
1288
|
const workspaceId = requestWorkspaceId(req);
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1289
|
+
respondWithPerformanceTraceJson({
|
|
1290
|
+
req,
|
|
1291
|
+
res,
|
|
1292
|
+
route: '/api/taskforce/archive',
|
|
1293
|
+
workspaceId,
|
|
1294
|
+
load: () => core.listArchive(workspaceId),
|
|
1295
|
+
});
|
|
1186
1296
|
});
|
|
1187
1297
|
// POST /api/taskforce/task/:id/complete - Archive as complete
|
|
1188
1298
|
addRoute('POST', '/api/taskforce/task/:id/complete', async (req, res, params) => {
|
|
@@ -1193,7 +1303,7 @@ export function registerTaskRoutes(deps) {
|
|
|
1193
1303
|
const durableRouter = createTaskHttpRootRouter(req, workspaceId);
|
|
1194
1304
|
const archived = durableRouter.enabled
|
|
1195
1305
|
? durableRouter.archive(params.id, false, null)
|
|
1196
|
-
: core.archiveTask(params.id, false, undefined, workspaceId, { actorRef:
|
|
1306
|
+
: core.archiveTask(params.id, false, undefined, workspaceId, { actorRef: requestHumanActorRef(req) });
|
|
1197
1307
|
if (!archived) {
|
|
1198
1308
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1199
1309
|
res.end(JSON.stringify({ error: 'Task not found' }));
|
|
@@ -1221,7 +1331,7 @@ export function registerTaskRoutes(deps) {
|
|
|
1221
1331
|
const durableRouter = createTaskHttpRootRouter(req, workspaceId);
|
|
1222
1332
|
const archived = durableRouter.enabled
|
|
1223
1333
|
? durableRouter.archive(params.id, true, reason)
|
|
1224
|
-
: core.archiveTask(params.id, true, reason, workspaceId, { actorRef:
|
|
1334
|
+
: core.archiveTask(params.id, true, reason, workspaceId, { actorRef: requestHumanActorRef(req) });
|
|
1225
1335
|
if (!archived) {
|
|
1226
1336
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1227
1337
|
res.end(JSON.stringify({ error: 'Task not found' }));
|
|
@@ -1247,7 +1357,7 @@ export function registerTaskRoutes(deps) {
|
|
|
1247
1357
|
const durableRouter = createTaskHttpRootRouter(req, workspaceId);
|
|
1248
1358
|
const deleted = durableRouter.enabled
|
|
1249
1359
|
? durableRouter.delete(params.id)
|
|
1250
|
-
: core.softDeleteTask(params.id, workspaceId, { actorRef:
|
|
1360
|
+
: core.softDeleteTask(params.id, workspaceId, { actorRef: requestHumanActorRef(req), source: 'ui' });
|
|
1251
1361
|
if (!deleted) {
|
|
1252
1362
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1253
1363
|
res.end(JSON.stringify({ error: 'Task not found' }));
|
|
@@ -1272,7 +1382,7 @@ export function registerTaskRoutes(deps) {
|
|
|
1272
1382
|
const durableRouter = createTaskHttpRootRouter(req, workspaceId);
|
|
1273
1383
|
const restored = durableRouter.enabled
|
|
1274
1384
|
? durableRouter.restore(params.id)
|
|
1275
|
-
: core.restoreDeletedTask(params.id, workspaceId, { actorRef:
|
|
1385
|
+
: core.restoreDeletedTask(params.id, workspaceId, { actorRef: requestHumanActorRef(req) });
|
|
1276
1386
|
if (!restored) {
|
|
1277
1387
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1278
1388
|
res.end(JSON.stringify({ error: 'Deleted task not found' }));
|
|
@@ -1362,7 +1472,7 @@ export function registerTaskRoutes(deps) {
|
|
|
1362
1472
|
continue;
|
|
1363
1473
|
}
|
|
1364
1474
|
const task = core.restoreDeletedTask(deletedRecordId, workspaceId, {
|
|
1365
|
-
actorRef:
|
|
1475
|
+
actorRef: requestHumanActorRef(req),
|
|
1366
1476
|
restoreDestination,
|
|
1367
1477
|
});
|
|
1368
1478
|
if (!task) {
|
|
@@ -1533,7 +1643,7 @@ export function registerTaskRoutes(deps) {
|
|
|
1533
1643
|
const durableRouter = createTaskHttpRootRouter(req, workspaceId);
|
|
1534
1644
|
const restored = durableRouter.enabled
|
|
1535
1645
|
? durableRouter.unarchive(params.id)
|
|
1536
|
-
: core.unarchiveTask(params.id, workspaceId, { actorRef:
|
|
1646
|
+
: core.unarchiveTask(params.id, workspaceId, { actorRef: requestHumanActorRef(req) });
|
|
1537
1647
|
if (!restored) {
|
|
1538
1648
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1539
1649
|
res.end(JSON.stringify({ error: 'Archived task not found' }));
|
package/dist/server/routes.js
CHANGED
|
@@ -602,7 +602,7 @@ export function createRoutes(core, context = {}) {
|
|
|
602
602
|
taskId: input.taskId,
|
|
603
603
|
workspaceId: input.workspaceId,
|
|
604
604
|
action: 'task-attachments-changed',
|
|
605
|
-
actor: 'user',
|
|
605
|
+
actor: String(input.actorRef || '').trim() || 'user',
|
|
606
606
|
createdAt: input.createdAt || new Date().toISOString(),
|
|
607
607
|
details: {
|
|
608
608
|
changes: {
|
|
@@ -725,6 +725,7 @@ export function createRoutes(core, context = {}) {
|
|
|
725
725
|
asset,
|
|
726
726
|
beforeTask: taskBeforeLink,
|
|
727
727
|
createdAt: asset.updatedAt,
|
|
728
|
+
actorRef: params.actorRef,
|
|
728
729
|
});
|
|
729
730
|
}
|
|
730
731
|
}
|
|
@@ -788,7 +789,7 @@ export function createRoutes(core, context = {}) {
|
|
|
788
789
|
return false;
|
|
789
790
|
return /^workspaces\/[^/]+\/assets\/documents\/.+\.(md|markdown)$/.test(normalized);
|
|
790
791
|
};
|
|
791
|
-
const
|
|
792
|
+
const resolveCanonicalContentRead = async (params) => {
|
|
792
793
|
const resolvedObjectStorage = resolveEffectiveObjectStorageConfig();
|
|
793
794
|
const objectStorageClient = getObjectStorageClient();
|
|
794
795
|
const { basePath } = core.getPaths();
|
|
@@ -800,17 +801,19 @@ export function createRoutes(core, context = {}) {
|
|
|
800
801
|
: params.asset.storageProvider === 'local'
|
|
801
802
|
? 'local'
|
|
802
803
|
: null;
|
|
804
|
+
const contentKind = params.asset.kind === 'document' ? 'document' : 'asset';
|
|
805
|
+
const contentLabel = params.asset.kind === 'document' ? 'Document' : 'Asset';
|
|
803
806
|
const resolvedContent = publishedContentResolver && resolverOwnedProvider
|
|
804
807
|
? publishedContentResolver.resolve({
|
|
805
808
|
tenantId: core.getTenantId(),
|
|
806
809
|
workspaceId: params.asset.workspaceId,
|
|
807
|
-
kind:
|
|
810
|
+
kind: contentKind,
|
|
808
811
|
logicalPath: logicalStorageKey,
|
|
809
812
|
legacyProvider: resolverOwnedProvider
|
|
810
813
|
})
|
|
811
814
|
: {
|
|
812
815
|
source: 'legacy',
|
|
813
|
-
kind:
|
|
816
|
+
kind: contentKind,
|
|
814
817
|
logicalPath: logicalStorageKey,
|
|
815
818
|
physicalKey: logicalStorageKey,
|
|
816
819
|
storageProvider: params.asset.storageProvider
|
|
@@ -818,6 +821,31 @@ export function createRoutes(core, context = {}) {
|
|
|
818
821
|
if (resolvedContent.source === 'deleted') {
|
|
819
822
|
return { statusCode: 404 };
|
|
820
823
|
}
|
|
824
|
+
if (resolvedContent.source === 'versioned' && (resolvedContent.storageProvider !== params.asset.storageProvider
|
|
825
|
+
|| resolvedContent.contentSha256 !== params.asset.contentSha256
|
|
826
|
+
|| resolvedContent.sizeBytes !== params.asset.sizeBytes)) {
|
|
827
|
+
return {
|
|
828
|
+
statusCode: 409,
|
|
829
|
+
headers: { 'Content-Type': 'application/json' },
|
|
830
|
+
body: Buffer.from(JSON.stringify({
|
|
831
|
+
error: params.asset.kind === 'document'
|
|
832
|
+
? 'The published document head does not match the current canonical document metadata.'
|
|
833
|
+
: 'The published asset head does not match the current canonical asset metadata.',
|
|
834
|
+
code: params.asset.kind === 'document' ? 'MCP_DOCUMENT_HEAD_STALE' : 'WORKSPACE_ASSET_HEAD_STALE',
|
|
835
|
+
details: {
|
|
836
|
+
assetId: params.asset.assetId,
|
|
837
|
+
currentVersion: params.asset.version,
|
|
838
|
+
currentStorageProvider: params.asset.storageProvider,
|
|
839
|
+
currentContentSha256: params.asset.contentSha256,
|
|
840
|
+
currentSizeBytes: params.asset.sizeBytes,
|
|
841
|
+
headVersionId: resolvedContent.versionId,
|
|
842
|
+
headStorageProvider: resolvedContent.storageProvider,
|
|
843
|
+
headContentSha256: resolvedContent.contentSha256,
|
|
844
|
+
headSizeBytes: resolvedContent.sizeBytes,
|
|
845
|
+
},
|
|
846
|
+
})),
|
|
847
|
+
};
|
|
848
|
+
}
|
|
821
849
|
const physicalStorageKey = normalizeRelativeStorageKey(resolvedContent.physicalKey);
|
|
822
850
|
if (!physicalStorageKey)
|
|
823
851
|
return { statusCode: 404 };
|
|
@@ -852,7 +880,7 @@ export function createRoutes(core, context = {}) {
|
|
|
852
880
|
'Content-Disposition': contentDisposition
|
|
853
881
|
},
|
|
854
882
|
body: resolvedContent.source === 'versioned'
|
|
855
|
-
? verifyWorkspaceSyncPublishedContentBody(object.body, resolvedContent,
|
|
883
|
+
? verifyWorkspaceSyncPublishedContentBody(object.body, resolvedContent, `${contentLabel} ${params.asset.assetId}`)
|
|
856
884
|
: object.body
|
|
857
885
|
};
|
|
858
886
|
}
|
|
@@ -879,7 +907,7 @@ export function createRoutes(core, context = {}) {
|
|
|
879
907
|
'Content-Disposition': contentDisposition
|
|
880
908
|
},
|
|
881
909
|
body: resolvedContent.source === 'versioned'
|
|
882
|
-
? verifyWorkspaceSyncPublishedContentBody(body, resolvedContent,
|
|
910
|
+
? verifyWorkspaceSyncPublishedContentBody(body, resolvedContent, `${contentLabel} ${params.asset.assetId}`)
|
|
883
911
|
: body
|
|
884
912
|
};
|
|
885
913
|
}
|
|
@@ -1497,7 +1525,7 @@ export function createRoutes(core, context = {}) {
|
|
|
1497
1525
|
sanitizeDownloadFilename,
|
|
1498
1526
|
contentTypeForExt,
|
|
1499
1527
|
inferInlineDocumentFromPath,
|
|
1500
|
-
|
|
1528
|
+
resolveCanonicalContentRead,
|
|
1501
1529
|
inferWorkspaceIdFromStoragePath,
|
|
1502
1530
|
inferTaskIdFromStoragePath,
|
|
1503
1531
|
});
|