@taskforcehq/taskforce 0.3.329 → 0.3.332
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/Taskforce.module.css +290 -45
- package/dist/components/features/AiProfilesModule.js +3 -5
- package/dist/components/features/TaskSettings.js +70 -14
- package/dist/components/features/TaskforceAgentsModule.d.ts +1 -2
- package/dist/components/features/TaskforceAgentsModule.js +586 -116
- package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.d.ts +2 -2
- package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.js +2 -2
- package/dist/components/features/agentConnections/AgentConnectionControl.d.ts +15 -0
- package/dist/components/features/agentConnections/AgentConnectionControl.js +125 -0
- package/dist/components/features/agentConnections/ModelConnectionsPanel.d.ts +18 -0
- package/dist/components/features/agentConnections/ModelConnectionsPanel.js +393 -0
- package/dist/components/features/agentConnections/modelConnectionsApi.d.ts +65 -0
- package/dist/components/features/agentConnections/modelConnectionsApi.js +129 -0
- package/dist/components/features/planning/PlanningModule.js +15 -3
- package/dist/components/features/planning/planningMetadata.d.ts +10 -0
- package/dist/components/features/planning/planningMetadata.js +8 -0
- package/dist/components/features/taskforceAgentAssignments/AgentBehaviorAssignments.js +15 -24
- package/dist/components/features/taskforceAgentEditorModel.d.ts +2 -1
- package/dist/components/features/taskforceAgentEditorModel.js +3 -1
- package/dist/components/features/workflowManager/workflowManagerApi.d.ts +1 -1
- package/dist/components/features/workflowManager/workflowManagerApi.js +3 -0
- package/dist/components/task/TaskWorkflowAssignmentField.js +68 -10
- package/dist/components/views/StandaloneLayout.js +62 -27
- package/dist/components/views/panels/PlanningDrawer.d.ts +2 -1
- package/dist/components/views/panels/PlanningDrawer.js +12 -8
- package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +4 -3
- package/dist/components/views/standalone/modals/SyncStatusModal.js +33 -38
- package/dist/config/envSchema.js +55 -1
- package/dist/core/McpTokenService.d.ts +25 -1
- package/dist/core/McpTokenService.js +106 -15
- package/dist/core/TaskChecklistCommandService.d.ts +27 -0
- package/dist/core/TaskChecklistCommandService.js +83 -5
- package/dist/core/TaskLifecycleCommandService.d.ts +3 -1
- package/dist/core/TaskLifecycleCommandService.js +6 -2
- package/dist/core/Taskforce.d.ts +43 -3
- package/dist/core/Taskforce.js +158 -7
- package/dist/core/types.d.ts +9 -0
- package/dist/documentReviews/DocumentReviewCommentStore.d.ts +2 -0
- package/dist/documentReviews/DocumentReviewCommentStore.js +28 -9
- package/dist/documentReviews/commandService.d.ts +2 -1
- package/dist/documentReviews/commandService.js +6 -3
- package/dist/hooks/sync/orchestratorShared.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.js +2 -0
- package/dist/hooks/sync/useLocalSyncCoordinatorSnapshot.d.ts +3 -0
- package/dist/hooks/sync/useLocalSyncCoordinatorSnapshot.js +85 -9
- package/dist/hooks/sync/useSyncStatusControls.d.ts +4 -1
- package/dist/hooks/sync/useSyncStatusControls.js +47 -11
- package/dist/hooks/useSyncOrchestrator.d.ts +3 -0
- package/dist/hooks/useSyncOrchestrator.js +111 -2
- package/dist/hooks/useTaskData.d.ts +31 -1
- package/dist/hooks/useTaskData.js +29 -7
- package/dist/hooks/useTaskMutations.d.ts +3 -1
- package/dist/hooks/useTaskMutations.js +46 -12
- package/dist/hooks/useTaskforce.d.ts +2 -0
- package/dist/hooks/useTaskforce.js +11 -2
- package/dist/mcp/clientRegistry.d.ts +2 -2
- package/dist/mcp/clientRegistry.js +1 -1
- package/dist/mcp/documentAssetRegistrar.js +9 -6
- package/dist/mcp/durableTaskMutationRouter.d.ts +4 -1
- package/dist/mcp/durableTaskMutationRouter.js +10 -0
- package/dist/mcp/durableTaskRelationshipMutationRouter.js +4 -0
- package/dist/mcp/httpProtocolRouting.d.ts +11 -0
- package/dist/mcp/httpProtocolRouting.js +22 -0
- package/dist/mcp/localCliHealth.d.ts +2 -2
- package/dist/mcp/localCliHealth.js +2 -2
- package/dist/mcp/localServiceProxy.d.ts +1 -0
- package/dist/mcp/localServiceProxy.js +530 -68
- package/dist/mcp/planningAttachmentCommandAdapter.d.ts +3 -1
- package/dist/mcp/planningAttachmentCommandAdapter.js +20 -2
- package/dist/mcp/runtime.d.ts +54 -79
- package/dist/mcp/runtime.js +408 -102
- package/dist/mcp/taskPlanningRegistrar.js +25 -6
- package/dist/mcp/toolProfiles.d.ts +2 -0
- package/dist/mcp/toolProfiles.js +11 -0
- package/dist/mcp/toolRegistry.d.ts +1 -0
- package/dist/mcp/toolRegistry.js +9 -8
- package/dist/mcp/workflowExecutionRegistrar.js +4 -4
- package/dist/migrations/taskSchemaMigrations.d.ts +5 -0
- package/dist/migrations/taskSchemaMigrations.js +415 -0
- package/dist/runtime/appGates.d.ts +2 -1
- package/dist/runtime/appGates.js +7 -0
- package/dist/server/buildInfo.d.ts +1 -0
- package/dist/server/buildInfo.js +5 -1
- package/dist/server/cloudMcpCapacityTelemetry.d.ts +68 -0
- package/dist/server/cloudMcpCapacityTelemetry.js +221 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +108 -11
- package/dist/server/localDatabaseIdentity.js +4 -1
- package/dist/server/localServiceLease.js +2 -1
- package/dist/server/localServiceSessions.d.ts +14 -17
- package/dist/server/localServiceSessions.js +308 -94
- package/dist/server/localWorkspaceSyncServerRuntime.js +25 -19
- package/dist/server/routes/admin.d.ts +4 -0
- package/dist/server/routes/admin.js +126 -5
- package/dist/server/routes/agents.d.ts +7 -0
- package/dist/server/routes/agents.js +277 -24
- package/dist/server/routes/billing.js +5 -1
- package/dist/server/routes/durableTaskHttpRouters.d.ts +2 -0
- package/dist/server/routes/executorPhaseAApprovals.d.ts +19 -0
- package/dist/server/routes/executorPhaseAApprovals.js +140 -0
- package/dist/server/routes/executorPhaseAIssuerRedemptions.d.ts +11 -0
- package/dist/server/routes/executorPhaseAIssuerRedemptions.js +24 -0
- package/dist/server/routes/githubReviewEvidence.d.ts +19 -0
- package/dist/server/routes/githubReviewEvidence.js +116 -0
- package/dist/server/routes/localService.d.ts +15 -4
- package/dist/server/routes/localService.js +136 -40
- package/dist/server/routes/modelConnections.d.ts +24 -0
- package/dist/server/routes/modelConnections.js +205 -0
- package/dist/server/routes/shared.d.ts +22 -0
- package/dist/server/routes/shared.js +298 -182
- package/dist/server/routes/sync.js +1 -0
- package/dist/server/routes/syncAuxRoutes.js +5 -1
- package/dist/server/routes/syncPushRoutes.d.ts +1 -0
- package/dist/server/routes/syncPushRoutes.js +1 -0
- package/dist/server/routes/syncV3FeedRoutes.js +10 -1
- package/dist/server/routes/syncV3OperationRoutes.js +35 -9
- package/dist/server/routes/tasks.js +21 -2
- package/dist/server/routes/workflows.js +9 -1
- package/dist/server/routes.js +59 -0
- package/dist/service/localServiceCli.d.ts +2 -2
- package/dist/service/localServiceCli.js +4 -3
- package/dist/services/codexAppServerModelGateway.d.ts +54 -5
- package/dist/services/codexAppServerModelGateway.js +246 -21
- package/dist/services/codexDeviceCodeLoginManager.d.ts +39 -0
- package/dist/services/codexDeviceCodeLoginManager.js +265 -0
- package/dist/services/codexModelProviderConnectionLifecycle.d.ts +8 -0
- package/dist/services/codexModelProviderConnectionLifecycle.js +73 -0
- package/dist/services/executorPhaseAApprovalRuntime.d.ts +14 -0
- package/dist/services/executorPhaseAApprovalRuntime.js +24 -0
- package/dist/services/executorPhaseAApprovalService.d.ts +40 -0
- package/dist/services/executorPhaseAApprovalService.js +609 -0
- package/dist/services/executorPhaseADriftEvaluator.d.ts +78 -0
- package/dist/services/executorPhaseADriftEvaluator.js +93 -0
- package/dist/services/executorPhaseAIssuerCore.d.ts +139 -0
- package/dist/services/executorPhaseAIssuerCore.js +402 -0
- package/dist/services/executorPhaseAIssuerRedemptionService.d.ts +34 -0
- package/dist/services/executorPhaseAIssuerRedemptionService.js +158 -0
- package/dist/services/githubReviewEvidenceRuntime.d.ts +14 -0
- package/dist/services/githubReviewEvidenceRuntime.js +30 -0
- package/dist/services/githubReviewEvidenceService.d.ts +93 -0
- package/dist/services/githubReviewEvidenceService.js +336 -0
- package/dist/services/modelGateway.d.ts +7 -0
- package/dist/services/modelProviderConnectionService.d.ts +71 -0
- package/dist/services/modelProviderConnectionService.js +279 -0
- package/dist/services/modelProviderCredentialHome.d.ts +32 -0
- package/dist/services/modelProviderCredentialHome.js +271 -0
- package/dist/services/ownerScopedModelConnectionGateway.d.ts +12 -0
- package/dist/services/ownerScopedModelConnectionGateway.js +30 -0
- package/dist/services/taskforceAgentConnectionBindingService.d.ts +66 -0
- package/dist/services/taskforceAgentConnectionBindingService.js +185 -0
- package/dist/services/taskforceAgentMcpBridge.d.ts +2 -2
- package/dist/services/taskforceAgentMcpBridge.js +42 -29
- package/dist/shared/adminEdgeProxy.d.ts +5 -0
- package/dist/shared/adminEdgeProxy.js +94 -0
- package/dist/shared/adminProxySessionCookies.d.ts +16 -0
- package/dist/shared/adminProxySessionCookies.js +86 -0
- package/dist/shared/aiProfileColors.d.ts +31 -1
- package/dist/shared/aiProfileColors.js +14 -6
- package/dist/shared/executorPhaseAApprovalGoldenVector.json +269 -0
- package/dist/shared/executorPhaseAApprovalReceipt.d.ts +112 -0
- package/dist/shared/executorPhaseAApprovalReceipt.js +58 -0
- package/dist/shared/executorPhaseAApprovalTrustPolicy.json +10 -0
- package/dist/shared/executorPhaseADriftContracts.d.ts +37 -0
- package/dist/shared/executorPhaseADriftContracts.js +63 -0
- package/dist/shared/executorPhaseAIssuerContracts.d.ts +75 -0
- package/dist/shared/executorPhaseAIssuerContracts.js +90 -0
- package/dist/shared/executorPhaseARunAuthorization.d.ts +120 -0
- package/dist/shared/executorPhaseARunAuthorization.js +232 -0
- package/dist/shared/executorPhaseASchemaValidation.d.ts +7 -0
- package/dist/shared/executorPhaseASchemaValidation.js +53 -0
- package/dist/shared/executorPhaseASchemas/approval-receipt.schema.json +106 -0
- package/dist/shared/executorPhaseASchemas/drift-attestation.schema.json +54 -0
- package/dist/shared/executorPhaseASchemas/drift-request.schema.json +47 -0
- package/dist/shared/executorPhaseASchemas/foundation-manifest.schema.json +29 -0
- package/dist/shared/executorPhaseASchemas/issuer-audit-record.schema.json +24 -0
- package/dist/shared/executorPhaseASchemas/issuer-proof.schema.json +43 -0
- package/dist/shared/executorPhaseASchemas/issuer-redemption-request.schema.json +18 -0
- package/dist/shared/executorPhaseASchemas/issuer-redemption-response.schema.json +39 -0
- package/dist/shared/executorPhaseASchemas/logical-envelope.schema.json +149 -0
- package/dist/shared/executorPhaseASchemas/preflight-report.schema.json +216 -0
- package/dist/shared/executorPhaseASchemas/run-capability.schema.json +111 -0
- package/dist/shared/executorPhaseASchemas/sender-proof.schema.json +37 -0
- package/dist/shared/executorPhaseASchemas/workload-grant.schema.json +68 -0
- package/dist/shared/executorPhaseASchemas/workload-jwt-claims.schema.json +67 -0
- package/dist/shared/executorPhaseATiming.d.ts +1 -0
- package/dist/shared/executorPhaseATiming.js +1 -0
- package/dist/shared/planningIdentity.js +1 -1
- package/dist/shared/productLogoKeys.d.ts +2 -0
- package/dist/shared/productLogoKeys.js +18 -0
- package/dist/shared/taskforceAgentDefinition.d.ts +0 -1
- package/dist/shared/taskforceAgentDefinition.js +6 -9
- package/dist/shared/taskforceAgentModels.js +7 -3
- package/dist/shared/userFeatureGrants.d.ts +4 -0
- package/dist/shared/userFeatureGrants.js +7 -0
- package/dist/storage/executorPhaseAApprovalReceiptStore.d.ts +37 -0
- package/dist/storage/executorPhaseAApprovalReceiptStore.js +81 -0
- package/dist/storage/executorPhaseARunAuthorizationStore.d.ts +103 -0
- package/dist/storage/executorPhaseARunAuthorizationStore.js +492 -0
- package/dist/storage/githubReviewEvidenceInstallationStore.d.ts +26 -0
- package/dist/storage/githubReviewEvidenceInstallationStore.js +89 -0
- package/dist/storage/modelProviderConnectionStore.d.ts +99 -0
- package/dist/storage/modelProviderConnectionStore.js +446 -0
- package/dist/storage/postgresAdapter.d.ts +4 -0
- package/dist/storage/postgresAdapter.js +14 -1
- package/dist/storage/postgresRequestDiagnostics.d.ts +9 -1
- package/dist/storage/postgresRequestDiagnostics.js +15 -2
- package/dist/storage/postgresWorker.js +20 -2
- package/dist/storage/postgresWorkerProtocol.d.ts +7 -0
- package/dist/storage/taskforceAgentConnectionBindingStore.d.ts +31 -0
- package/dist/storage/taskforceAgentConnectionBindingStore.js +84 -0
- package/dist/storage/workflowStore.d.ts +2 -0
- package/dist/storage/workflowStore.js +39 -2
- package/dist/sync/cloudSyncApi.js +4 -2
- package/dist/sync/collaborationSyncState.js +5 -0
- package/dist/sync/coordinator/localSyncExecutionPermitStore.js +60 -29
- package/dist/sync/coordinator/localWorkspaceSyncPendingV2PushProgress.d.ts +10 -0
- package/dist/sync/coordinator/localWorkspaceSyncPendingV2PushProgress.js +29 -0
- package/dist/sync/coordinator/localWorkspaceSyncRunner.d.ts +3 -1
- package/dist/sync/coordinator/localWorkspaceSyncRunner.js +18 -0
- package/dist/sync/coordinator/localWorkspaceSyncRunnerState.d.ts +15 -1
- package/dist/sync/coordinator/localWorkspaceSyncRunnerState.js +99 -2
- package/dist/sync/coordinator/workspaceSyncCredentialBroker.d.ts +1 -0
- package/dist/sync/coordinator/workspaceSyncCredentialBroker.js +33 -4
- package/dist/sync/engine/workspaceSyncContentLifecycleAdapter.d.ts +2 -1
- package/dist/sync/engine/workspaceSyncContentVersionStore.d.ts +38 -0
- package/dist/sync/engine/workspaceSyncContentVersionStore.js +50 -1
- package/dist/sync/engine/workspaceSyncEngineLifecyclePolicy.js +6 -6
- package/dist/sync/engine/workspaceSyncEnginePushCompletion.d.ts +14 -0
- package/dist/sync/engine/workspaceSyncEnginePushCompletion.js +84 -0
- package/dist/sync/engine/workspaceSyncEngineRecovery.js +1 -0
- package/dist/sync/engine/workspaceSyncEngineRuntime.d.ts +1 -0
- package/dist/sync/engine/workspaceSyncEngineRuntime.js +1 -0
- package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +112 -38
- package/dist/sync/engine/workspaceSyncEngineServerSnapshotReader.js +6 -0
- package/dist/sync/engine/workspaceSyncEngineTransfers.js +3 -1
- package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.d.ts +1 -0
- package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.js +31 -0
- package/dist/sync/syncApplyHandlers.d.ts +3 -0
- package/dist/sync/syncApplyHandlers.js +81 -15
- package/dist/sync/syncService.d.ts +15 -0
- package/dist/sync/syncService.js +83 -18
- package/dist/sync/taskforceAgentSyncAuthorization.d.ts +16 -0
- package/dist/sync/taskforceAgentSyncAuthorization.js +20 -0
- package/dist/sync/v3/combinedFeedCapability.js +4 -0
- package/dist/sync/v3/durableInitiativeMutationRouter.js +18 -10
- package/dist/sync/v3/durableOperationReplay.d.ts +14 -0
- package/dist/sync/v3/durableOperationReplay.js +18 -0
- package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +3 -0
- package/dist/sync/v3/durableTaskChecklistMutationRouter.d.ts +2 -1
- package/dist/sync/v3/durableTaskChecklistMutationRouter.js +58 -15
- package/dist/sync/v3/durableTaskCommentMutationRouter.js +3 -0
- package/dist/sync/v3/durableTaskHttpRootMutationRouter.d.ts +1 -1
- package/dist/sync/v3/durableTaskHttpRootMutationRouter.js +10 -0
- package/dist/sync/v3/durableTaxonomyMutationRouter.js +5 -0
- package/dist/sync/v3/durableWorkflowRuntimeMutationRouter.d.ts +18 -7
- package/dist/sync/v3/durableWorkflowRuntimeMutationRouter.js +89 -23
- package/dist/sync/v3/durableWorkstreamMutationRouter.js +18 -10
- package/dist/sync/v3/durableWorkstreamTaskOrderMutationRouter.js +16 -5
- package/dist/sync/v3/localOutboxDispatchRuntime.js +2 -2
- package/dist/sync/v3/localOutboxRecoveryPreparationService.js +8 -2
- package/dist/sync/v3/localTaskChecklistOutboxHandler.js +16 -2
- package/dist/sync/v3/localTaskChecklistOutboxService.d.ts +4 -0
- package/dist/sync/v3/localTaskChecklistOutboxService.js +14 -1
- package/dist/sync/v3/localTaskCreateOutboxService.d.ts +3 -1
- package/dist/sync/v3/localTaskCreateOutboxService.js +6 -1
- package/dist/sync/v3/localTaskDeleteOutboxService.js +1 -0
- package/dist/sync/v3/localTaskLifecycleOutboxService.d.ts +2 -1
- package/dist/sync/v3/localTaskLifecycleOutboxService.js +5 -1
- package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -1
- package/dist/sync/v3/localTaskMetadataOutboxService.js +6 -2
- package/dist/sync/v3/localTaskStatusOutboxService.d.ts +10 -1
- package/dist/sync/v3/localTaskStatusOutboxService.js +238 -16
- package/dist/sync/v3/syncDurabilityStore.d.ts +36 -1
- package/dist/sync/v3/syncDurabilityStore.js +202 -8
- package/dist/sync/v3/taskChecklistMutationService.d.ts +11 -0
- package/dist/sync/v3/taskChecklistMutationService.js +44 -1
- package/dist/sync/v3/taskStatusMutationService.d.ts +29 -1
- package/dist/sync/v3/taskStatusMutationService.js +119 -3
- package/dist/sync/v3/workflowAssignmentSync.d.ts +5 -0
- package/dist/sync/v3/workflowAssignmentSync.js +27 -0
- package/dist/sync/v3/workflowRuntimeMutationService.d.ts +5 -0
- package/dist/sync/v3/workflowRuntimeMutationService.js +124 -71
- package/dist/sync/v3/workspaceSyncJournalReader.js +1 -1
- package/dist/sync/v3/workspaceSyncOperationRetentionService.d.ts +16 -0
- package/dist/sync/v3/workspaceSyncOperationRetentionService.js +83 -0
- package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +9 -2
- package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +25 -3
- package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +7 -0
- package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +44 -32
- package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +9 -5
- package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +54 -38
- package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.d.ts +10 -0
- package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +70 -31
- package/dist/sync/v3/workspaceSyncV3CombinedWireContract.d.ts +2 -1
- package/dist/sync/v3/workspaceSyncV3CombinedWireContract.js +16 -0
- package/dist/sync/v3/workspaceSyncV3CoverageRegistry.js +6 -0
- package/dist/sync/v3/workspaceSyncV3Feed.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3Feed.js +6 -1
- package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +2 -0
- package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +49 -18
- package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +3 -0
- package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.d.ts +5 -0
- package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.js +36 -2
- package/dist/sync/v3/workspaceSyncV3RepairSnapshotRetentionService.d.ts +17 -0
- package/dist/sync/v3/workspaceSyncV3RepairSnapshotRetentionService.js +64 -0
- package/dist/sync/v3/workspaceSyncV3WorkflowRuntimeApply.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3WorkflowRuntimeApply.js +50 -5
- package/dist/sync/v3/workspaceSyncV3WorkflowTaskCoverCoverage.d.ts +7 -0
- package/dist/sync/v3/workspaceSyncV3WorkflowTaskCoverCoverage.js +33 -0
- package/dist/sync/v3/workspaceSyncV3WorkflowTaskCoverWireContract.d.ts +7 -0
- package/dist/sync/v3/workspaceSyncV3WorkflowTaskCoverWireContract.js +8 -0
- package/dist/sync/workspaceRepair.d.ts +5 -2
- package/dist/sync/workspaceRepair.js +161 -6
- package/dist/sync/workspaceSyncModel.d.ts +21 -0
- package/dist/sync/workspaceSyncModel.js +35 -7
- package/dist/sync/workspaceSyncState.d.ts +1 -0
- package/dist/sync/workspaceSyncV2SnapshotBuilder.d.ts +6 -0
- package/dist/sync/workspaceSyncV2SnapshotBuilder.js +5 -0
- package/dist/ui/assets/AiIdentityRosterCard-CDMPtnbC.js +1 -0
- package/dist/ui/assets/AiProfilesModule-Cww85C4E.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-B68Wg-RV.js → AnnotatedAttachmentWorkspace-Dt9TKYSH.js} +1 -1
- package/dist/ui/assets/{AssetTraySortControl-C8Rztyal.js → AssetTraySortControl-CKgapnxu.js} +1 -1
- package/dist/ui/assets/{ContextAttachmentManager-Cj3c7X9y.js → ContextAttachmentManager-T-cEVcOZ.js} +2 -2
- package/dist/ui/assets/{DocumentWorkspace-B5uvzems.js → DocumentWorkspace-HD4MUjAZ.js} +1 -1
- package/dist/ui/assets/{EntityActivityTimeline-BVFvCB5R.js → EntityActivityTimeline-_V0zfMuh.js} +1 -1
- package/dist/ui/assets/PlanningModule-mTkBygsB.js +1 -0
- package/dist/ui/assets/{PlansPage-AY4cGWco.js → PlansPage-Btiq06VO.js} +1 -1
- package/dist/ui/assets/{TaskContextUpload-DZxTVesW.js → TaskContextUpload-C00NVUpn.js} +1 -1
- package/dist/ui/assets/TaskSettings-Cp-L-4xh.js +12 -0
- package/dist/ui/assets/TaskforceAgentsModule-Coq9-PUp.js +24 -0
- package/dist/ui/assets/TaskforceAgentsModule-b-8R25cJ.css +1 -0
- package/dist/ui/assets/{WorkflowManagerModule-DEr5di_r.js → WorkflowManagerModule-CuqRzMmM.js} +1 -1
- package/dist/ui/assets/index-BZ-hdZSk.css +1 -0
- package/dist/ui/assets/index-DSQINwt9.js +7 -0
- package/dist/ui/assets/{vendor-icons-D9Lpw-j4.js → vendor-icons-Bsq-mcEn.js} +1 -1
- package/dist/ui/branding/logos/letterhead_dark.png +0 -0
- package/dist/ui/branding/logos/letterhead_light.png +0 -0
- package/dist/ui/branding/logos/signature_dark.png +0 -0
- package/dist/ui/branding/logos/signature_light.png +0 -0
- package/dist/ui/index.html +3 -3
- package/dist/utils/aiProfileDefaultLogo.js +16 -48
- package/dist/utils/httpSetCookie.d.ts +1 -0
- package/dist/utils/httpSetCookie.js +4 -1
- package/dist/utils/localRuntimeUrl.d.ts +1 -0
- package/dist/utils/localRuntimeUrl.js +14 -0
- package/dist/utils/productLogoRegistry.d.ts +2 -0
- package/dist/utils/productLogoRegistry.js +36 -0
- package/dist/utils/syncStatusPresentation.d.ts +4 -0
- package/dist/utils/syncStatusPresentation.js +17 -0
- package/dist/utils/taskEvents.d.ts +2 -0
- package/dist/utils/workspaceSyncPresentation.d.ts +1 -1
- package/dist/utils/workspaceSyncPresentation.js +23 -17
- package/package.json +24 -4
- package/dist/ui/assets/AiIdentityRosterCard-BZM4aQnM.js +0 -1
- package/dist/ui/assets/AiProfilesModule-BbAEWtHH.js +0 -1
- package/dist/ui/assets/PlanningModule-B5DWQOGz.js +0 -1
- package/dist/ui/assets/TaskSettings-BeWAuyZj.js +0 -12
- package/dist/ui/assets/TaskforceAgentsModule-BifCYtjR.js +0 -21
- package/dist/ui/assets/TaskforceAgentsModule-D6IC0S7Q.css +0 -1
- package/dist/ui/assets/index-I5zhdtgt.js +0 -7
- package/dist/ui/assets/index-VRFsx3wz.css +0 -1
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
import { createPrivateKey, createPublicKey, randomUUID, sign, verify } from 'crypto';
|
|
2
|
+
import trustPolicyJson from '../shared/executorPhaseAApprovalTrustPolicy.json' with { type: 'json' };
|
|
3
|
+
import { EXECUTOR_PHASE_A_MAX_PREFLIGHT_AGE_MS, EXECUTOR_PHASE_A_MAX_PREFLIGHT_BYTES, EXECUTOR_PHASE_A_MUTATION_TTL_MS, EXECUTOR_PHASE_A_ORDINARY_CLEANUP_TTL_MS, EXECUTOR_PHASE_A_PREFLIGHT_SCHEMA, EXECUTOR_PHASE_A_RECEIPT_PAYLOAD_SCHEMA, EXECUTOR_PHASE_A_RECEIPT_TTL_MS, EXECUTOR_PHASE_A_TRUST_POLICY_SCHEMA, canonicalizeExecutorPhaseAJson, sha256ExecutorPhaseABytes, } from '../shared/executorPhaseAApprovalReceipt.js';
|
|
4
|
+
import { assertExecutorPhaseAAccountAuthorities, executorPhaseABrokerKeyFingerprint, executorPhaseARoleMapSha256, } from '../shared/executorPhaseARunAuthorization.js';
|
|
5
|
+
import { assertExecutorPhaseASchema } from '../shared/executorPhaseASchemaValidation.js';
|
|
6
|
+
const PREFLIGHT_KEYS = [
|
|
7
|
+
'approvalContext', 'authorities', 'baseAmi', 'budgets', 'collectedAt', 'expiresAt', 'failures',
|
|
8
|
+
'immutableInputs', 'logicalEnvelope', 'logicalEnvelopeSha256', 'pricing', 'quotas', 'requestSha256',
|
|
9
|
+
'runId', 'schema', 'secretNameScan', 'source', 'status', 'target',
|
|
10
|
+
].sort();
|
|
11
|
+
const FROZEN_PHASE_A_WORKSPACE_ID = 'workspace-019c771a-b6d7-7bc8-b837-c1133f3ba7a2';
|
|
12
|
+
const FROZEN_PHASE_A_TASK_ID = 'task-62fecb296bbe07ca535b6e6630fc458f';
|
|
13
|
+
const ISO_UTC_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/;
|
|
14
|
+
export class ExecutorPhaseAApprovalError extends Error {
|
|
15
|
+
statusCode;
|
|
16
|
+
code;
|
|
17
|
+
constructor(message, statusCode = 400, code = 'executor_phase_a_approval_invalid') {
|
|
18
|
+
super(message);
|
|
19
|
+
this.statusCode = statusCode;
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.name = 'ExecutorPhaseAApprovalError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function assertExecutorPhaseAApprovalTrustPolicy(policy) {
|
|
25
|
+
if (policy.schema !== EXECUTOR_PHASE_A_TRUST_POLICY_SCHEMA
|
|
26
|
+
|| policy.issuer !== 'https://performance-app.taskforcehq.ai'
|
|
27
|
+
|| policy.audience !== 'taskforce-agent-executor-phase-a'
|
|
28
|
+
|| policy.environment !== 'performance'
|
|
29
|
+
|| policy.workspaceId !== FROZEN_PHASE_A_WORKSPACE_ID
|
|
30
|
+
|| policy.taskId !== FROZEN_PHASE_A_TASK_ID
|
|
31
|
+
|| policy.taskReference !== 'T-1653'
|
|
32
|
+
|| !Array.isArray(policy.keys)) {
|
|
33
|
+
throw new Error('The committed Phase A approval trust policy has an invalid frozen authority contract.');
|
|
34
|
+
}
|
|
35
|
+
const keyIds = new Set();
|
|
36
|
+
for (const key of policy.keys) {
|
|
37
|
+
const validFrom = ISO_UTC_TIMESTAMP.test(key.validFrom) ? Date.parse(key.validFrom) : Number.NaN;
|
|
38
|
+
const validUntil = ISO_UTC_TIMESTAMP.test(key.validUntil) ? Date.parse(key.validUntil) : Number.NaN;
|
|
39
|
+
if (!/^[A-Za-z0-9_-]{1,64}$/.test(key.keyId) || keyIds.has(key.keyId)
|
|
40
|
+
|| !['active', 'retired', 'disabled'].includes(key.status)
|
|
41
|
+
|| !Number.isFinite(validFrom) || !Number.isFinite(validUntil)
|
|
42
|
+
|| validFrom >= validUntil) {
|
|
43
|
+
throw new Error('The committed Phase A approval trust policy contains an invalid or duplicate key entry.');
|
|
44
|
+
}
|
|
45
|
+
keyIds.add(key.keyId);
|
|
46
|
+
try {
|
|
47
|
+
const encoded = String(key.publicKeySpkiBase64 || '');
|
|
48
|
+
const der = Buffer.from(encoded, 'base64');
|
|
49
|
+
const publicKey = createPublicKey({ key: der, format: 'der', type: 'spki' });
|
|
50
|
+
if (publicKey.asymmetricKeyType !== 'ed25519' || der.toString('base64') !== encoded)
|
|
51
|
+
throw new Error('invalid');
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new Error(`The committed Phase A approval trust key ${key.keyId} is not canonical Ed25519 SPKI.`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function resolveExecutorPhaseAApprovalTrustKey(policy, keyId, signedAt, usage) {
|
|
59
|
+
const timestamp = Date.parse(signedAt);
|
|
60
|
+
if (!Number.isFinite(timestamp))
|
|
61
|
+
return null;
|
|
62
|
+
const key = policy.keys.find((candidate) => candidate.keyId === keyId);
|
|
63
|
+
if (!key || key.status === 'disabled')
|
|
64
|
+
return null;
|
|
65
|
+
if (usage === 'sign' && key.status !== 'active')
|
|
66
|
+
return null;
|
|
67
|
+
const validFrom = Date.parse(key.validFrom);
|
|
68
|
+
const validUntil = Date.parse(key.validUntil);
|
|
69
|
+
if (!Number.isFinite(validFrom) || timestamp < validFrom
|
|
70
|
+
|| !Number.isFinite(validUntil) || timestamp >= validUntil)
|
|
71
|
+
return null;
|
|
72
|
+
return key;
|
|
73
|
+
}
|
|
74
|
+
function exactKeys(value, expected) {
|
|
75
|
+
const actual = Object.keys(value).sort();
|
|
76
|
+
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
|
77
|
+
}
|
|
78
|
+
function parseIso(value, field) {
|
|
79
|
+
if (typeof value !== 'string' || !ISO_UTC_TIMESTAMP.test(value)) {
|
|
80
|
+
throw new ExecutorPhaseAApprovalError(`${field} must be an ISO 8601 UTC timestamp.`);
|
|
81
|
+
}
|
|
82
|
+
const timestamp = Date.parse(value);
|
|
83
|
+
if (!Number.isFinite(timestamp))
|
|
84
|
+
throw new ExecutorPhaseAApprovalError(`${field} is invalid.`);
|
|
85
|
+
return timestamp;
|
|
86
|
+
}
|
|
87
|
+
function boundedMoney(value, field) {
|
|
88
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value <= 0 || value > 1_000_000_000_000) {
|
|
89
|
+
throw new ExecutorPhaseAApprovalError(`${field} must be a positive bounded safe integer.`);
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
function assertIJson(value, path = '$') {
|
|
94
|
+
if (typeof value === 'number' && Number.isInteger(value) && !Number.isSafeInteger(value)) {
|
|
95
|
+
throw new ExecutorPhaseAApprovalError(`Preflight contains an unsafe I-JSON integer at ${path}.`);
|
|
96
|
+
}
|
|
97
|
+
if (typeof value === 'string' && /[\uD800-\uDFFF]/u.test(value.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/gu, ''))) {
|
|
98
|
+
throw new ExecutorPhaseAApprovalError(`Preflight contains invalid Unicode at ${path}.`);
|
|
99
|
+
}
|
|
100
|
+
if (Array.isArray(value))
|
|
101
|
+
value.forEach((item, index) => assertIJson(item, `${path}[${index}]`));
|
|
102
|
+
else if (value && typeof value === 'object') {
|
|
103
|
+
for (const [key, child] of Object.entries(value))
|
|
104
|
+
assertIJson(child, `${path}.${key}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export function parseCanonicalExecutorPhaseAPreflight(bytes, policy, now = new Date()) {
|
|
108
|
+
assertExecutorPhaseAApprovalTrustPolicy(policy);
|
|
109
|
+
if (bytes.length === 0 || bytes.length > EXECUTOR_PHASE_A_MAX_PREFLIGHT_BYTES) {
|
|
110
|
+
throw new ExecutorPhaseAApprovalError('Preflight size is outside the allowed bounds.', 413);
|
|
111
|
+
}
|
|
112
|
+
let parsed;
|
|
113
|
+
try {
|
|
114
|
+
parsed = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
throw new ExecutorPhaseAApprovalError('Preflight must be valid UTF-8 JSON.');
|
|
118
|
+
}
|
|
119
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
120
|
+
throw new ExecutorPhaseAApprovalError('Preflight must be a JSON object.');
|
|
121
|
+
}
|
|
122
|
+
const input = parsed;
|
|
123
|
+
assertIJson(input);
|
|
124
|
+
if (!exactKeys(input, PREFLIGHT_KEYS))
|
|
125
|
+
throw new ExecutorPhaseAApprovalError('Preflight fields do not match the Phase A schema.');
|
|
126
|
+
const canonical = canonicalizeExecutorPhaseAJson(input);
|
|
127
|
+
if (!canonical.equals(bytes))
|
|
128
|
+
throw new ExecutorPhaseAApprovalError('Preflight bytes must be RFC 8785 canonical JSON.');
|
|
129
|
+
try {
|
|
130
|
+
assertExecutorPhaseASchema('report', input);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
throw new ExecutorPhaseAApprovalError('Preflight fields do not match the Phase A schema.');
|
|
134
|
+
}
|
|
135
|
+
if (input.schema !== EXECUTOR_PHASE_A_PREFLIGHT_SCHEMA || input.status !== 'pass') {
|
|
136
|
+
throw new ExecutorPhaseAApprovalError('Preflight is not a passing Phase A v3 preflight.');
|
|
137
|
+
}
|
|
138
|
+
if (!Array.isArray(input.failures) || input.failures.length !== 0) {
|
|
139
|
+
throw new ExecutorPhaseAApprovalError('Preflight contains failures.');
|
|
140
|
+
}
|
|
141
|
+
const approvalContext = input.approvalContext;
|
|
142
|
+
const target = input.target;
|
|
143
|
+
const source = input.source;
|
|
144
|
+
const budgets = input.budgets;
|
|
145
|
+
if (!approvalContext || Array.isArray(approvalContext) || !target || Array.isArray(target)
|
|
146
|
+
|| !source || Array.isArray(source) || !budgets || Array.isArray(budgets)) {
|
|
147
|
+
throw new ExecutorPhaseAApprovalError('Preflight binding objects are invalid.');
|
|
148
|
+
}
|
|
149
|
+
if (approvalContext.workspaceId !== policy.workspaceId
|
|
150
|
+
|| approvalContext.taskId !== policy.taskId
|
|
151
|
+
|| approvalContext.taskReference !== policy.taskReference
|
|
152
|
+
|| approvalContext.environment !== policy.environment
|
|
153
|
+
|| target.region !== 'us-west-2') {
|
|
154
|
+
throw new ExecutorPhaseAApprovalError('Preflight authority or deployment scope is not approved.');
|
|
155
|
+
}
|
|
156
|
+
if (typeof input.runId !== 'string' || !/^phase-a-[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/.test(input.runId)) {
|
|
157
|
+
throw new ExecutorPhaseAApprovalError('runId is invalid.');
|
|
158
|
+
}
|
|
159
|
+
if (typeof source.commit !== 'string' || !/^[a-f0-9]{40}$/.test(source.commit) || source.clean !== true) {
|
|
160
|
+
throw new ExecutorPhaseAApprovalError('Preflight source must be clean and use a full lowercase Git hash.');
|
|
161
|
+
}
|
|
162
|
+
if (typeof input.logicalEnvelopeSha256 !== 'string' || !/^[a-f0-9]{64}$/.test(input.logicalEnvelopeSha256)) {
|
|
163
|
+
throw new ExecutorPhaseAApprovalError('logicalEnvelopeSha256 is invalid.');
|
|
164
|
+
}
|
|
165
|
+
const computedLogicalEnvelopeSha256 = sha256ExecutorPhaseABytes(canonicalizeExecutorPhaseAJson(input.logicalEnvelope));
|
|
166
|
+
if (input.logicalEnvelopeSha256 !== computedLogicalEnvelopeSha256) {
|
|
167
|
+
throw new ExecutorPhaseAApprovalError('logicalEnvelopeSha256 does not match the canonical logical envelope.');
|
|
168
|
+
}
|
|
169
|
+
if (typeof target.accountId !== 'string' || !/^\d{12}$/.test(target.accountId)) {
|
|
170
|
+
throw new ExecutorPhaseAApprovalError('Preflight target accountId is invalid.');
|
|
171
|
+
}
|
|
172
|
+
if (typeof target.readPrincipalArn !== 'string' || !/^arn:aws:(?:iam::\d{12}:(?:user|role)\/[A-Za-z0-9+=,.@_\/-]+|sts::\d{12}:assumed-role\/[A-Za-z0-9+=,.@_\/-]+)$/.test(target.readPrincipalArn)) {
|
|
173
|
+
throw new ExecutorPhaseAApprovalError('Preflight target readPrincipalArn is invalid.');
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
assertExecutorPhaseAAccountAuthorities(String(target.accountId), String(target.readPrincipalArn), target.mutationRoles);
|
|
177
|
+
if (executorPhaseABrokerKeyFingerprint(String(target.brokerPublicKeySpkiBase64 || '')) !== target.brokerKeyFingerprint) {
|
|
178
|
+
throw new TypeError('fingerprint mismatch');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
throw new ExecutorPhaseAApprovalError('Preflight mutation roles or broker public-key binding is invalid.');
|
|
183
|
+
}
|
|
184
|
+
if (typeof target.availabilityZone !== 'string' || !/^us-west-2[a-z]$/.test(target.availabilityZone)) {
|
|
185
|
+
throw new ExecutorPhaseAApprovalError('availabilityZone must be one us-west-2 Availability Zone.');
|
|
186
|
+
}
|
|
187
|
+
const temporaryBudgetUsdMicros = boundedMoney(budgets.temporaryUsdMicros, 'temporaryUsdMicros');
|
|
188
|
+
const alarmUsdMicros = boundedMoney(budgets.alarmUsdMicros, 'alarmUsdMicros');
|
|
189
|
+
boundedMoney(budgets.foundationMonthlyCeilingUsdMicros, 'foundationMonthlyCeilingUsdMicros');
|
|
190
|
+
if (alarmUsdMicros > temporaryBudgetUsdMicros) {
|
|
191
|
+
throw new ExecutorPhaseAApprovalError('alarmUsdMicros cannot exceed temporaryUsdMicros.');
|
|
192
|
+
}
|
|
193
|
+
const collectedAt = parseIso(input.collectedAt, 'collectedAt');
|
|
194
|
+
const expiresAt = parseIso(input.expiresAt, 'expiresAt');
|
|
195
|
+
const nowMs = now.getTime();
|
|
196
|
+
if (collectedAt > nowMs || collectedAt < nowMs - EXECUTOR_PHASE_A_MAX_PREFLIGHT_AGE_MS) {
|
|
197
|
+
throw new ExecutorPhaseAApprovalError('Preflight is stale or from the future.');
|
|
198
|
+
}
|
|
199
|
+
if (expiresAt <= nowMs || expiresAt > collectedAt + EXECUTOR_PHASE_A_MAX_PREFLIGHT_AGE_MS) {
|
|
200
|
+
throw new ExecutorPhaseAApprovalError('Preflight expiry is invalid or has elapsed.');
|
|
201
|
+
}
|
|
202
|
+
return input;
|
|
203
|
+
}
|
|
204
|
+
function durableRecordInvalid(message) {
|
|
205
|
+
throw new ExecutorPhaseAApprovalError(`The durable Phase A approval winner is unusable: ${message}`, 500, 'executor_phase_a_approval_durable_record_invalid');
|
|
206
|
+
}
|
|
207
|
+
function assertUsableDurableWinner(record, preflight, canonicalPreflightBytes, policy, now) {
|
|
208
|
+
let receipt;
|
|
209
|
+
try {
|
|
210
|
+
const parsed = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(record.canonicalReceiptBytes));
|
|
211
|
+
assertIJson(parsed);
|
|
212
|
+
assertExecutorPhaseASchema('approvalReceipt', parsed);
|
|
213
|
+
if (!canonicalizeExecutorPhaseAJson(parsed).equals(record.canonicalReceiptBytes))
|
|
214
|
+
durableRecordInvalid('receipt bytes are not canonical JSON.');
|
|
215
|
+
receipt = parsed;
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
if (error instanceof ExecutorPhaseAApprovalError)
|
|
219
|
+
throw error;
|
|
220
|
+
durableRecordInvalid('receipt bytes do not satisfy the approval receipt contract.');
|
|
221
|
+
}
|
|
222
|
+
const payloadBytes = canonicalizeExecutorPhaseAJson(receipt.payload);
|
|
223
|
+
const preflightSha256 = sha256ExecutorPhaseABytes(canonicalPreflightBytes);
|
|
224
|
+
const receiptSha256 = sha256ExecutorPhaseABytes(record.canonicalReceiptBytes);
|
|
225
|
+
if (!record.canonicalPreflightBytes.equals(canonicalPreflightBytes)
|
|
226
|
+
|| record.preflightSha256 !== preflightSha256
|
|
227
|
+
|| record.logicalEnvelopeSha256 !== preflight.logicalEnvelopeSha256
|
|
228
|
+
|| record.receiptSha256 !== receiptSha256) {
|
|
229
|
+
durableRecordInvalid('stored hashes or canonical preflight bytes are inconsistent.');
|
|
230
|
+
}
|
|
231
|
+
const payload = receipt.payload;
|
|
232
|
+
const approvedAt = Date.parse(payload.approvedAt);
|
|
233
|
+
const expiresAt = Date.parse(payload.expiresAt);
|
|
234
|
+
if (payload.receiptId !== record.receiptId
|
|
235
|
+
|| record.receiptKind !== 'main'
|
|
236
|
+
|| record.originalCapabilityId !== null
|
|
237
|
+
|| record.residualInventorySha256 !== null
|
|
238
|
+
|| payload.receiptKind !== 'main'
|
|
239
|
+
|| payload.originalCapabilityId !== null
|
|
240
|
+
|| payload.residualInventorySha256 !== null
|
|
241
|
+
|| payload.issuer !== policy.issuer
|
|
242
|
+
|| payload.audience !== policy.audience
|
|
243
|
+
|| payload.environment !== policy.environment
|
|
244
|
+
|| payload.workspaceId !== record.workspaceId
|
|
245
|
+
|| payload.taskId !== record.taskId
|
|
246
|
+
|| payload.taskReference !== record.taskReference
|
|
247
|
+
|| payload.actorUserId !== record.actorUserId
|
|
248
|
+
|| payload.runId !== record.runId
|
|
249
|
+
|| payload.preflightSha256 !== record.preflightSha256
|
|
250
|
+
|| payload.logicalEnvelopeSha256 !== record.logicalEnvelopeSha256
|
|
251
|
+
|| payload.accountId !== preflight.target.accountId
|
|
252
|
+
|| payload.readPrincipalArn !== preflight.target.readPrincipalArn
|
|
253
|
+
|| !canonicalizeExecutorPhaseAJson(payload.mutationRoles).equals(canonicalizeExecutorPhaseAJson(preflight.target.mutationRoles))
|
|
254
|
+
|| payload.roleMapSha256 !== executorPhaseARoleMapSha256(preflight.target.mutationRoles)
|
|
255
|
+
|| payload.oidcIssuer !== preflight.target.oidcIssuer
|
|
256
|
+
|| payload.oidcAudience !== preflight.target.oidcAudience
|
|
257
|
+
|| payload.brokerPublicKeySpkiBase64 !== preflight.target.brokerPublicKeySpkiBase64
|
|
258
|
+
|| payload.brokerKeyFingerprint !== preflight.target.brokerKeyFingerprint
|
|
259
|
+
|| payload.region !== preflight.target.region
|
|
260
|
+
|| payload.availabilityZone !== preflight.target.availabilityZone
|
|
261
|
+
|| !canonicalizeExecutorPhaseAJson(payload.budgets).equals(canonicalizeExecutorPhaseAJson(preflight.budgets))
|
|
262
|
+
|| payload.sourceCommit !== preflight.source.commit
|
|
263
|
+
|| payload.approvedAt !== record.approvedAt
|
|
264
|
+
|| payload.expiresAt !== record.expiresAt
|
|
265
|
+
|| payload.mutationDeadline !== new Date(approvedAt + EXECUTOR_PHASE_A_MUTATION_TTL_MS).toISOString()
|
|
266
|
+
|| payload.cleanupDeadline !== new Date(approvedAt + EXECUTOR_PHASE_A_ORDINARY_CLEANUP_TTL_MS).toISOString()
|
|
267
|
+
|| receipt.envelope.keyId !== record.keyId
|
|
268
|
+
|| receipt.envelope.payloadSha256 !== sha256ExecutorPhaseABytes(payloadBytes)) {
|
|
269
|
+
durableRecordInvalid('receipt bindings do not match the durable record and preflight.');
|
|
270
|
+
}
|
|
271
|
+
if (!Number.isFinite(approvedAt) || approvedAt > now.getTime()
|
|
272
|
+
|| !Number.isFinite(expiresAt) || expiresAt <= now.getTime()
|
|
273
|
+
|| expiresAt > Date.parse(preflight.expiresAt)) {
|
|
274
|
+
durableRecordInvalid('receipt validity interval cannot be used.');
|
|
275
|
+
}
|
|
276
|
+
const trustKey = resolveExecutorPhaseAApprovalTrustKey(policy, receipt.envelope.keyId, payload.approvedAt, 'verify');
|
|
277
|
+
if (!trustKey)
|
|
278
|
+
durableRecordInvalid('receipt signing key is not trusted for its approval time.');
|
|
279
|
+
const signature = Buffer.from(receipt.envelope.signature, 'base64url');
|
|
280
|
+
if (signature.length !== 64 || signature.toString('base64url') !== receipt.envelope.signature) {
|
|
281
|
+
durableRecordInvalid('receipt signature encoding is not canonical.');
|
|
282
|
+
}
|
|
283
|
+
const publicKey = createPublicKey({ key: Buffer.from(trustKey.publicKeySpkiBase64, 'base64'), format: 'der', type: 'spki' });
|
|
284
|
+
if (!verify(null, payloadBytes, publicKey, signature))
|
|
285
|
+
durableRecordInvalid('receipt signature is invalid.');
|
|
286
|
+
}
|
|
287
|
+
function assertUsableCleanupWinner(record, originalCapabilityId, residualInventorySha256, preflight, policy, now) {
|
|
288
|
+
let receipt;
|
|
289
|
+
try {
|
|
290
|
+
const parsed = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(record.canonicalReceiptBytes));
|
|
291
|
+
assertIJson(parsed);
|
|
292
|
+
assertExecutorPhaseASchema('approvalReceipt', parsed);
|
|
293
|
+
if (!canonicalizeExecutorPhaseAJson(parsed).equals(record.canonicalReceiptBytes)) {
|
|
294
|
+
durableRecordInvalid('cleanup receipt bytes are not canonical JSON.');
|
|
295
|
+
}
|
|
296
|
+
receipt = parsed;
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
if (error instanceof ExecutorPhaseAApprovalError)
|
|
300
|
+
throw error;
|
|
301
|
+
durableRecordInvalid('cleanup receipt bytes do not satisfy the approval receipt contract.');
|
|
302
|
+
}
|
|
303
|
+
const payload = receipt.payload;
|
|
304
|
+
const payloadBytes = canonicalizeExecutorPhaseAJson(payload);
|
|
305
|
+
const approvedAt = Date.parse(payload.approvedAt);
|
|
306
|
+
const expiresAt = Date.parse(payload.expiresAt);
|
|
307
|
+
const cleanupDeadline = Date.parse(payload.cleanupDeadline);
|
|
308
|
+
if (record.receiptKind !== 'cleanup-only'
|
|
309
|
+
|| record.originalCapabilityId !== originalCapabilityId
|
|
310
|
+
|| record.residualInventorySha256 !== residualInventorySha256
|
|
311
|
+
|| payload.receiptId !== record.receiptId
|
|
312
|
+
|| payload.receiptKind !== 'cleanup-only'
|
|
313
|
+
|| payload.originalCapabilityId !== originalCapabilityId
|
|
314
|
+
|| payload.residualInventorySha256 !== residualInventorySha256
|
|
315
|
+
|| payload.issuer !== policy.issuer || payload.audience !== policy.audience
|
|
316
|
+
|| payload.environment !== policy.environment
|
|
317
|
+
|| payload.workspaceId !== record.workspaceId || payload.taskId !== record.taskId
|
|
318
|
+
|| payload.taskReference !== record.taskReference || payload.actorUserId !== record.actorUserId
|
|
319
|
+
|| payload.runId !== record.runId || payload.preflightSha256 !== record.preflightSha256
|
|
320
|
+
|| payload.logicalEnvelopeSha256 !== record.logicalEnvelopeSha256
|
|
321
|
+
|| payload.accountId !== preflight.target.accountId
|
|
322
|
+
|| payload.readPrincipalArn !== preflight.target.readPrincipalArn
|
|
323
|
+
|| !canonicalizeExecutorPhaseAJson(payload.mutationRoles).equals(canonicalizeExecutorPhaseAJson(preflight.target.mutationRoles))
|
|
324
|
+
|| payload.roleMapSha256 !== executorPhaseARoleMapSha256(preflight.target.mutationRoles)
|
|
325
|
+
|| payload.oidcIssuer !== preflight.target.oidcIssuer || payload.oidcAudience !== preflight.target.oidcAudience
|
|
326
|
+
|| payload.brokerPublicKeySpkiBase64 !== preflight.target.brokerPublicKeySpkiBase64
|
|
327
|
+
|| payload.brokerKeyFingerprint !== preflight.target.brokerKeyFingerprint
|
|
328
|
+
|| payload.region !== preflight.target.region || payload.availabilityZone !== preflight.target.availabilityZone
|
|
329
|
+
|| !canonicalizeExecutorPhaseAJson(payload.budgets).equals(canonicalizeExecutorPhaseAJson(preflight.budgets))
|
|
330
|
+
|| payload.sourceCommit !== preflight.source.commit
|
|
331
|
+
|| payload.approvedAt !== record.approvedAt || payload.expiresAt !== record.expiresAt
|
|
332
|
+
|| payload.mutationDeadline !== payload.approvedAt
|
|
333
|
+
|| !Number.isFinite(approvedAt) || approvedAt > now.getTime()
|
|
334
|
+
|| !Number.isFinite(expiresAt) || expiresAt <= now.getTime()
|
|
335
|
+
|| expiresAt > approvedAt + EXECUTOR_PHASE_A_RECEIPT_TTL_MS
|
|
336
|
+
|| !Number.isFinite(cleanupDeadline) || cleanupDeadline <= approvedAt
|
|
337
|
+
|| cleanupDeadline > approvedAt + 2 * 60 * 60 * 1000
|
|
338
|
+
|| receipt.envelope.keyId !== record.keyId
|
|
339
|
+
|| receipt.envelope.payloadSha256 !== sha256ExecutorPhaseABytes(payloadBytes)
|
|
340
|
+
|| record.receiptSha256 !== sha256ExecutorPhaseABytes(record.canonicalReceiptBytes)) {
|
|
341
|
+
durableRecordInvalid('cleanup receipt bindings do not match the hard-stopped run.');
|
|
342
|
+
}
|
|
343
|
+
const trustKey = resolveExecutorPhaseAApprovalTrustKey(policy, receipt.envelope.keyId, payload.approvedAt, 'verify');
|
|
344
|
+
if (!trustKey)
|
|
345
|
+
durableRecordInvalid('cleanup receipt signing key is not trusted for its approval time.');
|
|
346
|
+
const signature = Buffer.from(receipt.envelope.signature, 'base64url');
|
|
347
|
+
if (signature.length !== 64 || signature.toString('base64url') !== receipt.envelope.signature) {
|
|
348
|
+
durableRecordInvalid('cleanup receipt signature encoding is not canonical.');
|
|
349
|
+
}
|
|
350
|
+
const publicKey = createPublicKey({ key: Buffer.from(trustKey.publicKeySpkiBase64, 'base64'), format: 'der', type: 'spki' });
|
|
351
|
+
if (!verify(null, payloadBytes, publicKey, signature))
|
|
352
|
+
durableRecordInvalid('cleanup receipt signature is invalid.');
|
|
353
|
+
}
|
|
354
|
+
export function resolveExecutorPhaseAApprovalSigningConfig(env = process.env, policy = trustPolicyJson) {
|
|
355
|
+
assertExecutorPhaseAApprovalTrustPolicy(policy);
|
|
356
|
+
const enabledRaw = String(env.TASKFORCE_PHASE_A_APPROVAL_ISSUER_ENABLED || 'false').trim().toLowerCase();
|
|
357
|
+
if (!['true', 'false', '1', '0', 'yes', 'no', 'on', 'off'].includes(enabledRaw)) {
|
|
358
|
+
throw new Error('TASKFORCE_PHASE_A_APPROVAL_ISSUER_ENABLED must be a boolean.');
|
|
359
|
+
}
|
|
360
|
+
const enabled = ['true', '1', 'yes', 'on'].includes(enabledRaw);
|
|
361
|
+
const keyId = String(env.TASKFORCE_PHASE_A_APPROVAL_SIGNING_KEY_ID || '').trim();
|
|
362
|
+
const encoded = String(env.TASKFORCE_PHASE_A_APPROVAL_SIGNING_PRIVATE_KEY_PKCS8_BASE64 || '').trim();
|
|
363
|
+
if (!keyId && !encoded) {
|
|
364
|
+
if (enabled)
|
|
365
|
+
throw new Error('Phase A approval signing key configuration is required when the issuer is enabled.');
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
if (!keyId || !encoded)
|
|
369
|
+
throw new Error('TASKFORCE_PHASE_A_APPROVAL_SIGNING_KEY_ID and TASKFORCE_PHASE_A_APPROVAL_SIGNING_PRIVATE_KEY_PKCS8_BASE64 must be configured together.');
|
|
370
|
+
let privateKey;
|
|
371
|
+
try {
|
|
372
|
+
const der = Buffer.from(encoded, 'base64');
|
|
373
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(encoded) || der.toString('base64') !== encoded)
|
|
374
|
+
throw new Error('invalid');
|
|
375
|
+
privateKey = createPrivateKey({ key: der, format: 'der', type: 'pkcs8' });
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
throw new Error('TASKFORCE_PHASE_A_APPROVAL_SIGNING_PRIVATE_KEY_PKCS8_BASE64 must contain a base64-encoded PKCS#8 private key.');
|
|
379
|
+
}
|
|
380
|
+
if (privateKey.asymmetricKeyType !== 'ed25519')
|
|
381
|
+
throw new Error('The Phase A approval private key must be Ed25519.');
|
|
382
|
+
const now = Date.now();
|
|
383
|
+
const trusted = policy.keys.find((key) => key.keyId === keyId && key.status === 'active'
|
|
384
|
+
&& Date.parse(key.validFrom) <= now && now < Date.parse(key.validUntil));
|
|
385
|
+
if (!trusted)
|
|
386
|
+
throw new Error(`The Phase A approval key ID ${keyId} is not an active in-validity key in the committed trust policy.`);
|
|
387
|
+
const derivedSpki = createPublicKey(privateKey).export({ format: 'der', type: 'spki' }).toString('base64');
|
|
388
|
+
if (derivedSpki !== trusted.publicKeySpkiBase64)
|
|
389
|
+
throw new Error('The Phase A approval private key does not match its committed trust-policy public key.');
|
|
390
|
+
return enabled ? { keyId, privateKey } : null;
|
|
391
|
+
}
|
|
392
|
+
export class ExecutorPhaseAApprovalService {
|
|
393
|
+
options;
|
|
394
|
+
constructor(options) {
|
|
395
|
+
this.options = options;
|
|
396
|
+
}
|
|
397
|
+
issue(canonicalPreflightBytes, actorUserId) {
|
|
398
|
+
const policy = this.options.policy || trustPolicyJson;
|
|
399
|
+
const now = this.options.now?.() || new Date();
|
|
400
|
+
const preflight = parseCanonicalExecutorPhaseAPreflight(canonicalPreflightBytes, policy, now);
|
|
401
|
+
if (!/^user-[A-Za-z0-9][A-Za-z0-9_-]{7,127}$/.test(actorUserId)) {
|
|
402
|
+
throw new ExecutorPhaseAApprovalError('The approval actor must be an authenticated Taskforce human user.', 403);
|
|
403
|
+
}
|
|
404
|
+
const preflightSha256 = sha256ExecutorPhaseABytes(canonicalPreflightBytes);
|
|
405
|
+
const existing = this.options.store.findRun(preflight.approvalContext.workspaceId, preflight.approvalContext.taskId, preflight.runId);
|
|
406
|
+
if (existing) {
|
|
407
|
+
const retry = this.options.store.persist({
|
|
408
|
+
...existing,
|
|
409
|
+
actorUserId,
|
|
410
|
+
preflightSha256,
|
|
411
|
+
logicalEnvelopeSha256: preflight.logicalEnvelopeSha256,
|
|
412
|
+
canonicalPreflightBytes,
|
|
413
|
+
});
|
|
414
|
+
assertUsableDurableWinner(retry.record, preflight, canonicalPreflightBytes, policy, now);
|
|
415
|
+
return { bytes: retry.record.canonicalReceiptBytes, receiptId: retry.record.receiptId, digest: retry.record.receiptSha256, created: false };
|
|
416
|
+
}
|
|
417
|
+
const approvedAt = now.toISOString();
|
|
418
|
+
const receiptExpiresAt = new Date(Math.min(Date.parse(preflight.expiresAt), now.getTime() + EXECUTOR_PHASE_A_RECEIPT_TTL_MS)).toISOString();
|
|
419
|
+
const signingTrustKey = resolveExecutorPhaseAApprovalTrustKey(policy, this.options.signing.keyId, approvedAt, 'sign');
|
|
420
|
+
const signingPublicKey = createPublicKey(this.options.signing.privateKey).export({ format: 'der', type: 'spki' }).toString('base64');
|
|
421
|
+
if (!signingTrustKey || signingPublicKey !== signingTrustKey.publicKeySpkiBase64) {
|
|
422
|
+
throw new ExecutorPhaseAApprovalError('The Phase A approval signing key is no longer active and trusted.', 503, 'executor_phase_a_approval_signer_unavailable');
|
|
423
|
+
}
|
|
424
|
+
const receiptId = this.options.randomId?.() || `approval-receipt-${randomUUID()}`;
|
|
425
|
+
if (!/^(?:approval-receipt|taskforce-approval)-[A-Za-z0-9][A-Za-z0-9_-]{7,127}$/.test(receiptId)) {
|
|
426
|
+
throw new Error('The Phase A approval receipt ID generator returned an invalid identifier.');
|
|
427
|
+
}
|
|
428
|
+
const payload = {
|
|
429
|
+
schema: EXECUTOR_PHASE_A_RECEIPT_PAYLOAD_SCHEMA,
|
|
430
|
+
receiptId,
|
|
431
|
+
receiptKind: 'main',
|
|
432
|
+
originalCapabilityId: null,
|
|
433
|
+
residualInventorySha256: null,
|
|
434
|
+
issuer: policy.issuer,
|
|
435
|
+
audience: policy.audience,
|
|
436
|
+
environment: policy.environment,
|
|
437
|
+
workspaceId: preflight.approvalContext.workspaceId,
|
|
438
|
+
taskId: preflight.approvalContext.taskId,
|
|
439
|
+
taskReference: preflight.approvalContext.taskReference,
|
|
440
|
+
actorUserId,
|
|
441
|
+
runId: preflight.runId,
|
|
442
|
+
preflightSha256,
|
|
443
|
+
logicalEnvelopeSha256: preflight.logicalEnvelopeSha256,
|
|
444
|
+
accountId: preflight.target.accountId,
|
|
445
|
+
readPrincipalArn: preflight.target.readPrincipalArn,
|
|
446
|
+
mutationRoles: structuredClone(preflight.target.mutationRoles),
|
|
447
|
+
roleMapSha256: executorPhaseARoleMapSha256(preflight.target.mutationRoles),
|
|
448
|
+
oidcIssuer: preflight.target.oidcIssuer,
|
|
449
|
+
oidcAudience: preflight.target.oidcAudience,
|
|
450
|
+
brokerPublicKeySpkiBase64: preflight.target.brokerPublicKeySpkiBase64,
|
|
451
|
+
brokerKeyFingerprint: preflight.target.brokerKeyFingerprint,
|
|
452
|
+
region: preflight.target.region,
|
|
453
|
+
availabilityZone: preflight.target.availabilityZone,
|
|
454
|
+
budgets: structuredClone(preflight.budgets),
|
|
455
|
+
sourceCommit: preflight.source.commit,
|
|
456
|
+
approvedAt,
|
|
457
|
+
expiresAt: receiptExpiresAt,
|
|
458
|
+
mutationDeadline: new Date(now.getTime() + EXECUTOR_PHASE_A_MUTATION_TTL_MS).toISOString(),
|
|
459
|
+
cleanupDeadline: new Date(now.getTime() + EXECUTOR_PHASE_A_ORDINARY_CLEANUP_TTL_MS).toISOString(),
|
|
460
|
+
};
|
|
461
|
+
const payloadBytes = canonicalizeExecutorPhaseAJson(payload);
|
|
462
|
+
const receipt = {
|
|
463
|
+
payload,
|
|
464
|
+
envelope: {
|
|
465
|
+
algorithm: 'Ed25519',
|
|
466
|
+
keyId: this.options.signing.keyId,
|
|
467
|
+
payloadSha256: sha256ExecutorPhaseABytes(payloadBytes),
|
|
468
|
+
signature: sign(null, payloadBytes, this.options.signing.privateKey).toString('base64url'),
|
|
469
|
+
},
|
|
470
|
+
};
|
|
471
|
+
const receiptBytes = canonicalizeExecutorPhaseAJson(receipt);
|
|
472
|
+
const receiptSha256 = sha256ExecutorPhaseABytes(receiptBytes);
|
|
473
|
+
const persisted = this.options.store.persist({
|
|
474
|
+
receiptId: payload.receiptId,
|
|
475
|
+
workspaceId: payload.workspaceId,
|
|
476
|
+
taskId: payload.taskId,
|
|
477
|
+
taskReference: payload.taskReference,
|
|
478
|
+
runId: payload.runId,
|
|
479
|
+
receiptKind: payload.receiptKind,
|
|
480
|
+
originalCapabilityId: payload.originalCapabilityId,
|
|
481
|
+
residualInventorySha256: payload.residualInventorySha256,
|
|
482
|
+
actorUserId,
|
|
483
|
+
preflightSha256,
|
|
484
|
+
logicalEnvelopeSha256: payload.logicalEnvelopeSha256,
|
|
485
|
+
canonicalPreflightBytes,
|
|
486
|
+
canonicalReceiptBytes: receiptBytes,
|
|
487
|
+
receiptSha256,
|
|
488
|
+
keyId: this.options.signing.keyId,
|
|
489
|
+
approvedAt,
|
|
490
|
+
expiresAt: receiptExpiresAt,
|
|
491
|
+
createdAt: approvedAt,
|
|
492
|
+
});
|
|
493
|
+
assertUsableDurableWinner(persisted.record, preflight, canonicalPreflightBytes, policy, now);
|
|
494
|
+
return {
|
|
495
|
+
bytes: persisted.record.canonicalReceiptBytes,
|
|
496
|
+
receiptId: persisted.record.receiptId,
|
|
497
|
+
digest: persisted.record.receiptSha256,
|
|
498
|
+
created: persisted.created,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
issueCleanup(originalCapabilityId, residualInventorySha256, actorUserId) {
|
|
502
|
+
const policy = this.options.policy || trustPolicyJson;
|
|
503
|
+
const now = this.options.now?.() || new Date();
|
|
504
|
+
if (!/^user-[A-Za-z0-9][A-Za-z0-9_-]{7,127}$/.test(actorUserId)) {
|
|
505
|
+
throw new ExecutorPhaseAApprovalError('The cleanup approval actor must be an authenticated Taskforce human user.', 403);
|
|
506
|
+
}
|
|
507
|
+
if (!/^run-capability-[A-Za-z0-9][A-Za-z0-9_-]{7,127}$/.test(originalCapabilityId)
|
|
508
|
+
|| !/^[a-f0-9]{64}$/.test(residualInventorySha256)) {
|
|
509
|
+
throw new ExecutorPhaseAApprovalError('Cleanup approval requires an original capability and residual-inventory digest.');
|
|
510
|
+
}
|
|
511
|
+
const original = this.options.runAuthorizationStore?.findCapability(originalCapabilityId);
|
|
512
|
+
if (!original || original.capabilityKind !== 'main' || original.status !== 'awaiting-cleanup-approval') {
|
|
513
|
+
throw new ExecutorPhaseAApprovalError('Cleanup approval requires the exact hard-stopped main capability.', 409);
|
|
514
|
+
}
|
|
515
|
+
const mainRecord = this.options.store.findRun(original.workspaceId, original.taskId, original.runId, 'main');
|
|
516
|
+
if (!mainRecord || mainRecord.receiptId !== original.receiptId
|
|
517
|
+
|| mainRecord.receiptSha256 !== original.receiptSha256
|
|
518
|
+
|| mainRecord.preflightSha256 !== original.preflightSha256
|
|
519
|
+
|| mainRecord.logicalEnvelopeSha256 !== original.logicalEnvelopeSha256) {
|
|
520
|
+
throw new ExecutorPhaseAApprovalError('The original capability has no matching durable main approval.', 409);
|
|
521
|
+
}
|
|
522
|
+
const originalIssuedAt = new Date(original.issuedAt);
|
|
523
|
+
const preflight = parseCanonicalExecutorPhaseAPreflight(mainRecord.canonicalPreflightBytes, policy, originalIssuedAt);
|
|
524
|
+
assertUsableDurableWinner(mainRecord, preflight, mainRecord.canonicalPreflightBytes, policy, originalIssuedAt);
|
|
525
|
+
const approvedAt = now.toISOString();
|
|
526
|
+
const signingTrustKey = resolveExecutorPhaseAApprovalTrustKey(policy, this.options.signing.keyId, approvedAt, 'sign');
|
|
527
|
+
const signingPublicKey = createPublicKey(this.options.signing.privateKey).export({ format: 'der', type: 'spki' }).toString('base64');
|
|
528
|
+
if (!signingTrustKey || signingPublicKey !== signingTrustKey.publicKeySpkiBase64) {
|
|
529
|
+
throw new ExecutorPhaseAApprovalError('The Phase A approval signing key is no longer active and trusted.', 503, 'executor_phase_a_approval_signer_unavailable');
|
|
530
|
+
}
|
|
531
|
+
const receiptId = this.options.randomId?.() || `approval-receipt-${randomUUID()}`;
|
|
532
|
+
if (!/^(?:approval-receipt|taskforce-approval)-[A-Za-z0-9][A-Za-z0-9_-]{7,127}$/.test(receiptId)
|
|
533
|
+
|| receiptId === original.receiptId) {
|
|
534
|
+
throw new Error('The Phase A cleanup receipt ID generator returned an invalid or reused identifier.');
|
|
535
|
+
}
|
|
536
|
+
const payload = {
|
|
537
|
+
schema: EXECUTOR_PHASE_A_RECEIPT_PAYLOAD_SCHEMA,
|
|
538
|
+
receiptId,
|
|
539
|
+
receiptKind: 'cleanup-only',
|
|
540
|
+
originalCapabilityId,
|
|
541
|
+
residualInventorySha256,
|
|
542
|
+
issuer: policy.issuer,
|
|
543
|
+
audience: policy.audience,
|
|
544
|
+
environment: policy.environment,
|
|
545
|
+
workspaceId: preflight.approvalContext.workspaceId,
|
|
546
|
+
taskId: preflight.approvalContext.taskId,
|
|
547
|
+
taskReference: preflight.approvalContext.taskReference,
|
|
548
|
+
actorUserId,
|
|
549
|
+
runId: preflight.runId,
|
|
550
|
+
preflightSha256: mainRecord.preflightSha256,
|
|
551
|
+
logicalEnvelopeSha256: preflight.logicalEnvelopeSha256,
|
|
552
|
+
accountId: preflight.target.accountId,
|
|
553
|
+
readPrincipalArn: preflight.target.readPrincipalArn,
|
|
554
|
+
mutationRoles: structuredClone(preflight.target.mutationRoles),
|
|
555
|
+
roleMapSha256: executorPhaseARoleMapSha256(preflight.target.mutationRoles),
|
|
556
|
+
oidcIssuer: preflight.target.oidcIssuer,
|
|
557
|
+
oidcAudience: preflight.target.oidcAudience,
|
|
558
|
+
brokerPublicKeySpkiBase64: preflight.target.brokerPublicKeySpkiBase64,
|
|
559
|
+
brokerKeyFingerprint: preflight.target.brokerKeyFingerprint,
|
|
560
|
+
region: preflight.target.region,
|
|
561
|
+
availabilityZone: preflight.target.availabilityZone,
|
|
562
|
+
budgets: structuredClone(preflight.budgets),
|
|
563
|
+
sourceCommit: preflight.source.commit,
|
|
564
|
+
approvedAt,
|
|
565
|
+
expiresAt: new Date(now.getTime() + EXECUTOR_PHASE_A_RECEIPT_TTL_MS).toISOString(),
|
|
566
|
+
mutationDeadline: approvedAt,
|
|
567
|
+
cleanupDeadline: new Date(now.getTime() + 2 * 60 * 60 * 1000).toISOString(),
|
|
568
|
+
};
|
|
569
|
+
const payloadBytes = canonicalizeExecutorPhaseAJson(payload);
|
|
570
|
+
const receipt = {
|
|
571
|
+
payload,
|
|
572
|
+
envelope: {
|
|
573
|
+
algorithm: 'Ed25519',
|
|
574
|
+
keyId: this.options.signing.keyId,
|
|
575
|
+
payloadSha256: sha256ExecutorPhaseABytes(payloadBytes),
|
|
576
|
+
signature: sign(null, payloadBytes, this.options.signing.privateKey).toString('base64url'),
|
|
577
|
+
},
|
|
578
|
+
};
|
|
579
|
+
const receiptBytes = canonicalizeExecutorPhaseAJson(receipt);
|
|
580
|
+
const receiptSha256 = sha256ExecutorPhaseABytes(receiptBytes);
|
|
581
|
+
const persisted = this.options.store.persist({
|
|
582
|
+
receiptId,
|
|
583
|
+
workspaceId: original.workspaceId,
|
|
584
|
+
taskId: original.taskId,
|
|
585
|
+
taskReference: preflight.approvalContext.taskReference,
|
|
586
|
+
runId: original.runId,
|
|
587
|
+
receiptKind: 'cleanup-only',
|
|
588
|
+
originalCapabilityId,
|
|
589
|
+
residualInventorySha256,
|
|
590
|
+
actorUserId,
|
|
591
|
+
preflightSha256: mainRecord.preflightSha256,
|
|
592
|
+
logicalEnvelopeSha256: mainRecord.logicalEnvelopeSha256,
|
|
593
|
+
canonicalPreflightBytes: mainRecord.canonicalPreflightBytes,
|
|
594
|
+
canonicalReceiptBytes: receiptBytes,
|
|
595
|
+
receiptSha256,
|
|
596
|
+
keyId: this.options.signing.keyId,
|
|
597
|
+
approvedAt,
|
|
598
|
+
expiresAt: payload.expiresAt,
|
|
599
|
+
createdAt: approvedAt,
|
|
600
|
+
});
|
|
601
|
+
assertUsableCleanupWinner(persisted.record, originalCapabilityId, residualInventorySha256, preflight, policy, now);
|
|
602
|
+
return {
|
|
603
|
+
bytes: persisted.record.canonicalReceiptBytes,
|
|
604
|
+
receiptId: persisted.record.receiptId,
|
|
605
|
+
digest: persisted.record.receiptSha256,
|
|
606
|
+
created: persisted.created,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
}
|