@kbediako/codex-orchestrator 0.1.38 → 0.2.1
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/.agents/plugins/marketplace.json +20 -0
- package/README.md +46 -317
- package/bin/codex-orchestrator.js +161 -0
- package/codex.orchestrator.json +149 -13
- package/dist/bin/codex-orchestrator.js +797 -1154
- package/dist/orchestrator/src/cli/adapters/CommandBuilder.js +50 -0
- package/dist/orchestrator/src/cli/adapters/CommandPlanner.js +22 -4
- package/dist/orchestrator/src/cli/adapters/CommandReviewer.js +3 -3
- package/dist/orchestrator/src/cli/adapters/CommandTester.js +2 -2
- package/dist/orchestrator/src/cli/adapters/cloudFailureDiagnostics.js +295 -11
- package/dist/orchestrator/src/cli/coStatusAttachCliShell.js +402 -0
- package/dist/orchestrator/src/cli/coStatusCliShell.js +451 -0
- package/dist/orchestrator/src/cli/coStatusOperatorAutopilotCliShell.js +120 -0
- package/dist/orchestrator/src/cli/codexCliShell.js +119 -0
- package/dist/orchestrator/src/cli/codexDefaultsSetup.js +265 -36
- package/dist/orchestrator/src/cli/config/delegationConfig.js +317 -5
- package/dist/orchestrator/src/cli/config/repoConfigPolicy.js +2 -3
- package/dist/orchestrator/src/cli/config/userConfig.js +28 -13
- package/dist/orchestrator/src/cli/control/authenticatedControlRouteGate.js +69 -0
- package/dist/orchestrator/src/cli/control/authenticatedRouteComposition.js +267 -0
- package/dist/orchestrator/src/cli/control/authenticatedRouteController.js +5 -0
- package/dist/orchestrator/src/cli/control/authenticatedRouteDispatcher.js +41 -0
- package/dist/orchestrator/src/cli/control/compatibilityIssuePresenter.js +1035 -0
- package/dist/orchestrator/src/cli/control/confirmationApproveController.js +62 -0
- package/dist/orchestrator/src/cli/control/confirmationCreateController.js +69 -0
- package/dist/orchestrator/src/cli/control/confirmationIssueConsumeController.js +43 -0
- package/dist/orchestrator/src/cli/control/confirmationListController.js +22 -0
- package/dist/orchestrator/src/cli/control/confirmationValidateController.js +58 -0
- package/dist/orchestrator/src/cli/control/confirmations.js +25 -3
- package/dist/orchestrator/src/cli/control/controlActionCancelConfirmation.js +65 -0
- package/dist/orchestrator/src/cli/control/controlActionController.js +77 -0
- package/dist/orchestrator/src/cli/control/controlActionControllerSequencing.js +161 -0
- package/dist/orchestrator/src/cli/control/controlActionExecution.js +142 -0
- package/dist/orchestrator/src/cli/control/controlActionFinalization.js +43 -0
- package/dist/orchestrator/src/cli/control/controlActionOutcome.js +60 -0
- package/dist/orchestrator/src/cli/control/controlActionPreflight.js +476 -0
- package/dist/orchestrator/src/cli/control/controlAuthenticatedRouteHandoff.js +57 -0
- package/dist/orchestrator/src/cli/control/controlBootstrapAssembly.js +39 -0
- package/dist/orchestrator/src/cli/control/controlBootstrapMetadataPersistence.js +16 -0
- package/dist/orchestrator/src/cli/control/controlEventTransport.js +49 -0
- package/dist/orchestrator/src/cli/control/controlExpiryLifecycle.js +102 -0
- package/dist/orchestrator/src/cli/control/controlHostOwnership.js +480 -0
- package/dist/orchestrator/src/cli/control/controlHostSupervision.js +630 -0
- package/dist/orchestrator/src/cli/control/controlOversightFacade.js +8 -0
- package/dist/orchestrator/src/cli/control/controlOversightReadContract.js +1 -0
- package/dist/orchestrator/src/cli/control/controlOversightReadService.js +16 -0
- package/dist/orchestrator/src/cli/control/controlOversightUpdateContract.js +1 -0
- package/dist/orchestrator/src/cli/control/controlPersistenceFiles.js +6 -0
- package/dist/orchestrator/src/cli/control/controlQuestionChildResolution.js +18 -0
- package/dist/orchestrator/src/cli/control/controlRequestContext.js +42 -0
- package/dist/orchestrator/src/cli/control/controlRequestController.js +9 -0
- package/dist/orchestrator/src/cli/control/controlRequestPredispatch.js +17 -0
- package/dist/orchestrator/src/cli/control/controlRequestRouteDispatch.js +44 -0
- package/dist/orchestrator/src/cli/control/controlRuntime.js +1003 -0
- package/dist/orchestrator/src/cli/control/controlServer.js +23 -1456
- package/dist/orchestrator/src/cli/control/controlServerAuditAndErrorHelpers.js +115 -0
- package/dist/orchestrator/src/cli/control/controlServerAuthenticatedRouteBranch.js +29 -0
- package/dist/orchestrator/src/cli/control/controlServerBootstrapLifecycle.js +30 -0
- package/dist/orchestrator/src/cli/control/controlServerBootstrapStartSequence.js +21 -0
- package/dist/orchestrator/src/cli/control/controlServerOwnedRuntimeLifecycle.js +67 -0
- package/dist/orchestrator/src/cli/control/controlServerPublicLifecycle.js +756 -0
- package/dist/orchestrator/src/cli/control/controlServerPublicRouteHelpers.js +86 -0
- package/dist/orchestrator/src/cli/control/controlServerReadyInstanceLifecycle.js +25 -0
- package/dist/orchestrator/src/cli/control/controlServerReadyInstanceStartup.js +18 -0
- package/dist/orchestrator/src/cli/control/controlServerRequestBodyHelpers.js +37 -0
- package/dist/orchestrator/src/cli/control/controlServerRequestShell.js +40 -0
- package/dist/orchestrator/src/cli/control/controlServerRequestShellBinding.js +17 -0
- package/dist/orchestrator/src/cli/control/controlServerSeedLoading.js +27 -0
- package/dist/orchestrator/src/cli/control/controlServerSeededRuntimeAssembly.js +186 -0
- package/dist/orchestrator/src/cli/control/controlServerStartupInputPreparation.js +31 -0
- package/dist/orchestrator/src/cli/control/controlServerStartupSequence.js +49 -0
- package/dist/orchestrator/src/cli/control/controlState.js +233 -2
- package/dist/orchestrator/src/cli/control/controlStatusDashboard.js +1904 -0
- package/dist/orchestrator/src/cli/control/controlTelegramBridgeBootstrapLifecycle.js +22 -0
- package/dist/orchestrator/src/cli/control/controlTelegramBridgeLifecycle.js +67 -0
- package/dist/orchestrator/src/cli/control/controlTelegramBridgeOversightFacadeFactory.js +8 -0
- package/dist/orchestrator/src/cli/control/controlTelegramCommandController.js +49 -0
- package/dist/orchestrator/src/cli/control/controlTelegramDispatchRead.js +40 -0
- package/dist/orchestrator/src/cli/control/controlTelegramPollingController.js +89 -0
- package/dist/orchestrator/src/cli/control/controlTelegramProjectionNotificationController.js +29 -0
- package/dist/orchestrator/src/cli/control/controlTelegramPushState.js +63 -0
- package/dist/orchestrator/src/cli/control/controlTelegramQuestionRead.js +13 -0
- package/dist/orchestrator/src/cli/control/controlTelegramReadController.js +216 -0
- package/dist/orchestrator/src/cli/control/controlTelegramUpdateHandler.js +63 -0
- package/dist/orchestrator/src/cli/control/controlWatcher.js +73 -5
- package/dist/orchestrator/src/cli/control/delegationRegisterController.js +35 -0
- package/dist/orchestrator/src/cli/control/dynamicToolBridgePolicy.js +139 -0
- package/dist/orchestrator/src/cli/control/eventsSseController.js +12 -0
- package/dist/orchestrator/src/cli/control/linearBudgetState.js +1789 -0
- package/dist/orchestrator/src/cli/control/linearDispatchSource.js +1137 -0
- package/dist/orchestrator/src/cli/control/linearGraphqlClient.js +150 -0
- package/dist/orchestrator/src/cli/control/linearRateLimit.js +102 -0
- package/dist/orchestrator/src/cli/control/linearWebhookController.js +499 -0
- package/dist/orchestrator/src/cli/control/liveLinearAdvisoryRuntime.js +70 -0
- package/dist/orchestrator/src/cli/control/observabilityApiController.js +173 -0
- package/dist/orchestrator/src/cli/control/observabilityReadModel.js +500 -0
- package/dist/orchestrator/src/cli/control/observabilitySurface.js +284 -0
- package/dist/orchestrator/src/cli/control/observabilityUpdateNotifier.js +22 -0
- package/dist/orchestrator/src/cli/control/operatorDashboardPresenter.js +252 -0
- package/dist/orchestrator/src/cli/control/providerAgentCapacity.js +70 -0
- package/dist/orchestrator/src/cli/control/providerControlHostFreshnessGauge.js +1068 -0
- package/dist/orchestrator/src/cli/control/providerIntakeState.js +473 -0
- package/dist/orchestrator/src/cli/control/providerIssueHandoff.js +6811 -0
- package/dist/orchestrator/src/cli/control/providerIssueObservability.js +1348 -0
- package/dist/orchestrator/src/cli/control/providerIssueRetryQueue.js +84 -0
- package/dist/orchestrator/src/cli/control/providerLinearRuntimeProof.js +588 -0
- package/dist/orchestrator/src/cli/control/providerLinearScreenshotProof.js +473 -0
- package/dist/orchestrator/src/cli/control/providerLinearWorkerTruth.js +383 -0
- package/dist/orchestrator/src/cli/control/providerLinearWorkflowAudit.js +254 -0
- package/dist/orchestrator/src/cli/control/providerLinearWorkflowFacade.js +5573 -0
- package/dist/orchestrator/src/cli/control/providerLinearWorkflowStates.js +115 -0
- package/dist/orchestrator/src/cli/control/providerMergeCloseout.js +1868 -0
- package/dist/orchestrator/src/cli/control/providerOperatorAutopilot.js +1580 -0
- package/dist/orchestrator/src/cli/control/providerOperatorAutopilotLifecycle.js +154 -0
- package/dist/orchestrator/src/cli/control/providerOperatorAutopilotLocalRolloutExecution.js +1006 -0
- package/dist/orchestrator/src/cli/control/providerPollingHealth.js +435 -0
- package/dist/orchestrator/src/cli/control/providerTerminalCleanup.js +516 -0
- package/dist/orchestrator/src/cli/control/providerWorkerHosts.js +191 -0
- package/dist/orchestrator/src/cli/control/providerWorkflowConfigStore.js +515 -0
- package/dist/orchestrator/src/cli/control/questionChildResolutionAdapter.js +361 -0
- package/dist/orchestrator/src/cli/control/questionQueueController.js +181 -0
- package/dist/orchestrator/src/cli/control/questionReadRetryDeduplication.js +9 -0
- package/dist/orchestrator/src/cli/control/questionReadSequence.js +10 -0
- package/dist/orchestrator/src/cli/control/securityViolationController.js +27 -0
- package/dist/orchestrator/src/cli/control/selectedRunProjection.js +1885 -0
- package/dist/orchestrator/src/cli/control/telegramOversightApiClient.js +48 -0
- package/dist/orchestrator/src/cli/control/telegramOversightBridge.js +180 -0
- package/dist/orchestrator/src/cli/control/telegramOversightBridgeProjectionDeliveryQueue.js +25 -0
- package/dist/orchestrator/src/cli/control/telegramOversightBridgeRuntimeLifecycle.js +45 -0
- package/dist/orchestrator/src/cli/control/telegramOversightBridgeStateStore.js +77 -0
- package/dist/orchestrator/src/cli/control/telegramOversightControlActionApiClient.js +45 -0
- package/dist/orchestrator/src/cli/control/trackerDispatchPilot.js +439 -0
- package/dist/orchestrator/src/cli/control/uiDataController.js +34 -0
- package/dist/orchestrator/src/cli/control/uiSessionController.js +100 -0
- package/dist/orchestrator/src/cli/controlHostCliShell.js +860 -0
- package/dist/orchestrator/src/cli/controlHostFreshnessGaugeCliShell.js +129 -0
- package/dist/orchestrator/src/cli/controlHostSupervisionCliShell.js +2127 -0
- package/dist/orchestrator/src/cli/delegationCliShell.js +62 -0
- package/dist/orchestrator/src/cli/delegationServer.js +567 -678
- package/dist/orchestrator/src/cli/delegationServerCliShell.js +52 -0
- package/dist/orchestrator/src/cli/delegationServerQuestionFlowShell.js +228 -0
- package/dist/orchestrator/src/cli/delegationServerToolDispatchShell.js +411 -0
- package/dist/orchestrator/src/cli/delegationServerTransport.js +274 -0
- package/dist/orchestrator/src/cli/delegationSetup.js +51 -171
- package/dist/orchestrator/src/cli/devtoolsCliShell.js +34 -0
- package/dist/orchestrator/src/cli/doctor.js +678 -164
- package/dist/orchestrator/src/cli/doctorCliRequestShell.js +72 -0
- package/dist/orchestrator/src/cli/doctorCliShell.js +138 -0
- package/dist/orchestrator/src/cli/doctorUsage.js +119 -15
- package/dist/orchestrator/src/cli/exec/experience.js +16 -2
- package/dist/orchestrator/src/cli/exec/summary.js +3 -0
- package/dist/orchestrator/src/cli/execCliShell.js +51 -0
- package/dist/orchestrator/src/cli/flowCliRequestShell.js +44 -0
- package/dist/orchestrator/src/cli/flowCliShell.js +239 -0
- package/dist/orchestrator/src/cli/frontendTestCliRequestShell.js +80 -0
- package/dist/orchestrator/src/cli/frontendTestCliShell.js +41 -0
- package/dist/orchestrator/src/cli/init.js +95 -1
- package/dist/orchestrator/src/cli/initCliShell.js +50 -0
- package/dist/orchestrator/src/cli/linearCliShell.js +1200 -0
- package/dist/orchestrator/src/cli/mcpEnableCliShell.js +132 -0
- package/dist/orchestrator/src/cli/metrics/metricsAggregator.js +3 -2
- package/dist/orchestrator/src/cli/metrics/metricsRecorder.js +56 -0
- package/dist/orchestrator/src/cli/orchestrator.js +66 -1376
- package/dist/orchestrator/src/cli/planCliShell.js +19 -0
- package/dist/orchestrator/src/cli/prCliShell.js +41 -0
- package/dist/orchestrator/src/cli/providerLinearChildLanePhaseContract.js +204 -0
- package/dist/orchestrator/src/cli/providerLinearChildLaneRunner.js +1835 -0
- package/dist/orchestrator/src/cli/providerLinearChildLaneShell.js +2420 -0
- package/dist/orchestrator/src/cli/providerLinearChildStreamShell.js +385 -0
- package/dist/orchestrator/src/cli/providerLinearWorkerRunner.js +6834 -0
- package/dist/orchestrator/src/cli/resumeCliShell.js +14 -0
- package/dist/orchestrator/src/cli/reviewCliLaunchShell.js +72 -0
- package/dist/orchestrator/src/cli/rlm/alignment.js +3 -3
- package/dist/orchestrator/src/cli/rlm/context.js +94 -7
- package/dist/orchestrator/src/cli/rlm/rlmCodexRuntimeShell.js +546 -0
- package/dist/orchestrator/src/cli/rlm/symbolic.js +4 -2
- package/dist/orchestrator/src/cli/rlmCliRequestShell.js +42 -0
- package/dist/orchestrator/src/cli/rlmCompletionCliShell.js +46 -0
- package/dist/orchestrator/src/cli/rlmLaunchCliShell.js +51 -0
- package/dist/orchestrator/src/cli/rlmRunner.js +83 -523
- package/dist/orchestrator/src/cli/run/blockMemory.js +500 -0
- package/dist/orchestrator/src/cli/run/manifest.js +410 -73
- package/dist/orchestrator/src/cli/run/manifestPersister.js +45 -14
- package/dist/orchestrator/src/cli/run/runMemoryController.js +216 -0
- package/dist/orchestrator/src/cli/run/source0.js +690 -0
- package/dist/orchestrator/src/cli/run/workspacePath.js +101 -0
- package/dist/orchestrator/src/cli/runtime/mode.js +2 -1
- package/dist/orchestrator/src/cli/runtime/provider.js +39 -2
- package/dist/orchestrator/src/cli/selfCheckCliShell.js +12 -0
- package/dist/orchestrator/src/cli/services/commandRunner.js +698 -18
- package/dist/orchestrator/src/cli/services/execRuntime.js +66 -1
- package/dist/orchestrator/src/cli/services/orchestratorAutoScoutEvidenceRecorder.js +71 -0
- package/dist/orchestrator/src/cli/services/orchestratorCloudBranchResolution.js +8 -0
- package/dist/orchestrator/src/cli/services/orchestratorCloudEnvironmentResolution.js +22 -0
- package/dist/orchestrator/src/cli/services/orchestratorCloudExecutionLifecycleShell.js +39 -0
- package/dist/orchestrator/src/cli/services/orchestratorCloudPromptBuilder.js +37 -0
- package/dist/orchestrator/src/cli/services/orchestratorCloudRouteFallbackContract.js +45 -0
- package/dist/orchestrator/src/cli/services/orchestratorCloudRouteShell.js +36 -0
- package/dist/orchestrator/src/cli/services/orchestratorCloudTargetExecutor.js +277 -0
- package/dist/orchestrator/src/cli/services/orchestratorControlPlaneLifecycle.js +98 -0
- package/dist/orchestrator/src/cli/services/orchestratorControlPlaneLifecycleShell.js +54 -0
- package/dist/orchestrator/src/cli/services/orchestratorExecutionLifecycle.js +112 -0
- package/dist/orchestrator/src/cli/services/orchestratorExecutionModePolicy.js +27 -0
- package/dist/orchestrator/src/cli/services/orchestratorExecutionRouteAdapterShell.js +59 -0
- package/dist/orchestrator/src/cli/services/orchestratorExecutionRouteDecisionShell.js +57 -0
- package/dist/orchestrator/src/cli/services/orchestratorExecutionRouteState.js +21 -0
- package/dist/orchestrator/src/cli/services/orchestratorExecutionRouter.js +2 -0
- package/dist/orchestrator/src/cli/services/orchestratorLocalPipelineExecutor.js +149 -0
- package/dist/orchestrator/src/cli/services/orchestratorLocalRouteShell.js +63 -0
- package/dist/orchestrator/src/cli/services/orchestratorPlanShell.js +54 -0
- package/dist/orchestrator/src/cli/services/orchestratorPlanTargetTracker.js +16 -0
- package/dist/orchestrator/src/cli/services/orchestratorResumePreparationShell.js +84 -0
- package/dist/orchestrator/src/cli/services/orchestratorResumeTokenValidation.js +15 -0
- package/dist/orchestrator/src/cli/services/orchestratorRunLifecycleCompletion.js +31 -0
- package/dist/orchestrator/src/cli/services/orchestratorRunLifecycleExecutionRegistration.js +37 -0
- package/dist/orchestrator/src/cli/services/orchestratorRunLifecycleOrchestrationShell.js +83 -0
- package/dist/orchestrator/src/cli/services/orchestratorRunLifecycleTaskManagerShell.js +37 -0
- package/dist/orchestrator/src/cli/services/orchestratorRuntimeManifestMutation.js +20 -0
- package/dist/orchestrator/src/cli/services/orchestratorStartPreparationShell.js +56 -0
- package/dist/orchestrator/src/cli/services/orchestratorStatusShell.js +70 -0
- package/dist/orchestrator/src/cli/services/pipelineResolver.js +7 -3
- package/dist/orchestrator/src/cli/services/plannerMemory.js +119 -0
- package/dist/orchestrator/src/cli/services/runPreparation.js +7 -3
- package/dist/orchestrator/src/cli/services/runSummaryWriter.js +9 -0
- package/dist/orchestrator/src/cli/setupBootstrapShell.js +114 -0
- package/dist/orchestrator/src/cli/setupCliShell.js +51 -0
- package/dist/orchestrator/src/cli/skillsCliShell.js +56 -0
- package/dist/orchestrator/src/cli/startCliRequestShell.js +53 -0
- package/dist/orchestrator/src/cli/startCliShell.js +68 -0
- package/dist/orchestrator/src/cli/statusCliShell.js +22 -0
- package/dist/orchestrator/src/cli/utils/authProvenanceFingerprint.js +27 -0
- package/dist/orchestrator/src/cli/utils/cloudPreflight.js +285 -7
- package/dist/orchestrator/src/cli/utils/codexFeatures.js +60 -0
- package/dist/orchestrator/src/cli/utils/delegationConfigParser.js +250 -0
- package/dist/orchestrator/src/cli/utils/delegationMcpHealth.js +1382 -0
- package/dist/orchestrator/src/cli/utils/devtools.js +2 -54
- package/dist/orchestrator/src/cli/utils/mcpServerEntry.js +53 -0
- package/dist/orchestrator/src/cli/utils/packageProgramResolver.js +151 -0
- package/dist/orchestrator/src/cli/utils/providerOverrideEnv.js +71 -0
- package/dist/orchestrator/src/cli/utils/trailingJsonObject.js +59 -0
- package/dist/orchestrator/src/learning/crystalizer.js +2 -2
- package/dist/orchestrator/src/manager.js +74 -4
- package/dist/orchestrator/src/persistence/ExperienceStore.js +233 -49
- package/dist/orchestrator/src/persistence/TaskStateStore.js +6 -6
- package/dist/orchestrator/src/persistence/lockFile.js +70 -4
- package/dist/orchestrator/src/persistence/sanitizeIdentifier.js +39 -0
- package/dist/orchestrator/src/sync/createCloudSyncWorker.js +3 -2
- package/dist/orchestrator/src/utils/atomicWrite.js +17 -2
- package/dist/packages/orchestrator/src/exec/unified-exec.js +99 -6
- package/dist/packages/orchestrator/src/instructions/promptPacks.js +150 -19
- package/dist/packages/sdk-node/src/orchestrator.js +137 -13
- package/dist/packages/shared/config/designConfig.js +8 -1
- package/dist/packages/shared/streams/stdio.js +1 -1
- package/dist/scripts/design/pipeline/permit.js +15 -0
- package/dist/scripts/lib/docs-catalog.js +399 -0
- package/dist/scripts/lib/docs-helpers.js +87 -5
- package/dist/scripts/lib/pr-watch-merge.js +1088 -80
- package/dist/scripts/lib/provider-run-contract.js +26 -0
- package/dist/scripts/lib/review-command-intent-classification.js +532 -0
- package/dist/scripts/lib/review-command-probe-classification.js +385 -0
- package/dist/scripts/lib/review-execution-boundary-preflight.js +279 -0
- package/dist/scripts/lib/review-execution-runtime.js +753 -0
- package/dist/scripts/lib/review-execution-state.js +1144 -0
- package/dist/scripts/lib/review-execution-telemetry.js +215 -0
- package/dist/scripts/lib/review-inspection-target-parsing.js +78 -0
- package/dist/scripts/lib/review-launch-attempt.js +601 -0
- package/dist/scripts/lib/review-meta-surface-boundary-analysis.js +300 -0
- package/dist/scripts/lib/review-meta-surface-normalization.js +746 -0
- package/dist/scripts/lib/review-non-interactive-handoff.js +61 -0
- package/dist/scripts/lib/review-prompt-context.js +376 -0
- package/dist/scripts/lib/review-scope-advisory.js +286 -0
- package/dist/scripts/lib/review-scope-paths.js +123 -0
- package/dist/scripts/lib/review-shell-command-parser.js +389 -0
- package/dist/scripts/lib/review-shell-env-interpreter.js +340 -0
- package/dist/scripts/lib/run-manifests.js +192 -36
- package/dist/scripts/lib/spark-policy-classifier.js +593 -0
- package/dist/scripts/run-review.js +507 -1777
- package/docs/README.md +43 -20
- package/docs/book/README.md +19 -0
- package/docs/book/codex-cli-0124-adoption.md +68 -0
- package/docs/book/local-hook-impact.md +73 -0
- package/docs/book/operations.md +60 -0
- package/docs/book/public-posture.md +34 -0
- package/docs/book/setup.md +91 -0
- package/docs/book/skills.md +11 -0
- package/docs/guides/codex-version-policy.md +104 -0
- package/docs/public/downstream-setup.md +113 -0
- package/docs/public/provider-onboarding.md +173 -0
- package/package.json +23 -10
- package/plugins/codex-orchestrator/.codex-plugin/plugin.json +30 -0
- package/plugins/codex-orchestrator/.mcp.json +13 -0
- package/plugins/codex-orchestrator/launcher.mjs +361 -0
- package/schemas/manifest.json +411 -0
- package/skills/README.md +26 -0
- package/skills/collab-subagents-first/SKILL.md +1 -1
- package/skills/delegation-usage/DELEGATION_GUIDE.md +30 -12
- package/skills/delegation-usage/SKILL.md +25 -14
- package/skills/land/SKILL.md +77 -0
- package/skills/linear/SKILL.md +255 -0
- package/skills/release/SKILL.md +47 -3
- package/skills/standalone-review/SKILL.md +6 -1
- package/templates/README.md +4 -2
- package/templates/codex/.codex/agents/awaiter-high.toml +2 -2
- package/templates/codex/.codex/agents/worker-complex.toml +1 -1
- package/templates/codex/.codex/config.toml +3 -4
- package/templates/codex/.codex/providers/README.md +13 -0
- package/templates/codex/.codex/providers/control.example.json +18 -0
- package/templates/codex/.codex/providers/provider.env.example +15 -0
- package/templates/codex/AGENTS.md +15 -8
- package/templates/codex/mcp-client.json +5 -1
- package/docs/assets/setup.gif +0 -0
|
@@ -0,0 +1,2420 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
3
|
+
import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { PROVIDER_CONTROL_HOST_RUN_ID_ENV, PROVIDER_CONTROL_HOST_TASK_ID_ENV, PROVIDER_LAUNCH_SOURCE_ENV, PROVIDER_LAUNCH_TOKEN_ENV } from '../../../scripts/lib/provider-run-contract.js';
|
|
7
|
+
import { PROVIDER_LINEAR_AUDIT_ENV_VAR } from './control/providerLinearWorkflowAudit.js';
|
|
8
|
+
import { resolveLiveLinearTrackedIssueById } from './control/linearDispatchSource.js';
|
|
9
|
+
import { sanitizeRunId } from '../persistence/sanitizeRunId.js';
|
|
10
|
+
import { logger } from '../logger.js';
|
|
11
|
+
import { PROVIDER_LINEAR_WORKER_PROOF_FILENAME, defaultExecRunner, loadProviderLinearWorkerContext, refreshProviderLinearWorkerProofSnapshot, transactProviderLinearWorkerChildLanes } from './providerLinearWorkerRunner.js';
|
|
12
|
+
import { isChildLaneParentDirtySuppressionCode, findDeterministicProviderMutationSuppression, resolveProviderLinearWorkerAttemptStartedAt } from './control/providerLinearWorkerTruth.js';
|
|
13
|
+
import { resolveProviderLinearAuditPath, summarizeProviderLinearAuditPath } from './control/providerLinearWorkflowAudit.js';
|
|
14
|
+
import { PROVIDER_LINEAR_CHILD_LANE_FILES_ENV, PROVIDER_LINEAR_CHILD_LANE_INSTRUCTIONS_ENV, PROVIDER_LINEAR_CHILD_LANE_PARENT_SNAPSHOT_BASE_SHA_ENV, PROVIDER_LINEAR_CHILD_LANE_PARENT_SNAPSHOT_CAPTURED_AT_ENV, PROVIDER_LINEAR_CHILD_LANE_PARENT_SNAPSHOT_ISSUE_STATE_ENV, PROVIDER_LINEAR_CHILD_LANE_PARENT_SNAPSHOT_ISSUE_STATE_TYPE_ENV, PROVIDER_LINEAR_CHILD_LANE_PARENT_SNAPSHOT_ISSUE_UPDATED_AT_ENV, PROVIDER_LINEAR_CHILD_LANE_PARENT_WORKSPACE_PATH_ENV, PROVIDER_LINEAR_CHILD_LANE_PHASES_ENV, PROVIDER_LINEAR_CHILD_LANE_PROOF_FILENAME, PROVIDER_LINEAR_CHILD_LANE_PURPOSE_ENV, PROVIDER_LINEAR_CHILD_LANE_STREAM_ENV } from './providerLinearChildLaneRunner.js';
|
|
15
|
+
import { formatProviderLinearChildLanePathSelectors, providerLinearChildLanePathMatchesSelectors, providerLinearChildLanePathSelectorsEqual, providerLinearChildLanePathSelectorsOverlap, resolveProviderLinearChildLaneScopeContract, resolveProviderLinearChildLaneSupportedPhases } from './providerLinearChildLanePhaseContract.js';
|
|
16
|
+
import { applyResolvedProgramInvocationEnvOverrides, resolveCodexOrchestratorBootstrapInvocation } from './utils/packageProgramResolver.js';
|
|
17
|
+
import { slugify } from './utils/strings.js';
|
|
18
|
+
import { parseTrailingJsonObject } from './utils/trailingJsonObject.js';
|
|
19
|
+
import { countGuardrailCommands, resolveGuardrailsRequiredForManifest, resolveGuardrailsRequiredSourceForManifest, stripNonApplicableGuardrailSummaryLines } from './run/manifest.js';
|
|
20
|
+
const execFileAsync = promisify(execFile);
|
|
21
|
+
const PROVIDER_LINEAR_CHILD_LANE_PIPELINE_ID = 'provider-linear-child-lane';
|
|
22
|
+
const PROVIDER_LINEAR_CHILD_LANE_MUTATION_REASON = 'Only the parent provider-linear-worker may mutate the issue lifecycle. Same-issue child lanes are bounded helpers that must return patch artifacts for parent review.';
|
|
23
|
+
export const PROVIDER_LINEAR_CHILD_LANE_PARALLEL_FIRST_CAP = 2;
|
|
24
|
+
const PROVIDER_LINEAR_CHILD_LANE_IN_FLIGHT_STALE_MS = 30 * 60 * 1000;
|
|
25
|
+
const PROVIDER_LINEAR_CHILD_LANE_LAUNCH_RECOVERY_POLL_INTERVAL_MS = 250;
|
|
26
|
+
const PROVIDER_LINEAR_CHILD_LANE_ENV_KEYS_TO_REMOVE = [
|
|
27
|
+
'MCP_RUNNER_TASK_ID',
|
|
28
|
+
'CODEX_ORCHESTRATOR_TASK_ID',
|
|
29
|
+
'CODEX_ORCHESTRATOR_RUN_ID',
|
|
30
|
+
'CODEX_ORCHESTRATOR_PIPELINE_ID',
|
|
31
|
+
'CODEX_ORCHESTRATOR_MANIFEST_PATH',
|
|
32
|
+
'CODEX_ORCHESTRATOR_RUN_DIR',
|
|
33
|
+
'CODEX_ORCHESTRATOR_RUNTIME_MODE',
|
|
34
|
+
'CODEX_ORCHESTRATOR_RUNTIME_MODE_ACTIVE',
|
|
35
|
+
'CODEX_RUNTIME_MODE',
|
|
36
|
+
'CODEX_ORCHESTRATOR_APPSERVER_SESSION_ID',
|
|
37
|
+
'CODEX_THREAD_ID',
|
|
38
|
+
PROVIDER_CONTROL_HOST_TASK_ID_ENV,
|
|
39
|
+
PROVIDER_CONTROL_HOST_RUN_ID_ENV,
|
|
40
|
+
PROVIDER_LAUNCH_SOURCE_ENV,
|
|
41
|
+
PROVIDER_LAUNCH_TOKEN_ENV,
|
|
42
|
+
PROVIDER_LINEAR_AUDIT_ENV_VAR
|
|
43
|
+
];
|
|
44
|
+
const PROVIDER_LINEAR_CHILD_LANE_OPTIONAL_ENV_KEYS = [
|
|
45
|
+
PROVIDER_LINEAR_CHILD_LANE_INSTRUCTIONS_ENV,
|
|
46
|
+
PROVIDER_LINEAR_CHILD_LANE_PARENT_SNAPSHOT_BASE_SHA_ENV,
|
|
47
|
+
PROVIDER_LINEAR_CHILD_LANE_PARENT_SNAPSHOT_CAPTURED_AT_ENV,
|
|
48
|
+
PROVIDER_LINEAR_CHILD_LANE_PARENT_SNAPSHOT_ISSUE_UPDATED_AT_ENV,
|
|
49
|
+
PROVIDER_LINEAR_CHILD_LANE_PARENT_SNAPSHOT_ISSUE_STATE_ENV,
|
|
50
|
+
PROVIDER_LINEAR_CHILD_LANE_PARENT_SNAPSHOT_ISSUE_STATE_TYPE_ENV
|
|
51
|
+
];
|
|
52
|
+
const DEFAULT_DEPENDENCIES = {
|
|
53
|
+
execRunner: defaultExecRunner,
|
|
54
|
+
readDir: async (path) => await readdir(path, { withFileTypes: true }),
|
|
55
|
+
transactChildLanes: async (runDir, action) => await transactProviderLinearWorkerChildLanes(runDir, action),
|
|
56
|
+
readParentDirtyPaths: async (workspacePath) => {
|
|
57
|
+
const modified = await execFileAsync('git', ['-C', workspacePath, 'diff', '--name-only', '--relative', 'HEAD', '--'], {
|
|
58
|
+
maxBuffer: 10 * 1024 * 1024
|
|
59
|
+
});
|
|
60
|
+
const untracked = await execFileAsync('git', ['-C', workspacePath, 'ls-files', '--others', '--exclude-standard'], {
|
|
61
|
+
maxBuffer: 10 * 1024 * 1024
|
|
62
|
+
});
|
|
63
|
+
return normalizeScopeEntries([...modified.stdout.split(/\r?\n/u), ...untracked.stdout.split(/\r?\n/u)]);
|
|
64
|
+
},
|
|
65
|
+
refreshProofSnapshot: async (runDir, auditPath, env) => {
|
|
66
|
+
await refreshProviderLinearWorkerProofSnapshot(runDir, auditPath, undefined, undefined, env, {
|
|
67
|
+
emitProgressEvent: (message) => logger.warn(message)
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
readTrackedIssue: async ({ issueId, sourceSetup, env }) => {
|
|
71
|
+
const resolution = await resolveLiveLinearTrackedIssueById({
|
|
72
|
+
issueId,
|
|
73
|
+
sourceSetup,
|
|
74
|
+
env
|
|
75
|
+
});
|
|
76
|
+
return resolution.kind === 'ready' ? resolution.tracked_issue : null;
|
|
77
|
+
},
|
|
78
|
+
readParentHeadSha: async (workspacePath) => {
|
|
79
|
+
const result = await execFileAsync('git', ['-C', workspacePath, 'rev-parse', 'HEAD']);
|
|
80
|
+
return normalizeOptionalString(result.stdout);
|
|
81
|
+
},
|
|
82
|
+
applyPatchArtifact: async (workspacePath, patchPath) => {
|
|
83
|
+
await execFileAsync('git', ['-C', workspacePath, 'apply', '--3way', '--whitespace=nowarn', patchPath]);
|
|
84
|
+
},
|
|
85
|
+
readChildLaneProof: async (proofPath) => JSON.parse(await readFile(proofPath, 'utf8')),
|
|
86
|
+
now: () => new Date().toISOString(),
|
|
87
|
+
sleep: async (ms) => {
|
|
88
|
+
await new Promise((resolvePromise) => {
|
|
89
|
+
setTimeout(resolvePromise, ms);
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
warn: (message) => {
|
|
93
|
+
logger.warn(message);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
async function refreshProviderLinearChildLaneProofSnapshotBestEffort(input) {
|
|
97
|
+
try {
|
|
98
|
+
await input.deps.refreshProofSnapshot(input.runDir, input.auditPath, input.env);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
input.deps.warn(`provider-linear-child-lane warning: failed to refresh proof snapshot ${input.warningContext}: ${error instanceof Error ? error.message : String(error)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export async function runProviderLinearChildLaneShell(params, overrides = {}) {
|
|
105
|
+
const deps = { ...DEFAULT_DEPENDENCIES, ...overrides };
|
|
106
|
+
const env = params.env ?? process.env;
|
|
107
|
+
let context;
|
|
108
|
+
try {
|
|
109
|
+
context = await loadProviderLinearWorkerContext(env);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
return failureResult({
|
|
113
|
+
action: normalizeAction(params.action) ?? 'launch',
|
|
114
|
+
issueId: null,
|
|
115
|
+
issueIdentifier: null,
|
|
116
|
+
sourceSetup: null,
|
|
117
|
+
stream: null,
|
|
118
|
+
childRun: null,
|
|
119
|
+
childLane: null,
|
|
120
|
+
code: 'provider_worker_child_lane_context_missing',
|
|
121
|
+
message: error instanceof Error ? error.message : String(error),
|
|
122
|
+
status: 412
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const sourceSetup = context.sourceSetup ?? null;
|
|
126
|
+
if (context.pipelineId !== 'provider-linear-worker') {
|
|
127
|
+
return failureResult({
|
|
128
|
+
action: normalizeAction(params.action) ?? 'launch',
|
|
129
|
+
issueId: context.issueId,
|
|
130
|
+
issueIdentifier: context.issueIdentifier,
|
|
131
|
+
sourceSetup,
|
|
132
|
+
stream: params.streamName ?? null,
|
|
133
|
+
childRun: null,
|
|
134
|
+
childLane: null,
|
|
135
|
+
code: 'provider_worker_child_lane_requires_provider_worker',
|
|
136
|
+
message: 'linear child-lane is only available inside provider-linear-worker runs.',
|
|
137
|
+
status: 409
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (!context.providerControlHostRecordedInManifest ||
|
|
141
|
+
!context.providerControlHostTaskId ||
|
|
142
|
+
!context.providerControlHostRunId ||
|
|
143
|
+
!context.providerControlHostMatchesManifest) {
|
|
144
|
+
const message = formatProviderWorkerChildLaneProvenanceInvalidMessage(context, env);
|
|
145
|
+
return failureResult({
|
|
146
|
+
action: normalizeAction(params.action) ?? 'launch',
|
|
147
|
+
issueId: context.issueId,
|
|
148
|
+
issueIdentifier: context.issueIdentifier,
|
|
149
|
+
sourceSetup,
|
|
150
|
+
stream: params.streamName ?? null,
|
|
151
|
+
childRun: null,
|
|
152
|
+
childLane: null,
|
|
153
|
+
code: 'provider_worker_child_lane_provenance_invalid',
|
|
154
|
+
message,
|
|
155
|
+
status: 412
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const action = normalizeAction(params.action);
|
|
159
|
+
if (!action) {
|
|
160
|
+
return failureResult({
|
|
161
|
+
action: 'launch',
|
|
162
|
+
issueId: context.issueId,
|
|
163
|
+
issueIdentifier: context.issueIdentifier,
|
|
164
|
+
sourceSetup,
|
|
165
|
+
stream: params.streamName ?? null,
|
|
166
|
+
childRun: null,
|
|
167
|
+
childLane: null,
|
|
168
|
+
code: 'provider_worker_child_lane_action_invalid',
|
|
169
|
+
message: 'linear child-lane requires --action launch|accept|reject|invalidate.',
|
|
170
|
+
status: 422
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
if (action === 'launch') {
|
|
175
|
+
return await launchChildLane({
|
|
176
|
+
...params,
|
|
177
|
+
action,
|
|
178
|
+
env
|
|
179
|
+
}, context, deps, sourceSetup);
|
|
180
|
+
}
|
|
181
|
+
return await resolveChildLaneDecision({
|
|
182
|
+
...params,
|
|
183
|
+
action,
|
|
184
|
+
env
|
|
185
|
+
}, context, deps, sourceSetup);
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
return failureResult({
|
|
189
|
+
action,
|
|
190
|
+
issueId: context.issueId,
|
|
191
|
+
issueIdentifier: context.issueIdentifier,
|
|
192
|
+
sourceSetup,
|
|
193
|
+
stream: normalizeChildLaneStreamName(params.streamName) ?? params.streamName ?? null,
|
|
194
|
+
childRun: null,
|
|
195
|
+
childLane: null,
|
|
196
|
+
code: 'provider_worker_child_lane_unhandled_failure',
|
|
197
|
+
message: error instanceof Error ? error.message : String(error),
|
|
198
|
+
status: 502
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async function launchChildLane(params, context, deps, sourceSetup) {
|
|
203
|
+
const stream = normalizeChildLaneStreamName(params.streamName);
|
|
204
|
+
if (!stream) {
|
|
205
|
+
return failureResult({
|
|
206
|
+
action: 'launch',
|
|
207
|
+
issueId: context.issueId,
|
|
208
|
+
issueIdentifier: context.issueIdentifier,
|
|
209
|
+
sourceSetup,
|
|
210
|
+
stream: params.streamName ?? null,
|
|
211
|
+
childRun: null,
|
|
212
|
+
childLane: null,
|
|
213
|
+
code: 'provider_worker_child_lane_stream_invalid',
|
|
214
|
+
message: 'Provider worker child lanes require a non-empty stream name after slug normalization.',
|
|
215
|
+
status: 422
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
const purpose = normalizeOptionalString(params.purpose);
|
|
219
|
+
let scope;
|
|
220
|
+
try {
|
|
221
|
+
scope = normalizeChildLaneScope(params.files ?? [], params.phases ?? [], context.repoRoot);
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
const message = formatChildLaneScopeLaunchFailureMessage(error instanceof Error ? error.message : String(error));
|
|
225
|
+
return failureResult({
|
|
226
|
+
action: 'launch',
|
|
227
|
+
issueId: context.issueId,
|
|
228
|
+
issueIdentifier: context.issueIdentifier,
|
|
229
|
+
sourceSetup,
|
|
230
|
+
stream,
|
|
231
|
+
childRun: null,
|
|
232
|
+
childLane: null,
|
|
233
|
+
code: 'provider_worker_child_lane_scope_missing',
|
|
234
|
+
message,
|
|
235
|
+
status: 422
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (!purpose || !scope) {
|
|
239
|
+
return failureResult({
|
|
240
|
+
action: 'launch',
|
|
241
|
+
issueId: context.issueId,
|
|
242
|
+
issueIdentifier: context.issueIdentifier,
|
|
243
|
+
sourceSetup,
|
|
244
|
+
stream,
|
|
245
|
+
childRun: null,
|
|
246
|
+
childLane: null,
|
|
247
|
+
code: 'provider_worker_child_lane_scope_missing',
|
|
248
|
+
message: formatChildLaneScopeLaunchFailureMessage('Provider worker child lanes require --purpose and at least one declared file or supported phase scope.'),
|
|
249
|
+
status: 422
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
let dirtyScopeConflict;
|
|
253
|
+
try {
|
|
254
|
+
dirtyScopeConflict = await resolveParentDirtyScopeConflict(context.repoRoot, scope, deps);
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
return failureResult({
|
|
258
|
+
action: 'launch',
|
|
259
|
+
issueId: context.issueId,
|
|
260
|
+
issueIdentifier: context.issueIdentifier,
|
|
261
|
+
sourceSetup,
|
|
262
|
+
stream,
|
|
263
|
+
childRun: null,
|
|
264
|
+
childLane: null,
|
|
265
|
+
code: 'provider_worker_child_lane_parent_dirty_check_failed',
|
|
266
|
+
message: `Failed to inspect parent workspace state before launching child lane: ${error instanceof Error ? error.message : String(error)}`,
|
|
267
|
+
status: 502
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
if (dirtyScopeConflict) {
|
|
271
|
+
const retrySuppression = await resolveSameAttemptParentDirtyRetrySuppression(context.runDir, context.issueId, params.env);
|
|
272
|
+
if (retrySuppression) {
|
|
273
|
+
return failureResult({
|
|
274
|
+
action: 'launch',
|
|
275
|
+
issueId: context.issueId,
|
|
276
|
+
issueIdentifier: context.issueIdentifier,
|
|
277
|
+
sourceSetup,
|
|
278
|
+
stream,
|
|
279
|
+
childRun: null,
|
|
280
|
+
childLane: null,
|
|
281
|
+
code: 'provider_worker_child_lane_parent_dirty_retry_suppressed',
|
|
282
|
+
message: `${dirtyScopeConflict} Same-attempt retry suppression is in effect: ${retrySuppression.instruction}`,
|
|
283
|
+
status: 409
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return failureResult({
|
|
287
|
+
action: 'launch',
|
|
288
|
+
issueId: context.issueId,
|
|
289
|
+
issueIdentifier: context.issueIdentifier,
|
|
290
|
+
sourceSetup,
|
|
291
|
+
stream,
|
|
292
|
+
childRun: null,
|
|
293
|
+
childLane: null,
|
|
294
|
+
code: 'provider_worker_child_lane_parent_dirty',
|
|
295
|
+
message: dirtyScopeConflict,
|
|
296
|
+
status: 409
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
const parentSnapshot = await resolveParentSnapshot(context, params.env, deps);
|
|
300
|
+
const baseSha = await deps.readParentHeadSha(context.repoRoot);
|
|
301
|
+
const childTaskId = `${context.taskId}-${stream}`;
|
|
302
|
+
const childRunsRoot = resolveWorkspaceScopedArtifactDir(context.repoRoot, params.env.CODEX_ORCHESTRATOR_RUNS_DIR, '.runs');
|
|
303
|
+
const now = deps.now();
|
|
304
|
+
const launchReservation = buildReservedChildLaneRecord({
|
|
305
|
+
stream,
|
|
306
|
+
childRunsRoot,
|
|
307
|
+
childTaskId,
|
|
308
|
+
context,
|
|
309
|
+
purpose,
|
|
310
|
+
instructions: params.instructions ?? null,
|
|
311
|
+
scope,
|
|
312
|
+
sourceSetup,
|
|
313
|
+
parentSnapshot: {
|
|
314
|
+
...parentSnapshot,
|
|
315
|
+
base_sha: baseSha
|
|
316
|
+
},
|
|
317
|
+
now
|
|
318
|
+
});
|
|
319
|
+
const reservation = await deps.transactChildLanes(context.runDir, async (records) => {
|
|
320
|
+
const conflicting = findPendingChildLaneConflict(records, stream, scope);
|
|
321
|
+
if (conflicting) {
|
|
322
|
+
return {
|
|
323
|
+
records,
|
|
324
|
+
result: { conflicting, capExhausted: [], reserved: null }
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
const countedLanes = selectChildLanesCountingTowardParallelFirstCap(records, now);
|
|
328
|
+
if (countedLanes.length >= PROVIDER_LINEAR_CHILD_LANE_PARALLEL_FIRST_CAP) {
|
|
329
|
+
return {
|
|
330
|
+
records,
|
|
331
|
+
result: { conflicting: null, capExhausted: countedLanes, reserved: null }
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
records: [...records, launchReservation],
|
|
336
|
+
result: { conflicting: null, capExhausted: [], reserved: launchReservation }
|
|
337
|
+
};
|
|
338
|
+
});
|
|
339
|
+
if (reservation.conflicting) {
|
|
340
|
+
return failureResult({
|
|
341
|
+
action: 'launch',
|
|
342
|
+
issueId: context.issueId,
|
|
343
|
+
issueIdentifier: context.issueIdentifier,
|
|
344
|
+
sourceSetup,
|
|
345
|
+
stream,
|
|
346
|
+
childRun: null,
|
|
347
|
+
childLane: reservation.conflicting,
|
|
348
|
+
code: 'provider_worker_child_lane_scope_conflict',
|
|
349
|
+
message: describePendingChildLaneConflict(stream, reservation.conflicting),
|
|
350
|
+
status: 409
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
if (reservation.capExhausted.length > 0) {
|
|
354
|
+
return failureResult({
|
|
355
|
+
action: 'launch',
|
|
356
|
+
issueId: context.issueId,
|
|
357
|
+
issueIdentifier: context.issueIdentifier,
|
|
358
|
+
sourceSetup,
|
|
359
|
+
stream,
|
|
360
|
+
childRun: null,
|
|
361
|
+
childLane: null,
|
|
362
|
+
code: 'provider_worker_child_lane_cap_exhausted',
|
|
363
|
+
message: describeChildLaneCapExhaustion(reservation.capExhausted),
|
|
364
|
+
status: 409
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
const invocation = resolveCodexOrchestratorInvocation(params.env);
|
|
368
|
+
const args = [
|
|
369
|
+
...invocation.argsPrefix,
|
|
370
|
+
'start',
|
|
371
|
+
PROVIDER_LINEAR_CHILD_LANE_PIPELINE_ID,
|
|
372
|
+
'--task',
|
|
373
|
+
childTaskId,
|
|
374
|
+
'--parent-run',
|
|
375
|
+
context.runId,
|
|
376
|
+
'--issue-provider',
|
|
377
|
+
'linear',
|
|
378
|
+
'--issue-id',
|
|
379
|
+
context.issueId,
|
|
380
|
+
'--issue-identifier',
|
|
381
|
+
context.issueIdentifier,
|
|
382
|
+
'--format',
|
|
383
|
+
'json',
|
|
384
|
+
'--no-interactive'
|
|
385
|
+
];
|
|
386
|
+
if (context.issueUpdatedAt) {
|
|
387
|
+
args.push('--issue-updated-at', context.issueUpdatedAt);
|
|
388
|
+
}
|
|
389
|
+
const runtimeMode = normalizeRuntimeMode(params.env.CODEX_ORCHESTRATOR_RUNTIME_MODE_ACTIVE ?? params.env.CODEX_ORCHESTRATOR_RUNTIME_MODE);
|
|
390
|
+
if (runtimeMode) {
|
|
391
|
+
args.push('--runtime-mode', runtimeMode);
|
|
392
|
+
}
|
|
393
|
+
const childStartEnv = buildProviderLinearChildLaneStartEnv(params.env, {
|
|
394
|
+
repoRoot: context.repoRoot,
|
|
395
|
+
taskId: childTaskId,
|
|
396
|
+
stream,
|
|
397
|
+
purpose,
|
|
398
|
+
instructions: params.instructions ?? null,
|
|
399
|
+
scope,
|
|
400
|
+
parentWorkspacePath: context.repoRoot,
|
|
401
|
+
parentSnapshot: {
|
|
402
|
+
...parentSnapshot,
|
|
403
|
+
base_sha: baseSha
|
|
404
|
+
},
|
|
405
|
+
sourceSetup
|
|
406
|
+
});
|
|
407
|
+
applyResolvedProgramInvocationEnvOverrides(childStartEnv, invocation.envOverrides);
|
|
408
|
+
const execAbortController = new AbortController();
|
|
409
|
+
let execSettled = false;
|
|
410
|
+
const execPromise = deps.execRunner({
|
|
411
|
+
command: invocation.command,
|
|
412
|
+
args,
|
|
413
|
+
cwd: context.repoRoot,
|
|
414
|
+
env: childStartEnv,
|
|
415
|
+
mirrorOutput: false,
|
|
416
|
+
abortSignal: execAbortController.signal
|
|
417
|
+
});
|
|
418
|
+
void execPromise.then(() => {
|
|
419
|
+
execSettled = true;
|
|
420
|
+
}, () => {
|
|
421
|
+
execSettled = true;
|
|
422
|
+
});
|
|
423
|
+
let execResult = null;
|
|
424
|
+
let recoveredLaunch = null;
|
|
425
|
+
let launchError = null;
|
|
426
|
+
const recoveredLaunchPromise = waitForRecoveredChildLaneLaunchCandidate({
|
|
427
|
+
reservation: launchReservation,
|
|
428
|
+
context,
|
|
429
|
+
childRunsRoot,
|
|
430
|
+
deps,
|
|
431
|
+
isExecSettled: () => execSettled,
|
|
432
|
+
now
|
|
433
|
+
});
|
|
434
|
+
const advisoryRecoveredLaunchPromise = recoveredLaunchPromise.catch((error) => {
|
|
435
|
+
deps.warn(`provider-linear-child-lane warning: ignored launch-recovery scan failure while awaiting exec result: ${error instanceof Error ? error.message : String(error)}`);
|
|
436
|
+
return null;
|
|
437
|
+
});
|
|
438
|
+
try {
|
|
439
|
+
const launchOutcome = await Promise.race([
|
|
440
|
+
execPromise.then((result) => ({ kind: 'exec', result })),
|
|
441
|
+
advisoryRecoveredLaunchPromise.then((recovered) => ({ kind: 'recovered', recovered }))
|
|
442
|
+
]);
|
|
443
|
+
if (launchOutcome.kind === 'recovered') {
|
|
444
|
+
if (launchOutcome.recovered) {
|
|
445
|
+
recoveredLaunch = launchOutcome.recovered;
|
|
446
|
+
if (!execSettled) {
|
|
447
|
+
execAbortController.abort(new Error(`Recovered child lane ${recoveredLaunch.childRun.run_id} from manifest/proof while the launcher reservation still reported ${launchReservation.run_id}.`));
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
execResult = launchOutcome.result;
|
|
453
|
+
}
|
|
454
|
+
if (!execResult) {
|
|
455
|
+
try {
|
|
456
|
+
execResult = await execPromise;
|
|
457
|
+
}
|
|
458
|
+
catch (error) {
|
|
459
|
+
launchError = error;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
launchError = error;
|
|
465
|
+
}
|
|
466
|
+
if (!recoveredLaunch) {
|
|
467
|
+
recoveredLaunch = await advisoryRecoveredLaunchPromise;
|
|
468
|
+
}
|
|
469
|
+
if (launchError && !recoveredLaunch) {
|
|
470
|
+
await removeReservedChildLane(context.runDir, launchReservation, deps);
|
|
471
|
+
return failureResult({
|
|
472
|
+
action: 'launch',
|
|
473
|
+
issueId: context.issueId,
|
|
474
|
+
issueIdentifier: context.issueIdentifier,
|
|
475
|
+
sourceSetup,
|
|
476
|
+
stream,
|
|
477
|
+
childRun: null,
|
|
478
|
+
childLane: null,
|
|
479
|
+
code: 'provider_worker_child_lane_launch_failed',
|
|
480
|
+
message: launchError instanceof Error ? launchError.message : String(launchError),
|
|
481
|
+
status: 502
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
const childRun = recoveredLaunch?.childRun ??
|
|
485
|
+
(execResult
|
|
486
|
+
? await parseProviderChildLaneRunResult(execResult.stdout, context.repoRoot, childStartEnv.CODEX_ORCHESTRATOR_RUNS_DIR ?? join(context.repoRoot, '.runs'), childTaskId)
|
|
487
|
+
: null);
|
|
488
|
+
if (!childRun) {
|
|
489
|
+
await removeReservedChildLane(context.runDir, launchReservation, deps);
|
|
490
|
+
const detail = execResult
|
|
491
|
+
? [execResult.stderr.trim(), execResult.stdout.trim()].filter(Boolean)[0] ?? 'unknown child-lane output'
|
|
492
|
+
: 'unknown child-lane output';
|
|
493
|
+
return failureResult({
|
|
494
|
+
action: 'launch',
|
|
495
|
+
issueId: context.issueId,
|
|
496
|
+
issueIdentifier: context.issueIdentifier,
|
|
497
|
+
sourceSetup,
|
|
498
|
+
stream,
|
|
499
|
+
childRun: null,
|
|
500
|
+
childLane: null,
|
|
501
|
+
code: 'provider_worker_child_lane_output_invalid',
|
|
502
|
+
message: `Could not parse child lane output: ${detail}`,
|
|
503
|
+
status: 502
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
const proofPath = join(childRun.artifact_root, PROVIDER_LINEAR_CHILD_LANE_PROOF_FILENAME);
|
|
507
|
+
const childProof = recoveredLaunch?.proof ?? (await deps.readChildLaneProof(proofPath).catch(() => null));
|
|
508
|
+
if (childRun.status === 'succeeded' && (!childProof || !childProof.patch_artifact_path)) {
|
|
509
|
+
await removeReservedChildLane(context.runDir, launchReservation, deps);
|
|
510
|
+
return failureResult({
|
|
511
|
+
action: 'launch',
|
|
512
|
+
issueId: context.issueId,
|
|
513
|
+
issueIdentifier: context.issueIdentifier,
|
|
514
|
+
sourceSetup,
|
|
515
|
+
stream,
|
|
516
|
+
childRun,
|
|
517
|
+
childLane: null,
|
|
518
|
+
code: 'provider_worker_child_lane_proof_missing',
|
|
519
|
+
message: `Child lane ${stream} did not produce a readable proof bundle with a patch artifact.`,
|
|
520
|
+
status: 502
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
let recordedScope = recoveredLaunch?.childLane.scope ?? scope;
|
|
524
|
+
if (!recoveredLaunch && childProof?.scope) {
|
|
525
|
+
try {
|
|
526
|
+
const proofScope = resolveProviderLinearChildLaneScopeContract(childProof.scope);
|
|
527
|
+
if (!areChildLaneScopesEquivalent(scope, proofScope)) {
|
|
528
|
+
await removeReservedChildLane(context.runDir, launchReservation, deps);
|
|
529
|
+
return failureResult({
|
|
530
|
+
action: 'launch',
|
|
531
|
+
issueId: context.issueId,
|
|
532
|
+
issueIdentifier: context.issueIdentifier,
|
|
533
|
+
sourceSetup,
|
|
534
|
+
stream,
|
|
535
|
+
childRun,
|
|
536
|
+
childLane: null,
|
|
537
|
+
code: 'provider_worker_child_lane_proof_invalid',
|
|
538
|
+
message: 'Child lane proof scope does not match the parent-launched scope contract.',
|
|
539
|
+
status: 409
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
recordedScope = proofScope;
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
await removeReservedChildLane(context.runDir, launchReservation, deps);
|
|
546
|
+
return failureResult({
|
|
547
|
+
action: 'launch',
|
|
548
|
+
issueId: context.issueId,
|
|
549
|
+
issueIdentifier: context.issueIdentifier,
|
|
550
|
+
sourceSetup,
|
|
551
|
+
stream,
|
|
552
|
+
childRun,
|
|
553
|
+
childLane: null,
|
|
554
|
+
code: 'provider_worker_child_lane_proof_invalid',
|
|
555
|
+
message: `Child lane ${stream} wrote an invalid proof scope contract: ${error instanceof Error ? error.message : String(error)}`,
|
|
556
|
+
status: 409
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
const childLane = recoveredLaunch?.childLane ?? (() => {
|
|
561
|
+
const zeroBytePatch = execResult?.exitCode === 0 && childRun.status === 'succeeded' && childProof?.patch_bytes === 0;
|
|
562
|
+
return {
|
|
563
|
+
stream,
|
|
564
|
+
pipeline_id: PROVIDER_LINEAR_CHILD_LANE_PIPELINE_ID,
|
|
565
|
+
task_id: childTaskId,
|
|
566
|
+
run_id: childRun.run_id,
|
|
567
|
+
status: childRun.status,
|
|
568
|
+
manifest_path: childRun.manifest_path,
|
|
569
|
+
artifact_root: childRun.artifact_root,
|
|
570
|
+
log_path: childRun.log_path,
|
|
571
|
+
summary: childRun.summary ?? normalizeOptionalString(childProof?.last_message),
|
|
572
|
+
guardrails_required: childRun.guardrails_required,
|
|
573
|
+
guardrails_required_source: childRun.guardrails_required_source,
|
|
574
|
+
guardrail_command_count: childRun.guardrail_command_count,
|
|
575
|
+
issue_id: context.issueId,
|
|
576
|
+
issue_identifier: context.issueIdentifier,
|
|
577
|
+
workspace_path: context.repoRoot,
|
|
578
|
+
source_setup: sourceSetup,
|
|
579
|
+
launched_at: launchReservation.launched_at,
|
|
580
|
+
purpose,
|
|
581
|
+
instructions: normalizeOptionalString(params.instructions),
|
|
582
|
+
scope: recordedScope,
|
|
583
|
+
parent_snapshot: childProof?.parent_snapshot ?? {
|
|
584
|
+
...parentSnapshot,
|
|
585
|
+
base_sha: baseSha,
|
|
586
|
+
captured_at: deps.now()
|
|
587
|
+
},
|
|
588
|
+
lane_workspace_path: childProof?.lane_workspace_path ?? null,
|
|
589
|
+
patch_artifact_path: childProof?.patch_artifact_path ?? null,
|
|
590
|
+
patch_bytes: childProof?.patch_bytes ?? null,
|
|
591
|
+
decision: zeroBytePatch ? 'rejected' : 'pending',
|
|
592
|
+
in_flight_action: null,
|
|
593
|
+
in_flight_started_at: null,
|
|
594
|
+
decision_at: zeroBytePatch ? deps.now() : null,
|
|
595
|
+
decision_reason: zeroBytePatch ? buildNoOutputAdvisoryChildLaneDecisionReason(childRun) : null
|
|
596
|
+
};
|
|
597
|
+
})();
|
|
598
|
+
let recordedChildLaneForResult = childLane;
|
|
599
|
+
try {
|
|
600
|
+
const recorded = await deps.transactChildLanes(context.runDir, async (records) => {
|
|
601
|
+
const current = findChildLaneByIdentity(records, launchReservation);
|
|
602
|
+
if (!current) {
|
|
603
|
+
return {
|
|
604
|
+
records,
|
|
605
|
+
result: null
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
const recordedChildLane = mergeCompletedChildLaneWithParentDecision(current, childLane);
|
|
609
|
+
const next = replaceChildLaneRecord(records, current, recordedChildLane);
|
|
610
|
+
if (!next) {
|
|
611
|
+
return {
|
|
612
|
+
records,
|
|
613
|
+
result: null
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
return {
|
|
617
|
+
records: next,
|
|
618
|
+
result: recordedChildLane
|
|
619
|
+
};
|
|
620
|
+
});
|
|
621
|
+
if (!recorded) {
|
|
622
|
+
await removeReservedChildLane(context.runDir, launchReservation, deps);
|
|
623
|
+
return failureResult({
|
|
624
|
+
action: 'launch',
|
|
625
|
+
issueId: context.issueId,
|
|
626
|
+
issueIdentifier: context.issueIdentifier,
|
|
627
|
+
sourceSetup,
|
|
628
|
+
stream,
|
|
629
|
+
childRun,
|
|
630
|
+
childLane,
|
|
631
|
+
code: 'provider_worker_child_lane_record_failed',
|
|
632
|
+
message: 'Failed to replace the reserved child lane entry with the completed child run record.',
|
|
633
|
+
status: 502
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
recordedChildLaneForResult = recorded;
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
await removeReservedChildLane(context.runDir, launchReservation, deps).catch(() => undefined);
|
|
640
|
+
return failureResult({
|
|
641
|
+
action: 'launch',
|
|
642
|
+
issueId: context.issueId,
|
|
643
|
+
issueIdentifier: context.issueIdentifier,
|
|
644
|
+
sourceSetup,
|
|
645
|
+
stream,
|
|
646
|
+
childRun,
|
|
647
|
+
childLane,
|
|
648
|
+
code: 'provider_worker_child_lane_record_failed',
|
|
649
|
+
message: `Failed to record child lane lineage: ${error instanceof Error ? error.message : String(error)}`,
|
|
650
|
+
status: 502
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
await refreshProviderLinearChildLaneProofSnapshotBestEffort({
|
|
654
|
+
deps,
|
|
655
|
+
runDir: context.runDir,
|
|
656
|
+
auditPath: params.env[PROVIDER_LINEAR_AUDIT_ENV_VAR] ?? null,
|
|
657
|
+
env: params.env,
|
|
658
|
+
warningContext: `after recording child lane ${stream}`
|
|
659
|
+
});
|
|
660
|
+
const launchExitedNonZero = recoveredLaunch ? false : execResult ? execResult.exitCode !== 0 : false;
|
|
661
|
+
if (launchExitedNonZero || childRun.status !== 'succeeded') {
|
|
662
|
+
const childFailureDetail = normalizeOptionalString(childProof?.last_message) ??
|
|
663
|
+
normalizeOptionalString(recordedChildLaneForResult.summary) ??
|
|
664
|
+
normalizeOptionalString(childRun.summary);
|
|
665
|
+
return failureResult({
|
|
666
|
+
action: 'launch',
|
|
667
|
+
issueId: context.issueId,
|
|
668
|
+
issueIdentifier: context.issueIdentifier,
|
|
669
|
+
sourceSetup,
|
|
670
|
+
stream,
|
|
671
|
+
childRun,
|
|
672
|
+
childLane: recordedChildLaneForResult,
|
|
673
|
+
code: 'provider_worker_child_lane_run_failed',
|
|
674
|
+
message: `Child lane ${stream} completed with status ${childRun.status}.${childFailureDetail ? ` ${childFailureDetail}` : ''} ${PROVIDER_LINEAR_CHILD_LANE_MUTATION_REASON}`,
|
|
675
|
+
status: 502
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
return {
|
|
679
|
+
ok: true,
|
|
680
|
+
operation: 'child-lane',
|
|
681
|
+
action: 'launched',
|
|
682
|
+
issue: {
|
|
683
|
+
id: context.issueId,
|
|
684
|
+
identifier: context.issueIdentifier
|
|
685
|
+
},
|
|
686
|
+
source_setup: sourceSetup,
|
|
687
|
+
stream,
|
|
688
|
+
child_run: childRun,
|
|
689
|
+
child_lane: recordedChildLaneForResult
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
async function resolveChildLaneDecision(params, context, deps, sourceSetup) {
|
|
693
|
+
const stream = normalizeChildLaneStreamName(params.streamName);
|
|
694
|
+
if (!stream) {
|
|
695
|
+
return failureResult({
|
|
696
|
+
action: params.action,
|
|
697
|
+
issueId: context.issueId,
|
|
698
|
+
issueIdentifier: context.issueIdentifier,
|
|
699
|
+
sourceSetup,
|
|
700
|
+
stream: params.streamName ?? null,
|
|
701
|
+
childRun: null,
|
|
702
|
+
childLane: null,
|
|
703
|
+
code: 'provider_worker_child_lane_stream_invalid',
|
|
704
|
+
message: 'Provider worker child lane decisions require --stream.',
|
|
705
|
+
status: 422
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
const childRunsRoot = resolveWorkspaceScopedArtifactDir(context.repoRoot, params.env.CODEX_ORCHESTRATOR_RUNS_DIR, '.runs');
|
|
709
|
+
if (params.action !== 'accept') {
|
|
710
|
+
const finalized = await finalizePendingChildLaneDecision({
|
|
711
|
+
action: params.action,
|
|
712
|
+
context,
|
|
713
|
+
childRunsRoot,
|
|
714
|
+
stream,
|
|
715
|
+
deps,
|
|
716
|
+
reason: normalizeOptionalString(params.reason),
|
|
717
|
+
now: deps.now()
|
|
718
|
+
});
|
|
719
|
+
if (finalized.kind !== 'updated') {
|
|
720
|
+
return childLaneDecisionFailureResult({
|
|
721
|
+
action: params.action,
|
|
722
|
+
context,
|
|
723
|
+
sourceSetup,
|
|
724
|
+
stream,
|
|
725
|
+
outcome: finalized
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
await refreshProviderLinearChildLaneProofSnapshotBestEffort({
|
|
729
|
+
deps,
|
|
730
|
+
runDir: context.runDir,
|
|
731
|
+
auditPath: params.env[PROVIDER_LINEAR_AUDIT_ENV_VAR] ?? null,
|
|
732
|
+
env: params.env,
|
|
733
|
+
warningContext: `after finalizing ${finalized.decision} child lane ${stream}`
|
|
734
|
+
});
|
|
735
|
+
return {
|
|
736
|
+
ok: true,
|
|
737
|
+
operation: 'child-lane',
|
|
738
|
+
action: finalized.decision === 'rejected' ? 'rejected' : 'invalidated',
|
|
739
|
+
issue: {
|
|
740
|
+
id: context.issueId,
|
|
741
|
+
identifier: context.issueIdentifier
|
|
742
|
+
},
|
|
743
|
+
source_setup: sourceSetup,
|
|
744
|
+
stream,
|
|
745
|
+
child_run: null,
|
|
746
|
+
child_lane: finalized.childLane
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
const claimed = await claimPendingChildLaneAcceptance({
|
|
750
|
+
context,
|
|
751
|
+
childRunsRoot,
|
|
752
|
+
stream,
|
|
753
|
+
deps,
|
|
754
|
+
now: deps.now()
|
|
755
|
+
});
|
|
756
|
+
if (claimed.kind !== 'claimed') {
|
|
757
|
+
return childLaneDecisionFailureResult({
|
|
758
|
+
action: 'accept',
|
|
759
|
+
context,
|
|
760
|
+
sourceSetup,
|
|
761
|
+
stream,
|
|
762
|
+
outcome: claimed
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
const target = claimed.childLane;
|
|
766
|
+
let currentHeadSha;
|
|
767
|
+
let currentIssue;
|
|
768
|
+
try {
|
|
769
|
+
currentHeadSha = await deps.readParentHeadSha(context.repoRoot);
|
|
770
|
+
currentIssue = await resolveParentSnapshot(context, params.env, deps);
|
|
771
|
+
}
|
|
772
|
+
catch (error) {
|
|
773
|
+
try {
|
|
774
|
+
await releaseClaimedChildLaneAcceptance(context.runDir, target, deps);
|
|
775
|
+
}
|
|
776
|
+
catch (releaseError) {
|
|
777
|
+
deps.warn(`provider-linear-child-lane warning: failed to release accept claim after snapshot read failure for ${stream}: ${releaseError instanceof Error ? releaseError.message : String(releaseError)}`);
|
|
778
|
+
}
|
|
779
|
+
throw error;
|
|
780
|
+
}
|
|
781
|
+
const staleReason = resolveChildLaneStaleReason(target, currentHeadSha, currentIssue);
|
|
782
|
+
if (staleReason) {
|
|
783
|
+
const invalidated = await finalizeClaimedChildLaneDecision({
|
|
784
|
+
context,
|
|
785
|
+
target,
|
|
786
|
+
decision: 'invalidated',
|
|
787
|
+
decisionReason: staleReason,
|
|
788
|
+
deps,
|
|
789
|
+
now: deps.now()
|
|
790
|
+
});
|
|
791
|
+
await refreshProviderLinearChildLaneProofSnapshotBestEffort({
|
|
792
|
+
deps,
|
|
793
|
+
runDir: context.runDir,
|
|
794
|
+
auditPath: params.env[PROVIDER_LINEAR_AUDIT_ENV_VAR] ?? null,
|
|
795
|
+
env: params.env,
|
|
796
|
+
warningContext: `after invalidating stale child lane ${stream}`
|
|
797
|
+
});
|
|
798
|
+
return failureResult({
|
|
799
|
+
action: 'accept',
|
|
800
|
+
issueId: context.issueId,
|
|
801
|
+
issueIdentifier: context.issueIdentifier,
|
|
802
|
+
sourceSetup,
|
|
803
|
+
stream,
|
|
804
|
+
childRun: null,
|
|
805
|
+
childLane: invalidated ?? target,
|
|
806
|
+
code: 'provider_worker_child_lane_stale',
|
|
807
|
+
message: staleReason,
|
|
808
|
+
status: 409
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
if (target.status !== 'succeeded' || !target.patch_artifact_path) {
|
|
812
|
+
await releaseClaimedChildLaneAcceptance(context.runDir, target, deps);
|
|
813
|
+
return failureResult({
|
|
814
|
+
action: 'accept',
|
|
815
|
+
issueId: context.issueId,
|
|
816
|
+
issueIdentifier: context.issueIdentifier,
|
|
817
|
+
sourceSetup,
|
|
818
|
+
stream,
|
|
819
|
+
childRun: null,
|
|
820
|
+
childLane: target,
|
|
821
|
+
code: 'provider_worker_child_lane_patch_missing',
|
|
822
|
+
message: 'Child lane has no accepted patch artifact to apply.',
|
|
823
|
+
status: 409
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
const artifactRoot = resolveAcceptedChildLaneArtifactRoot(context.repoRoot, childRunsRoot, target);
|
|
827
|
+
if (!artifactRoot) {
|
|
828
|
+
await releaseClaimedChildLaneAcceptance(context.runDir, target, deps);
|
|
829
|
+
return failureResult({
|
|
830
|
+
action: 'accept',
|
|
831
|
+
issueId: context.issueId,
|
|
832
|
+
issueIdentifier: context.issueIdentifier,
|
|
833
|
+
sourceSetup,
|
|
834
|
+
stream,
|
|
835
|
+
childRun: null,
|
|
836
|
+
childLane: target,
|
|
837
|
+
code: 'provider_worker_child_lane_patch_invalid',
|
|
838
|
+
message: 'Child lane artifact root must stay anchored to the expected workspace-local child run directory before parent acceptance.',
|
|
839
|
+
status: 409
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
const patchArtifactPath = resolveAcceptedPatchArtifactPath(context.repoRoot, target, artifactRoot);
|
|
843
|
+
if (!patchArtifactPath) {
|
|
844
|
+
await releaseClaimedChildLaneAcceptance(context.runDir, target, deps);
|
|
845
|
+
return failureResult({
|
|
846
|
+
action: 'accept',
|
|
847
|
+
issueId: context.issueId,
|
|
848
|
+
issueIdentifier: context.issueIdentifier,
|
|
849
|
+
sourceSetup,
|
|
850
|
+
stream,
|
|
851
|
+
childRun: null,
|
|
852
|
+
childLane: target,
|
|
853
|
+
code: 'provider_worker_child_lane_patch_invalid',
|
|
854
|
+
message: 'Child lane patch artifact must stay within the child lane artifact root before parent acceptance.',
|
|
855
|
+
status: 409
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
const proofPath = join(artifactRoot, PROVIDER_LINEAR_CHILD_LANE_PROOF_FILENAME);
|
|
859
|
+
const acceptedProof = await deps.readChildLaneProof(proofPath).catch(() => null);
|
|
860
|
+
let acceptedProofScope = null;
|
|
861
|
+
if (!acceptedProof) {
|
|
862
|
+
await releaseClaimedChildLaneAcceptance(context.runDir, target, deps);
|
|
863
|
+
return failureResult({
|
|
864
|
+
action: 'accept',
|
|
865
|
+
issueId: context.issueId,
|
|
866
|
+
issueIdentifier: context.issueIdentifier,
|
|
867
|
+
sourceSetup,
|
|
868
|
+
stream,
|
|
869
|
+
childRun: null,
|
|
870
|
+
childLane: target,
|
|
871
|
+
code: 'provider_worker_child_lane_proof_missing',
|
|
872
|
+
message: 'Child lane acceptance requires a readable proof bundle before parent apply.',
|
|
873
|
+
status: 409
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
if (acceptedProof) {
|
|
877
|
+
if (!acceptedProof.scope) {
|
|
878
|
+
await releaseClaimedChildLaneAcceptance(context.runDir, target, deps);
|
|
879
|
+
return failureResult({
|
|
880
|
+
action: 'accept',
|
|
881
|
+
issueId: context.issueId,
|
|
882
|
+
issueIdentifier: context.issueIdentifier,
|
|
883
|
+
sourceSetup,
|
|
884
|
+
stream,
|
|
885
|
+
childRun: null,
|
|
886
|
+
childLane: target,
|
|
887
|
+
code: 'provider_worker_child_lane_proof_invalid',
|
|
888
|
+
message: 'Child lane proof is missing scope contract metadata.',
|
|
889
|
+
status: 409
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
const proofViolation = resolveAcceptedChildLaneProofViolation(context.runId, target, acceptedProof, context.repoRoot, artifactRoot, patchArtifactPath);
|
|
893
|
+
if (proofViolation) {
|
|
894
|
+
await releaseClaimedChildLaneAcceptance(context.runDir, target, deps);
|
|
895
|
+
return failureResult({
|
|
896
|
+
action: 'accept',
|
|
897
|
+
issueId: context.issueId,
|
|
898
|
+
issueIdentifier: context.issueIdentifier,
|
|
899
|
+
sourceSetup,
|
|
900
|
+
stream,
|
|
901
|
+
childRun: null,
|
|
902
|
+
childLane: target,
|
|
903
|
+
code: 'provider_worker_child_lane_proof_invalid',
|
|
904
|
+
message: proofViolation,
|
|
905
|
+
status: 409
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
acceptedProofScope = acceptedProof.scope;
|
|
909
|
+
}
|
|
910
|
+
let patchChangedPaths;
|
|
911
|
+
try {
|
|
912
|
+
patchChangedPaths = await readPatchChangedPaths(patchArtifactPath);
|
|
913
|
+
}
|
|
914
|
+
catch (error) {
|
|
915
|
+
await releaseClaimedChildLaneAcceptance(context.runDir, target, deps);
|
|
916
|
+
return failureResult({
|
|
917
|
+
action: 'accept',
|
|
918
|
+
issueId: context.issueId,
|
|
919
|
+
issueIdentifier: context.issueIdentifier,
|
|
920
|
+
sourceSetup,
|
|
921
|
+
stream,
|
|
922
|
+
childRun: null,
|
|
923
|
+
childLane: target,
|
|
924
|
+
code: 'provider_worker_child_lane_patch_invalid',
|
|
925
|
+
message: `Child lane patch artifact could not be inspected before parent acceptance: ${error instanceof Error ? error.message : String(error)}`,
|
|
926
|
+
status: 409
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
const patchScopeViolation = resolveAcceptedPatchScopeViolation(target.scope, patchChangedPaths, acceptedProofScope);
|
|
930
|
+
if (patchScopeViolation) {
|
|
931
|
+
await releaseClaimedChildLaneAcceptance(context.runDir, target, deps);
|
|
932
|
+
return failureResult({
|
|
933
|
+
action: 'accept',
|
|
934
|
+
issueId: context.issueId,
|
|
935
|
+
issueIdentifier: context.issueIdentifier,
|
|
936
|
+
sourceSetup,
|
|
937
|
+
stream,
|
|
938
|
+
childRun: null,
|
|
939
|
+
childLane: target,
|
|
940
|
+
code: isProofScopeViolation(patchScopeViolation)
|
|
941
|
+
? 'provider_worker_child_lane_proof_invalid'
|
|
942
|
+
: 'provider_worker_child_lane_patch_scope_invalid',
|
|
943
|
+
message: patchScopeViolation,
|
|
944
|
+
status: 409
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
let dirtyPatchConflict;
|
|
948
|
+
try {
|
|
949
|
+
dirtyPatchConflict = await resolveAcceptedPatchDirtyConflict(context.repoRoot, patchChangedPaths, deps);
|
|
950
|
+
}
|
|
951
|
+
catch (error) {
|
|
952
|
+
await releaseClaimedChildLaneAcceptance(context.runDir, target, deps);
|
|
953
|
+
return failureResult({
|
|
954
|
+
action: 'accept',
|
|
955
|
+
issueId: context.issueId,
|
|
956
|
+
issueIdentifier: context.issueIdentifier,
|
|
957
|
+
sourceSetup,
|
|
958
|
+
stream,
|
|
959
|
+
childRun: null,
|
|
960
|
+
childLane: target,
|
|
961
|
+
code: 'provider_worker_child_lane_parent_dirty_check_failed',
|
|
962
|
+
message: `Failed to inspect parent workspace state before accepting child lane: ${error instanceof Error ? error.message : String(error)}`,
|
|
963
|
+
status: 502
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
if (dirtyPatchConflict) {
|
|
967
|
+
await releaseClaimedChildLaneAcceptance(context.runDir, target, deps);
|
|
968
|
+
return failureResult({
|
|
969
|
+
action: 'accept',
|
|
970
|
+
issueId: context.issueId,
|
|
971
|
+
issueIdentifier: context.issueIdentifier,
|
|
972
|
+
sourceSetup,
|
|
973
|
+
stream,
|
|
974
|
+
childRun: null,
|
|
975
|
+
childLane: target,
|
|
976
|
+
code: 'provider_worker_child_lane_parent_dirty',
|
|
977
|
+
message: dirtyPatchConflict,
|
|
978
|
+
status: 409
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
try {
|
|
982
|
+
await deps.applyPatchArtifact(context.repoRoot, patchArtifactPath);
|
|
983
|
+
}
|
|
984
|
+
catch (error) {
|
|
985
|
+
await releaseClaimedChildLaneAcceptance(context.runDir, target, deps);
|
|
986
|
+
return failureResult({
|
|
987
|
+
action: 'accept',
|
|
988
|
+
issueId: context.issueId,
|
|
989
|
+
issueIdentifier: context.issueIdentifier,
|
|
990
|
+
sourceSetup,
|
|
991
|
+
stream,
|
|
992
|
+
childRun: null,
|
|
993
|
+
childLane: target,
|
|
994
|
+
code: 'provider_worker_child_lane_apply_failed',
|
|
995
|
+
message: error instanceof Error ? error.message : String(error),
|
|
996
|
+
status: 409
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
const accepted = await finalizeClaimedChildLaneDecision({
|
|
1000
|
+
context,
|
|
1001
|
+
target,
|
|
1002
|
+
decision: 'accepted',
|
|
1003
|
+
decisionReason: normalizeOptionalString(params.reason) ?? defaultDecisionReason('accept', target),
|
|
1004
|
+
deps,
|
|
1005
|
+
now: deps.now()
|
|
1006
|
+
});
|
|
1007
|
+
if (!accepted) {
|
|
1008
|
+
return failureResult({
|
|
1009
|
+
action: 'accept',
|
|
1010
|
+
issueId: context.issueId,
|
|
1011
|
+
issueIdentifier: context.issueIdentifier,
|
|
1012
|
+
sourceSetup,
|
|
1013
|
+
stream,
|
|
1014
|
+
childRun: null,
|
|
1015
|
+
childLane: target,
|
|
1016
|
+
code: 'provider_worker_child_lane_update_failed',
|
|
1017
|
+
message: `Failed to update child lane ${stream}.`,
|
|
1018
|
+
status: 502
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
await refreshProviderLinearChildLaneProofSnapshotBestEffort({
|
|
1022
|
+
deps,
|
|
1023
|
+
runDir: context.runDir,
|
|
1024
|
+
auditPath: params.env[PROVIDER_LINEAR_AUDIT_ENV_VAR] ?? null,
|
|
1025
|
+
env: params.env,
|
|
1026
|
+
warningContext: `after accepting child lane ${stream}`
|
|
1027
|
+
});
|
|
1028
|
+
return {
|
|
1029
|
+
ok: true,
|
|
1030
|
+
operation: 'child-lane',
|
|
1031
|
+
action: 'accepted',
|
|
1032
|
+
issue: {
|
|
1033
|
+
id: context.issueId,
|
|
1034
|
+
identifier: context.issueIdentifier
|
|
1035
|
+
},
|
|
1036
|
+
source_setup: sourceSetup,
|
|
1037
|
+
stream,
|
|
1038
|
+
child_run: null,
|
|
1039
|
+
child_lane: accepted
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
function childLaneDecisionFailureResult(input) {
|
|
1043
|
+
if (input.outcome.kind === 'not_found') {
|
|
1044
|
+
return failureResult({
|
|
1045
|
+
action: input.action,
|
|
1046
|
+
issueId: input.context.issueId,
|
|
1047
|
+
issueIdentifier: input.context.issueIdentifier,
|
|
1048
|
+
sourceSetup: input.sourceSetup,
|
|
1049
|
+
stream: input.stream,
|
|
1050
|
+
childRun: null,
|
|
1051
|
+
childLane: null,
|
|
1052
|
+
code: 'provider_worker_child_lane_not_found',
|
|
1053
|
+
message: `No pending child lane found for stream ${input.stream}.`,
|
|
1054
|
+
status: 404
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
if (input.outcome.kind === 'provenance_invalid') {
|
|
1058
|
+
return failureResult({
|
|
1059
|
+
action: input.action,
|
|
1060
|
+
issueId: input.context.issueId,
|
|
1061
|
+
issueIdentifier: input.context.issueIdentifier,
|
|
1062
|
+
sourceSetup: input.sourceSetup,
|
|
1063
|
+
stream: input.stream,
|
|
1064
|
+
childRun: null,
|
|
1065
|
+
childLane: input.outcome.childLane,
|
|
1066
|
+
code: 'provider_worker_child_lane_provenance_invalid',
|
|
1067
|
+
message: input.outcome.message,
|
|
1068
|
+
status: 409
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
if (input.outcome.kind === 'in_flight') {
|
|
1072
|
+
return failureResult({
|
|
1073
|
+
action: input.action,
|
|
1074
|
+
issueId: input.context.issueId,
|
|
1075
|
+
issueIdentifier: input.context.issueIdentifier,
|
|
1076
|
+
sourceSetup: input.sourceSetup,
|
|
1077
|
+
stream: input.stream,
|
|
1078
|
+
childRun: null,
|
|
1079
|
+
childLane: input.outcome.childLane,
|
|
1080
|
+
code: 'provider_worker_child_lane_decision_in_flight',
|
|
1081
|
+
message: `Pending child lane ${input.stream} already has an in-flight ${input.outcome.inFlightAction} decision; wait for that parent-owned decision to finish before retrying ${input.action}.`,
|
|
1082
|
+
status: 409
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
return failureResult({
|
|
1086
|
+
action: input.action,
|
|
1087
|
+
issueId: input.context.issueId,
|
|
1088
|
+
issueIdentifier: input.context.issueIdentifier,
|
|
1089
|
+
sourceSetup: input.sourceSetup,
|
|
1090
|
+
stream: input.stream,
|
|
1091
|
+
childRun: null,
|
|
1092
|
+
childLane: input.outcome.childLane,
|
|
1093
|
+
code: 'provider_worker_child_lane_not_ready',
|
|
1094
|
+
message: `Pending child lane ${input.stream} is still launching and cannot be ${input.action === 'accept' ? 'accepted' : input.action === 'reject' ? 'rejected' : 'invalidated'} yet.`,
|
|
1095
|
+
status: 409
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
async function resolveSameAttemptParentDirtyRetrySuppression(runDir, issueId, env) {
|
|
1099
|
+
const auditPath = resolveProviderLinearAuditPath(env);
|
|
1100
|
+
if (!auditPath) {
|
|
1101
|
+
return null;
|
|
1102
|
+
}
|
|
1103
|
+
let rawProof;
|
|
1104
|
+
try {
|
|
1105
|
+
rawProof = await readFile(join(runDir, PROVIDER_LINEAR_WORKER_PROOF_FILENAME), 'utf8');
|
|
1106
|
+
}
|
|
1107
|
+
catch {
|
|
1108
|
+
return null;
|
|
1109
|
+
}
|
|
1110
|
+
let parsedProof;
|
|
1111
|
+
try {
|
|
1112
|
+
parsedProof = JSON.parse(rawProof);
|
|
1113
|
+
}
|
|
1114
|
+
catch {
|
|
1115
|
+
return null;
|
|
1116
|
+
}
|
|
1117
|
+
const attemptStartedAt = resolveProviderLinearWorkerAttemptStartedAt(parsedProof);
|
|
1118
|
+
if (!attemptStartedAt) {
|
|
1119
|
+
return null;
|
|
1120
|
+
}
|
|
1121
|
+
let audit = null;
|
|
1122
|
+
try {
|
|
1123
|
+
audit = await summarizeProviderLinearAuditPath(auditPath);
|
|
1124
|
+
}
|
|
1125
|
+
catch (error) {
|
|
1126
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1127
|
+
logger.warn(`provider linear child-lane warning: failed to summarize provider-linear audit at ${auditPath}; proceeding without same-attempt retry suppression. error=${message}`);
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
const suppression = findDeterministicProviderMutationSuppression(audit, 'child-lane', {
|
|
1131
|
+
recordedAtNotBefore: attemptStartedAt,
|
|
1132
|
+
action: 'launch',
|
|
1133
|
+
issueId
|
|
1134
|
+
});
|
|
1135
|
+
return suppression && isChildLaneParentDirtySuppressionCode(suppression.error_code)
|
|
1136
|
+
? suppression
|
|
1137
|
+
: null;
|
|
1138
|
+
}
|
|
1139
|
+
async function finalizePendingChildLaneDecision(input) {
|
|
1140
|
+
const decision = mapActionToDecision(input.action);
|
|
1141
|
+
return await input.deps.transactChildLanes(input.context.runDir, async (records) => {
|
|
1142
|
+
const resolved = await resolvePendingChildLaneDecisionTarget({
|
|
1143
|
+
records,
|
|
1144
|
+
context: input.context,
|
|
1145
|
+
childRunsRoot: input.childRunsRoot,
|
|
1146
|
+
stream: input.stream,
|
|
1147
|
+
action: input.action,
|
|
1148
|
+
deps: input.deps,
|
|
1149
|
+
now: input.now
|
|
1150
|
+
});
|
|
1151
|
+
if (resolved.kind !== 'ready') {
|
|
1152
|
+
return { records: resolved.records, result: resolved.outcome };
|
|
1153
|
+
}
|
|
1154
|
+
const target = resolved.target;
|
|
1155
|
+
const updated = {
|
|
1156
|
+
...target,
|
|
1157
|
+
decision,
|
|
1158
|
+
in_flight_action: null,
|
|
1159
|
+
in_flight_started_at: null,
|
|
1160
|
+
decision_at: input.now,
|
|
1161
|
+
decision_reason: input.reason ?? defaultDecisionReason(input.action, target)
|
|
1162
|
+
};
|
|
1163
|
+
const next = replaceChildLaneRecord(resolved.records, target, updated);
|
|
1164
|
+
if (!next) {
|
|
1165
|
+
return { records: resolved.records, result: { kind: 'not_found' } };
|
|
1166
|
+
}
|
|
1167
|
+
return {
|
|
1168
|
+
records: next,
|
|
1169
|
+
result: { kind: 'updated', childLane: updated, decision }
|
|
1170
|
+
};
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
async function claimPendingChildLaneAcceptance(input) {
|
|
1174
|
+
return await input.deps.transactChildLanes(input.context.runDir, async (records) => {
|
|
1175
|
+
const resolved = await resolvePendingChildLaneDecisionTarget({
|
|
1176
|
+
records,
|
|
1177
|
+
context: input.context,
|
|
1178
|
+
childRunsRoot: input.childRunsRoot,
|
|
1179
|
+
stream: input.stream,
|
|
1180
|
+
action: 'accept',
|
|
1181
|
+
deps: input.deps,
|
|
1182
|
+
now: input.now
|
|
1183
|
+
});
|
|
1184
|
+
if (resolved.kind !== 'ready') {
|
|
1185
|
+
return { records: resolved.records, result: resolved.outcome };
|
|
1186
|
+
}
|
|
1187
|
+
const target = resolved.target;
|
|
1188
|
+
const claimed = {
|
|
1189
|
+
...target,
|
|
1190
|
+
in_flight_action: 'accept',
|
|
1191
|
+
in_flight_started_at: input.now
|
|
1192
|
+
};
|
|
1193
|
+
const next = replaceChildLaneRecord(resolved.records, target, claimed);
|
|
1194
|
+
if (!next) {
|
|
1195
|
+
return { records: resolved.records, result: { kind: 'not_found' } };
|
|
1196
|
+
}
|
|
1197
|
+
return {
|
|
1198
|
+
records: next,
|
|
1199
|
+
result: { kind: 'claimed', childLane: claimed }
|
|
1200
|
+
};
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
async function resolvePendingChildLaneDecisionTarget(input) {
|
|
1204
|
+
const target = findLatestPendingChildLane(input.records, input.stream);
|
|
1205
|
+
if (!target) {
|
|
1206
|
+
return { kind: 'blocked', outcome: { kind: 'not_found' }, records: input.records };
|
|
1207
|
+
}
|
|
1208
|
+
const preRepairBlocked = resolveChildLaneDecisionPreRepairBlockedOutcome(input.context, input.stream, target, input.now);
|
|
1209
|
+
if (preRepairBlocked) {
|
|
1210
|
+
return { kind: 'blocked', outcome: preRepairBlocked, records: input.records };
|
|
1211
|
+
}
|
|
1212
|
+
let records = input.records;
|
|
1213
|
+
let resolvedTarget = target;
|
|
1214
|
+
const repaired = await repairPendingLaunchingChildLaneDecisionTarget({
|
|
1215
|
+
records,
|
|
1216
|
+
target,
|
|
1217
|
+
context: input.context,
|
|
1218
|
+
childRunsRoot: input.childRunsRoot,
|
|
1219
|
+
action: input.action,
|
|
1220
|
+
deps: input.deps,
|
|
1221
|
+
now: input.now
|
|
1222
|
+
});
|
|
1223
|
+
if (repaired) {
|
|
1224
|
+
records = repaired.records;
|
|
1225
|
+
if (!repaired.target) {
|
|
1226
|
+
return { kind: 'blocked', outcome: { kind: 'not_found' }, records };
|
|
1227
|
+
}
|
|
1228
|
+
resolvedTarget = repaired.target;
|
|
1229
|
+
}
|
|
1230
|
+
const postRepairBlocked = resolveChildLaneDecisionPreRepairBlockedOutcome(input.context, input.stream, resolvedTarget, input.now);
|
|
1231
|
+
if (postRepairBlocked) {
|
|
1232
|
+
return { kind: 'blocked', outcome: postRepairBlocked, records };
|
|
1233
|
+
}
|
|
1234
|
+
const blocked = resolveChildLaneDecisionReadinessBlockedOutcome(resolvedTarget, input.action);
|
|
1235
|
+
if (blocked) {
|
|
1236
|
+
return { kind: 'blocked', outcome: blocked, records };
|
|
1237
|
+
}
|
|
1238
|
+
return { kind: 'ready', records, target: resolvedTarget };
|
|
1239
|
+
}
|
|
1240
|
+
function resolveChildLaneDecisionPreRepairBlockedOutcome(context, stream, target, now) {
|
|
1241
|
+
const provenanceViolation = resolveChildLaneDecisionProvenanceViolation(context, stream, target);
|
|
1242
|
+
if (provenanceViolation) {
|
|
1243
|
+
return {
|
|
1244
|
+
kind: 'provenance_invalid',
|
|
1245
|
+
childLane: target,
|
|
1246
|
+
message: provenanceViolation
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
if (target.in_flight_action && !isStaleInFlightChildLane(target, now)) {
|
|
1250
|
+
return {
|
|
1251
|
+
kind: 'in_flight',
|
|
1252
|
+
childLane: target,
|
|
1253
|
+
inFlightAction: target.in_flight_action
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
return null;
|
|
1257
|
+
}
|
|
1258
|
+
function resolveChildLaneDecisionReadinessBlockedOutcome(target, action) {
|
|
1259
|
+
if (action === 'accept' && target.status === 'launching') {
|
|
1260
|
+
return {
|
|
1261
|
+
kind: 'not_ready',
|
|
1262
|
+
childLane: target
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
1267
|
+
async function repairPendingLaunchingChildLaneDecisionTarget(input) {
|
|
1268
|
+
if (input.target.status !== 'launching' || !input.target.run_id.startsWith('launching-')) {
|
|
1269
|
+
return null;
|
|
1270
|
+
}
|
|
1271
|
+
const candidate = await findChildLaneReservationRepairCandidate({
|
|
1272
|
+
reservation: input.target,
|
|
1273
|
+
context: input.context,
|
|
1274
|
+
childRunsRoot: input.childRunsRoot,
|
|
1275
|
+
action: input.action,
|
|
1276
|
+
deps: input.deps,
|
|
1277
|
+
now: input.now
|
|
1278
|
+
});
|
|
1279
|
+
if (!candidate) {
|
|
1280
|
+
return null;
|
|
1281
|
+
}
|
|
1282
|
+
const repairedReservation = {
|
|
1283
|
+
...input.target,
|
|
1284
|
+
decision: 'invalidated',
|
|
1285
|
+
in_flight_action: null,
|
|
1286
|
+
in_flight_started_at: null,
|
|
1287
|
+
decision_at: input.now,
|
|
1288
|
+
decision_reason: buildChildLaneReservationRepairDecisionReason(input.target, candidate)
|
|
1289
|
+
};
|
|
1290
|
+
const withRetiredReservation = replaceChildLaneRecord(input.records, input.target, repairedReservation);
|
|
1291
|
+
if (!withRetiredReservation) {
|
|
1292
|
+
return null;
|
|
1293
|
+
}
|
|
1294
|
+
const existingCandidate = withRetiredReservation.find((entry) => matchesChildLaneRecordIdentity(entry, candidate)) ?? null;
|
|
1295
|
+
if (existingCandidate && existingCandidate.decision !== 'pending') {
|
|
1296
|
+
return null;
|
|
1297
|
+
}
|
|
1298
|
+
const repairedTarget = existingCandidate
|
|
1299
|
+
? mergeCompletedChildLaneWithParentDecision(existingCandidate, candidate)
|
|
1300
|
+
: candidate;
|
|
1301
|
+
const nextRecords = existingCandidate
|
|
1302
|
+
? (replaceChildLaneRecord(withRetiredReservation, existingCandidate, repairedTarget) ?? withRetiredReservation)
|
|
1303
|
+
: [...withRetiredReservation.filter((entry) => !matchesChildLaneRecordIdentity(entry, repairedTarget)), repairedTarget];
|
|
1304
|
+
return {
|
|
1305
|
+
records: nextRecords,
|
|
1306
|
+
target: repairedTarget.decision === 'pending' ? repairedTarget : null
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
async function releaseClaimedChildLaneAcceptance(runDir, target, deps) {
|
|
1310
|
+
await deps.transactChildLanes(runDir, async (records) => {
|
|
1311
|
+
const next = replaceChildLaneRecord(records, target, {
|
|
1312
|
+
...target,
|
|
1313
|
+
in_flight_action: null,
|
|
1314
|
+
in_flight_started_at: null
|
|
1315
|
+
});
|
|
1316
|
+
return {
|
|
1317
|
+
records: next ?? records,
|
|
1318
|
+
result: undefined
|
|
1319
|
+
};
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
async function finalizeClaimedChildLaneDecision(input) {
|
|
1323
|
+
return await input.deps.transactChildLanes(input.context.runDir, async (records) => {
|
|
1324
|
+
const next = replaceChildLaneRecord(records, input.target, {
|
|
1325
|
+
...input.target,
|
|
1326
|
+
decision: input.decision,
|
|
1327
|
+
in_flight_action: null,
|
|
1328
|
+
in_flight_started_at: null,
|
|
1329
|
+
decision_at: input.now,
|
|
1330
|
+
decision_reason: input.decisionReason
|
|
1331
|
+
});
|
|
1332
|
+
if (!next) {
|
|
1333
|
+
return {
|
|
1334
|
+
records,
|
|
1335
|
+
result: null
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
const updated = findChildLaneByIdentity(next, input.target);
|
|
1339
|
+
return {
|
|
1340
|
+
records: next,
|
|
1341
|
+
result: updated
|
|
1342
|
+
};
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
function findLatestPendingChildLane(records, stream) {
|
|
1346
|
+
return [...records]
|
|
1347
|
+
.reverse()
|
|
1348
|
+
.find((entry) => entry.stream === stream && entry.decision === 'pending') ?? null;
|
|
1349
|
+
}
|
|
1350
|
+
function findChildLaneByIdentity(records, target) {
|
|
1351
|
+
return records.find((entry) => matchesChildLaneRecordIdentity(entry, target)) ?? null;
|
|
1352
|
+
}
|
|
1353
|
+
async function waitForRecoveredChildLaneLaunchCandidate(input) {
|
|
1354
|
+
const readRecoveredCandidate = async () => {
|
|
1355
|
+
try {
|
|
1356
|
+
return await findRecoveredChildLaneLaunchCandidate({
|
|
1357
|
+
reservation: input.reservation,
|
|
1358
|
+
context: input.context,
|
|
1359
|
+
childRunsRoot: input.childRunsRoot,
|
|
1360
|
+
deps: input.deps,
|
|
1361
|
+
now: input.now
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
catch (error) {
|
|
1365
|
+
input.deps.warn(`provider-linear-child-lane warning: failed to scan for recovered child-lane launch candidate: ${error instanceof Error ? error.message : String(error)}`);
|
|
1366
|
+
return null;
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
while (!input.isExecSettled()) {
|
|
1370
|
+
const candidate = await readRecoveredCandidate();
|
|
1371
|
+
if (candidate) {
|
|
1372
|
+
return candidate;
|
|
1373
|
+
}
|
|
1374
|
+
await input.deps.sleep(PROVIDER_LINEAR_CHILD_LANE_LAUNCH_RECOVERY_POLL_INTERVAL_MS);
|
|
1375
|
+
}
|
|
1376
|
+
return await readRecoveredCandidate();
|
|
1377
|
+
}
|
|
1378
|
+
async function findRecoveredChildLaneLaunchCandidate(input) {
|
|
1379
|
+
const childTaskCliRoot = join(input.childRunsRoot, input.reservation.task_id, 'cli');
|
|
1380
|
+
let entries;
|
|
1381
|
+
try {
|
|
1382
|
+
entries = await input.deps.readDir(childTaskCliRoot);
|
|
1383
|
+
}
|
|
1384
|
+
catch (error) {
|
|
1385
|
+
if (error?.code === 'ENOENT') {
|
|
1386
|
+
return null;
|
|
1387
|
+
}
|
|
1388
|
+
throw error;
|
|
1389
|
+
}
|
|
1390
|
+
const candidates = [];
|
|
1391
|
+
for (const entry of entries) {
|
|
1392
|
+
if (!entry.isDirectory() || entry.name === input.reservation.run_id) {
|
|
1393
|
+
continue;
|
|
1394
|
+
}
|
|
1395
|
+
const runId = (() => {
|
|
1396
|
+
try {
|
|
1397
|
+
return sanitizeRunId(entry.name);
|
|
1398
|
+
}
|
|
1399
|
+
catch {
|
|
1400
|
+
return null;
|
|
1401
|
+
}
|
|
1402
|
+
})();
|
|
1403
|
+
if (!runId) {
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
const candidate = await readRecoveredChildLaneLaunchCandidate({
|
|
1407
|
+
reservation: input.reservation,
|
|
1408
|
+
context: input.context,
|
|
1409
|
+
childRunsRoot: input.childRunsRoot,
|
|
1410
|
+
deps: input.deps,
|
|
1411
|
+
now: input.now,
|
|
1412
|
+
runId
|
|
1413
|
+
}).catch(() => null);
|
|
1414
|
+
if (candidate) {
|
|
1415
|
+
candidates.push(candidate);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
// Fail closed when recovery is ambiguous; parent repair needs one exact child run candidate.
|
|
1419
|
+
return candidates.length === 1 ? candidates[0] : null;
|
|
1420
|
+
}
|
|
1421
|
+
async function readRecoveredChildLaneLaunchCandidate(input) {
|
|
1422
|
+
const artifactRoot = resolve(input.childRunsRoot, input.reservation.task_id, 'cli', input.runId);
|
|
1423
|
+
const manifestPath = join(artifactRoot, 'manifest.json');
|
|
1424
|
+
let rawManifest;
|
|
1425
|
+
try {
|
|
1426
|
+
rawManifest = JSON.parse(await readFile(manifestPath, 'utf8'));
|
|
1427
|
+
}
|
|
1428
|
+
catch {
|
|
1429
|
+
return null;
|
|
1430
|
+
}
|
|
1431
|
+
const childRun = parseProviderLinearChildLaneRunManifest({
|
|
1432
|
+
manifest: rawManifest,
|
|
1433
|
+
repoRoot: input.context.repoRoot,
|
|
1434
|
+
artifactRoot,
|
|
1435
|
+
manifestPath,
|
|
1436
|
+
taskId: input.reservation.task_id,
|
|
1437
|
+
runId: input.runId,
|
|
1438
|
+
parentRunId: input.context.runId,
|
|
1439
|
+
issueId: input.context.issueId,
|
|
1440
|
+
issueIdentifier: input.context.issueIdentifier
|
|
1441
|
+
});
|
|
1442
|
+
if (!childRun) {
|
|
1443
|
+
return null;
|
|
1444
|
+
}
|
|
1445
|
+
const proof = await input.deps.readChildLaneProof(join(artifactRoot, PROVIDER_LINEAR_CHILD_LANE_PROOF_FILENAME)).catch(() => null);
|
|
1446
|
+
if (!proof || !proof.scope || !proof.parent_snapshot) {
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
const childLane = buildRepairedChildLaneRecord({
|
|
1450
|
+
reservation: input.reservation,
|
|
1451
|
+
childRun,
|
|
1452
|
+
proof,
|
|
1453
|
+
now: input.now
|
|
1454
|
+
});
|
|
1455
|
+
const proofViolation = resolveChildLaneReservationRepairProofViolation({
|
|
1456
|
+
reservation: input.reservation,
|
|
1457
|
+
childRun,
|
|
1458
|
+
candidate: childLane,
|
|
1459
|
+
proof,
|
|
1460
|
+
context: input.context,
|
|
1461
|
+
repoRoot: input.context.repoRoot,
|
|
1462
|
+
childRunsRoot: input.childRunsRoot,
|
|
1463
|
+
action: childRun.status === 'succeeded' ? 'accept' : 'reject'
|
|
1464
|
+
});
|
|
1465
|
+
if (proofViolation) {
|
|
1466
|
+
return null;
|
|
1467
|
+
}
|
|
1468
|
+
return {
|
|
1469
|
+
childRun,
|
|
1470
|
+
childLane,
|
|
1471
|
+
proof
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
async function findChildLaneReservationRepairCandidate(input) {
|
|
1475
|
+
const childTaskCliRoot = join(input.childRunsRoot, input.reservation.task_id, 'cli');
|
|
1476
|
+
let entries;
|
|
1477
|
+
try {
|
|
1478
|
+
entries = await input.deps.readDir(childTaskCliRoot);
|
|
1479
|
+
}
|
|
1480
|
+
catch (error) {
|
|
1481
|
+
if (error?.code === 'ENOENT') {
|
|
1482
|
+
return null;
|
|
1483
|
+
}
|
|
1484
|
+
throw error;
|
|
1485
|
+
}
|
|
1486
|
+
const candidates = [];
|
|
1487
|
+
for (const entry of entries) {
|
|
1488
|
+
if (!entry.isDirectory() || entry.name === input.reservation.run_id) {
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
1491
|
+
const runId = (() => {
|
|
1492
|
+
try {
|
|
1493
|
+
return sanitizeRunId(entry.name);
|
|
1494
|
+
}
|
|
1495
|
+
catch {
|
|
1496
|
+
return null;
|
|
1497
|
+
}
|
|
1498
|
+
})();
|
|
1499
|
+
if (!runId) {
|
|
1500
|
+
continue;
|
|
1501
|
+
}
|
|
1502
|
+
const candidate = await readChildLaneReservationRepairCandidate({
|
|
1503
|
+
reservation: input.reservation,
|
|
1504
|
+
context: input.context,
|
|
1505
|
+
childRunsRoot: input.childRunsRoot,
|
|
1506
|
+
action: input.action,
|
|
1507
|
+
deps: input.deps,
|
|
1508
|
+
now: input.now,
|
|
1509
|
+
runId
|
|
1510
|
+
});
|
|
1511
|
+
if (candidate) {
|
|
1512
|
+
candidates.push(candidate);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
return candidates.length === 1 ? candidates[0] : null;
|
|
1516
|
+
}
|
|
1517
|
+
async function readChildLaneReservationRepairCandidate(input) {
|
|
1518
|
+
const artifactRoot = resolve(input.childRunsRoot, input.reservation.task_id, 'cli', input.runId);
|
|
1519
|
+
const manifestPath = join(artifactRoot, 'manifest.json');
|
|
1520
|
+
let rawManifest;
|
|
1521
|
+
try {
|
|
1522
|
+
rawManifest = JSON.parse(await readFile(manifestPath, 'utf8'));
|
|
1523
|
+
}
|
|
1524
|
+
catch {
|
|
1525
|
+
return null;
|
|
1526
|
+
}
|
|
1527
|
+
const childRun = parseProviderLinearChildLaneRunManifest({
|
|
1528
|
+
manifest: rawManifest,
|
|
1529
|
+
repoRoot: input.context.repoRoot,
|
|
1530
|
+
artifactRoot,
|
|
1531
|
+
manifestPath,
|
|
1532
|
+
taskId: input.reservation.task_id,
|
|
1533
|
+
runId: input.runId,
|
|
1534
|
+
parentRunId: input.context.runId,
|
|
1535
|
+
issueId: input.context.issueId,
|
|
1536
|
+
issueIdentifier: input.context.issueIdentifier
|
|
1537
|
+
});
|
|
1538
|
+
if (!childRun) {
|
|
1539
|
+
return null;
|
|
1540
|
+
}
|
|
1541
|
+
const proof = await input.deps.readChildLaneProof(join(artifactRoot, PROVIDER_LINEAR_CHILD_LANE_PROOF_FILENAME)).catch(() => null);
|
|
1542
|
+
if (!proof || !proof.scope || !proof.parent_snapshot) {
|
|
1543
|
+
return null;
|
|
1544
|
+
}
|
|
1545
|
+
const candidate = buildRepairedChildLaneRecord({
|
|
1546
|
+
reservation: input.reservation,
|
|
1547
|
+
childRun,
|
|
1548
|
+
proof,
|
|
1549
|
+
now: input.now
|
|
1550
|
+
});
|
|
1551
|
+
const proofViolation = resolveChildLaneReservationRepairProofViolation({
|
|
1552
|
+
reservation: input.reservation,
|
|
1553
|
+
childRun,
|
|
1554
|
+
candidate,
|
|
1555
|
+
proof,
|
|
1556
|
+
context: input.context,
|
|
1557
|
+
repoRoot: input.context.repoRoot,
|
|
1558
|
+
childRunsRoot: input.childRunsRoot,
|
|
1559
|
+
action: input.action
|
|
1560
|
+
});
|
|
1561
|
+
return proofViolation ? null : candidate;
|
|
1562
|
+
}
|
|
1563
|
+
function parseProviderLinearChildLaneRunManifest(input) {
|
|
1564
|
+
const manifestRunId = normalizeOptionalString(input.manifest.run_id);
|
|
1565
|
+
const manifestTaskId = normalizeOptionalString(input.manifest.task_id);
|
|
1566
|
+
const manifestPipelineId = normalizeOptionalString(input.manifest.pipeline_id);
|
|
1567
|
+
const manifestParentRunId = normalizeOptionalString(input.manifest.parent_run_id);
|
|
1568
|
+
const manifestIssueId = normalizeOptionalString(input.manifest.issue_id);
|
|
1569
|
+
const manifestIssueIdentifier = normalizeOptionalString(input.manifest.issue_identifier);
|
|
1570
|
+
const status = normalizeOptionalString(input.manifest.status);
|
|
1571
|
+
if (manifestRunId !== input.runId ||
|
|
1572
|
+
manifestTaskId !== input.taskId ||
|
|
1573
|
+
manifestPipelineId !== PROVIDER_LINEAR_CHILD_LANE_PIPELINE_ID ||
|
|
1574
|
+
manifestParentRunId !== input.parentRunId ||
|
|
1575
|
+
manifestIssueId !== input.issueId ||
|
|
1576
|
+
manifestIssueIdentifier !== input.issueIdentifier ||
|
|
1577
|
+
!status) {
|
|
1578
|
+
return null;
|
|
1579
|
+
}
|
|
1580
|
+
const recordedArtifactRoot = normalizeOptionalString(input.manifest.artifact_root);
|
|
1581
|
+
if (recordedArtifactRoot && resolveRunPath(input.repoRoot, recordedArtifactRoot) !== input.artifactRoot) {
|
|
1582
|
+
return null;
|
|
1583
|
+
}
|
|
1584
|
+
const recordedManifestPath = normalizeOptionalString(input.manifest.manifest_path) ?? normalizeOptionalString(input.manifest.manifest);
|
|
1585
|
+
if (recordedManifestPath && resolveRunPath(input.repoRoot, recordedManifestPath) !== input.manifestPath) {
|
|
1586
|
+
return null;
|
|
1587
|
+
}
|
|
1588
|
+
const recordedLogPath = normalizeOptionalString(input.manifest.log_path);
|
|
1589
|
+
const logPath = recordedLogPath ? resolveRunPath(input.repoRoot, recordedLogPath) : join(input.artifactRoot, 'run.log');
|
|
1590
|
+
if (recordedLogPath && !isPathWithinRoot(input.artifactRoot, logPath)) {
|
|
1591
|
+
return null;
|
|
1592
|
+
}
|
|
1593
|
+
return {
|
|
1594
|
+
run_id: input.runId,
|
|
1595
|
+
task_id: input.taskId,
|
|
1596
|
+
pipeline_id: PROVIDER_LINEAR_CHILD_LANE_PIPELINE_ID,
|
|
1597
|
+
status,
|
|
1598
|
+
artifact_root: input.artifactRoot,
|
|
1599
|
+
manifest_path: input.manifestPath,
|
|
1600
|
+
log_path: logPath,
|
|
1601
|
+
summary: stripNonApplicableGuardrailSummaryLines(input.manifest, normalizeOptionalString(input.manifest.summary)),
|
|
1602
|
+
guardrails_required: resolveGuardrailsRequiredForManifest(input.manifest),
|
|
1603
|
+
guardrails_required_source: resolveGuardrailsRequiredSourceForManifest(input.manifest),
|
|
1604
|
+
guardrail_command_count: countGuardrailCommands(input.manifest),
|
|
1605
|
+
runtime_mode_requested: normalizeOptionalString(input.manifest.runtime_mode_requested),
|
|
1606
|
+
runtime_mode: normalizeOptionalString(input.manifest.runtime_mode),
|
|
1607
|
+
runtime_provider: normalizeOptionalString(input.manifest.runtime_provider)
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
function buildRepairedChildLaneRecord(input) {
|
|
1611
|
+
const zeroBytePatch = input.childRun.status === 'succeeded' && input.proof.patch_bytes === 0;
|
|
1612
|
+
return {
|
|
1613
|
+
stream: input.reservation.stream,
|
|
1614
|
+
pipeline_id: PROVIDER_LINEAR_CHILD_LANE_PIPELINE_ID,
|
|
1615
|
+
task_id: input.reservation.task_id,
|
|
1616
|
+
run_id: input.childRun.run_id,
|
|
1617
|
+
status: input.childRun.status,
|
|
1618
|
+
manifest_path: input.childRun.manifest_path,
|
|
1619
|
+
artifact_root: input.childRun.artifact_root,
|
|
1620
|
+
log_path: input.childRun.log_path,
|
|
1621
|
+
summary: input.childRun.summary,
|
|
1622
|
+
guardrails_required: input.childRun.guardrails_required,
|
|
1623
|
+
guardrails_required_source: input.childRun.guardrails_required_source,
|
|
1624
|
+
guardrail_command_count: input.childRun.guardrail_command_count,
|
|
1625
|
+
issue_id: input.reservation.issue_id,
|
|
1626
|
+
issue_identifier: input.reservation.issue_identifier,
|
|
1627
|
+
workspace_path: input.reservation.workspace_path,
|
|
1628
|
+
source_setup: input.reservation.source_setup,
|
|
1629
|
+
launched_at: input.reservation.launched_at,
|
|
1630
|
+
purpose: input.reservation.purpose,
|
|
1631
|
+
instructions: input.reservation.instructions,
|
|
1632
|
+
scope: input.reservation.scope,
|
|
1633
|
+
parent_snapshot: input.reservation.parent_snapshot,
|
|
1634
|
+
lane_workspace_path: normalizeOptionalString(input.proof.lane_workspace_path),
|
|
1635
|
+
patch_artifact_path: normalizeOptionalString(input.proof.patch_artifact_path),
|
|
1636
|
+
patch_bytes: Number.isFinite(input.proof.patch_bytes) ? input.proof.patch_bytes : null,
|
|
1637
|
+
decision: zeroBytePatch ? 'rejected' : 'pending',
|
|
1638
|
+
in_flight_action: null,
|
|
1639
|
+
in_flight_started_at: null,
|
|
1640
|
+
decision_at: zeroBytePatch ? input.now : null,
|
|
1641
|
+
decision_reason: zeroBytePatch ? buildNoOutputAdvisoryChildLaneDecisionReason(input.childRun) : null
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
function resolveChildLaneReservationRecoveryTimingViolation(input) {
|
|
1645
|
+
const reservationLaunchedAt = normalizeOptionalString(input.reservation.launched_at);
|
|
1646
|
+
const proofCompletedAt = normalizeOptionalString(input.proof.updated_at) ?? normalizeOptionalString(input.proof.last_event_at);
|
|
1647
|
+
if (!reservationLaunchedAt || !proofCompletedAt) {
|
|
1648
|
+
return 'Child lane proof timing is missing; cannot repair or recover a launching reservation safely.';
|
|
1649
|
+
}
|
|
1650
|
+
const reservationLaunchedMs = Date.parse(reservationLaunchedAt);
|
|
1651
|
+
const proofCompletedMs = Date.parse(proofCompletedAt);
|
|
1652
|
+
if (!Number.isFinite(reservationLaunchedMs) || !Number.isFinite(proofCompletedMs)) {
|
|
1653
|
+
return 'Child lane proof timing is invalid; cannot repair or recover a launching reservation safely.';
|
|
1654
|
+
}
|
|
1655
|
+
if (proofCompletedMs < reservationLaunchedMs) {
|
|
1656
|
+
return 'Child lane proof completion predates the pending launching reservation.';
|
|
1657
|
+
}
|
|
1658
|
+
return null;
|
|
1659
|
+
}
|
|
1660
|
+
function resolveChildLaneReservationRepairProofViolation(input) {
|
|
1661
|
+
if (normalizeOptionalString(input.proof.purpose) !== normalizeOptionalString(input.reservation.purpose)) {
|
|
1662
|
+
return 'Child lane proof purpose does not match the pending launching reservation.';
|
|
1663
|
+
}
|
|
1664
|
+
if (normalizeOptionalString(input.proof.instructions) !== normalizeOptionalString(input.reservation.instructions)) {
|
|
1665
|
+
return 'Child lane proof instructions do not match the pending launching reservation.';
|
|
1666
|
+
}
|
|
1667
|
+
if (!areChildLaneScopesEquivalent(input.proof.scope, input.reservation.scope)) {
|
|
1668
|
+
return 'Child lane proof scope does not match the pending launching reservation.';
|
|
1669
|
+
}
|
|
1670
|
+
if (!areChildLaneParentSnapshotsEquivalent(input.proof.parent_snapshot, input.reservation.parent_snapshot)) {
|
|
1671
|
+
return 'Child lane proof parent snapshot does not match the pending launching reservation.';
|
|
1672
|
+
}
|
|
1673
|
+
if (normalizeOptionalString(input.proof.status) !== input.childRun.status) {
|
|
1674
|
+
return 'Child lane proof status does not match the child manifest status.';
|
|
1675
|
+
}
|
|
1676
|
+
const recoveryTimingViolation = resolveChildLaneReservationRecoveryTimingViolation({
|
|
1677
|
+
reservation: input.reservation,
|
|
1678
|
+
proof: input.proof
|
|
1679
|
+
});
|
|
1680
|
+
if (recoveryTimingViolation) {
|
|
1681
|
+
return recoveryTimingViolation;
|
|
1682
|
+
}
|
|
1683
|
+
const artifactRoot = resolveAcceptedChildLaneArtifactRoot(input.repoRoot, input.childRunsRoot, input.candidate);
|
|
1684
|
+
if (!artifactRoot) {
|
|
1685
|
+
return 'Child lane artifact root must stay anchored to the expected workspace-local child run directory before reservation repair.';
|
|
1686
|
+
}
|
|
1687
|
+
const proofLineageViolation = resolveChildLaneProofLineageViolation(input.context.runId, input.candidate, input.proof);
|
|
1688
|
+
if (proofLineageViolation) {
|
|
1689
|
+
return proofLineageViolation;
|
|
1690
|
+
}
|
|
1691
|
+
if (input.action !== 'accept') {
|
|
1692
|
+
return null;
|
|
1693
|
+
}
|
|
1694
|
+
const patchArtifactPath = resolveAcceptedPatchArtifactPath(input.repoRoot, input.candidate, artifactRoot);
|
|
1695
|
+
if (!patchArtifactPath) {
|
|
1696
|
+
return 'Child lane proof patch artifact path must stay within the child lane artifact root before reservation repair.';
|
|
1697
|
+
}
|
|
1698
|
+
return resolveAcceptedChildLaneProofViolation(input.context.runId, input.candidate, input.proof, input.repoRoot, artifactRoot, patchArtifactPath);
|
|
1699
|
+
}
|
|
1700
|
+
function mergeCompletedChildLaneWithParentDecision(current, completed) {
|
|
1701
|
+
if (current.decision === 'pending') {
|
|
1702
|
+
return {
|
|
1703
|
+
...completed,
|
|
1704
|
+
in_flight_action: current.in_flight_action,
|
|
1705
|
+
in_flight_started_at: current.in_flight_started_at
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
return {
|
|
1709
|
+
...completed,
|
|
1710
|
+
decision: current.decision,
|
|
1711
|
+
in_flight_action: null,
|
|
1712
|
+
in_flight_started_at: null,
|
|
1713
|
+
decision_at: current.decision_at,
|
|
1714
|
+
decision_reason: current.decision_reason
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
function replaceChildLaneRecord(records, target, replacement) {
|
|
1718
|
+
let replaced = false;
|
|
1719
|
+
const next = records.map((entry) => {
|
|
1720
|
+
if (replaced || !matchesChildLaneRecordIdentity(entry, target)) {
|
|
1721
|
+
return entry;
|
|
1722
|
+
}
|
|
1723
|
+
replaced = true;
|
|
1724
|
+
return replacement;
|
|
1725
|
+
});
|
|
1726
|
+
return replaced ? next : null;
|
|
1727
|
+
}
|
|
1728
|
+
function matchesChildLaneRecordIdentity(left, right) {
|
|
1729
|
+
return left.stream === right.stream && left.task_id === right.task_id && left.run_id === right.run_id;
|
|
1730
|
+
}
|
|
1731
|
+
function buildChildLaneReservationRepairDecisionReason(reservation, repaired) {
|
|
1732
|
+
return `Reconciled stale launching reservation ${reservation.run_id} to recovered child run ${repaired.run_id} after matching manifest/proof recovery.`;
|
|
1733
|
+
}
|
|
1734
|
+
function normalizeAction(value) {
|
|
1735
|
+
return value === 'launch' || value === 'accept' || value === 'reject' || value === 'invalidate'
|
|
1736
|
+
? value
|
|
1737
|
+
: null;
|
|
1738
|
+
}
|
|
1739
|
+
function mapActionToDecision(action) {
|
|
1740
|
+
if (action === 'accept') {
|
|
1741
|
+
return 'accepted';
|
|
1742
|
+
}
|
|
1743
|
+
if (action === 'reject') {
|
|
1744
|
+
return 'rejected';
|
|
1745
|
+
}
|
|
1746
|
+
return 'invalidated';
|
|
1747
|
+
}
|
|
1748
|
+
function defaultDecisionReason(action, childLane) {
|
|
1749
|
+
if (action === 'accept') {
|
|
1750
|
+
return `Parent accepted patch artifact ${childLane.patch_artifact_path ?? '(missing patch path)'}.`;
|
|
1751
|
+
}
|
|
1752
|
+
if (action === 'reject') {
|
|
1753
|
+
return 'Parent rejected child lane output.';
|
|
1754
|
+
}
|
|
1755
|
+
return 'Parent invalidated child lane output.';
|
|
1756
|
+
}
|
|
1757
|
+
function buildReservedChildLaneRecord(input) {
|
|
1758
|
+
const reservationRunId = buildChildLaneReservationRunId();
|
|
1759
|
+
const artifactRoot = join(input.childRunsRoot, input.childTaskId, 'cli', reservationRunId);
|
|
1760
|
+
return {
|
|
1761
|
+
stream: input.stream,
|
|
1762
|
+
pipeline_id: PROVIDER_LINEAR_CHILD_LANE_PIPELINE_ID,
|
|
1763
|
+
task_id: input.childTaskId,
|
|
1764
|
+
run_id: reservationRunId,
|
|
1765
|
+
status: 'launching',
|
|
1766
|
+
manifest_path: join(artifactRoot, 'manifest.json'),
|
|
1767
|
+
artifact_root: artifactRoot,
|
|
1768
|
+
log_path: join(artifactRoot, 'run.log'),
|
|
1769
|
+
summary: 'Child lane reserved before child run startup.',
|
|
1770
|
+
issue_id: input.context.issueId,
|
|
1771
|
+
issue_identifier: input.context.issueIdentifier,
|
|
1772
|
+
workspace_path: input.context.repoRoot,
|
|
1773
|
+
source_setup: input.sourceSetup,
|
|
1774
|
+
launched_at: input.now,
|
|
1775
|
+
purpose: input.purpose,
|
|
1776
|
+
instructions: normalizeOptionalString(input.instructions),
|
|
1777
|
+
scope: input.scope,
|
|
1778
|
+
parent_snapshot: input.parentSnapshot,
|
|
1779
|
+
lane_workspace_path: null,
|
|
1780
|
+
patch_artifact_path: null,
|
|
1781
|
+
patch_bytes: null,
|
|
1782
|
+
decision: 'pending',
|
|
1783
|
+
in_flight_action: null,
|
|
1784
|
+
in_flight_started_at: null,
|
|
1785
|
+
decision_at: null,
|
|
1786
|
+
decision_reason: null
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
function buildChildLaneReservationRunId() {
|
|
1790
|
+
return `launching-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
1791
|
+
}
|
|
1792
|
+
function findPendingChildLaneConflict(records, stream, scope) {
|
|
1793
|
+
return records.find((entry) => entry.decision === 'pending' &&
|
|
1794
|
+
(entry.stream === stream || scopesOverlap(entry.scope, scope))) ?? null;
|
|
1795
|
+
}
|
|
1796
|
+
function selectChildLanesCountingTowardParallelFirstCap(records, now) {
|
|
1797
|
+
return records.filter((entry) => !isStaleInFlightChildLane(entry, now) &&
|
|
1798
|
+
(entry.decision === 'pending' || entry.in_flight_action !== null));
|
|
1799
|
+
}
|
|
1800
|
+
function isStaleInFlightChildLane(entry, now) {
|
|
1801
|
+
if (!entry.in_flight_action) {
|
|
1802
|
+
return false;
|
|
1803
|
+
}
|
|
1804
|
+
const startedAt = normalizeOptionalString(entry.in_flight_started_at);
|
|
1805
|
+
if (!startedAt) {
|
|
1806
|
+
return true;
|
|
1807
|
+
}
|
|
1808
|
+
const startedMs = Date.parse(startedAt);
|
|
1809
|
+
const nowMs = Date.parse(now);
|
|
1810
|
+
if (!Number.isFinite(startedMs) || !Number.isFinite(nowMs)) {
|
|
1811
|
+
return true;
|
|
1812
|
+
}
|
|
1813
|
+
return nowMs - startedMs >= PROVIDER_LINEAR_CHILD_LANE_IN_FLIGHT_STALE_MS;
|
|
1814
|
+
}
|
|
1815
|
+
function describeChildLaneCapExhaustion(records) {
|
|
1816
|
+
const sample = records
|
|
1817
|
+
.slice(0, PROVIDER_LINEAR_CHILD_LANE_PARALLEL_FIRST_CAP)
|
|
1818
|
+
.map((entry) => `${entry.stream}:${entry.run_id}`)
|
|
1819
|
+
.join(', ');
|
|
1820
|
+
return `Same-issue child-lane cap exhausted: ${records.length}/${PROVIDER_LINEAR_CHILD_LANE_PARALLEL_FIRST_CAP} active, pending, or unaccepted lane(s) already count toward the parallel-first cap (${sample}). Do not launch another lane; record \`stay_serial\` with reason \`existing_child_lane_active\` and include \`cap_exhausted\` in the summary. This cap preserves provider admission constraints instead of bypassing them.`;
|
|
1821
|
+
}
|
|
1822
|
+
function describePendingChildLaneConflict(stream, conflict) {
|
|
1823
|
+
return conflict.stream === stream
|
|
1824
|
+
? `Child lane stream ${stream} already has unresolved lane ${conflict.run_id}; accept, reject, or invalidate that lane before relaunching the same stream.`
|
|
1825
|
+
: `Child lane scope overlaps unresolved lane ${conflict.stream} (${conflict.run_id}); reject, accept, or invalidate that lane before launching another overlapping lane.`;
|
|
1826
|
+
}
|
|
1827
|
+
async function removeReservedChildLane(runDir, reserved, deps) {
|
|
1828
|
+
await deps.transactChildLanes(runDir, async (records) => ({
|
|
1829
|
+
records: removePendingChildLaneRecord(records, reserved),
|
|
1830
|
+
result: undefined
|
|
1831
|
+
}));
|
|
1832
|
+
}
|
|
1833
|
+
function removePendingChildLaneRecord(records, target) {
|
|
1834
|
+
return records.filter((entry) => !matchesChildLaneRecordIdentity(entry, target) || entry.decision !== 'pending');
|
|
1835
|
+
}
|
|
1836
|
+
function resolveChildLaneDecisionProvenanceViolation(context, stream, childLane) {
|
|
1837
|
+
const expectedTaskId = `${context.taskId}-${stream}`;
|
|
1838
|
+
if (childLane.pipeline_id !== PROVIDER_LINEAR_CHILD_LANE_PIPELINE_ID) {
|
|
1839
|
+
return `Pending child lane ${stream} must remain recorded as ${PROVIDER_LINEAR_CHILD_LANE_PIPELINE_ID}; recorded pipeline was ${childLane.pipeline_id}.`;
|
|
1840
|
+
}
|
|
1841
|
+
if (childLane.task_id !== expectedTaskId) {
|
|
1842
|
+
return `Pending child lane ${stream} must stay bound to task ${expectedTaskId}; recorded task was ${childLane.task_id}.`;
|
|
1843
|
+
}
|
|
1844
|
+
if (childLane.issue_id !== context.issueId || childLane.issue_identifier !== context.issueIdentifier) {
|
|
1845
|
+
return `Pending child lane ${stream} must stay bound to issue ${context.issueIdentifier}; recorded issue was ${childLane.issue_identifier} (${childLane.issue_id}).`;
|
|
1846
|
+
}
|
|
1847
|
+
return null;
|
|
1848
|
+
}
|
|
1849
|
+
function resolveAcceptedPatchArtifactPath(repoRoot, childLane, artifactRoot) {
|
|
1850
|
+
const patchArtifactPath = normalizeOptionalString(childLane.patch_artifact_path);
|
|
1851
|
+
if (!patchArtifactPath) {
|
|
1852
|
+
return null;
|
|
1853
|
+
}
|
|
1854
|
+
const resolvedPatchArtifactPath = resolveRunPath(repoRoot, patchArtifactPath);
|
|
1855
|
+
return isPathWithinRoot(artifactRoot, resolvedPatchArtifactPath) ? resolvedPatchArtifactPath : null;
|
|
1856
|
+
}
|
|
1857
|
+
function resolveAcceptedChildLaneArtifactRoot(repoRoot, childRunsRoot, childLane) {
|
|
1858
|
+
const taskId = normalizeOptionalString(childLane.task_id);
|
|
1859
|
+
const artifactRoot = normalizeOptionalString(childLane.artifact_root);
|
|
1860
|
+
const manifestPath = normalizeOptionalString(childLane.manifest_path);
|
|
1861
|
+
const runId = normalizeOptionalString(childLane.run_id);
|
|
1862
|
+
if (!taskId || !artifactRoot || !runId) {
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
let safeRunId;
|
|
1866
|
+
try {
|
|
1867
|
+
safeRunId = sanitizeRunId(runId);
|
|
1868
|
+
}
|
|
1869
|
+
catch {
|
|
1870
|
+
return null;
|
|
1871
|
+
}
|
|
1872
|
+
const expectedArtifactRoot = resolve(childRunsRoot, taskId, 'cli', safeRunId);
|
|
1873
|
+
const resolvedArtifactRoot = resolveRunPath(repoRoot, artifactRoot);
|
|
1874
|
+
if (resolvedArtifactRoot !== expectedArtifactRoot) {
|
|
1875
|
+
return null;
|
|
1876
|
+
}
|
|
1877
|
+
if (manifestPath) {
|
|
1878
|
+
const resolvedManifestPath = resolveRunPath(repoRoot, manifestPath);
|
|
1879
|
+
if (resolvedManifestPath !== join(expectedArtifactRoot, 'manifest.json')) {
|
|
1880
|
+
return null;
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
return expectedArtifactRoot;
|
|
1884
|
+
}
|
|
1885
|
+
async function readPatchChangedPaths(patchPath) {
|
|
1886
|
+
const rawPatch = await readFile(patchPath, 'utf8');
|
|
1887
|
+
const changedPaths = new Set();
|
|
1888
|
+
for (const line of rawPatch.split(/\r?\n/u)) {
|
|
1889
|
+
const parsed = parseGitDiffHeaderPaths(line);
|
|
1890
|
+
if (!parsed) {
|
|
1891
|
+
continue;
|
|
1892
|
+
}
|
|
1893
|
+
for (const candidate of parsed) {
|
|
1894
|
+
if (candidate === 'dev/null') {
|
|
1895
|
+
continue;
|
|
1896
|
+
}
|
|
1897
|
+
const normalized = normalizeScopeFileEntry(candidate);
|
|
1898
|
+
if (normalized) {
|
|
1899
|
+
changedPaths.add(normalized);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
return [...changedPaths];
|
|
1904
|
+
}
|
|
1905
|
+
function parseGitDiffHeaderPaths(line) {
|
|
1906
|
+
if (!line.startsWith('diff --git ')) {
|
|
1907
|
+
return null;
|
|
1908
|
+
}
|
|
1909
|
+
const tokens = [];
|
|
1910
|
+
let index = 'diff --git '.length;
|
|
1911
|
+
while (index < line.length && tokens.length < 2) {
|
|
1912
|
+
while (line[index] === ' ') {
|
|
1913
|
+
index += 1;
|
|
1914
|
+
}
|
|
1915
|
+
if (index >= line.length) {
|
|
1916
|
+
break;
|
|
1917
|
+
}
|
|
1918
|
+
if (line[index] === '"') {
|
|
1919
|
+
let end = index + 1;
|
|
1920
|
+
let escaped = false;
|
|
1921
|
+
while (end < line.length) {
|
|
1922
|
+
const current = line[end];
|
|
1923
|
+
if (escaped) {
|
|
1924
|
+
escaped = false;
|
|
1925
|
+
}
|
|
1926
|
+
else if (current === '\\') {
|
|
1927
|
+
escaped = true;
|
|
1928
|
+
}
|
|
1929
|
+
else if (current === '"') {
|
|
1930
|
+
end += 1;
|
|
1931
|
+
break;
|
|
1932
|
+
}
|
|
1933
|
+
end += 1;
|
|
1934
|
+
}
|
|
1935
|
+
tokens.push(line.slice(index, end));
|
|
1936
|
+
index = end;
|
|
1937
|
+
continue;
|
|
1938
|
+
}
|
|
1939
|
+
let end = index;
|
|
1940
|
+
while (end < line.length && line[end] !== ' ') {
|
|
1941
|
+
end += 1;
|
|
1942
|
+
}
|
|
1943
|
+
tokens.push(line.slice(index, end));
|
|
1944
|
+
index = end;
|
|
1945
|
+
}
|
|
1946
|
+
if (tokens.length !== 2) {
|
|
1947
|
+
return null;
|
|
1948
|
+
}
|
|
1949
|
+
return tokens
|
|
1950
|
+
.map((token) => decodeGitDiffPathToken(token))
|
|
1951
|
+
.map((token) => (token.startsWith('a/') || token.startsWith('b/') ? token.slice(2) : token));
|
|
1952
|
+
}
|
|
1953
|
+
function decodeGitDiffPathToken(token) {
|
|
1954
|
+
if (!(token.startsWith('"') && token.endsWith('"'))) {
|
|
1955
|
+
return token;
|
|
1956
|
+
}
|
|
1957
|
+
const bytes = [];
|
|
1958
|
+
const raw = token.slice(1, -1);
|
|
1959
|
+
for (let index = 0; index < raw.length; index += 1) {
|
|
1960
|
+
const current = raw[index];
|
|
1961
|
+
if (current !== '\\') {
|
|
1962
|
+
bytes.push(...Buffer.from(current, 'utf8'));
|
|
1963
|
+
continue;
|
|
1964
|
+
}
|
|
1965
|
+
const next = raw[index + 1];
|
|
1966
|
+
if (next === undefined) {
|
|
1967
|
+
bytes.push('\\'.charCodeAt(0));
|
|
1968
|
+
continue;
|
|
1969
|
+
}
|
|
1970
|
+
if (/[0-7]{3}/u.test(raw.slice(index + 1, index + 4))) {
|
|
1971
|
+
bytes.push(parseInt(raw.slice(index + 1, index + 4), 8));
|
|
1972
|
+
index += 3;
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1975
|
+
switch (next) {
|
|
1976
|
+
case '\\':
|
|
1977
|
+
case '"':
|
|
1978
|
+
bytes.push(next.charCodeAt(0));
|
|
1979
|
+
break;
|
|
1980
|
+
case 'n':
|
|
1981
|
+
bytes.push('\n'.charCodeAt(0));
|
|
1982
|
+
break;
|
|
1983
|
+
case 'r':
|
|
1984
|
+
bytes.push('\r'.charCodeAt(0));
|
|
1985
|
+
break;
|
|
1986
|
+
case 't':
|
|
1987
|
+
bytes.push('\t'.charCodeAt(0));
|
|
1988
|
+
break;
|
|
1989
|
+
default:
|
|
1990
|
+
bytes.push(next.charCodeAt(0));
|
|
1991
|
+
break;
|
|
1992
|
+
}
|
|
1993
|
+
index += 1;
|
|
1994
|
+
}
|
|
1995
|
+
return Buffer.from(bytes).toString('utf8');
|
|
1996
|
+
}
|
|
1997
|
+
function resolveAcceptedPatchScopeViolation(scope, patchChangedPaths, proofScope = null) {
|
|
1998
|
+
let expectedScope;
|
|
1999
|
+
try {
|
|
2000
|
+
expectedScope = resolveProviderLinearChildLaneScopeContract(scope);
|
|
2001
|
+
}
|
|
2002
|
+
catch (error) {
|
|
2003
|
+
return `Child lane parent ledger recorded an invalid scope contract: ${error instanceof Error ? error.message : String(error)}`;
|
|
2004
|
+
}
|
|
2005
|
+
const persistedScopeViolation = resolvePersistedScopeContractViolation('Child lane parent ledger', scope, expectedScope);
|
|
2006
|
+
if (persistedScopeViolation) {
|
|
2007
|
+
return persistedScopeViolation;
|
|
2008
|
+
}
|
|
2009
|
+
if (proofScope) {
|
|
2010
|
+
let expectedProofScope;
|
|
2011
|
+
try {
|
|
2012
|
+
expectedProofScope = resolveProviderLinearChildLaneScopeContract(proofScope);
|
|
2013
|
+
}
|
|
2014
|
+
catch (error) {
|
|
2015
|
+
return `Child lane proof bundle recorded an invalid scope contract: ${error instanceof Error ? error.message : String(error)}`;
|
|
2016
|
+
}
|
|
2017
|
+
const persistedProofViolation = resolvePersistedScopeContractViolation('Child lane proof bundle', proofScope, expectedProofScope);
|
|
2018
|
+
if (persistedProofViolation) {
|
|
2019
|
+
return persistedProofViolation;
|
|
2020
|
+
}
|
|
2021
|
+
if (!areChildLaneScopesEquivalent(expectedScope, expectedProofScope)) {
|
|
2022
|
+
return 'Child lane proof scope contract does not match the parent ledger scope contract.';
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
if (patchChangedPaths.length === 0) {
|
|
2026
|
+
return 'Child lane patch does not declare any repo-relative file targets, so parent acceptance cannot verify the bounded scope.';
|
|
2027
|
+
}
|
|
2028
|
+
const outOfScopePaths = patchChangedPaths.filter((entry) => !providerLinearChildLanePathMatchesSelectors(entry, expectedScope.allowed_path_selectors ?? []));
|
|
2029
|
+
if (outOfScopePaths.length === 0) {
|
|
2030
|
+
return null;
|
|
2031
|
+
}
|
|
2032
|
+
return `Child lane patch touches files outside the declared scope contract (${outOfScopePaths.join(', ')}). Allowed selectors: ${formatProviderLinearChildLanePathSelectors(expectedScope.allowed_path_selectors ?? [])}.`;
|
|
2033
|
+
}
|
|
2034
|
+
async function resolveAcceptedPatchDirtyConflict(workspacePath, patchChangedPaths, deps) {
|
|
2035
|
+
const dirtyPaths = (await deps.readParentDirtyPaths(workspacePath)).filter((entry) => !isIgnoredParentArtifactPath(entry));
|
|
2036
|
+
if (dirtyPaths.length === 0) {
|
|
2037
|
+
return null;
|
|
2038
|
+
}
|
|
2039
|
+
const overlapping = patchChangedPaths.filter((entry) => dirtyPaths.includes(entry));
|
|
2040
|
+
if (overlapping.length === 0) {
|
|
2041
|
+
return null;
|
|
2042
|
+
}
|
|
2043
|
+
return `Parent workspace already has pending edits to files this child-lane patch would update (${overlapping.join(', ')}); clean, commit, or incorporate those parent edits before accepting the child-lane patch.`;
|
|
2044
|
+
}
|
|
2045
|
+
async function resolveParentSnapshot(context, env, deps) {
|
|
2046
|
+
const trackedIssue = await deps.readTrackedIssue({
|
|
2047
|
+
issueId: context.issueId,
|
|
2048
|
+
sourceSetup: context.sourceSetup,
|
|
2049
|
+
env
|
|
2050
|
+
});
|
|
2051
|
+
return {
|
|
2052
|
+
issue_updated_at: trackedIssue?.updated_at ?? context.issueUpdatedAt ?? null,
|
|
2053
|
+
issue_state: trackedIssue?.state ?? null,
|
|
2054
|
+
issue_state_type: trackedIssue?.state_type ?? null,
|
|
2055
|
+
captured_at: deps.now()
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
function resolveChildLaneStaleReason(childLane, currentHeadSha, currentIssue) {
|
|
2059
|
+
if (childLane.parent_snapshot.base_sha && currentHeadSha && childLane.parent_snapshot.base_sha !== currentHeadSha) {
|
|
2060
|
+
return `Child lane ${childLane.stream} is stale because the parent workspace HEAD moved from ${childLane.parent_snapshot.base_sha} to ${currentHeadSha}.`;
|
|
2061
|
+
}
|
|
2062
|
+
if (childLane.parent_snapshot.issue_updated_at &&
|
|
2063
|
+
currentIssue.issue_updated_at &&
|
|
2064
|
+
childLane.parent_snapshot.issue_updated_at !== currentIssue.issue_updated_at) {
|
|
2065
|
+
return `Child lane ${childLane.stream} is stale because the issue updated_at changed from ${childLane.parent_snapshot.issue_updated_at} to ${currentIssue.issue_updated_at}.`;
|
|
2066
|
+
}
|
|
2067
|
+
if (childLane.parent_snapshot.issue_state &&
|
|
2068
|
+
currentIssue.issue_state &&
|
|
2069
|
+
childLane.parent_snapshot.issue_state !== currentIssue.issue_state) {
|
|
2070
|
+
return `Child lane ${childLane.stream} is stale because the issue state changed from ${childLane.parent_snapshot.issue_state} to ${currentIssue.issue_state}.`;
|
|
2071
|
+
}
|
|
2072
|
+
return null;
|
|
2073
|
+
}
|
|
2074
|
+
function normalizeChildLaneStreamName(value) {
|
|
2075
|
+
if (!value) {
|
|
2076
|
+
return null;
|
|
2077
|
+
}
|
|
2078
|
+
const normalized = slugify(value, '').toLowerCase();
|
|
2079
|
+
return normalized.length > 0 ? normalized : null;
|
|
2080
|
+
}
|
|
2081
|
+
function normalizeChildLaneScope(files, phases, repoRoot) {
|
|
2082
|
+
const normalizedFiles = normalizeScopeEntries(files, 'file', repoRoot);
|
|
2083
|
+
const normalizedPhases = normalizeScopeEntries(phases, 'phase');
|
|
2084
|
+
if (normalizedFiles.length === 0 && normalizedPhases.length === 0) {
|
|
2085
|
+
return null;
|
|
2086
|
+
}
|
|
2087
|
+
return resolveProviderLinearChildLaneScopeContract({
|
|
2088
|
+
files: normalizedFiles,
|
|
2089
|
+
phases: normalizedPhases
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
function normalizeScopeEntries(values, kind = 'file', repoRoot) {
|
|
2093
|
+
return [
|
|
2094
|
+
...new Set(values
|
|
2095
|
+
.map((value) => normalizeOptionalString(value))
|
|
2096
|
+
.map((value) => {
|
|
2097
|
+
if (!value) {
|
|
2098
|
+
return null;
|
|
2099
|
+
}
|
|
2100
|
+
return kind === 'file' ? normalizeScopeFileEntry(value, repoRoot) : normalizeScopePhaseEntry(value);
|
|
2101
|
+
})
|
|
2102
|
+
.filter((value) => value !== null))
|
|
2103
|
+
];
|
|
2104
|
+
}
|
|
2105
|
+
function normalizeScopePhaseEntry(value) {
|
|
2106
|
+
const normalizedInput = normalizeOptionalString(value);
|
|
2107
|
+
if (!normalizedInput) {
|
|
2108
|
+
return null;
|
|
2109
|
+
}
|
|
2110
|
+
const normalized = normalizedInput.toLowerCase();
|
|
2111
|
+
return normalized.length > 0 ? normalized : null;
|
|
2112
|
+
}
|
|
2113
|
+
function normalizeScopeFileEntry(value, repoRoot) {
|
|
2114
|
+
const normalizedInput = normalizeOptionalString(value);
|
|
2115
|
+
if (!normalizedInput) {
|
|
2116
|
+
return null;
|
|
2117
|
+
}
|
|
2118
|
+
if (repoRoot && isAbsolute(normalizedInput)) {
|
|
2119
|
+
const absoluteCandidate = resolve(normalizedInput);
|
|
2120
|
+
if (!isPathWithinRoot(repoRoot, absoluteCandidate)) {
|
|
2121
|
+
return null;
|
|
2122
|
+
}
|
|
2123
|
+
const relativeToRoot = relative(repoRoot, absoluteCandidate);
|
|
2124
|
+
const relativePosix = posix.normalize(relativeToRoot.replaceAll('\\', '/'));
|
|
2125
|
+
return relativePosix === '' || relativePosix === '.'
|
|
2126
|
+
? null
|
|
2127
|
+
: relativePosix;
|
|
2128
|
+
}
|
|
2129
|
+
const normalized = posix.normalize(normalizedInput.replaceAll('\\', '/'));
|
|
2130
|
+
const withoutCurrentDir = normalized.replace(/^(?:\.\/)+/u, '');
|
|
2131
|
+
const trimmed = withoutCurrentDir.replace(/\/+/gu, '/').replace(/\/$/u, '');
|
|
2132
|
+
if (trimmed === '' || trimmed === '.' || trimmed === '..' || trimmed.startsWith('../')) {
|
|
2133
|
+
return null;
|
|
2134
|
+
}
|
|
2135
|
+
return trimmed;
|
|
2136
|
+
}
|
|
2137
|
+
async function resolveParentDirtyScopeConflict(workspacePath, scope, deps) {
|
|
2138
|
+
const dirtyPaths = (await deps.readParentDirtyPaths(workspacePath)).filter((entry) => !isIgnoredParentArtifactPath(entry));
|
|
2139
|
+
if (dirtyPaths.length === 0) {
|
|
2140
|
+
return null;
|
|
2141
|
+
}
|
|
2142
|
+
const sample = dirtyPaths.slice(0, 5).join(', ');
|
|
2143
|
+
if (scope.phases.length > 0) {
|
|
2144
|
+
return `Parent workspace has pending changes (${sample}); phase-scoped child lanes launch from HEAD and therefore require a clean parent workspace baseline. Move workpad/temp scratch files such as .tmp/workpad.md outside the repo (for example /tmp), remove stale scratch artifacts, or narrow to a file-scoped lane after the parent-owned edits are clean.`;
|
|
2145
|
+
}
|
|
2146
|
+
const overlapping = scope.files.filter((entry) => dirtyPaths.includes(entry));
|
|
2147
|
+
if (overlapping.length === 0) {
|
|
2148
|
+
return null;
|
|
2149
|
+
}
|
|
2150
|
+
return `Parent workspace already has in-scope pending changes (${overlapping.join(', ')}); child lanes launch from HEAD and would miss those parent edits. Clean, commit, move scratch workpad/temp artifacts outside the repo (for example /tmp), or narrow the lane scope before launching it.`;
|
|
2151
|
+
}
|
|
2152
|
+
function isIgnoredParentArtifactPath(path) {
|
|
2153
|
+
return path === '.child-lanes' || path.startsWith('.child-lanes/') || path === '.tmp/workpad.md';
|
|
2154
|
+
}
|
|
2155
|
+
function formatChildLaneScopeLaunchFailureMessage(message) {
|
|
2156
|
+
const supportedPhases = resolveProviderLinearChildLaneSupportedPhases().join(', ');
|
|
2157
|
+
return `${message} Supported child-lane phases are: ${supportedPhases}. Do not use unsupported classification or analysis phases; use parent-owned source inspection, a supported file-scoped docs/tests lane, or a serial/no-go parallelization decision when no supported bounded slice exists.`;
|
|
2158
|
+
}
|
|
2159
|
+
function buildNoOutputAdvisoryChildLaneDecisionReason(childRun) {
|
|
2160
|
+
const summary = normalizeOptionalString(childRun.summary);
|
|
2161
|
+
return summary
|
|
2162
|
+
? `No-output advisory: child lane produced a zero-byte patch. Use the child manifest/proof and summary "${summary}" as advisory evidence only; parent owns any implementation patch and final evidence path.`
|
|
2163
|
+
: 'No-output advisory: child lane produced a zero-byte patch. Use the child manifest/proof as advisory evidence only; parent owns any implementation patch and final evidence path.';
|
|
2164
|
+
}
|
|
2165
|
+
function scopesOverlap(left, right) {
|
|
2166
|
+
try {
|
|
2167
|
+
const resolvedLeft = resolveProviderLinearChildLaneScopeContract(left);
|
|
2168
|
+
const resolvedRight = resolveProviderLinearChildLaneScopeContract(right);
|
|
2169
|
+
return providerLinearChildLanePathSelectorsOverlap(resolvedLeft.allowed_path_selectors ?? [], resolvedRight.allowed_path_selectors ?? []);
|
|
2170
|
+
}
|
|
2171
|
+
catch {
|
|
2172
|
+
return (left.files.some((entry) => right.files.includes(entry)) ||
|
|
2173
|
+
left.phases.length > 0 ||
|
|
2174
|
+
right.phases.length > 0);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
function compareStringSets(left, right) {
|
|
2178
|
+
if (left.length !== right.length) {
|
|
2179
|
+
return false;
|
|
2180
|
+
}
|
|
2181
|
+
const sortedLeft = [...left].sort();
|
|
2182
|
+
const sortedRight = [...right].sort();
|
|
2183
|
+
return sortedLeft.every((value, index) => value === sortedRight[index]);
|
|
2184
|
+
}
|
|
2185
|
+
function areChildLaneScopesEquivalent(left, right) {
|
|
2186
|
+
return (compareStringSets(left.files, right.files) &&
|
|
2187
|
+
compareStringSets(left.phases, right.phases) &&
|
|
2188
|
+
normalizeOptionalString(left.phase_contract_version) === normalizeOptionalString(right.phase_contract_version) &&
|
|
2189
|
+
providerLinearChildLanePathSelectorsEqual(left.allowed_path_selectors ?? [], right.allowed_path_selectors ?? []));
|
|
2190
|
+
}
|
|
2191
|
+
function areChildLaneParentSnapshotsEquivalent(left, right) {
|
|
2192
|
+
return (normalizeOptionalString(left.base_sha) === normalizeOptionalString(right.base_sha) &&
|
|
2193
|
+
normalizeOptionalString(left.issue_updated_at) === normalizeOptionalString(right.issue_updated_at) &&
|
|
2194
|
+
normalizeOptionalString(left.issue_state) === normalizeOptionalString(right.issue_state) &&
|
|
2195
|
+
normalizeOptionalString(left.issue_state_type) === normalizeOptionalString(right.issue_state_type));
|
|
2196
|
+
}
|
|
2197
|
+
function resolvePersistedScopeContractViolation(sourceLabel, persistedScope, expectedScope) {
|
|
2198
|
+
const persistedVersion = normalizeOptionalString(persistedScope.phase_contract_version);
|
|
2199
|
+
const persistedSelectors = persistedScope.allowed_path_selectors ?? null;
|
|
2200
|
+
if (persistedScope.phases.length > 0 && (!persistedVersion || !persistedSelectors || persistedSelectors.length === 0)) {
|
|
2201
|
+
return `${sourceLabel} is missing persisted phase-scope contract metadata for phases (${expectedScope.phases.join(', ')}).`;
|
|
2202
|
+
}
|
|
2203
|
+
if (persistedVersion && persistedVersion !== expectedScope.phase_contract_version) {
|
|
2204
|
+
return `${sourceLabel} recorded scope contract version ${persistedVersion} but expected ${expectedScope.phase_contract_version}.`;
|
|
2205
|
+
}
|
|
2206
|
+
if (persistedSelectors &&
|
|
2207
|
+
!providerLinearChildLanePathSelectorsEqual(persistedSelectors, expectedScope.allowed_path_selectors ?? [])) {
|
|
2208
|
+
return `${sourceLabel} recorded path selectors (${formatProviderLinearChildLanePathSelectors(persistedSelectors)}) that do not match the expected selectors (${formatProviderLinearChildLanePathSelectors(expectedScope.allowed_path_selectors ?? [])}).`;
|
|
2209
|
+
}
|
|
2210
|
+
return null;
|
|
2211
|
+
}
|
|
2212
|
+
function resolveChildLaneProofLineageViolation(parentRunId, childLane, proof) {
|
|
2213
|
+
if (proof.task_id !== childLane.task_id ||
|
|
2214
|
+
proof.run_id !== childLane.run_id ||
|
|
2215
|
+
proof.parent_run_id !== parentRunId ||
|
|
2216
|
+
proof.stream !== childLane.stream ||
|
|
2217
|
+
proof.issue_id !== childLane.issue_id ||
|
|
2218
|
+
proof.issue_identifier !== childLane.issue_identifier) {
|
|
2219
|
+
return 'Child lane proof lineage does not match the parent ledger record.';
|
|
2220
|
+
}
|
|
2221
|
+
return null;
|
|
2222
|
+
}
|
|
2223
|
+
function resolveAcceptedChildLaneProofViolation(parentRunId, childLane, proof, repoRoot, artifactRoot, patchArtifactPath) {
|
|
2224
|
+
const lineageViolation = resolveChildLaneProofLineageViolation(parentRunId, childLane, proof);
|
|
2225
|
+
if (lineageViolation) {
|
|
2226
|
+
return lineageViolation;
|
|
2227
|
+
}
|
|
2228
|
+
const proofPatchArtifactPath = resolveAcceptedPatchArtifactPath(repoRoot, {
|
|
2229
|
+
...childLane,
|
|
2230
|
+
patch_artifact_path: proof.patch_artifact_path
|
|
2231
|
+
}, artifactRoot);
|
|
2232
|
+
if (!proofPatchArtifactPath || proofPatchArtifactPath !== patchArtifactPath) {
|
|
2233
|
+
return 'Child lane proof patch artifact path does not match the parent-anchored patch artifact path.';
|
|
2234
|
+
}
|
|
2235
|
+
return null;
|
|
2236
|
+
}
|
|
2237
|
+
function isProofScopeViolation(message) {
|
|
2238
|
+
return (message.startsWith('Child lane proof bundle') ||
|
|
2239
|
+
message.startsWith('Child lane proof scope contract'));
|
|
2240
|
+
}
|
|
2241
|
+
function normalizeRuntimeMode(value) {
|
|
2242
|
+
if (typeof value !== 'string') {
|
|
2243
|
+
return null;
|
|
2244
|
+
}
|
|
2245
|
+
const normalized = value.trim().toLowerCase();
|
|
2246
|
+
return normalized === 'cli' || normalized === 'appserver' ? normalized : null;
|
|
2247
|
+
}
|
|
2248
|
+
function buildProviderLinearChildLaneStartEnv(env, input) {
|
|
2249
|
+
const sanitized = { ...process.env, ...env };
|
|
2250
|
+
for (const key of PROVIDER_LINEAR_CHILD_LANE_ENV_KEYS_TO_REMOVE) {
|
|
2251
|
+
delete sanitized[key];
|
|
2252
|
+
}
|
|
2253
|
+
for (const key of PROVIDER_LINEAR_CHILD_LANE_OPTIONAL_ENV_KEYS) {
|
|
2254
|
+
sanitized[key] = '';
|
|
2255
|
+
}
|
|
2256
|
+
delete sanitized.CO_LINEAR_WORKSPACE_ID;
|
|
2257
|
+
delete sanitized.CO_LINEAR_TEAM_ID;
|
|
2258
|
+
delete sanitized.CO_LINEAR_PROJECT_ID;
|
|
2259
|
+
sanitized.CODEX_ORCHESTRATOR_ROOT = input.repoRoot;
|
|
2260
|
+
sanitized.CODEX_ORCHESTRATOR_REPO_CONFIG_PATH =
|
|
2261
|
+
normalizeOptionalString(sanitized.CODEX_ORCHESTRATOR_REPO_CONFIG_PATH) ?? join(input.repoRoot, 'codex.orchestrator.json');
|
|
2262
|
+
sanitized.CODEX_ORCHESTRATOR_RUNS_DIR = resolveWorkspaceScopedArtifactDir(input.repoRoot, sanitized.CODEX_ORCHESTRATOR_RUNS_DIR, '.runs');
|
|
2263
|
+
sanitized.CODEX_ORCHESTRATOR_OUT_DIR = resolveWorkspaceScopedArtifactDir(input.repoRoot, sanitized.CODEX_ORCHESTRATOR_OUT_DIR, 'out');
|
|
2264
|
+
sanitized.MCP_RUNNER_TASK_ID = input.taskId;
|
|
2265
|
+
sanitized[PROVIDER_LINEAR_CHILD_LANE_STREAM_ENV] = input.stream;
|
|
2266
|
+
sanitized[PROVIDER_LINEAR_CHILD_LANE_PURPOSE_ENV] = input.purpose;
|
|
2267
|
+
sanitized[PROVIDER_LINEAR_CHILD_LANE_INSTRUCTIONS_ENV] = input.instructions ?? '';
|
|
2268
|
+
sanitized[PROVIDER_LINEAR_CHILD_LANE_FILES_ENV] = JSON.stringify(input.scope.files);
|
|
2269
|
+
sanitized[PROVIDER_LINEAR_CHILD_LANE_PHASES_ENV] = JSON.stringify(input.scope.phases);
|
|
2270
|
+
sanitized[PROVIDER_LINEAR_CHILD_LANE_PARENT_WORKSPACE_PATH_ENV] = input.parentWorkspacePath;
|
|
2271
|
+
sanitized[PROVIDER_LINEAR_CHILD_LANE_PARENT_SNAPSHOT_BASE_SHA_ENV] = input.parentSnapshot.base_sha ?? '';
|
|
2272
|
+
sanitized[PROVIDER_LINEAR_CHILD_LANE_PARENT_SNAPSHOT_CAPTURED_AT_ENV] = input.parentSnapshot.captured_at ?? '';
|
|
2273
|
+
sanitized[PROVIDER_LINEAR_CHILD_LANE_PARENT_SNAPSHOT_ISSUE_UPDATED_AT_ENV] = input.parentSnapshot.issue_updated_at ?? '';
|
|
2274
|
+
sanitized[PROVIDER_LINEAR_CHILD_LANE_PARENT_SNAPSHOT_ISSUE_STATE_ENV] = input.parentSnapshot.issue_state ?? '';
|
|
2275
|
+
sanitized[PROVIDER_LINEAR_CHILD_LANE_PARENT_SNAPSHOT_ISSUE_STATE_TYPE_ENV] = input.parentSnapshot.issue_state_type ?? '';
|
|
2276
|
+
if (input.sourceSetup?.provider === 'linear') {
|
|
2277
|
+
sanitized.CO_LINEAR_WORKSPACE_ID = input.sourceSetup.workspace_id ?? '';
|
|
2278
|
+
sanitized.CO_LINEAR_TEAM_ID = input.sourceSetup.team_id ?? '';
|
|
2279
|
+
sanitized.CO_LINEAR_PROJECT_ID = input.sourceSetup.project_id ?? '';
|
|
2280
|
+
}
|
|
2281
|
+
return sanitized;
|
|
2282
|
+
}
|
|
2283
|
+
function resolveCodexOrchestratorInvocation(env) {
|
|
2284
|
+
const invocation = resolveCodexOrchestratorBootstrapInvocation({ env, execPath: process.execPath });
|
|
2285
|
+
return { command: invocation.command, argsPrefix: invocation.args, envOverrides: invocation.envOverrides };
|
|
2286
|
+
}
|
|
2287
|
+
async function parseProviderChildLaneRunResult(raw, repoRoot, childRunsRoot, taskId) {
|
|
2288
|
+
const parsed = parseTrailingJsonObject(raw);
|
|
2289
|
+
if (!parsed) {
|
|
2290
|
+
return null;
|
|
2291
|
+
}
|
|
2292
|
+
const runId = normalizeOptionalString(parsed.run_id);
|
|
2293
|
+
const status = normalizeOptionalString(parsed.status);
|
|
2294
|
+
const artifactRoot = normalizeOptionalString(parsed.artifact_root);
|
|
2295
|
+
const manifestPath = normalizeOptionalString(parsed.manifest) ?? (artifactRoot ? join(artifactRoot, 'manifest.json') : null);
|
|
2296
|
+
if (!runId || !status || !artifactRoot || !manifestPath) {
|
|
2297
|
+
return null;
|
|
2298
|
+
}
|
|
2299
|
+
const safeRunId = (() => {
|
|
2300
|
+
try {
|
|
2301
|
+
return sanitizeRunId(runId);
|
|
2302
|
+
}
|
|
2303
|
+
catch {
|
|
2304
|
+
return null;
|
|
2305
|
+
}
|
|
2306
|
+
})();
|
|
2307
|
+
if (!safeRunId) {
|
|
2308
|
+
return null;
|
|
2309
|
+
}
|
|
2310
|
+
const expectedRunRoot = resolve(childRunsRoot, taskId, 'cli', safeRunId);
|
|
2311
|
+
const resolvedArtifactRoot = resolveRunPath(repoRoot, artifactRoot);
|
|
2312
|
+
const resolvedManifestPath = resolveRunPath(repoRoot, manifestPath);
|
|
2313
|
+
const resolvedLogPath = normalizeOptionalString(parsed.log_path);
|
|
2314
|
+
const normalizedLogPath = resolvedLogPath ? resolveRunPath(repoRoot, resolvedLogPath) : null;
|
|
2315
|
+
if (!isPathWithinRoot(expectedRunRoot, resolvedArtifactRoot) ||
|
|
2316
|
+
!isPathWithinRoot(expectedRunRoot, resolvedManifestPath) ||
|
|
2317
|
+
(normalizedLogPath && !isPathWithinRoot(expectedRunRoot, normalizedLogPath))) {
|
|
2318
|
+
return null;
|
|
2319
|
+
}
|
|
2320
|
+
let manifestRecord = null;
|
|
2321
|
+
try {
|
|
2322
|
+
const candidate = JSON.parse(await readFile(resolvedManifestPath, 'utf8'));
|
|
2323
|
+
if (candidate && typeof candidate === 'object' && !Array.isArray(candidate)) {
|
|
2324
|
+
manifestRecord = candidate;
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
catch {
|
|
2328
|
+
manifestRecord = null;
|
|
2329
|
+
}
|
|
2330
|
+
const rawSummary = normalizeOptionalString(parsed.summary);
|
|
2331
|
+
const summary = manifestRecord
|
|
2332
|
+
? stripNonApplicableGuardrailSummaryLines(manifestRecord, rawSummary)
|
|
2333
|
+
: rawSummary;
|
|
2334
|
+
return {
|
|
2335
|
+
run_id: safeRunId,
|
|
2336
|
+
task_id: taskId,
|
|
2337
|
+
pipeline_id: PROVIDER_LINEAR_CHILD_LANE_PIPELINE_ID,
|
|
2338
|
+
status,
|
|
2339
|
+
artifact_root: resolvedArtifactRoot,
|
|
2340
|
+
manifest_path: resolvedManifestPath,
|
|
2341
|
+
log_path: normalizedLogPath,
|
|
2342
|
+
summary,
|
|
2343
|
+
guardrails_required: manifestRecord ? resolveGuardrailsRequiredForManifest(manifestRecord) : null,
|
|
2344
|
+
guardrails_required_source: manifestRecord ? resolveGuardrailsRequiredSourceForManifest(manifestRecord) : null,
|
|
2345
|
+
guardrail_command_count: manifestRecord ? countGuardrailCommands(manifestRecord) : null,
|
|
2346
|
+
runtime_mode_requested: normalizeOptionalString(parsed.runtime_mode_requested),
|
|
2347
|
+
runtime_mode: normalizeOptionalString(parsed.runtime_mode),
|
|
2348
|
+
runtime_provider: normalizeOptionalString(parsed.runtime_provider)
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
function resolveRunPath(repoRoot, value) {
|
|
2352
|
+
return isAbsolute(value) ? resolve(value) : resolve(repoRoot, value);
|
|
2353
|
+
}
|
|
2354
|
+
function resolveWorkspaceScopedArtifactDir(repoRoot, value, fallbackDirname) {
|
|
2355
|
+
const normalized = normalizeOptionalString(value);
|
|
2356
|
+
const fallback = join(repoRoot, fallbackDirname);
|
|
2357
|
+
if (!normalized) {
|
|
2358
|
+
return fallback;
|
|
2359
|
+
}
|
|
2360
|
+
const candidate = isAbsolute(normalized) ? resolve(normalized) : resolve(repoRoot, normalized);
|
|
2361
|
+
if (isPathWithinRoot(repoRoot, candidate)) {
|
|
2362
|
+
return candidate;
|
|
2363
|
+
}
|
|
2364
|
+
if (basename(dirname(repoRoot)) !== '.workspaces') {
|
|
2365
|
+
return fallback;
|
|
2366
|
+
}
|
|
2367
|
+
const sharedRoot = dirname(dirname(repoRoot));
|
|
2368
|
+
if (isPathWithinRoot(sharedRoot, candidate)) {
|
|
2369
|
+
return resolve(repoRoot, relative(sharedRoot, candidate));
|
|
2370
|
+
}
|
|
2371
|
+
return fallback;
|
|
2372
|
+
}
|
|
2373
|
+
function isPathWithinRoot(root, candidate) {
|
|
2374
|
+
const relativePath = relative(root, candidate);
|
|
2375
|
+
return relativePath === '' || (!relativePath.startsWith('..') && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath));
|
|
2376
|
+
}
|
|
2377
|
+
function failureResult(input) {
|
|
2378
|
+
return {
|
|
2379
|
+
ok: false,
|
|
2380
|
+
operation: 'child-lane',
|
|
2381
|
+
action: input.action,
|
|
2382
|
+
issue_id: input.issueId,
|
|
2383
|
+
issue_identifier: input.issueIdentifier,
|
|
2384
|
+
source_setup: input.sourceSetup,
|
|
2385
|
+
stream: input.stream,
|
|
2386
|
+
child_run: input.childRun,
|
|
2387
|
+
child_lane: input.childLane,
|
|
2388
|
+
error: {
|
|
2389
|
+
code: input.code,
|
|
2390
|
+
message: input.message,
|
|
2391
|
+
status: input.status
|
|
2392
|
+
}
|
|
2393
|
+
};
|
|
2394
|
+
}
|
|
2395
|
+
function formatProviderWorkerChildLaneProvenanceInvalidMessage(context, env) {
|
|
2396
|
+
const manifestLaunchSource = normalizeOptionalString(context.manifest.provider_launch_source) ??
|
|
2397
|
+
normalizeOptionalString(context.manifest.providerLaunchSource);
|
|
2398
|
+
const manifestTaskId = normalizeOptionalString(context.manifest.provider_control_host_task_id) ??
|
|
2399
|
+
normalizeOptionalString(context.manifest.providerControlHostTaskId);
|
|
2400
|
+
const manifestRunId = normalizeOptionalString(context.manifest.provider_control_host_run_id) ??
|
|
2401
|
+
normalizeOptionalString(context.manifest.providerControlHostRunId);
|
|
2402
|
+
const envLaunchSource = normalizeOptionalString(env[PROVIDER_LAUNCH_SOURCE_ENV]);
|
|
2403
|
+
const envTaskId = normalizeOptionalString(env[PROVIDER_CONTROL_HOST_TASK_ID_ENV]);
|
|
2404
|
+
const envRunId = normalizeOptionalString(env[PROVIDER_CONTROL_HOST_RUN_ID_ENV]);
|
|
2405
|
+
return [
|
|
2406
|
+
'linear child-lane requires provider control-host provenance recorded on the parent provider-worker manifest and matching active environment.',
|
|
2407
|
+
'Required manifest fields: provider_launch_source=control-host, provider_control_host_task_id, provider_control_host_run_id.',
|
|
2408
|
+
`Parent manifest: ${context.manifestPath}.`,
|
|
2409
|
+
`Manifest values: provider_launch_source=${manifestLaunchSource ?? 'missing'}, provider_control_host_task_id=${manifestTaskId ?? 'missing'}, provider_control_host_run_id=${manifestRunId ?? 'missing'}.`,
|
|
2410
|
+
`Active env values: ${PROVIDER_LAUNCH_SOURCE_ENV}=${envLaunchSource ?? 'missing'}, ${PROVIDER_CONTROL_HOST_TASK_ID_ENV}=${envTaskId ?? 'missing'}, ${PROVIDER_CONTROL_HOST_RUN_ID_ENV}=${envRunId ?? 'missing'}.`,
|
|
2411
|
+
`recorded=${String(context.providerControlHostRecordedInManifest)} matches=${String(context.providerControlHostMatchesManifest)}.`
|
|
2412
|
+
].join(' ');
|
|
2413
|
+
}
|
|
2414
|
+
function normalizeOptionalString(value) {
|
|
2415
|
+
if (typeof value !== 'string') {
|
|
2416
|
+
return null;
|
|
2417
|
+
}
|
|
2418
|
+
const trimmed = value.trim();
|
|
2419
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
2420
|
+
}
|