codex-workflow-v2 2.0.0-beta.13.9 → 2.0.0-beta.14.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/README.md +27 -388
- package/dist/reviewer-runtime-build.json +98 -34
- package/dist/src/alpha6/adoption.d.ts +3 -1
- package/dist/src/alpha6/adoption.js +48 -8
- package/dist/src/alpha6/adoption.js.map +1 -1
- package/dist/src/alpha6/captured-check-evidence.d.ts +51 -0
- package/dist/src/alpha6/captured-check-evidence.js +152 -0
- package/dist/src/alpha6/captured-check-evidence.js.map +1 -0
- package/dist/src/alpha6/component-owner.d.ts +15 -1
- package/dist/src/alpha6/component-owner.js +44 -2
- package/dist/src/alpha6/component-owner.js.map +1 -1
- package/dist/src/alpha6/corrective-decision-boundary.d.ts +4 -0
- package/dist/src/alpha6/corrective-decision-boundary.js +53 -0
- package/dist/src/alpha6/corrective-decision-boundary.js.map +1 -0
- package/dist/src/alpha6/downstream-proof.d.ts +1 -25
- package/dist/src/alpha6/downstream-proof.js +0 -171
- package/dist/src/alpha6/downstream-proof.js.map +1 -1
- package/dist/src/alpha6/literal-test-invocation.d.ts +2 -0
- package/dist/src/alpha6/literal-test-invocation.js +106 -0
- package/dist/src/alpha6/literal-test-invocation.js.map +1 -0
- package/dist/src/alpha6/milestone.d.ts +2 -0
- package/dist/src/alpha6/milestone.js +10 -1
- package/dist/src/alpha6/milestone.js.map +1 -1
- package/dist/src/alpha6/navigation-dirty-carryover.d.ts +11 -0
- package/dist/src/alpha6/navigation-dirty-carryover.js +130 -0
- package/dist/src/alpha6/navigation-dirty-carryover.js.map +1 -0
- package/dist/src/alpha6/plan-integrity.js +7 -5
- package/dist/src/alpha6/plan-integrity.js.map +1 -1
- package/dist/src/alpha6/remediation.d.ts +8 -21
- package/dist/src/alpha6/remediation.js +225 -474
- package/dist/src/alpha6/remediation.js.map +1 -1
- package/dist/src/alpha6/root-cause-replan-carryover.d.ts +4 -1
- package/dist/src/alpha6/root-cause-replan-carryover.js +29 -5
- package/dist/src/alpha6/root-cause-replan-carryover.js.map +1 -1
- package/dist/src/alpha7/autonomy.d.ts +9 -0
- package/dist/src/alpha7/autonomy.js +40 -1
- package/dist/src/alpha7/autonomy.js.map +1 -1
- package/dist/src/beta1/project-transaction.d.ts +3 -1
- package/dist/src/beta1/project-transaction.js +20 -6
- package/dist/src/beta1/project-transaction.js.map +1 -1
- package/dist/src/checks/runner.d.ts +26 -0
- package/dist/src/checks/runner.js +223 -0
- package/dist/src/checks/runner.js.map +1 -0
- package/dist/src/checks/task-sync.d.ts +10 -0
- package/dist/src/checks/task-sync.js +41 -0
- package/dist/src/checks/task-sync.js.map +1 -0
- package/dist/src/checks/worker.d.ts +1 -0
- package/dist/src/checks/worker.js +154 -0
- package/dist/src/checks/worker.js.map +1 -0
- package/dist/src/cli-actions.d.ts +3 -3
- package/dist/src/cli-actions.js +8 -15
- package/dist/src/cli-actions.js.map +1 -1
- package/dist/src/cli.js +43 -97
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +54 -34
- package/dist/src/delegation.d.ts +5 -1
- package/dist/src/delegation.js +4 -0
- package/dist/src/delegation.js.map +1 -1
- package/dist/src/dependency-provenance.d.ts +2 -2
- package/dist/src/dependency-provenance.js +12 -69
- package/dist/src/dependency-provenance.js.map +1 -1
- package/dist/src/domain/base-sync-conflict.d.ts +13 -0
- package/dist/src/domain/base-sync-conflict.js +32 -0
- package/dist/src/domain/base-sync-conflict.js.map +1 -0
- package/dist/src/domain/step-start-admission.d.ts +4 -0
- package/dist/src/domain/step-start-admission.js +9 -0
- package/dist/src/domain/step-start-admission.js.map +1 -0
- package/dist/src/domain/virgin-registration.d.ts +3 -0
- package/dist/src/domain/virgin-registration.js +65 -0
- package/dist/src/domain/virgin-registration.js.map +1 -0
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js.map +1 -1
- package/dist/src/failed-checkpoint-update.d.ts +69 -0
- package/dist/src/failed-checkpoint-update.js +171 -0
- package/dist/src/failed-checkpoint-update.js.map +1 -0
- package/dist/src/gateway-handshake.js +0 -8
- package/dist/src/gateway-handshake.js.map +1 -1
- package/dist/src/git.d.ts +3 -1
- package/dist/src/git.js +41 -14
- package/dist/src/git.js.map +1 -1
- package/dist/src/graph.js +25 -3
- package/dist/src/graph.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/lifecycle/corrective-replan-credential-core.d.ts +2 -0
- package/dist/src/lifecycle/corrective-replan-credential-core.js +45 -9
- package/dist/src/lifecycle/corrective-replan-credential-core.js.map +1 -1
- package/dist/src/lifecycle/corrective-replan.js +12 -8
- package/dist/src/lifecycle/corrective-replan.js.map +1 -1
- package/dist/src/lifecycle/semantic-registry.js +4 -2
- package/dist/src/lifecycle/semantic-registry.js.map +1 -1
- package/dist/src/navigation-actions.d.ts +9 -0
- package/dist/src/navigation-actions.js +72 -0
- package/dist/src/navigation-actions.js.map +1 -0
- package/dist/src/navigation-update-artifact.d.ts +5 -0
- package/dist/src/navigation-update-artifact.js +227 -0
- package/dist/src/navigation-update-artifact.js.map +1 -0
- package/dist/src/navigation-update.d.ts +39 -0
- package/dist/src/navigation-update.js +82 -0
- package/dist/src/navigation-update.js.map +1 -0
- package/dist/src/observation.js +52 -29
- package/dist/src/observation.js.map +1 -1
- package/dist/src/observed-routes.js +10 -6
- package/dist/src/observed-routes.js.map +1 -1
- package/dist/src/pending-review-update.d.ts +0 -13
- package/dist/src/pending-review-update.js +1 -6
- package/dist/src/pending-review-update.js.map +1 -1
- package/dist/src/reviewer.d.ts +1 -1
- package/dist/src/reviewer.js +20 -17
- package/dist/src/reviewer.js.map +1 -1
- package/dist/src/runtime.d.ts +3 -0
- package/dist/src/runtime.js +6 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/state/corrective-replan-executor.d.ts +10 -0
- package/dist/src/state/corrective-replan-executor.js +38 -1
- package/dist/src/state/corrective-replan-executor.js.map +1 -1
- package/dist/src/state/corrective-replan-public-schema.js +15 -2
- package/dist/src/state/corrective-replan-public-schema.js.map +1 -1
- package/dist/src/state/corrective-replan-public.d.ts +9 -1
- package/dist/src/state/corrective-replan-public.js +50 -8
- package/dist/src/state/corrective-replan-public.js.map +1 -1
- package/dist/src/state/corrective-replan-transaction.d.ts +1 -0
- package/dist/src/state/corrective-replan-transaction.js +12 -11
- package/dist/src/state/corrective-replan-transaction.js.map +1 -1
- package/dist/src/state/corrective-yield-transaction.d.ts +1 -0
- package/dist/src/state/corrective-yield-transaction.js +11 -10
- package/dist/src/state/corrective-yield-transaction.js.map +1 -1
- package/dist/src/state/lock.d.ts +4 -0
- package/dist/src/state/lock.js +21 -0
- package/dist/src/state/lock.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-blocker-route.d.ts +8 -0
- package/dist/src/workflow-blocker-route.js +106 -0
- package/dist/src/workflow-blocker-route.js.map +1 -0
- package/dist/src/workflow.d.ts +88 -64
- package/dist/src/workflow.js +832 -1456
- package/dist/src/workflow.js.map +1 -1
- package/docs/autonomy-guardrails.md +26 -304
- package/docs/decisions.md +14 -112
- package/docs/development-flow.md +36 -237
- package/docs/project-memory.md +31 -50
- package/docs/release-app-evidence.md +190 -0
- package/docs/release.md +101 -388
- package/docs/updating-existing-project.md +65 -726
- package/package.json +11 -13
- package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +2 -2
- package/plugins/codex-workflow-gateway/references/chat-dispatch.md +88 -200
- package/plugins/codex-workflow-gateway/references/codebase-memory-routing.md +57 -0
- package/plugins/codex-workflow-gateway/references/protocol.md +51 -458
- package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +20 -4
- package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +21 -34
- package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +23 -5
- package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +92 -778
- package/references/state-machine.md +7 -5
- package/roles/delivery-coordinator.md +31 -11
- package/roles/scope-lead.md +1 -1
- package/roles/technical-planner.md +5 -2
- package/schemas/adoption-posture-event.schema.json +11 -0
- package/schemas/authorization-event.schema.json +1 -1
- package/schemas/corrective-decision-event.schema.json +26 -4
- package/schemas/corrective-replan-credentials.private.schema.json +8 -5
- package/schemas/corrective-replan-execution-event.private.schema.json +9 -0
- package/schemas/corrective-replan-public.schema.json +2 -1
- package/schemas/delegation-grant.schema.json +1 -1
- package/schemas/project-knowledge-map.schema.json +3 -1
- package/schemas/remediation-event.schema.json +10 -1
- package/schemas/task.schema.json +64 -2
- package/schemas/transition-definition.schema.json +4 -0
- package/schemas/transition-payloads.schema.json +16 -1
- package/src/alpha6/adoption.ts +1146 -0
- package/src/alpha6/captured-check-evidence.ts +132 -0
- package/src/alpha6/check-support-anchor.ts +128 -0
- package/src/alpha6/component-owner.ts +168 -0
- package/src/alpha6/corrective-decision-boundary.ts +45 -0
- package/src/alpha6/downstream-proof.ts +520 -0
- package/src/alpha6/failed-step-planning-recovery.ts +88 -0
- package/src/alpha6/handoff.ts +1443 -0
- package/src/alpha6/journal.ts +473 -0
- package/src/alpha6/literal-test-invocation.ts +84 -0
- package/src/alpha6/mechanical-feasibility.ts +488 -0
- package/src/alpha6/milestone.ts +2192 -0
- package/src/alpha6/navigation-dirty-carryover.ts +154 -0
- package/src/alpha6/npm-check-contract.ts +47 -0
- package/src/alpha6/plan-integrity.ts +298 -0
- package/src/alpha6/plan-risk.ts +1480 -0
- package/src/alpha6/preexecution-replan.ts +187 -0
- package/src/alpha6/remediation-cause.ts +98 -0
- package/src/alpha6/remediation.ts +2505 -0
- package/src/alpha6/review.ts +1198 -0
- package/src/alpha6/root-cause-replan-carryover.ts +491 -0
- package/src/alpha6/store-sidecars.ts +335 -0
- package/src/alpha7/autonomy.ts +449 -0
- package/src/alpha7/corrective-recovery.ts +1332 -0
- package/src/artifacts.ts +130 -0
- package/src/beta1/project-transaction.ts +364 -0
- package/src/change-explanation.ts +153 -0
- package/src/checks/runner.ts +245 -0
- package/src/checks/task-sync.ts +41 -0
- package/src/checks/worker.ts +149 -0
- package/src/cli-actions.ts +112 -0
- package/src/cli.ts +1490 -0
- package/src/contracts.ts +1512 -0
- package/src/credential-output.ts +89 -0
- package/src/credential-transport.ts +215 -0
- package/src/delegation.ts +194 -0
- package/src/dependency-provenance.ts +472 -0
- package/src/diagnostics.ts +93 -0
- package/src/domain/base-sync-conflict.ts +35 -0
- package/src/domain/completed-step-carryover.ts +76 -0
- package/src/domain/discovery.ts +27 -0
- package/src/domain/plan-semantics.ts +58 -0
- package/src/domain/step-start-admission.ts +10 -0
- package/src/domain/validation.ts +177 -0
- package/src/domain/virgin-registration.ts +46 -0
- package/src/errors.ts +24 -0
- package/src/failed-checkpoint-update.ts +203 -0
- package/src/fs-utils.ts +61 -0
- package/src/gateway-handshake.ts +95 -0
- package/src/git.ts +183 -0
- package/src/graph.ts +342 -0
- package/src/historical-step-provenance.ts +136 -0
- package/src/index.ts +23 -0
- package/src/lifecycle/canonical-hash.ts +28 -0
- package/src/lifecycle/catalog.ts +202 -0
- package/src/lifecycle/compiler-inspection.ts +29 -0
- package/src/lifecycle/core-static-readiness.ts +132 -0
- package/src/lifecycle/corrective-replan-authority.ts +136 -0
- package/src/lifecycle/corrective-replan-binding-manifest.ts +51 -0
- package/src/lifecycle/corrective-replan-credential-core.ts +437 -0
- package/src/lifecycle/corrective-replan-credential-schema.ts +54 -0
- package/src/lifecycle/corrective-replan-credentials.ts +48 -0
- package/src/lifecycle/corrective-replan.ts +847 -0
- package/src/lifecycle/evaluator.ts +48 -0
- package/src/lifecycle/fingerprint.ts +488 -0
- package/src/lifecycle/immutable.ts +8 -0
- package/src/lifecycle/implementation-table.ts +118 -0
- package/src/lifecycle/index.ts +8 -0
- package/src/lifecycle/schema-artifact.ts +263 -0
- package/src/lifecycle/semantic-registry.ts +574 -0
- package/src/lifecycle/types.ts +838 -0
- package/src/memory.ts +273 -0
- package/src/migration.ts +161 -0
- package/src/navigation-actions.ts +70 -0
- package/src/navigation-update-artifact.ts +198 -0
- package/src/navigation-update.ts +121 -0
- package/src/observation.ts +225 -0
- package/src/observed-routes.ts +665 -0
- package/src/operational-contract.ts +125 -0
- package/src/pending-review-update.ts +175 -0
- package/src/repository.ts +99 -0
- package/src/reviewer.ts +1879 -0
- package/src/runtime.ts +6 -0
- package/src/state/corrective-replan-executor.ts +819 -0
- package/src/state/corrective-replan-public-schema.ts +83 -0
- package/src/state/corrective-replan-public.ts +952 -0
- package/src/state/corrective-replan-transaction.ts +949 -0
- package/src/state/corrective-yield-executor.ts +327 -0
- package/src/state/corrective-yield-transaction.ts +730 -0
- package/src/state/lock.ts +902 -0
- package/src/state/store.ts +567 -0
- package/src/transition-core.ts +330 -0
- package/src/ulid.ts +24 -0
- package/src/version.ts +2 -0
- package/src/workflow-blocker-route.ts +109 -0
- package/src/workflow.ts +10342 -0
- package/docs/alpha7.1-implementation-brief.md +0 -268
- package/docs/alpha7.2-corrective-context-refresh-brief.md +0 -484
- package/docs/alpha7.2.1-remediation-recovery-brief.md +0 -86
- package/docs/beta1-stabilization-brief.md +0 -165
- package/docs/beta11-plan-integrity-recovery-brief.md +0 -38
- package/docs/beta13.2-signal-review-recovery.md +0 -38
- package/docs/beta2-initial-assembly-navigation-brief.md +0 -616
- package/docs/change-model.md +0 -118
- package/docs/delegated-approval.md +0 -254
- package/docs/lifecycle/state-machine-stabilization.md +0 -641
- package/docs/pdf/README.md +0 -24
- package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
- package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
- package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
- package/docs/pdf/requirements.txt +0 -1
- package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +0 -478
- package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +0 -506
- package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +0 -778
- package/docs/pending-review-update.md +0 -15
- package/docs/problem-briefs/01-pre-implementation-integrity.md +0 -482
- package/docs/problem-briefs/02-minimal-step-integrity.md +0 -411
- package/docs/problem-briefs/03-minimal-agent-context-integrity.md +0 -358
- package/docs/problem-briefs/04-task-dependency-and-structural-replacement-integrity.md +0 -573
- package/docs/problem-briefs/BRIEF-TEMPLATE.md +0 -56
- package/docs/problem-briefs/README.md +0 -120
- package/docs/problem-briefs/evidence/p01-mechanical-feasibility-corpus.md +0 -90
- package/docs/problem-briefs/evidence/signal-v4-pre-m3-replay.md +0 -246
- package/docs/split-required-recovery.md +0 -47
- package/docs/stable-release-defect-register.md +0 -730
- package/docs/validation-report.md +0 -182
- package/scripts/generate-pdf-docs.py +0 -524
- package/scripts/run-pdf-docs.mjs +0 -62
package/dist/src/workflow.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
+
import { assessNavigationDirtyCarryover } from './alpha6/navigation-dirty-carryover.js';
|
|
2
|
+
import { navigationUpdateSource, assertNavigationSourceInstalled, inspectNavigationTransport } from './navigation-update.js';
|
|
3
|
+
import { failedCheckpointUpdateSource, assertFailedCheckpointInstalled, inspectFailedCheckpointTransport, readFailedCheckpointDirtyTransport, FAILED_CHECKPOINT_SOURCE, FAILED_CHECKPOINT_TARGET } from './failed-checkpoint-update.js';
|
|
4
|
+
import { verifyNavigationUpdateArtifact } from './navigation-update-artifact.js';
|
|
5
|
+
import { taskSyncChecks, taskSyncChecksHash, assertNoPendingBaseSyncValidation, assessPendingBaseSyncValidation, assertPendingBaseSyncValidationReady } from './checks/task-sync.js';
|
|
6
|
+
import { isVirginRegistrationRoot } from './domain/virgin-registration.js';
|
|
7
|
+
import { currentBaseSyncConflict, recordBaseSyncConflict } from './domain/base-sync-conflict.js';
|
|
8
|
+
import { stepStartPhase } from './domain/step-start-admission.js';
|
|
9
|
+
import { correctiveDecisionCheckoutEligible } from './alpha6/corrective-decision-boundary.js';
|
|
10
|
+
import { prepareCapturedCheckEvidence, assertCapturedCheckEvidence, assertCurrentCapturedCheckEvidence } from './alpha6/captured-check-evidence.js';
|
|
1
11
|
import { readCorrectivePlanningAudit, readFailedStepPlanningRecovery } from './alpha6/failed-step-planning-recovery.js';
|
|
2
12
|
import { explainChange } from './change-explanation.js';
|
|
3
|
-
import {
|
|
13
|
+
import { verifiedPendingReviewTransport } from './pending-review-update.js';
|
|
4
14
|
import { appendFileSync, existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
|
|
5
15
|
import { createHash } from 'node:crypto';
|
|
6
16
|
import path from 'node:path';
|
|
7
17
|
import { PROTOCOL_VERSION, STATE_SCHEMA_VERSION, } from './contracts.js';
|
|
8
|
-
import { inspectBoundedWorkflowDependencyCommit, inspectDependencyKnowledgeRefresh, inspectDependencyProvenanceRecovery,
|
|
18
|
+
import { inspectBoundedWorkflowDependencyCommit, inspectDependencyKnowledgeRefresh, inspectDependencyProvenanceRecovery, } from './dependency-provenance.js';
|
|
9
19
|
import { inspectHistoricalStepProvenanceRecovery, omittedStepEvidenceCommits, } from './historical-step-provenance.js';
|
|
10
20
|
import { renderBrief, renderPlan, renderResult } from './artifacts.js';
|
|
11
21
|
import { assertDelegationCanAuthorize, hashDelegationPolicy, prepareDelegationGrantGate, } from './delegation.js';
|
|
@@ -15,22 +25,23 @@ import { deriveCompletedStepCarryover, normalizePlanStepDefinition, } from './do
|
|
|
15
25
|
import { pathAllowed, validatePlan } from './domain/validation.js';
|
|
16
26
|
import { WorkflowError } from './errors.js';
|
|
17
27
|
import { withObservedRouteMetadata } from './observed-routes.js';
|
|
28
|
+
import { withWorkflowBlockerRoute } from './workflow-blocker-route.js';
|
|
18
29
|
import { bindGraphEvidence, createGraphRefreshRequest, fallbackGraphBinding, inspectGraphBinding, validateGraphRequestCurrent, } from './graph.js';
|
|
19
|
-
import {
|
|
30
|
+
import { inspectWorkflowVersions } from './diagnostics.js';
|
|
20
31
|
import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, appendTaskWriterCredentialReplacement, assertTaskClaimAllowed, assertTaskMutationAllowedByC1, defaultTaskWorkerActor, prepareTaskCorrectiveYield, readTaskC1Posture, readTaskHandoffEvents, } from './alpha6/handoff.js';
|
|
21
|
-
import { assessDownstreamProofCarryover,
|
|
32
|
+
import { assessDownstreamProofCarryover, assessDownstreamProofRecovery, downstreamProofInvalidationSidecar, prepareDownstreamProofInvalidation, readDownstreamProofInvalidations, readDownstreamProofReplanRecoveries, hashDirtyWorktree, } from './alpha6/downstream-proof.js';
|
|
22
33
|
import { applyAdoptionPosture, assertAdoptionBaselinePreserved, buildAdoptionPreparation, initializeProjectRegistrationAdoption, readCurrentAdoptionPosture, } from './alpha6/adoption.js';
|
|
23
34
|
import { appendCurrentPlanRiskAudit, appendReboundPlanRiskAudit, buildCandidatePlanRiskAuditEvent, buildPlanRiskAuditEvent, readCurrentPlanRiskAudit, readCurrentPlanningObstructionAudit, readPlanRiskAuditEvents, validatePlanRiskAuditCandidate, } from './alpha6/plan-risk.js';
|
|
24
35
|
import { hashCompletedSteps, hashExecutionAuthorization, preparePreExecutionReplanEvent, preExecutionReplanSidecar, readCurrentPreExecutionReplan, } from './alpha6/preexecution-replan.js';
|
|
25
36
|
import { assessRootCauseReplanDirtyCarryover, matchesTerminalCorrectiveYield, } from './alpha6/root-cause-replan-carryover.js';
|
|
26
|
-
import { appendCorrectiveDecisionEvent,
|
|
37
|
+
import { appendCorrectiveDecisionEvent, firstFailurePlanningObstructionEvidence, appendPlanIntegrityRecoveryDecision, assertCorrectiveAuditorIndependence, assertGuardedRemediationAttemptAllowed, defaultCorrectiveAuditorActor, effectiveStepAllowedWrites, isKnowledgeReboundSupportDecision, findCurrentGuardedRemediationPosture, findFailedGuardedStepRequiringCorrectiveDecision, readGuardedRemediationPostureForStep, readCorrectiveDecisionEvents, readRemediationEvents, deriveRollingCauseDecision, deriveCurrentCauseDecisionForTask, prepareRemediationEvent, stopEscalateOverrideDecisionEventIds, } from './alpha6/remediation.js';
|
|
27
38
|
import { assessPlanIntegrityRecovery, defaultPlanIntegrityRecoveryActor, } from './alpha6/plan-integrity.js';
|
|
28
39
|
import { mechanicalCauseBindings, normalizeMechanicalFailures, } from './alpha6/remediation-cause.js';
|
|
29
40
|
import { assertMechanicalFeasibilityFresh, evaluateMechanicalFeasibility, hashMechanicalFeasibilityEvidence, } from './alpha6/mechanical-feasibility.js';
|
|
30
|
-
import { assessMilestoneInitialAssembly, applyInitialMilestonePlanTransaction, applyMilestoneScopeChangeTransaction, assertMilestoneDependenciesDeclared, assertMilestoneTaskRunnable, assessMilestoneTaskDependencies, assertMilestoneMembershipIntegrity, assertMilestoneScopeChangeStatusAllowed, inspectMilestoneWithIntegrity, normalizeMilestonePlan, prepareMilestoneScopeChangeCandidate, prepareMilestoneScopeChangeBundle, readMilestoneScopeChangeEvents, readMilestoneForScopeChange, readMilestoneWithIntegrity, } from './alpha6/milestone.js';
|
|
41
|
+
import { assessMilestoneInitialAssembly, applyInitialMilestonePlanTransaction, applyMilestoneScopeChangeTransaction, assertMilestoneDependenciesDeclared, assertMilestoneTaskRunnable, assessMilestoneTaskDependencies, assertMilestoneMembershipIntegrity, assertMilestoneScopeChangeStatusAllowed, inspectMilestoneWithIntegrity, normalizeMilestonePlan, prepareMilestoneScopeChangeCandidate, prepareMilestoneScopeChangeBundle, readMilestoneScopeChangeEvents, readMilestoneForScopeChange, assessMilestoneScopeCompletion, readMilestoneWithIntegrity, } from './alpha6/milestone.js';
|
|
31
42
|
import { applyProjectTransaction, recoverProjectTransactions, runRollbackProjectTransaction, } from './beta1/project-transaction.js';
|
|
32
43
|
import { buildCurrentStrictStepReviewCycle, countUnverifiedStrictStepReviews, ensurePendingStrictStepReview, isStepReviewRequired, latestFailedStepReviewRouting, latestPassedVerifiedStepReviewEvent, recordStrictStepReview, verifiedHistoricalStepReviewCommits, verifiedHistoricalStepReviewBindings, } from './alpha6/review.js';
|
|
33
|
-
import { applyMilestoneAutonomyContract, assertAutonomousPlanEvolution, prepareMilestoneAutonomyContract, readMilestoneAutonomyEvents, requireActiveMilestoneAutonomy, } from './alpha7/autonomy.js';
|
|
44
|
+
import { applyMilestoneAutonomyContract, bindMilestoneAutonomyToGrant, milestoneSemanticScopeHash, assertAutonomousPlanEvolution, prepareMilestoneAutonomyContract, readMilestoneAutonomyEvents, requireActiveMilestoneAutonomy, } from './alpha7/autonomy.js';
|
|
34
45
|
import { appendCorrectiveDecisionRecovery, readValidatedCorrectiveDecisionRecoveryForNavigation, readValidatedStopOverrideContextRebindIfApplicable, requireCorrectiveDecisionRecoveryForRun, } from './alpha7/corrective-recovery.js';
|
|
35
46
|
import { applyKnowledgeSelections, hashKnowledgeMap, inspectKnowledgeMap, normalizeRelativePath, reconcileKnowledgeMap, scanProjectKnowledge, selectKnowledgeSources, } from './memory.js';
|
|
36
47
|
import { canonicalJsonStringify, prepareSidecarAppend, sha256Hex } from './alpha6/store-sidecars.js';
|
|
@@ -38,7 +49,7 @@ import { ensureDirectory } from './fs-utils.js';
|
|
|
38
49
|
import { commitStep, mergeTaskBranch, runChecks, startLocalTaskBranch, syncBaseIntoTask, validateTaskHistory, } from './git.js';
|
|
39
50
|
import { changedFiles, currentBranch, gitIsAncestor, headCommit, isClean, resolveRepositoryIdentity, runGit, } from './repository.js';
|
|
40
51
|
import { launchStrictStepReviewer, prepareExternalStrictStepReviewPacket, prepareExternalStrictTaskReviewPacket, prepareReviewerRuntimePreflight, strictReviewInputHash, validateExternalStrictStepReviewInput, validateExternalStrictTaskReviewInput, } from './reviewer.js';
|
|
41
|
-
import { listCoreTaskOperationMutexes, inspectWriterLease as inspectRawWriterLease, toPublicWriterLease, WriterLockManager, } from './state/lock.js';
|
|
52
|
+
import { CoreTaskOperationLockManager, inspectCoreTaskOperationMutex, listCoreTaskOperationMutexes, inspectWriterLease as inspectRawWriterLease, toPublicWriterLease, WriterLockManager, } from './state/lock.js';
|
|
42
53
|
import { FileStateStore } from './state/store.js';
|
|
43
54
|
import { inspectCorrectiveReplanTaskTransactions } from './state/corrective-replan-transaction.js';
|
|
44
55
|
import { inspectCorrectiveYieldTaskTransactions } from './state/corrective-yield-transaction.js';
|
|
@@ -49,14 +60,14 @@ import { createEntityId } from './ulid.js';
|
|
|
49
60
|
import { PACKAGE_NAME, PACKAGE_VERSION } from './version.js';
|
|
50
61
|
import { correctiveReplanBlockedNavigation, correctiveReplanNavigation, evaluateTaskCorrectiveReplanAvailability, evaluateTaskCorrectiveReplanCandidate, identifyCorrectiveReplanPosture, prepareCorrectiveReplanCandidate, TASK_CORRECTIVE_REPLAN_TRANSITION_ID, } from './lifecycle/corrective-replan.js';
|
|
51
62
|
const MAX_STRICT_REVIEW_INFRASTRUCTURE_FAILURES = 2;
|
|
52
|
-
// Release-reviewed exact compatibility target; never infer support from PACKAGE_VERSION.
|
|
53
|
-
const CONSUMED_CARRYOVER_TARGET_VERSION = '2.0.0-beta.13.9';
|
|
54
63
|
export class WorkflowService {
|
|
55
64
|
store;
|
|
65
|
+
checkOptions;
|
|
56
66
|
now;
|
|
57
67
|
transactionHooks;
|
|
58
|
-
constructor(store = new FileStateStore(), now = () => new Date(), transactionHooks = {}) {
|
|
68
|
+
constructor(store = new FileStateStore(), now = () => new Date(), transactionHooks = {}, checkOptions = {}) {
|
|
59
69
|
this.store = store;
|
|
70
|
+
this.checkOptions = checkOptions;
|
|
60
71
|
this.now = now;
|
|
61
72
|
this.transactionHooks = transactionHooks;
|
|
62
73
|
}
|
|
@@ -91,6 +102,9 @@ export class WorkflowService {
|
|
|
91
102
|
const projectFile = path.join(projectRoot, 'project.json');
|
|
92
103
|
const projectAlreadyRegistered = existsSync(projectFile);
|
|
93
104
|
if (!projectAlreadyRegistered) {
|
|
105
|
+
if (!isVirginRegistrationRoot(projectRoot, this.store.root, identity, PACKAGE_VERSION)) {
|
|
106
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Missing registration with retained or unrecognized state requires explicit recovery.');
|
|
107
|
+
}
|
|
94
108
|
// The registration posture is a sidecar under the project root. A completely
|
|
95
109
|
// fresh CODEX_WORKFLOW_STATE_HOME has no project directory yet, so establish
|
|
96
110
|
// only that directory before writing the posture; project.json remains the
|
|
@@ -118,6 +132,36 @@ export class WorkflowService {
|
|
|
118
132
|
return lease ? this.publicWriterLeaseDiagnostic(lease) : null;
|
|
119
133
|
}
|
|
120
134
|
repairStaleWriterLease(repository, entityId) {
|
|
135
|
+
const identity = resolveRepositoryIdentity(repository);
|
|
136
|
+
const locksRoot = this.store.lockRoot(identity.projectId);
|
|
137
|
+
const core = inspectCoreTaskOperationMutex(locksRoot, entityId, this.store.root);
|
|
138
|
+
if (core) {
|
|
139
|
+
const project = this.store.readProject(identity.projectId);
|
|
140
|
+
if (!project.repositoryRoots.includes(identity.repositoryRoot)) {
|
|
141
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Repository is not registered for Core orphan repair.');
|
|
142
|
+
}
|
|
143
|
+
const taskRoot = this.store.taskRoot(identity.projectId, entityId);
|
|
144
|
+
this.store.readTask(identity.projectId, entityId);
|
|
145
|
+
return new CoreTaskOperationLockManager(locksRoot, this.now, this.store.root).repairExpiredOrphan(entityId, (mutex) => {
|
|
146
|
+
const replan = inspectCorrectiveReplanTaskTransactions(this.store.projectRoot(identity.projectId), this.store.root);
|
|
147
|
+
const yielded = inspectCorrectiveYieldTaskTransactions(this.store.projectRoot(identity.projectId), this.store.root);
|
|
148
|
+
if ([...replan.pending, ...replan.corrupt, ...yielded.pending, ...yielded.corrupt].some(item => item.taskId === entityId)) {
|
|
149
|
+
throw new WorkflowError('TASK_RECOVERY_REQUIRED', 'Pending or corrupt Task journal state requires transaction recovery.');
|
|
150
|
+
}
|
|
151
|
+
for (const name of ['.transactions', '.yield-transactions']) {
|
|
152
|
+
const exactTransaction = path.join(taskRoot, name, mutex.operationId);
|
|
153
|
+
try {
|
|
154
|
+
lstatSync(exactTransaction);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (error.code === 'ENOENT')
|
|
158
|
+
continue;
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
throw new WorkflowError('TASK_RECOVERY_REQUIRED', 'The Core operation has retained journal state; use transaction recovery.');
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
121
165
|
const context = this.#mutationContext(repository);
|
|
122
166
|
const lease = context.locks.repairStale(entityId);
|
|
123
167
|
return {
|
|
@@ -125,30 +169,8 @@ export class WorkflowService {
|
|
|
125
169
|
repaired: true,
|
|
126
170
|
};
|
|
127
171
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (snapshot.observation.kind !== 'assessment') {
|
|
131
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Stop override requires a coherent observation boundary.', {
|
|
132
|
-
observation: snapshot.observation,
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
const task = snapshot.tasks.find((candidate) => candidate.id === taskId) ?? null;
|
|
136
|
-
if (!task)
|
|
137
|
-
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
138
|
-
this.assertStopOverrideRepositoryBoundary(snapshot.identity, task);
|
|
139
|
-
return prepareStopEscalateOverride(this.store, task, stepId, expectedRevision, actor, reason, headCommit(snapshot.identity.repositoryRoot));
|
|
140
|
-
}
|
|
141
|
-
applyTaskStopOverride(repository, taskId, stepId, expectedRevision, actor, reason, confirmationCode) {
|
|
142
|
-
const snapshot = this.observationSnapshot(repository);
|
|
143
|
-
if (snapshot.observation.kind !== 'assessment') {
|
|
144
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Stop override requires a coherent observation boundary.', {
|
|
145
|
-
observation: snapshot.observation,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
const context = this.#mutationContext(repository);
|
|
149
|
-
const task = context.store.readTask(context.identity.projectId, taskId);
|
|
150
|
-
this.assertStopOverrideRepositoryBoundary(context.identity, task, context.locks.list());
|
|
151
|
-
return appendStopEscalateOverride(context.store, task, stepId, expectedRevision, actor, reason, headCommit(context.identity.repositoryRoot), confirmationCode, this.now());
|
|
172
|
+
authorizeDelegatedCorrectiveReplan(repository, taskId, candidate) {
|
|
173
|
+
return this.correctiveReplanController(repository).authorizeDelegated({ taskId, candidate });
|
|
152
174
|
}
|
|
153
175
|
prepareCorrectiveReplan(repository, taskId, candidate) {
|
|
154
176
|
return this.correctiveReplanController(repository).prepare({ taskId, candidate });
|
|
@@ -295,92 +317,6 @@ export class WorkflowService {
|
|
|
295
317
|
invalidation: invalidation.event,
|
|
296
318
|
};
|
|
297
319
|
}
|
|
298
|
-
recoverDownstreamProofReplan(repository, taskId, expectedTaskRevision, actor, reason) {
|
|
299
|
-
const context = this.#mutationContext(repository);
|
|
300
|
-
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
301
|
-
assertExpectedRevision(task, expectedTaskRevision);
|
|
302
|
-
// This repair intentionally runs before Project Knowledge refresh: the exact
|
|
303
|
-
// dependency-only transport can make the current map stale, while the
|
|
304
|
-
// recovery assessment still binds the stranded Task to its recorded rebind.
|
|
305
|
-
const observation = this.observationSnapshot(repository).observation;
|
|
306
|
-
if (observation.kind !== 'assessment') {
|
|
307
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream-proof replan recovery requires a coherent observation boundary.', {
|
|
308
|
-
observation,
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
const currentPlanRiskAuditExists = readPlanRiskAuditEvents(this.store, task).some((event) => event.taskRevision === task.revision && event.planHash === task.planHash);
|
|
312
|
-
const assessment = assessDownstreamProofReplanRecovery(this.store, context.identity.repositoryRoot, task, { currentPlanRiskAuditExists });
|
|
313
|
-
const environmentBlockers = [
|
|
314
|
-
...(!assessment.dependencyRecoveryCommit
|
|
315
|
-
? ['The exact beta.12.7 dependency compatibility must be registered before Task recovery.']
|
|
316
|
-
: []),
|
|
317
|
-
...(observation.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
|
|
318
|
-
...(observation.pendingProjectTransactions.length > 0
|
|
319
|
-
|| observation.pendingMilestoneTransactions.length > 0
|
|
320
|
-
|| observation.pendingTaskTransactions.length > 0
|
|
321
|
-
? ['No pending Workflow transaction may exist.'] : []),
|
|
322
|
-
...(observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
|
|
323
|
-
...(observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
|
|
324
|
-
];
|
|
325
|
-
if (!assessment.applicable || !assessment.eligible || environmentBlockers.length > 0) {
|
|
326
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Task is not eligible for bounded downstream-proof replan recovery.', {
|
|
327
|
-
taskId,
|
|
328
|
-
blockers: [...assessment.blockers, ...environmentBlockers],
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
const now = this.now();
|
|
332
|
-
const resultTask = {
|
|
333
|
-
...task,
|
|
334
|
-
revision: task.revision + 1,
|
|
335
|
-
updatedAt: now.toISOString(),
|
|
336
|
-
status: 'needs_fix',
|
|
337
|
-
authorizations: task.authorizations.map((authorization) => authorization.kind === 'execution' && authorization.decision === 'approved'
|
|
338
|
-
? { ...authorization, decision: 'superseded' }
|
|
339
|
-
: authorization),
|
|
340
|
-
review: emptyReview(),
|
|
341
|
-
resultHash: null,
|
|
342
|
-
evidenceHash: null,
|
|
343
|
-
blockReason: `${assessment.invalidation.activeStepId} downstream-proof recovery requires replacement of the full unfinished Step Plan before execution can resume.`,
|
|
344
|
-
};
|
|
345
|
-
const recovery = prepareDownstreamProofReplanRecovery(this.store, resultTask, assessment, { previousTaskRevision: task.revision, actor, reason }, now);
|
|
346
|
-
const projectRoot = this.store.projectRoot(task.projectId);
|
|
347
|
-
const relative = (absolute) => path.relative(projectRoot, absolute).split(path.sep).join('/');
|
|
348
|
-
const taskRoot = this.store.taskRoot(task.projectId, task.id);
|
|
349
|
-
applyProjectTransaction({
|
|
350
|
-
projectRoot,
|
|
351
|
-
projectId: task.projectId,
|
|
352
|
-
transactionId: `tx-${createEntityId('TASK', now.getTime()).toLowerCase()}`,
|
|
353
|
-
kind: 'downstream-proof-replan-recovery',
|
|
354
|
-
now,
|
|
355
|
-
targets: [
|
|
356
|
-
{
|
|
357
|
-
path: relative(path.join(taskRoot, downstreamProofReplanRecoverySidecar())),
|
|
358
|
-
content: recovery.postimage,
|
|
359
|
-
},
|
|
360
|
-
{ path: relative(path.join(taskRoot, 'state.json')), content: `${JSON.stringify(resultTask, null, 2)}\n` },
|
|
361
|
-
],
|
|
362
|
-
validate: () => {
|
|
363
|
-
const saved = this.store.readTask(task.projectId, task.id);
|
|
364
|
-
const events = readDownstreamProofReplanRecoveries(this.store, saved);
|
|
365
|
-
if (saved.revision !== resultTask.revision
|
|
366
|
-
|| saved.status !== 'needs_fix'
|
|
367
|
-
|| saved.planHash !== task.planHash
|
|
368
|
-
|| events.at(-1)?.eventHash !== recovery.event.eventHash
|
|
369
|
-
|| headCommit(context.identity.repositoryRoot) !== recovery.event.preservedHeadCommit
|
|
370
|
-
|| JSON.stringify(changedFiles(context.identity.repositoryRoot).sort())
|
|
371
|
-
!== JSON.stringify(recovery.event.dirtyFiles)) {
|
|
372
|
-
throw new WorkflowError('STATE_CORRUPT', 'Downstream-proof replan recovery transaction readback failed.', {
|
|
373
|
-
taskId,
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
validateTaskHistory(context.identity.repositoryRoot, saved);
|
|
377
|
-
},
|
|
378
|
-
});
|
|
379
|
-
return {
|
|
380
|
-
task: this.store.readTask(task.projectId, task.id),
|
|
381
|
-
recovery: recovery.event,
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
320
|
correctiveReplanController(repository) {
|
|
385
321
|
const identity = resolveRepositoryIdentity(repository);
|
|
386
322
|
return createCorrectiveReplanPublicController({
|
|
@@ -568,6 +504,7 @@ export class WorkflowService {
|
|
|
568
504
|
if (milestone.status === 'accepted' || milestone.status === 'cancelled') {
|
|
569
505
|
throw new WorkflowError('TRANSITION_BLOCKED', `Milestone ${milestoneId} is ${milestone.status}.`);
|
|
570
506
|
}
|
|
507
|
+
assertMilestoneScopeChangeStatusAllowed(milestone);
|
|
571
508
|
if (milestone.baseBranch !== baseBranch) {
|
|
572
509
|
throw new WorkflowError('TRANSITION_BLOCKED', 'Task and Milestone must use the same base branch.', {
|
|
573
510
|
taskBaseBranch: baseBranch,
|
|
@@ -1009,7 +946,7 @@ export class WorkflowService {
|
|
|
1009
946
|
milestoneId: milestone.id,
|
|
1010
947
|
oldTaskId: oldTask.id,
|
|
1011
948
|
incidentDependencies,
|
|
1012
|
-
requiredAction: '
|
|
949
|
+
requiredAction: 'The owner must decide whether to stop this Task or revise the Milestone scope through supported transitions; structural replacement is unavailable.',
|
|
1013
950
|
});
|
|
1014
951
|
}
|
|
1015
952
|
/* P04-B retains the existing one-for-one transaction below as implementation evidence,
|
|
@@ -1449,6 +1386,20 @@ export class WorkflowService {
|
|
|
1449
1386
|
locks.release(milestoneId, lease.token);
|
|
1450
1387
|
}
|
|
1451
1388
|
}
|
|
1389
|
+
bindMilestoneAutonomy(repository, milestoneId, expectedRevision, actor, grantId) {
|
|
1390
|
+
const { identity, locks } = this.#mutationContext(repository);
|
|
1391
|
+
const lease = locks.acquire(milestoneId, 'workflow-core:autonomy-binding');
|
|
1392
|
+
try {
|
|
1393
|
+
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
1394
|
+
assertExpectedRevision(milestone, expectedRevision);
|
|
1395
|
+
this.requireActiveKnowledgeMap(identity.repositoryRoot, identity.projectId);
|
|
1396
|
+
return bindMilestoneAutonomyToGrant({ store: this.store, projectId: identity.projectId,
|
|
1397
|
+
milestone, actor, grantId, now: this.now() });
|
|
1398
|
+
}
|
|
1399
|
+
finally {
|
|
1400
|
+
locks.release(milestoneId, lease.token);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1452
1403
|
evolveMilestonePlanAutonomously(repository, milestoneId, expectedRevision, plan, actor) {
|
|
1453
1404
|
const { identity, locks } = this.#mutationContext(repository);
|
|
1454
1405
|
const authorityLease = locks.acquire(milestoneId, 'workflow-core:milestone-authority');
|
|
@@ -1522,34 +1473,37 @@ export class WorkflowService {
|
|
|
1522
1473
|
locks.release(milestoneId, authorityLease.token);
|
|
1523
1474
|
}
|
|
1524
1475
|
}
|
|
1476
|
+
assertMilestoneValidationReady(repositoryRoot, milestone) {
|
|
1477
|
+
if (!['active', 'awaiting_final_acceptance'].includes(milestone.status)) {
|
|
1478
|
+
throw new WorkflowError('TRANSITION_BLOCKED', `Milestone ${milestone.id} cannot be validated while ${milestone.status}.`);
|
|
1479
|
+
}
|
|
1480
|
+
this.assertMilestoneAuthorizationFresh(milestone);
|
|
1481
|
+
if (currentBranch(repositoryRoot) !== milestone.baseBranch || !isClean(repositoryRoot)) {
|
|
1482
|
+
throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Milestone validation requires a clean base branch checkout.', {
|
|
1483
|
+
expectedBranch: milestone.baseBranch,
|
|
1484
|
+
actualBranch: currentBranch(repositoryRoot),
|
|
1485
|
+
changedFiles: changedFiles(repositoryRoot),
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
const incomplete = milestone.memberships.filter((membership) => {
|
|
1489
|
+
if (membership.disposition !== 'required')
|
|
1490
|
+
return false;
|
|
1491
|
+
return this.store.readTask(milestone.projectId, membership.taskId).status !== 'merged';
|
|
1492
|
+
});
|
|
1493
|
+
if (incomplete.length > 0) {
|
|
1494
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Required Milestone Tasks are not merged.', {
|
|
1495
|
+
taskIds: incomplete.map((membership) => membership.taskId),
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1525
1499
|
validateMilestone(repository, milestoneId, expectedRevision) {
|
|
1526
1500
|
const { identity, locks } = this.#mutationContext(repository);
|
|
1527
1501
|
const authorityLease = locks.acquire(milestoneId, 'workflow-core:milestone-authority');
|
|
1528
1502
|
try {
|
|
1529
1503
|
const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
|
|
1530
1504
|
assertExpectedRevision(milestone, expectedRevision);
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
}
|
|
1534
|
-
this.assertMilestoneAuthorizationFresh(milestone);
|
|
1535
|
-
if (currentBranch(identity.repositoryRoot) !== milestone.baseBranch || !isClean(identity.repositoryRoot)) {
|
|
1536
|
-
throw new WorkflowError('GIT_PRECONDITION_FAILED', 'Milestone validation requires a clean base branch checkout.', {
|
|
1537
|
-
expectedBranch: milestone.baseBranch,
|
|
1538
|
-
actualBranch: currentBranch(identity.repositoryRoot),
|
|
1539
|
-
changedFiles: changedFiles(identity.repositoryRoot),
|
|
1540
|
-
});
|
|
1541
|
-
}
|
|
1542
|
-
const incomplete = milestone.memberships.filter((membership) => {
|
|
1543
|
-
if (membership.disposition !== 'required')
|
|
1544
|
-
return false;
|
|
1545
|
-
return this.store.readTask(identity.projectId, membership.taskId).status !== 'merged';
|
|
1546
|
-
});
|
|
1547
|
-
if (incomplete.length > 0) {
|
|
1548
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Required Milestone Tasks are not merged.', {
|
|
1549
|
-
taskIds: incomplete.map((membership) => membership.taskId),
|
|
1550
|
-
});
|
|
1551
|
-
}
|
|
1552
|
-
const checks = runChecks(identity.repositoryRoot, milestone.checks);
|
|
1505
|
+
this.assertMilestoneValidationReady(identity.repositoryRoot, milestone);
|
|
1506
|
+
const checks = runChecks(identity.repositoryRoot, milestone.checks, this.checkOptions);
|
|
1553
1507
|
const baseHead = headCommit(identity.repositoryRoot);
|
|
1554
1508
|
const root = this.store.milestoneRoot(identity.projectId, milestoneId);
|
|
1555
1509
|
const stagedEvidence = this.store.stageArtifact(root, 'evidence.json', JSON.stringify({
|
|
@@ -2097,10 +2051,10 @@ export class WorkflowService {
|
|
|
2097
2051
|
const { identity } = this.#mutationContext(repository);
|
|
2098
2052
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
2099
2053
|
assertExpectedRevision(task, expectedRevision);
|
|
2100
|
-
this.
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2054
|
+
const contextBlocker = this.taskContextChangeBlocker(task, 'rebind', identity.repositoryRoot);
|
|
2055
|
+
// Preserve Plan-corruption precedence over the active-Step rejection.
|
|
2056
|
+
if (contextBlocker && !contextBlocker.activeStep)
|
|
2057
|
+
throw contextBlocker.error;
|
|
2104
2058
|
if (!task.planHash ||
|
|
2105
2059
|
!task.planObjective ||
|
|
2106
2060
|
task.knowledgeMapRevision === null ||
|
|
@@ -2110,10 +2064,8 @@ export class WorkflowService {
|
|
|
2110
2064
|
task.graphUse === null) {
|
|
2111
2065
|
throw new WorkflowError('STATE_CORRUPT', `Task ${taskId} has no complete Plan binding to rebind.`);
|
|
2112
2066
|
}
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
throw new WorkflowError('TRANSITION_BLOCKED', `Cannot rebind Task Plan knowledge while ${activeStep.id} is in progress.`);
|
|
2116
|
-
}
|
|
2067
|
+
if (contextBlocker)
|
|
2068
|
+
throw contextBlocker.error;
|
|
2117
2069
|
const knowledgeMap = this.requireActiveKnowledgeMap(identity.repositoryRoot, identity.projectId);
|
|
2118
2070
|
const knowledgeMapHash = hashKnowledgeMap(knowledgeMap);
|
|
2119
2071
|
if (task.knowledgeMapRevision === knowledgeMap.revision &&
|
|
@@ -2190,21 +2142,24 @@ export class WorkflowService {
|
|
|
2190
2142
|
const currentMap = this.store.readKnowledgeMap(identity.projectId);
|
|
2191
2143
|
assertExpectedRevision(task, expectedTaskRevision);
|
|
2192
2144
|
assertExpectedRevision(currentMap, expectedKnowledgeMapRevision);
|
|
2193
|
-
this.
|
|
2194
|
-
if (
|
|
2195
|
-
throw
|
|
2196
|
-
}
|
|
2197
|
-
const activeStep = task.steps.find((step) => step.status === 'in_progress');
|
|
2198
|
-
if (activeStep) {
|
|
2199
|
-
throw new WorkflowError('TRANSITION_BLOCKED', `Cannot refresh Task context while ${activeStep.id} is in progress.`);
|
|
2200
|
-
}
|
|
2145
|
+
const contextBlocker = this.taskContextChangeBlocker(task, 'refresh', identity.repositoryRoot);
|
|
2146
|
+
if (contextBlocker)
|
|
2147
|
+
throw contextBlocker.error;
|
|
2201
2148
|
const inspectedMap = inspectKnowledgeMap(currentMap, scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now()));
|
|
2202
2149
|
const transitions = [];
|
|
2203
|
-
const
|
|
2150
|
+
const retainedAudits = JSON.stringify(readPlanRiskAuditEvents(this.store, task));
|
|
2151
|
+
const retainedRemediation = JSON.stringify(readRemediationEvents(this.store, task));
|
|
2152
|
+
const transport = this.registeredPlanningContextTransport(task, identity.repositoryRoot);
|
|
2153
|
+
const planningRecovery = transport ? null : readFailedStepPlanningRecovery(this.store, task);
|
|
2154
|
+
const planningOnly = Boolean(planningRecovery || transport);
|
|
2155
|
+
const currentPlanRiskAudit = transport ? null : planningRecovery?.current ?? readCurrentPlanRiskAudit(this.store, task);
|
|
2204
2156
|
const currentPlanArtifactHash = task.planHash
|
|
2205
2157
|
? this.store.hashArtifact(this.store.taskRoot(identity.projectId, task.id), 'plan.md')
|
|
2206
2158
|
: null;
|
|
2207
2159
|
const refreshAssessment = assessDelegatedKnowledgeRefresh(currentMap, inspectedMap, task, currentPlanArtifactHash);
|
|
2160
|
+
if (planningOnly && refreshAssessment.mode !== 'delegated-content-only') {
|
|
2161
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Stopped planning context refresh permits content-only Knowledge drift.');
|
|
2162
|
+
}
|
|
2208
2163
|
// Validate every delegated transition before the first write so a narrow grant cannot
|
|
2209
2164
|
// leave the composite operation half-applied.
|
|
2210
2165
|
this.delegatedAuthorization(identity.projectId, delegationGrantId, 'task.execution_authorize', actor, task);
|
|
@@ -2251,11 +2206,15 @@ export class WorkflowService {
|
|
|
2251
2206
|
appendReboundPlanRiskAudit(this.store, task, refreshedTask, currentPlanRiskAudit, this.now());
|
|
2252
2207
|
}
|
|
2253
2208
|
transitions.push('task knowledge-rebind');
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2209
|
+
// A stopped Plan may regain its Knowledge binding, never execution authority.
|
|
2210
|
+
if (!planningOnly)
|
|
2211
|
+
refreshedTask = this.authorizeTask(repository, taskId, refreshedTask.revision, actor, refreshAssessment.mode === 'delegated-plan-bounded-supporting-source'
|
|
2212
|
+
? 'Delegated Plan-bounded supporting-source context refresh.'
|
|
2213
|
+
: 'Delegated content-only context refresh.', delegationGrantId);
|
|
2214
|
+
if (!planningOnly)
|
|
2215
|
+
transitions.push('task execution authorize');
|
|
2258
2216
|
return {
|
|
2217
|
+
authority: planningOnly ? 'planning-only' : 'execution',
|
|
2259
2218
|
mode: refreshAssessment.mode,
|
|
2260
2219
|
transitions,
|
|
2261
2220
|
addedSources: refreshAssessment.addedSources,
|
|
@@ -2265,8 +2224,27 @@ export class WorkflowService {
|
|
|
2265
2224
|
},
|
|
2266
2225
|
validate: (result) => {
|
|
2267
2226
|
const currentTask = this.store.readTask(identity.projectId, task.id);
|
|
2268
|
-
if (currentTask.revision !== result.task.revision || currentTask.status !== 'ready') {
|
|
2269
|
-
throw new WorkflowError('STATE_CORRUPT', 'Context refresh transaction did not commit
|
|
2227
|
+
if (currentTask.revision !== result.task.revision || currentTask.status !== (planningOnly ? 'awaiting_execution_authorization' : 'ready')) {
|
|
2228
|
+
throw new WorkflowError('STATE_CORRUPT', 'Context refresh transaction did not commit the expected Task posture.');
|
|
2229
|
+
}
|
|
2230
|
+
if (transport) {
|
|
2231
|
+
if (!readFailedCheckpointDirtyTransport(this.store, identity.repositoryRoot, currentTask, transport.stepId, transport.sourceHead, transport.sourceDirtyHash)) {
|
|
2232
|
+
throw new WorkflowError('STATE_CORRUPT', 'Transport context proof disappeared.');
|
|
2233
|
+
}
|
|
2234
|
+
if (JSON.stringify(currentTask.steps) !== JSON.stringify(task.steps)
|
|
2235
|
+
|| JSON.stringify(currentTask.authorizations) !== JSON.stringify(task.authorizations)
|
|
2236
|
+
|| JSON.stringify(readPlanRiskAuditEvents(this.store, currentTask)) !== retainedAudits
|
|
2237
|
+
|| JSON.stringify(readRemediationEvents(this.store, currentTask)) !== retainedRemediation) {
|
|
2238
|
+
throw new WorkflowError('STATE_CORRUPT', 'Transport context refresh changed retained evidence.');
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
if (planningRecovery && !transport) {
|
|
2242
|
+
const rebound = readFailedStepPlanningRecovery(this.store, currentTask);
|
|
2243
|
+
if (!rebound || rebound.baseline.eventHash !== planningRecovery.baseline.eventHash
|
|
2244
|
+
|| JSON.stringify(currentTask.steps) !== JSON.stringify(task.steps)
|
|
2245
|
+
|| JSON.stringify(currentTask.authorizations) !== JSON.stringify(task.authorizations)) {
|
|
2246
|
+
throw new WorkflowError('STATE_CORRUPT', 'Planning context refresh changed retained recovery evidence.');
|
|
2247
|
+
}
|
|
2270
2248
|
}
|
|
2271
2249
|
const currentKnowledgeMap = this.store.readKnowledgeMap(identity.projectId);
|
|
2272
2250
|
if (currentKnowledgeMap.revision !== result.knowledgeMap.revision) {
|
|
@@ -2339,6 +2317,7 @@ export class WorkflowService {
|
|
|
2339
2317
|
const { identity } = this.#mutationContext(repository);
|
|
2340
2318
|
this.readCurrentAdoptionPostureStrict(identity.projectId);
|
|
2341
2319
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
2320
|
+
assertNoPendingBaseSyncValidation(task);
|
|
2342
2321
|
assertExpectedRevision(task, expectedRevision);
|
|
2343
2322
|
if (readCurrentPreExecutionReplan(this.store, task)) {
|
|
2344
2323
|
throw new WorkflowError('TRANSITION_BLOCKED', 'Pre-execution replan requires task plan-set; the blocking audit cannot be replaced in place.', { taskId, requiredAction: 'task plan-set' });
|
|
@@ -2489,7 +2468,16 @@ export class WorkflowService {
|
|
|
2489
2468
|
handoffTask(repository, taskId, expectedRevision, actor, targetActor, reason, writerToken = null, grantId = null, expiresAt = null, replacePending = false) {
|
|
2490
2469
|
const context = this.#mutationContext(repository);
|
|
2491
2470
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
2471
|
+
assertNoPendingBaseSyncValidation(task);
|
|
2492
2472
|
assertExpectedRevision(task, expectedRevision);
|
|
2473
|
+
if (task.status === 'ready' && changedFiles(context.identity.repositoryRoot).length > 0) {
|
|
2474
|
+
const carryover = this.assessCurrentRootCauseReplanDirtyCarryover(context.identity.repositoryRoot, task);
|
|
2475
|
+
if (carryover.requiredActor && targetActor.trim() !== carryover.requiredActor) {
|
|
2476
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Native carryover handoff must return to its recorded execution actor.', {
|
|
2477
|
+
taskId, requiredActor: carryover.requiredActor,
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2493
2481
|
if (readCurrentPreExecutionReplan(this.store, task)) {
|
|
2494
2482
|
throw new WorkflowError('TRANSITION_BLOCKED', 'Task ownership cannot change while a pre-execution replan is pending.', {
|
|
2495
2483
|
taskId,
|
|
@@ -2793,7 +2781,7 @@ export class WorkflowService {
|
|
|
2793
2781
|
const step = requireStep(task, stepId);
|
|
2794
2782
|
const allowedWrites = effectiveStepAllowedWrites(this.store, task, stepId);
|
|
2795
2783
|
const remediationHistoryForStep = readRemediationEvents(this.store, task).filter((event) => event.stepId === stepId);
|
|
2796
|
-
if (
|
|
2784
|
+
if (stepStartPhase(step.status, false) === 'not-startable') {
|
|
2797
2785
|
throw new WorkflowError('TRANSITION_BLOCKED', `${stepId} cannot start from ${step.status}.`);
|
|
2798
2786
|
}
|
|
2799
2787
|
const planRiskAudit = readCurrentPlanRiskAudit(this.store, task);
|
|
@@ -2813,6 +2801,7 @@ export class WorkflowService {
|
|
|
2813
2801
|
? guardedPosture.correctiveDecision
|
|
2814
2802
|
: null;
|
|
2815
2803
|
const recovery = staleDecision?.decision === 'continue-fix'
|
|
2804
|
+
&& !isKnowledgeReboundSupportDecision(this.store, task, staleDecision)
|
|
2816
2805
|
? requireCorrectiveDecisionRecoveryForRun(this.store, task, stepId, actor, writerToken)
|
|
2817
2806
|
: null;
|
|
2818
2807
|
const stopOverrideContextRebind = staleDecision?.decision === 'stop-escalate'
|
|
@@ -2823,23 +2812,28 @@ export class WorkflowService {
|
|
|
2823
2812
|
}
|
|
2824
2813
|
const knowledgeMap = this.assertTaskKnowledgeBinding(context.identity.repositoryRoot, task);
|
|
2825
2814
|
const dirty = changedFiles(context.identity.repositoryRoot);
|
|
2826
|
-
const
|
|
2815
|
+
const startPhase = stepStartPhase(step.status, dirty.length > 0);
|
|
2816
|
+
const downstreamProofCarryover = startPhase === 'planned-dirty'
|
|
2827
2817
|
? assessDownstreamProofCarryover(this.store, context.identity.repositoryRoot, task, stepId)
|
|
2828
2818
|
: null;
|
|
2829
|
-
const planIntegrityDirtyCarryover =
|
|
2819
|
+
const planIntegrityDirtyCarryover = startPhase === 'planned-dirty'
|
|
2830
2820
|
? this.assessPostRebindCheckSupportDirtyCarryover(context.identity.repositoryRoot, task, stepId)
|
|
2831
2821
|
: null;
|
|
2832
|
-
const
|
|
2822
|
+
const navigationDirtyCarryover = startPhase === 'planned-dirty'
|
|
2823
|
+
? assessNavigationDirtyCarryover(this.store, context.identity.repositoryRoot, task, stepId)
|
|
2824
|
+
: null;
|
|
2825
|
+
const rootCauseReplanDirtyCarryover = startPhase === 'planned-dirty' && !navigationDirtyCarryover?.applicable
|
|
2833
2826
|
? this.assessCurrentRootCauseReplanDirtyCarryover(context.identity.repositoryRoot, task, stepId)
|
|
2834
2827
|
: null;
|
|
2835
|
-
if (
|
|
2836
|
-
&& dirty.length > 0
|
|
2828
|
+
if (startPhase === 'planned-dirty'
|
|
2837
2829
|
&& !resumesCorrectiveReplan
|
|
2838
2830
|
&& !downstreamProofCarryover?.eligible
|
|
2839
2831
|
&& !planIntegrityDirtyCarryover?.eligible
|
|
2832
|
+
&& !navigationDirtyCarryover?.eligible
|
|
2840
2833
|
&& !rootCauseReplanDirtyCarryover?.eligible) {
|
|
2841
2834
|
throw new WorkflowError('GIT_PRECONDITION_FAILED', `${stepId} must start from a clean checkout.`, {
|
|
2842
2835
|
changedFiles: dirty,
|
|
2836
|
+
...(navigationDirtyCarryover?.applicable ? { navigationDirtyCarryoverBlockers: navigationDirtyCarryover.blockers } : {}),
|
|
2843
2837
|
...(downstreamProofCarryover?.applicable
|
|
2844
2838
|
? { downstreamProofCarryoverBlockers: downstreamProofCarryover.blockers }
|
|
2845
2839
|
: {}),
|
|
@@ -2852,7 +2846,7 @@ export class WorkflowService {
|
|
|
2852
2846
|
});
|
|
2853
2847
|
}
|
|
2854
2848
|
const dirtyOutsideStep = dirty.filter((file) => !pathAllowed(file, allowedWrites));
|
|
2855
|
-
if ((
|
|
2849
|
+
if ((startPhase === 'failed-retry' || resumesCorrectiveReplan) && dirtyOutsideStep.length > 0) {
|
|
2856
2850
|
throw new WorkflowError('GIT_PRECONDITION_FAILED', `${stepId} retry has changes outside allowedWrites.`, {
|
|
2857
2851
|
changedFiles: dirtyOutsideStep,
|
|
2858
2852
|
});
|
|
@@ -3073,7 +3067,7 @@ export class WorkflowService {
|
|
|
3073
3067
|
return task;
|
|
3074
3068
|
}
|
|
3075
3069
|
validateTaskHistory(context.identity.repositoryRoot, task);
|
|
3076
|
-
const checks = runChecks(context.identity.repositoryRoot, step.checks);
|
|
3070
|
+
const checks = runChecks(context.identity.repositoryRoot, step.checks, this.checkOptions);
|
|
3077
3071
|
const failed = checks.filter((check) => !check.passed);
|
|
3078
3072
|
if (failed.length > 0) {
|
|
3079
3073
|
const now = this.now();
|
|
@@ -3081,13 +3075,16 @@ export class WorkflowService {
|
|
|
3081
3075
|
// Bind useful uncommitted work to this failure without treating it as
|
|
3082
3076
|
// completed-Step authority.
|
|
3083
3077
|
const dirtyFiles = changedFiles(context.identity.repositoryRoot).sort();
|
|
3078
|
+
const capturedEvidence = prepareCapturedCheckEvidence(this.store, task, checks);
|
|
3084
3079
|
const failurePause = {
|
|
3085
3080
|
recordedAt: now.toISOString(),
|
|
3086
3081
|
dirtyFiles,
|
|
3087
3082
|
dirtyWorktreeHash: hashDirtyWorktree(context.identity.repositoryRoot, dirtyFiles),
|
|
3088
3083
|
checksHash: sha256Hex(canonicalJsonStringify(checks)),
|
|
3089
3084
|
failedChecks: normalizedFailures,
|
|
3085
|
+
checksEvidence: capturedEvidence.reference,
|
|
3090
3086
|
};
|
|
3087
|
+
const failureC1Posture = readTaskC1Posture(this.store, task);
|
|
3091
3088
|
const preparedRemediation = reviewRequired && planRiskAudit
|
|
3092
3089
|
? prepareRemediationEvent(this.store, task, stepId, 'checks-failed', {
|
|
3093
3090
|
completionCommit: headCommit(context.identity.repositoryRoot),
|
|
@@ -3096,6 +3093,11 @@ export class WorkflowService {
|
|
|
3096
3093
|
reviewerAttestationHash: null,
|
|
3097
3094
|
executionTaskRevision: task.revision,
|
|
3098
3095
|
dirtyWorktreeHash: failurePause.dirtyWorktreeHash,
|
|
3096
|
+
...(failureC1Posture.state === 'none' ? { unclaimedExecutionBoundary: {
|
|
3097
|
+
kind: 'native-unclaimed-check-failure',
|
|
3098
|
+
actor: lease.owner,
|
|
3099
|
+
priorHandoffEventHash: failureC1Posture.latestEvent?.eventHash ?? null,
|
|
3100
|
+
} } : {}),
|
|
3099
3101
|
}, planRiskAudit, now, this.stopOverrideContextRebindDecisionId(task, stepId, context.identity.repositoryRoot), mechanicalCauseBindings(normalizedFailures))
|
|
3100
3102
|
: null;
|
|
3101
3103
|
const steps = task.steps.map((candidate) => candidate.id === stepId
|
|
@@ -3143,6 +3145,7 @@ export class WorkflowService {
|
|
|
3143
3145
|
: 'failed-step-continuation',
|
|
3144
3146
|
now,
|
|
3145
3147
|
targets: [
|
|
3148
|
+
...(capturedEvidence.target ? [capturedEvidence.target] : []),
|
|
3146
3149
|
...(preparedRemediation?.postimage
|
|
3147
3150
|
? [{
|
|
3148
3151
|
path: relative(path.join(taskRoot, 'remediation-events.jsonl')),
|
|
@@ -3158,6 +3161,7 @@ export class WorkflowService {
|
|
|
3158
3161
|
validate: () => {
|
|
3159
3162
|
const saved = this.store.readTask(task.projectId, task.id);
|
|
3160
3163
|
const savedStep = requireStep(saved, stepId);
|
|
3164
|
+
assertCapturedCheckEvidence(this.store, saved, savedStep.failurePause, checks);
|
|
3161
3165
|
const readbackDirtyFiles = changedFiles(context.identity.repositoryRoot).sort();
|
|
3162
3166
|
const remediationHash = preparedRemediation
|
|
3163
3167
|
? readRemediationEvents(this.store, saved).find((event) => event.eventId === preparedRemediation.event.eventId)?.eventHash ?? null
|
|
@@ -3522,6 +3526,7 @@ export class WorkflowService {
|
|
|
3522
3526
|
prepareExternalTaskReview(repository, taskId, expectedRevision, reviewMode = 'ordinary') {
|
|
3523
3527
|
const identity = resolveRepositoryIdentity(repository);
|
|
3524
3528
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
3529
|
+
assertNoPendingBaseSyncValidation(task);
|
|
3525
3530
|
assertExpectedRevision(task, expectedRevision);
|
|
3526
3531
|
const retryingUnverified = task.status === 'blocked' && task.review.status === 'unverified';
|
|
3527
3532
|
if (task.status !== 'validating' && !retryingUnverified) {
|
|
@@ -3604,6 +3609,7 @@ export class WorkflowService {
|
|
|
3604
3609
|
const { identity } = this.#mutationContext(repository);
|
|
3605
3610
|
this.readCurrentAdoptionPostureStrict(identity.projectId);
|
|
3606
3611
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
3612
|
+
assertNoPendingBaseSyncValidation(task);
|
|
3607
3613
|
assertExpectedRevision(task, expectedRevision);
|
|
3608
3614
|
this.proveOptionalWriterLeaseToken(this.#mutationContext(repository), taskId, writerToken);
|
|
3609
3615
|
this.assertTaskMutationAllowedByHandoff(task, actor ?? null, writerToken);
|
|
@@ -3654,12 +3660,52 @@ export class WorkflowService {
|
|
|
3654
3660
|
this.assertTaskKnowledgeBinding(context.identity.repositoryRoot, task);
|
|
3655
3661
|
return appendCorrectiveDecisionRecovery(this.store, task, stepId, expectedRevision, actor, writerToken, this.now());
|
|
3656
3662
|
}
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
+
/** Recording a machine finding may retain its exact failed execution context;
|
|
3664
|
+
* it never approves changed Knowledge or authorizes another task run. */
|
|
3665
|
+
assessPlanIntegrityRecoveryAdmission(repository, task, stepId) {
|
|
3666
|
+
const assessment = assessPlanIntegrityRecovery(this.store, repository, task, stepId);
|
|
3667
|
+
if (!assessment.eligible)
|
|
3668
|
+
return assessment;
|
|
3669
|
+
try {
|
|
3670
|
+
this.assertExecutionAuthorizationFresh(task, repository);
|
|
3671
|
+
const stored = this.store.readKnowledgeMap(task.projectId);
|
|
3672
|
+
const storedHash = hashKnowledgeMap(stored);
|
|
3673
|
+
if (stored.status !== 'active' || !stored.approval || stored.approval.mapHash !== storedHash
|
|
3674
|
+
|| task.knowledgeMapRevision !== stored.revision || task.knowledgeMapHash !== storedHash) {
|
|
3675
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Recovery requires the exact approved Task Knowledge binding.');
|
|
3676
|
+
}
|
|
3677
|
+
const inspected = inspectKnowledgeMap(stored, scanProjectKnowledge(repository, task.projectId, this.now()));
|
|
3678
|
+
if (inspected.status === 'active' && inspected.approval?.mapHash === storedHash && hashKnowledgeMap(inspected) === storedHash)
|
|
3679
|
+
return assessment;
|
|
3680
|
+
if (inspected.status !== 'stale' || !isContentOnlyKnowledgeRefresh(stored, inspected)) {
|
|
3681
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Recovery permits only content changes within the recorded failed execution.');
|
|
3682
|
+
}
|
|
3683
|
+
const step = requireStep(task, stepId);
|
|
3684
|
+
const pause = step.failurePause;
|
|
3685
|
+
const last = readRemediationEvents(this.store, task).filter(event => event.stepId === stepId).at(-1);
|
|
3686
|
+
const dirty = changedFiles(repository).sort();
|
|
3687
|
+
const changedKnowledge = inspected.entries.filter(entry => stored.entries.find(previous => previous.id === entry.id)?.contentHash !== entry.contentHash);
|
|
3688
|
+
// Only the immediately recorded native failure may defer Knowledge approval;
|
|
3689
|
+
// historical transport or later Task revisions never establish this admission.
|
|
3690
|
+
if (!pause || !last || last.failureKind !== 'checks-failed'
|
|
3691
|
+
|| last.failureEvidence.executionTaskRevision !== task.revision - 1
|
|
3692
|
+
|| last.failureEvidence.completionCommit !== headCommit(repository)
|
|
3693
|
+
|| last.failureEvidence.checksHash !== pause.checksHash
|
|
3694
|
+
|| last.failureEvidence.dirtyWorktreeHash !== pause.dirtyWorktreeHash
|
|
3695
|
+
|| JSON.stringify(dirty) !== JSON.stringify([...pause.dirtyFiles].sort())
|
|
3696
|
+
|| hashDirtyWorktree(repository, dirty) !== pause.dirtyWorktreeHash
|
|
3697
|
+
|| changedKnowledge.some(entry => !dirty.includes(entry.path)
|
|
3698
|
+
|| !pathAllowed(entry.path, step.allowedWrites) || pathAllowed(entry.path, step.forbiddenScope))) {
|
|
3699
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Recovery Knowledge drift must match the exact native failed-Step HEAD, checks and dirty bytes.');
|
|
3700
|
+
}
|
|
3701
|
+
assertCurrentCapturedCheckEvidence(this.store, task, stepId);
|
|
3702
|
+
return assessment;
|
|
3703
|
+
}
|
|
3704
|
+
catch (error) {
|
|
3705
|
+
if (!(error instanceof WorkflowError) || error.code !== 'TRANSITION_BLOCKED')
|
|
3706
|
+
throw error;
|
|
3707
|
+
return { ...assessment, eligible: false, blockers: [...assessment.blockers, error.message] };
|
|
3708
|
+
}
|
|
3663
3709
|
}
|
|
3664
3710
|
recoverPlanIntegrity(repository, taskId, stepId, expectedRevision, actor, writerToken) {
|
|
3665
3711
|
const context = this.#mutationContext(repository);
|
|
@@ -3688,29 +3734,12 @@ export class WorkflowService {
|
|
|
3688
3734
|
stepId,
|
|
3689
3735
|
});
|
|
3690
3736
|
}
|
|
3691
|
-
const assessment =
|
|
3737
|
+
const assessment = this.assessPlanIntegrityRecoveryAdmission(context.identity.repositoryRoot, task, stepId);
|
|
3692
3738
|
if (!assessment.applicable || !assessment.eligible || !assessment.evidence) {
|
|
3693
3739
|
throw new WorkflowError('TRANSITION_BLOCKED', 'Bounded Plan-integrity recovery is not eligible for the current Task state.', {
|
|
3694
|
-
taskId,
|
|
3695
|
-
stepId,
|
|
3696
|
-
applicable: assessment.applicable,
|
|
3697
|
-
blockers: assessment.blockers,
|
|
3740
|
+
taskId, stepId, applicable: assessment.applicable, blockers: assessment.blockers,
|
|
3698
3741
|
});
|
|
3699
3742
|
}
|
|
3700
|
-
const remediationEventIds = assessment.evidence.kind === 'missing-npm-script-outside-allowed-writes'
|
|
3701
|
-
? [assessment.evidence.remediationEventId]
|
|
3702
|
-
: assessment.evidence.remediationEvents.map((event) => event.eventId);
|
|
3703
|
-
const compatibility = [...(task.dependencyProvenanceRecoveries ?? [])].reverse().find((record) => record.commitSha === headCommit(context.identity.repositoryRoot)
|
|
3704
|
-
&& record.packageVersion === PACKAGE_VERSION
|
|
3705
|
-
&& task.systemCommits.includes(record.commitSha)
|
|
3706
|
-
&& record.planIntegrity?.stepId === stepId
|
|
3707
|
-
&& record.planIntegrity.planHash === task.planHash
|
|
3708
|
-
&& canonicalJsonStringify(record.planIntegrity.remediationEventIds) === canonicalJsonStringify(remediationEventIds)
|
|
3709
|
-
&& record.planIntegrity.dirtyWorktreeHash === hashDirtyWorktree(context.identity.repositoryRoot, record.planIntegrity.dirtyFiles));
|
|
3710
|
-
if (!compatibility) {
|
|
3711
|
-
this.assertExecutionAuthorizationFresh(task, context.identity.repositoryRoot);
|
|
3712
|
-
this.assertTaskKnowledgeBinding(context.identity.repositoryRoot, task);
|
|
3713
|
-
}
|
|
3714
3743
|
const event = appendPlanIntegrityRecoveryDecision(this.store, task, stepId, defaultPlanIntegrityRecoveryActor(task.id), assessment.evidence, this.now());
|
|
3715
3744
|
return { assessment, event };
|
|
3716
3745
|
}
|
|
@@ -3761,6 +3790,9 @@ export class WorkflowService {
|
|
|
3761
3790
|
resolution: currentCycle,
|
|
3762
3791
|
});
|
|
3763
3792
|
}
|
|
3793
|
+
if (!correctiveDecisionCheckoutEligible(this.store, identity.repositoryRoot, task, stepId)) {
|
|
3794
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Corrective decision requires the Task checkout to be clean or match its exact in-scope failed checkpoint.', { taskId, stepId });
|
|
3795
|
+
}
|
|
3764
3796
|
assertCorrectiveAuditorIndependence(this.store, task, stepId, correctiveAudit.auditor);
|
|
3765
3797
|
return appendCorrectiveDecisionEvent(this.store, task, stepId, correctiveAudit, planRiskAudit, task.planHash, this.now());
|
|
3766
3798
|
}
|
|
@@ -3768,6 +3800,7 @@ export class WorkflowService {
|
|
|
3768
3800
|
const context = this.#mutationContext(repository);
|
|
3769
3801
|
const { identity } = context;
|
|
3770
3802
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
3803
|
+
assertNoPendingBaseSyncValidation(task);
|
|
3771
3804
|
assertExpectedRevision(task, expectedRevision);
|
|
3772
3805
|
if (task.status !== 'validating' || task.review.status !== 'passed') {
|
|
3773
3806
|
throw new WorkflowError('TRANSITION_BLOCKED', 'Result requires a passing independent review.');
|
|
@@ -3788,6 +3821,7 @@ export class WorkflowService {
|
|
|
3788
3821
|
acceptTask(repository, taskId, expectedRevision, actor, delegationGrantId = null) {
|
|
3789
3822
|
const { identity } = this.#mutationContext(repository);
|
|
3790
3823
|
const task = this.store.readTask(identity.projectId, taskId);
|
|
3824
|
+
assertNoPendingBaseSyncValidation(task);
|
|
3791
3825
|
assertExpectedRevision(task, expectedRevision);
|
|
3792
3826
|
if (task.status !== 'awaiting_final_acceptance' || !task.resultHash || !task.evidenceHash) {
|
|
3793
3827
|
throw new WorkflowError('TRANSITION_BLOCKED', `Task ${taskId} is not ready for final acceptance.`);
|
|
@@ -3834,59 +3868,70 @@ export class WorkflowService {
|
|
|
3834
3868
|
}
|
|
3835
3869
|
syncTaskBase(repository, taskId, expectedRevision) {
|
|
3836
3870
|
const { identity } = this.#mutationContext(repository);
|
|
3837
|
-
|
|
3871
|
+
let task = this.store.readTask(identity.projectId, taskId);
|
|
3838
3872
|
assertExpectedRevision(task, expectedRevision);
|
|
3839
|
-
if (task.
|
|
3840
|
-
|
|
3841
|
-
}
|
|
3842
|
-
const currentBase = runGit(identity.repositoryRoot, ['rev-parse', task.baseBranch]);
|
|
3843
|
-
if (currentBase === task.baseCommit) {
|
|
3844
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Base branch has not advanced; synchronization is unnecessary.');
|
|
3845
|
-
}
|
|
3846
|
-
const synced = syncBaseIntoTask(identity.repositoryRoot, task.taskBranch, task.baseBranch);
|
|
3847
|
-
const checks = runChecks(identity.repositoryRoot, [...new Set(task.steps.flatMap((step) => step.checks))]);
|
|
3848
|
-
const failed = checks.filter((check) => !check.passed);
|
|
3849
|
-
if (failed.length > 0) {
|
|
3850
|
-
return this.store.writeTask({
|
|
3851
|
-
...task,
|
|
3852
|
-
status: 'needs_fix',
|
|
3853
|
-
baseCommit: synced.baseCommit,
|
|
3854
|
-
systemCommits: [...task.systemCommits, synced.syncCommit],
|
|
3855
|
-
review: emptyReview(),
|
|
3856
|
-
resultHash: null,
|
|
3857
|
-
blockReason: 'Checks failed after synchronizing the base branch.',
|
|
3858
|
-
}, expectedRevision);
|
|
3873
|
+
if (task.pendingBaseSyncValidation !== undefined) {
|
|
3874
|
+
assertPendingBaseSyncValidationReady(task);
|
|
3859
3875
|
}
|
|
3876
|
+
else {
|
|
3877
|
+
if (task.status !== 'merging' || task.workspaceOwner !== 'local' || !task.taskBranch || !task.baseCommit) {
|
|
3878
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Base sync is available only for an accepted local Task.');
|
|
3879
|
+
}
|
|
3880
|
+
const currentBase = runGit(identity.repositoryRoot, ['rev-parse', task.baseBranch]);
|
|
3881
|
+
if (currentBase === task.baseCommit) {
|
|
3882
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Base branch has not advanced; synchronization is unnecessary.');
|
|
3883
|
+
}
|
|
3884
|
+
const taskHead = headCommit(identity.repositoryRoot);
|
|
3885
|
+
if (currentBaseSyncConflict(this.store, task, taskHead, currentBase)) {
|
|
3886
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Base sync awaits an external decision for this exact conflicting HEAD pair.', {
|
|
3887
|
+
taskId, taskHead, baseHead: currentBase, waitFor: 'The base owner resolves the conflict on the base branch without changing the reviewed Task head.',
|
|
3888
|
+
});
|
|
3889
|
+
}
|
|
3890
|
+
let synced;
|
|
3891
|
+
try {
|
|
3892
|
+
synced = syncBaseIntoTask(identity.repositoryRoot, task.taskBranch, task.baseBranch);
|
|
3893
|
+
}
|
|
3894
|
+
catch (error) {
|
|
3895
|
+
if (error instanceof WorkflowError && error.code === 'GIT_PRECONDITION_FAILED'
|
|
3896
|
+
&& error.details.cleanAbortVerified === true && headCommit(identity.repositoryRoot) === taskHead
|
|
3897
|
+
&& runGit(identity.repositoryRoot, ['rev-parse', task.baseBranch]) === currentBase) {
|
|
3898
|
+
recordBaseSyncConflict(this.store, task, taskHead, currentBase);
|
|
3899
|
+
}
|
|
3900
|
+
throw error;
|
|
3901
|
+
}
|
|
3902
|
+
task = this.store.writeTask({
|
|
3903
|
+
...task, status: 'validating', baseCommit: synced.baseCommit,
|
|
3904
|
+
systemCommits: [...new Set([...task.systemCommits, synced.syncCommit])],
|
|
3905
|
+
evidenceHash: null, resultHash: null, review: emptyReview(),
|
|
3906
|
+
authorizations: task.authorizations.map(item => item.kind === 'final_acceptance' && item.decision === 'approved'
|
|
3907
|
+
? { ...item, decision: 'superseded' } : item),
|
|
3908
|
+
pendingBaseSyncValidation: { baseCommit: synced.baseCommit, syncCommit: synced.syncCommit,
|
|
3909
|
+
headCommit: headCommit(identity.repositoryRoot), checksHash: taskSyncChecksHash(task) },
|
|
3910
|
+
blockReason: 'Base synchronized; checks must complete before review and acceptance.',
|
|
3911
|
+
}, task.revision);
|
|
3912
|
+
}
|
|
3913
|
+
assertPendingBaseSyncValidationReady(task);
|
|
3914
|
+
const checks = runChecks(identity.repositoryRoot, taskSyncChecks(task), this.checkOptions);
|
|
3915
|
+
// A check that changes the repository cannot certify the recorded merge snapshot.
|
|
3916
|
+
assertPendingBaseSyncValidationReady(task);
|
|
3917
|
+
const { pendingBaseSyncValidation: _pending, ...validatedTask } = task;
|
|
3918
|
+
const failed = checks.some(check => !check.passed);
|
|
3860
3919
|
const root = this.store.taskRoot(task.projectId, task.id);
|
|
3861
3920
|
const stagedEvidence = this.store.stageArtifact(root, 'evidence.json', JSON.stringify({
|
|
3862
|
-
taskId: task.id,
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
reason: 'base-sync',
|
|
3866
|
-
checks,
|
|
3867
|
-
steps: task.steps.map((step) => ({ id: step.id, evidence: step.evidence })),
|
|
3868
|
-
systemCommits: [...task.systemCommits, synced.syncCommit],
|
|
3921
|
+
taskId: task.id, headCommit: headCommit(identity.repositoryRoot), generatedAt: this.now().toISOString(),
|
|
3922
|
+
reason: 'base-sync', checks, steps: task.steps.map(step => ({ id: step.id, evidence: step.evidence })),
|
|
3923
|
+
systemCommits: task.systemCommits,
|
|
3869
3924
|
}, null, 2));
|
|
3870
|
-
const
|
|
3871
|
-
?
|
|
3872
|
-
|
|
3873
|
-
const saved = this.store.writeTask({
|
|
3874
|
-
...task,
|
|
3875
|
-
status: 'validating',
|
|
3876
|
-
baseCommit: synced.baseCommit,
|
|
3877
|
-
systemCommits: [...task.systemCommits, synced.syncCommit],
|
|
3878
|
-
evidenceHash: stagedEvidence.hash,
|
|
3879
|
-
review: emptyReview(),
|
|
3880
|
-
resultHash: null,
|
|
3881
|
-
authorizations,
|
|
3882
|
-
blockReason: 'Base changed; validation, review, and final acceptance must be repeated.',
|
|
3883
|
-
}, expectedRevision);
|
|
3925
|
+
const saved = this.store.writeTask({ ...validatedTask, status: failed ? 'needs_fix' : 'validating', evidenceHash: stagedEvidence.hash,
|
|
3926
|
+
blockReason: failed ? 'Checks failed after synchronizing the base branch.'
|
|
3927
|
+
: 'Base changed; validation, review, and final acceptance must be repeated.' }, task.revision);
|
|
3884
3928
|
this.store.publishArtifact(stagedEvidence);
|
|
3885
3929
|
return saved;
|
|
3886
3930
|
}
|
|
3887
3931
|
mergeTask(repository, taskId, expectedRevision, writerToken, actor) {
|
|
3888
3932
|
const context = this.#mutationContext(repository);
|
|
3889
3933
|
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
3934
|
+
assertNoPendingBaseSyncValidation(task);
|
|
3890
3935
|
assertExpectedRevision(task, expectedRevision);
|
|
3891
3936
|
const lease = context.locks.heartbeat(taskId, writerToken);
|
|
3892
3937
|
this.assertTaskMutationAllowedByHandoff(task, actor ?? null, lease.token);
|
|
@@ -4019,7 +4064,7 @@ export class WorkflowService {
|
|
|
4019
4064
|
const { identity } = this.#mutationContext(repository);
|
|
4020
4065
|
return this.inspectAdoptionPreparation(identity, this.store.listTasks(identity.projectId), this.store.listMilestones(identity.projectId));
|
|
4021
4066
|
}
|
|
4022
|
-
applyAdoption(repository, actor, confirmationCode) {
|
|
4067
|
+
applyAdoption(repository, actor, confirmationCode, delegationGrantId = null) {
|
|
4023
4068
|
const context = this.#mutationContext(repository);
|
|
4024
4069
|
const tasks = context.store.listTasks(context.identity.projectId);
|
|
4025
4070
|
const milestones = context.store.listMilestones(context.identity.projectId);
|
|
@@ -4034,138 +4079,7 @@ export class WorkflowService {
|
|
|
4034
4079
|
sidecarInventory: this.collectAdoptionSidecarInventory(context.identity.projectId, tasks, milestones),
|
|
4035
4080
|
blockers: this.computeAdoptionBoundaryBlockers(context.identity, tasks, context.locks.list()),
|
|
4036
4081
|
now: this.now(),
|
|
4037
|
-
}, actor, confirmationCode);
|
|
4038
|
-
}
|
|
4039
|
-
pendingReviewUpdatePreflight(repository, taskId) {
|
|
4040
|
-
const snapshot = this.observationSnapshot(repository);
|
|
4041
|
-
const blockers = observationPreflightBlockers(snapshot.observation);
|
|
4042
|
-
try {
|
|
4043
|
-
pendingReviewRunnerFingerprint();
|
|
4044
|
-
}
|
|
4045
|
-
catch (error) {
|
|
4046
|
-
blockers.push(error instanceof Error ? error.message : String(error));
|
|
4047
|
-
}
|
|
4048
|
-
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
4049
|
-
let binding = null;
|
|
4050
|
-
if (PACKAGE_VERSION === PENDING_REVIEW_SOURCE_VERSION)
|
|
4051
|
-
blockers.push('The exact target runner must be newer than the supported source.');
|
|
4052
|
-
if (!task)
|
|
4053
|
-
blockers.push('Task not found.');
|
|
4054
|
-
if (task) {
|
|
4055
|
-
try {
|
|
4056
|
-
binding = pendingReviewSourceBinding(snapshot.identity.repositoryRoot, this.store, task);
|
|
4057
|
-
}
|
|
4058
|
-
catch (error) {
|
|
4059
|
-
blockers.push(error instanceof Error ? error.message : String(error));
|
|
4060
|
-
}
|
|
4061
|
-
const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
|
|
4062
|
-
if ([versions.declared, versions.locked, versions.installed, versions.currentBranch,
|
|
4063
|
-
...versions.milestoneBases.map((base) => base.version)].some((version) => version !== PENDING_REVIEW_SOURCE_VERSION)) {
|
|
4064
|
-
blockers.push('All source version surfaces must equal the exact supported beta.13.1.');
|
|
4065
|
-
}
|
|
4066
|
-
}
|
|
4067
|
-
const otherRunningSteps = snapshot.tasks.flatMap((candidate) => candidate.steps.filter((step) => step.status === 'in_progress' && (candidate.id !== taskId || step.id !== binding?.stepId)));
|
|
4068
|
-
if (otherRunningSteps.length)
|
|
4069
|
-
blockers.push('No other Step may be running.');
|
|
4070
|
-
const stale = snapshot.leases.filter((lease) => Date.parse(lease.expiresAt) <= this.now().getTime());
|
|
4071
|
-
if (snapshot.leases.some((lease) => !stale.includes(lease)))
|
|
4072
|
-
blockers.push('No live writer lease may exist.');
|
|
4073
|
-
if (stale.some((lease) => lease.entityId !== taskId))
|
|
4074
|
-
blockers.push('No unrelated stale writer lease may exist.');
|
|
4075
|
-
const staleLeaseRepair = blockers.length === 0 && binding
|
|
4076
|
-
? stale.map((lease) => ({ action: 'locks repair', entityId: lease.entityId })) : [];
|
|
4077
|
-
if (stale.length)
|
|
4078
|
-
blockers.push('Repair the exact stale Task lease with the installed source locks repair, then rerun source preflight.');
|
|
4079
|
-
return { readOnly: true, eligible: blockers.length === 0 && binding !== null, binding,
|
|
4080
|
-
bindingHash: binding ? pendingReviewBindingHash(binding) : null, blockers, staleLeaseRepair };
|
|
4081
|
-
}
|
|
4082
|
-
pendingReviewDependencyPreflight(repository, taskId, source) {
|
|
4083
|
-
const snapshot = this.observationSnapshot(repository);
|
|
4084
|
-
const blockers = observationPreflightBlockers(snapshot.observation);
|
|
4085
|
-
try {
|
|
4086
|
-
pendingReviewRunnerFingerprint();
|
|
4087
|
-
}
|
|
4088
|
-
catch (error) {
|
|
4089
|
-
blockers.push(error instanceof Error ? error.message : String(error));
|
|
4090
|
-
}
|
|
4091
|
-
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
4092
|
-
const taskHead = headCommit(snapshot.identity.repositoryRoot);
|
|
4093
|
-
let baseTransportCommit = null;
|
|
4094
|
-
if (snapshot.leases.length)
|
|
4095
|
-
blockers.push('No live or stale writer lease may exist.');
|
|
4096
|
-
if (!task)
|
|
4097
|
-
blockers.push('Task not found.');
|
|
4098
|
-
if (task && existsSync(path.join(this.store.taskRoot(task.projectId, task.id), PENDING_REVIEW_UPDATE_SIDECAR)))
|
|
4099
|
-
blockers.push('A pending review update is already recorded.');
|
|
4100
|
-
if (task) {
|
|
4101
|
-
try {
|
|
4102
|
-
// Receipt fields are claims, not authority. Reconstruct every source binding from
|
|
4103
|
-
// canonical Task/sidecars and the exact parents of the candidate Git commits.
|
|
4104
|
-
const parent = runGit(repository, ['rev-parse', 'HEAD^']);
|
|
4105
|
-
baseTransportCommit = runGit(repository, ['rev-parse', task.baseBranch]);
|
|
4106
|
-
const baseParent = runGit(repository, ['rev-parse', `${baseTransportCommit}^`]);
|
|
4107
|
-
const reconstructed = pendingReviewSourceBinding(repository, this.store, task, parent, baseParent);
|
|
4108
|
-
if (canonicalJsonStringify(reconstructed) !== canonicalJsonStringify(source)) {
|
|
4109
|
-
throw new Error('Source receipt does not match reconstructed Task/revision/Plan/HEAD/pending bindings.');
|
|
4110
|
-
}
|
|
4111
|
-
assertPendingReviewTransport(repository, reconstructed, taskHead, baseTransportCommit);
|
|
4112
|
-
if (snapshot.tasks.some((candidate) => candidate.steps.some((step) => step.status === 'in_progress' && (candidate.id !== taskId || step.id !== source.stepId)))) {
|
|
4113
|
-
throw new Error('No other Step may be running.');
|
|
4114
|
-
}
|
|
4115
|
-
const versions = inspectWorkflowVersions(repository, [task.baseBranch]);
|
|
4116
|
-
if ([versions.runtime, versions.declared, versions.locked, versions.installed, versions.currentBranch,
|
|
4117
|
-
...versions.milestoneBases.map((base) => base.version)].some((version) => version !== PACKAGE_VERSION)) {
|
|
4118
|
-
throw new Error('All target version surfaces must equal the exact target runner.');
|
|
4119
|
-
}
|
|
4120
|
-
}
|
|
4121
|
-
catch (error) {
|
|
4122
|
-
blockers.push(error instanceof Error ? error.message : String(error));
|
|
4123
|
-
}
|
|
4124
|
-
}
|
|
4125
|
-
return { readOnly: true, eligible: blockers.length === 0, blockers, taskHead, baseTransportCommit };
|
|
4126
|
-
}
|
|
4127
|
-
recoverPendingReviewDependency(repository, taskId, expectedRevision, source, actor, reason) {
|
|
4128
|
-
if (!actor.trim() || !reason.trim())
|
|
4129
|
-
throw new WorkflowError('INVALID_ARGUMENT', 'Actor and reason are required.');
|
|
4130
|
-
const initial = this.pendingReviewDependencyPreflight(repository, taskId, source);
|
|
4131
|
-
if (!initial.eligible)
|
|
4132
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Pending review dependency preflight failed.', initial);
|
|
4133
|
-
const context = this.#mutationContext(repository);
|
|
4134
|
-
const task = this.store.readTask(context.identity.projectId, taskId);
|
|
4135
|
-
assertExpectedRevision(task, expectedRevision);
|
|
4136
|
-
const preflight = this.pendingReviewDependencyPreflight(repository, taskId, source);
|
|
4137
|
-
if (!preflight.eligible || !preflight.baseTransportCommit) {
|
|
4138
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Pending review dependency preflight changed.', preflight);
|
|
4139
|
-
}
|
|
4140
|
-
const now = this.now();
|
|
4141
|
-
const nextTask = { ...task, revision: task.revision + 1, updatedAt: now.toISOString(),
|
|
4142
|
-
systemCommits: [...task.systemCommits, preflight.taskHead],
|
|
4143
|
-
dependencyProvenanceRecoveries: [...(task.dependencyProvenanceRecoveries ?? []), {
|
|
4144
|
-
commitSha: preflight.taskHead, parentCommitSha: source.completionCommit,
|
|
4145
|
-
packageName: PACKAGE_NAME, packageVersion: PACKAGE_VERSION, files: ['package-lock.json', 'package.json'],
|
|
4146
|
-
actor: actor.trim(), reason: reason.trim(), recordedAt: now.toISOString(),
|
|
4147
|
-
}] };
|
|
4148
|
-
const projectRoot = this.store.projectRoot(task.projectId);
|
|
4149
|
-
const event = preparePendingReviewUpdateEvent({ source, baseTransportCommit: preflight.baseTransportCommit }, preflight.taskHead, now.toISOString());
|
|
4150
|
-
applyProjectTransaction({ projectRoot, projectId: task.projectId,
|
|
4151
|
-
transactionId: `tx-${createEntityId('TASK', now.getTime()).toLowerCase()}`,
|
|
4152
|
-
kind: 'pending-review-dependency-recovery', now,
|
|
4153
|
-
targets: [{ path: path.relative(projectRoot, path.join(this.store.taskRoot(task.projectId, task.id), 'state.json')).split(path.sep).join('/'),
|
|
4154
|
-
content: `${JSON.stringify(nextTask, null, 2)}\n` },
|
|
4155
|
-
{ path: path.relative(projectRoot, path.join(this.store.taskRoot(task.projectId, task.id), PENDING_REVIEW_UPDATE_SIDECAR)).split(path.sep).join('/'),
|
|
4156
|
-
content: `${canonicalJsonStringify(event)}\n` }],
|
|
4157
|
-
validate: () => {
|
|
4158
|
-
const saved = this.store.readTask(task.projectId, task.id);
|
|
4159
|
-
if (saved.revision !== nextTask.revision || !verifiedPendingReviewTransport(repository, saved, source.stepId, this.store.taskRoot(task.projectId, task.id))) {
|
|
4160
|
-
throw new WorkflowError('STATE_CORRUPT', 'Pending review dependency recovery readback failed.');
|
|
4161
|
-
}
|
|
4162
|
-
const cycle = buildCurrentStrictStepReviewCycle(this.store, saved, source.stepId);
|
|
4163
|
-
if (cycle.resolution !== 'pending' || cycle.pendingEvent?.eventHash !== source.pendingEventHash) {
|
|
4164
|
-
throw new WorkflowError('STATE_CORRUPT', 'Pending review changed during dependency recovery.');
|
|
4165
|
-
}
|
|
4166
|
-
},
|
|
4167
|
-
});
|
|
4168
|
-
return this.store.readTask(task.projectId, task.id);
|
|
4082
|
+
}, actor, confirmationCode, delegationGrantId);
|
|
4169
4083
|
}
|
|
4170
4084
|
updatePreflight(repository) {
|
|
4171
4085
|
const snapshot = this.observationSnapshot(repository);
|
|
@@ -4248,6 +4162,110 @@ export class WorkflowService {
|
|
|
4248
4162
|
observation: snapshot.observation,
|
|
4249
4163
|
};
|
|
4250
4164
|
}
|
|
4165
|
+
navigationUpdateBoundary(repository, taskId) {
|
|
4166
|
+
const snapshot = this.observationSnapshot(repository);
|
|
4167
|
+
const observation = snapshot.observation;
|
|
4168
|
+
const task = snapshot.tasks.find(item => item.id === taskId);
|
|
4169
|
+
if (!task || observation.kind !== 'assessment' || snapshot.leases.length
|
|
4170
|
+
|| snapshot.tasks.some(item => item.steps.some(step => step.status === 'in_progress'))
|
|
4171
|
+
|| observation.pendingProjectTransactions.length || observation.pendingMilestoneTransactions.length
|
|
4172
|
+
|| observation.pendingTaskTransactions.length || observation.corruptTaskTransactions.length
|
|
4173
|
+
|| observation.coreTaskOperations.length) {
|
|
4174
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Navigation update requires coherent state and no leases or pending operations.');
|
|
4175
|
+
}
|
|
4176
|
+
const otherBases = snapshot.milestones.filter(item => !['accepted', 'cancelled'].includes(item.status)
|
|
4177
|
+
&& item.baseBranch !== task.baseBranch);
|
|
4178
|
+
if (otherBases.length)
|
|
4179
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Navigation update supports one active base branch.');
|
|
4180
|
+
return { snapshot, task, root: snapshot.identity.repositoryRoot };
|
|
4181
|
+
}
|
|
4182
|
+
failedCheckpointUpdateSourcePreflight(repository, taskId, tarball) {
|
|
4183
|
+
try {
|
|
4184
|
+
const { task, root } = this.navigationUpdateBoundary(repository, taskId);
|
|
4185
|
+
assertFailedCheckpointInstalled(root, task, FAILED_CHECKPOINT_SOURCE);
|
|
4186
|
+
const receipt = failedCheckpointUpdateSource(root, this.store, task, verifyNavigationUpdateArtifact(tarball));
|
|
4187
|
+
return { eligible: true, blockers: [], receipt };
|
|
4188
|
+
}
|
|
4189
|
+
catch (error) {
|
|
4190
|
+
return { eligible: false, blockers: [error instanceof Error ? error.message : String(error)], receipt: null };
|
|
4191
|
+
}
|
|
4192
|
+
}
|
|
4193
|
+
failedCheckpointUpdateTransportPreflight(repository, taskId, tarball, receipt) {
|
|
4194
|
+
try {
|
|
4195
|
+
const { task, root } = this.navigationUpdateBoundary(repository, taskId);
|
|
4196
|
+
const proof = inspectFailedCheckpointTransport(root, this.store, task, receipt, verifyNavigationUpdateArtifact(tarball, path.join(root, 'node_modules/codex-workflow-v2')));
|
|
4197
|
+
return { eligible: true, blockers: [], proof,
|
|
4198
|
+
action: 'update failed-checkpoint-recover', expectedRevision: task.revision, actor: receipt.actor };
|
|
4199
|
+
}
|
|
4200
|
+
catch (error) {
|
|
4201
|
+
return { eligible: false, blockers: [error instanceof Error ? error.message : String(error)], proof: null };
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
recoverFailedCheckpointUpdate(repository, taskId, expectedRevision, tarball, receipt, actor, reason) {
|
|
4205
|
+
this.#mutationContext(repository);
|
|
4206
|
+
const { task, root } = this.navigationUpdateBoundary(repository, taskId);
|
|
4207
|
+
assertExpectedRevision(task, expectedRevision);
|
|
4208
|
+
if (!actor.trim() || actor !== receipt?.actor || !reason.trim()) {
|
|
4209
|
+
throw new WorkflowError('INVALID_ARGUMENT', 'Failed-checkpoint update requires the original claimant and an explicit reason.');
|
|
4210
|
+
}
|
|
4211
|
+
const proof = inspectFailedCheckpointTransport(root, this.store, task, receipt, verifyNavigationUpdateArtifact(tarball, path.join(root, 'node_modules/codex-workflow-v2')));
|
|
4212
|
+
const updated = { ...task, status: 'awaiting_execution_authorization',
|
|
4213
|
+
systemCommits: [...task.systemCommits, proof.target.commitSha],
|
|
4214
|
+
dependencyProvenanceRecoveries: [...(task.dependencyProvenanceRecoveries ?? []), {
|
|
4215
|
+
...proof.target, packageName: PACKAGE_NAME, files: ['package-lock.json', 'package.json'], actor,
|
|
4216
|
+
reason: `${reason.trim()} [failed-checkpoint-update ${receipt.artifact.sha256}]`,
|
|
4217
|
+
recordedAt: this.now().toISOString(), failedCheckpoint: proof.binding,
|
|
4218
|
+
}],
|
|
4219
|
+
};
|
|
4220
|
+
validateTaskHistory(root, updated);
|
|
4221
|
+
return this.store.writeTask(updated, expectedRevision);
|
|
4222
|
+
}
|
|
4223
|
+
navigationUpdateSourcePreflight(repository, taskId, tarball) {
|
|
4224
|
+
try {
|
|
4225
|
+
const { task, root } = this.navigationUpdateBoundary(repository, taskId);
|
|
4226
|
+
assertNavigationSourceInstalled(root, task);
|
|
4227
|
+
const receipt = navigationUpdateSource(root, this.store, task, verifyNavigationUpdateArtifact(tarball), task.updatedAt);
|
|
4228
|
+
return { eligible: true, blockers: [], receipt };
|
|
4229
|
+
}
|
|
4230
|
+
catch (error) {
|
|
4231
|
+
return { eligible: false, blockers: [error instanceof Error ? error.message : String(error)], receipt: null };
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
navigationUpdateTransportPreflight(repository, taskId, tarball, receipt) {
|
|
4235
|
+
try {
|
|
4236
|
+
const { task, root } = this.navigationUpdateBoundary(repository, taskId);
|
|
4237
|
+
const proof = inspectNavigationTransport(root, this.store, task, receipt, verifyNavigationUpdateArtifact(tarball, path.join(root, 'node_modules/codex-workflow-v2')), this.now());
|
|
4238
|
+
return { eligible: true, blockers: [], proof,
|
|
4239
|
+
action: 'update navigation-recover', expectedRevision: task.revision, actor: receipt.actor };
|
|
4240
|
+
}
|
|
4241
|
+
catch (error) {
|
|
4242
|
+
return { eligible: false, blockers: [error instanceof Error ? error.message : String(error)], proof: null };
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
recoverNavigationUpdate(repository, taskId, expectedRevision, tarball, receipt, actor, reason) {
|
|
4246
|
+
this.#mutationContext(repository);
|
|
4247
|
+
const { task, root } = this.navigationUpdateBoundary(repository, taskId);
|
|
4248
|
+
assertExpectedRevision(task, expectedRevision);
|
|
4249
|
+
if (!actor.trim() || actor !== receipt?.actor || !reason.trim()) {
|
|
4250
|
+
throw new WorkflowError('INVALID_ARGUMENT', 'Navigation update requires the source claimant and an explicit reason.');
|
|
4251
|
+
}
|
|
4252
|
+
const preflight = this.navigationUpdateTransportPreflight(repository, taskId, tarball, receipt);
|
|
4253
|
+
if (!preflight.eligible || !preflight.proof) {
|
|
4254
|
+
throw new WorkflowError('TRANSITION_BLOCKED', 'Navigation transport preflight failed.', { blockers: preflight.blockers });
|
|
4255
|
+
}
|
|
4256
|
+
// Recompute the source/state/Git bindings immediately before the atomic optimistic state write.
|
|
4257
|
+
const boundary = this.navigationUpdateBoundary(repository, taskId);
|
|
4258
|
+
const proof = inspectNavigationTransport(root, this.store, boundary.task, receipt, verifyNavigationUpdateArtifact(tarball, path.join(root, 'node_modules/codex-workflow-v2')), this.now());
|
|
4259
|
+
const saved = this.store.writeTask({ ...boundary.task, status: 'awaiting_execution_authorization',
|
|
4260
|
+
systemCommits: [...boundary.task.systemCommits, ...proof.commits.map(item => item.commitSha)],
|
|
4261
|
+
dependencyProvenanceRecoveries: [...(boundary.task.dependencyProvenanceRecoveries ?? []),
|
|
4262
|
+
...proof.commits.map(item => ({ ...item, packageName: PACKAGE_NAME,
|
|
4263
|
+
files: ['package-lock.json', 'package.json'],
|
|
4264
|
+
actor, reason: `${reason.trim()} [navigation-update ${receipt.artifact.sha256}]`, recordedAt: this.now().toISOString() }))],
|
|
4265
|
+
}, expectedRevision);
|
|
4266
|
+
validateTaskHistory(root, saved);
|
|
4267
|
+
return saved;
|
|
4268
|
+
}
|
|
4251
4269
|
dependencyProvenanceRecoveryPreflight(repository, taskId) {
|
|
4252
4270
|
const snapshot = this.observationSnapshot(repository);
|
|
4253
4271
|
if (snapshot.observation.kind !== 'assessment') {
|
|
@@ -4258,299 +4276,7 @@ export class WorkflowService {
|
|
|
4258
4276
|
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
4259
4277
|
if (!task)
|
|
4260
4278
|
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
4261
|
-
|
|
4262
|
-
const carryover = this.assessCurrentRootCauseReplanDirtyCarryover(snapshot.identity.repositoryRoot, task, undefined, candidate?.parentCommitSha);
|
|
4263
|
-
const preflight = this.inspectDependencyProvenanceCandidate(snapshot, task, null, null, null, null, carryover.eligible ? carryover.compatibility : null);
|
|
4264
|
-
if (!preflight.rootCauseReplan)
|
|
4265
|
-
return preflight;
|
|
4266
|
-
let baseTip = null;
|
|
4267
|
-
let baseDependency = null;
|
|
4268
|
-
try {
|
|
4269
|
-
baseTip = runGit(snapshot.identity.repositoryRoot, ['rev-parse', task.baseBranch]);
|
|
4270
|
-
baseDependency = inspectBoundedWorkflowDependencyCommit(snapshot.identity.repositoryRoot, baseTip);
|
|
4271
|
-
}
|
|
4272
|
-
catch {
|
|
4273
|
-
// The blocker below keeps a missing or unreadable base reference fail closed.
|
|
4274
|
-
}
|
|
4275
|
-
const bridgeBlockers = [
|
|
4276
|
-
...(preflight.dependencyCommits.length !== 1
|
|
4277
|
-
? ['Corrective carryover recovery requires exactly one Task dependency-only commit.'] : []),
|
|
4278
|
-
...(preflight.parentCommitSha !== preflight.rootCauseReplan.sourceHeadCommit
|
|
4279
|
-
? ['Corrective carryover dependency parent must equal the bound source HEAD.'] : []),
|
|
4280
|
-
...(preflight.parentCommitSha
|
|
4281
|
-
&& workflowDependencyVersionAt(snapshot.identity.repositoryRoot, preflight.parentCommitSha) !== '2.0.0-beta.13.6'
|
|
4282
|
-
? ['Corrective carryover dependency parent must declare exact source beta.13.6.'] : []),
|
|
4283
|
-
...(preflight.dependencyCommits.at(-1)?.packageVersion !== PACKAGE_VERSION
|
|
4284
|
-
? [`Corrective carryover dependency commit must declare exact target ${PACKAGE_VERSION}.`] : []),
|
|
4285
|
-
...(!baseDependency
|
|
4286
|
-
|| baseDependency.commitSha !== baseTip
|
|
4287
|
-
|| baseDependency.packageVersion !== PACKAGE_VERSION
|
|
4288
|
-
|| workflowDependencyVersionAt(snapshot.identity.repositoryRoot, baseDependency.parentCommitSha)
|
|
4289
|
-
!== workflowDependencyVersionAt(snapshot.identity.repositoryRoot, preflight.parentCommitSha ?? '')
|
|
4290
|
-
? ['Corrective carryover Milestone-base tip must be one dependency-only commit with the same exact source and runtime target.'] : []),
|
|
4291
|
-
];
|
|
4292
|
-
return bridgeBlockers.length === 0
|
|
4293
|
-
? preflight
|
|
4294
|
-
: {
|
|
4295
|
-
...preflight,
|
|
4296
|
-
eligible: false,
|
|
4297
|
-
action: null,
|
|
4298
|
-
commitSha: null,
|
|
4299
|
-
parentCommitSha: null,
|
|
4300
|
-
blockers: [...new Set([...preflight.blockers, ...bridgeBlockers])],
|
|
4301
|
-
};
|
|
4302
|
-
}
|
|
4303
|
-
correctiveCarryoverUpdatePreflight(repository, taskId, candidate = null) {
|
|
4304
|
-
const snapshot = this.observationSnapshot(repository);
|
|
4305
|
-
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
4306
|
-
if (!task)
|
|
4307
|
-
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
4308
|
-
const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
|
|
4309
|
-
const sourceVersion = '2.0.0-beta.13.6';
|
|
4310
|
-
const targetVersion = CONSUMED_CARRYOVER_TARGET_VERSION;
|
|
4311
|
-
const sourceSurfaces = {
|
|
4312
|
-
declared: versions.declared,
|
|
4313
|
-
locked: versions.locked,
|
|
4314
|
-
installed: versions.installed,
|
|
4315
|
-
currentBranch: versions.currentBranch,
|
|
4316
|
-
baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
|
|
4317
|
-
};
|
|
4318
|
-
const c1 = readTaskC1Posture(this.store, task);
|
|
4319
|
-
if (c1.state !== 'claimed') {
|
|
4320
|
-
return this.consumedCarryoverSourceRecoveryPreflight(snapshot, task, sourceSurfaces, candidate);
|
|
4321
|
-
}
|
|
4322
|
-
const assessment = this.assessCurrentRootCauseReplanDirtyCarryover(snapshot.identity.repositoryRoot, task);
|
|
4323
|
-
const now = this.now().getTime();
|
|
4324
|
-
const activeLeases = snapshot.leases.filter((lease) => Date.parse(lease.expiresAt) > now);
|
|
4325
|
-
const staleLeases = snapshot.leases.filter((lease) => Date.parse(lease.expiresAt) <= now);
|
|
4326
|
-
const transactionCount = snapshot.observation.pendingProjectTransactions.length
|
|
4327
|
-
+ snapshot.observation.pendingMilestoneTransactions.length
|
|
4328
|
-
+ snapshot.observation.pendingTaskTransactions.length;
|
|
4329
|
-
const authorizationBlockers = [];
|
|
4330
|
-
if (assessment.eligible) {
|
|
4331
|
-
try {
|
|
4332
|
-
this.assertExecutionAuthorizationFresh(task, snapshot.identity.repositoryRoot);
|
|
4333
|
-
}
|
|
4334
|
-
catch (error) {
|
|
4335
|
-
authorizationBlockers.push(error instanceof Error ? error.message : String(error));
|
|
4336
|
-
}
|
|
4337
|
-
}
|
|
4338
|
-
const baseBlockers = [
|
|
4339
|
-
...assessment.blockers,
|
|
4340
|
-
...authorizationBlockers,
|
|
4341
|
-
...(PACKAGE_VERSION !== targetVersion ? [`Runner must be the exact ${targetVersion} compatibility target.`] : []),
|
|
4342
|
-
...Object.entries(sourceSurfaces)
|
|
4343
|
-
.filter(([, version]) => version !== sourceVersion)
|
|
4344
|
-
.map(([surface]) => `${surface} Workflow version must equal the exact ${sourceVersion} source.`),
|
|
4345
|
-
...observationPreflightBlockers(snapshot.observation),
|
|
4346
|
-
...(transactionCount > 0 ? ['No pending Workflow transaction may exist.'] : []),
|
|
4347
|
-
...(snapshot.observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
|
|
4348
|
-
...(snapshot.observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
|
|
4349
|
-
...(c1.state !== 'claimed' || assessment.compatibility?.claimant !== c1.claimant
|
|
4350
|
-
? ['The exact original Worker C1 claim must remain current.'] : []),
|
|
4351
|
-
...(activeLeases.length > 0 ? ['No live writer lease may exist during corrective carryover transport.'] : []),
|
|
4352
|
-
...(staleLeases.some((lease) => lease.entityId !== task.id
|
|
4353
|
-
|| c1.state !== 'claimed' || lease.owner !== c1.claimant
|
|
4354
|
-
|| createHash('sha256').update(lease.token).digest('hex') !== c1.writerLeaseTokenHash)
|
|
4355
|
-
? ['No unrelated or differently owned stale writer lease may exist.'] : []),
|
|
4356
|
-
...(staleLeases.length > 1 ? ['At most the exact corrective Task stale lease may exist.'] : []),
|
|
4357
|
-
];
|
|
4358
|
-
const repairableStale = baseBlockers.length === 0 && staleLeases.length === 1
|
|
4359
|
-
? staleLeases[0]
|
|
4360
|
-
: null;
|
|
4361
|
-
const blockers = [
|
|
4362
|
-
...baseBlockers,
|
|
4363
|
-
...(repairableStale
|
|
4364
|
-
? ['Repair the exact expired claimant lease with source locks repair, then run status, next, and this preflight again.']
|
|
4365
|
-
: []),
|
|
4366
|
-
];
|
|
4367
|
-
const compatibility = assessment.compatibility;
|
|
4368
|
-
return {
|
|
4369
|
-
readOnly: true,
|
|
4370
|
-
eligible: Boolean(assessment.eligible && compatibility && blockers.length === 0 && staleLeases.length === 0),
|
|
4371
|
-
action: assessment.eligible && compatibility && blockers.length === 0 && staleLeases.length === 0
|
|
4372
|
-
? 'update corrective-carryover-transport'
|
|
4373
|
-
: null,
|
|
4374
|
-
projectId: task.projectId,
|
|
4375
|
-
taskId: task.id,
|
|
4376
|
-
taskRevision: task.revision,
|
|
4377
|
-
stepId: compatibility?.stepId ?? null,
|
|
4378
|
-
sourceVersion,
|
|
4379
|
-
targetVersion,
|
|
4380
|
-
sourceHeadCommit: compatibility?.sourceHeadCommit ?? null,
|
|
4381
|
-
taskBranch: task.taskBranch,
|
|
4382
|
-
baseBranch: task.baseBranch,
|
|
4383
|
-
dirtyFiles: compatibility?.dirtyFiles ?? [],
|
|
4384
|
-
dirtyWorktreeHash: compatibility?.dirtyWorktreeHash ?? null,
|
|
4385
|
-
compatibilityHash: compatibility?.compatibilityHash ?? null,
|
|
4386
|
-
c1Posture: c1.state === 'claimed' ? {
|
|
4387
|
-
state: 'claimed',
|
|
4388
|
-
claimant: c1.claimant,
|
|
4389
|
-
claimEventId: compatibility?.c1ClaimEventId ?? null,
|
|
4390
|
-
claimEventHash: compatibility?.c1ClaimEventHash ?? null,
|
|
4391
|
-
} : null,
|
|
4392
|
-
staleLeaseRepair: repairableStale ? [{
|
|
4393
|
-
action: 'locks repair',
|
|
4394
|
-
entityId: task.id,
|
|
4395
|
-
owner: repairableStale.owner,
|
|
4396
|
-
expiresAt: repairableStale.expiresAt,
|
|
4397
|
-
}] : [],
|
|
4398
|
-
requiredTransport: [
|
|
4399
|
-
'Commit only package.json and package-lock.json on the active Milestone base.',
|
|
4400
|
-
'Commit only the same dependency files on the Task branch while preserving the bound dirty bytes.',
|
|
4401
|
-
'Install the exact target package, register dependency provenance, refresh context, replace the same Worker credential, then follow fresh next.',
|
|
4402
|
-
],
|
|
4403
|
-
blockers,
|
|
4404
|
-
};
|
|
4405
|
-
}
|
|
4406
|
-
consumedCarryoverSourceRecoveryPreflight(snapshot, task, sourceSurfaces, candidate) {
|
|
4407
|
-
const repositoryRoot = snapshot.identity.repositoryRoot;
|
|
4408
|
-
const sourceVersion = '2.0.0-beta.13.6';
|
|
4409
|
-
const targetVersion = CONSUMED_CARRYOVER_TARGET_VERSION;
|
|
4410
|
-
const c1 = readTaskC1Posture(this.store, task);
|
|
4411
|
-
const recorded = [...(task.dependencyProvenanceRecoveries ?? [])].reverse().find((record) => record.rootCauseReplan && task.systemCommits.includes(record.commitSha))?.rootCauseReplan;
|
|
4412
|
-
const currentHead = headCommit(repositoryRoot);
|
|
4413
|
-
const dirtyFiles = changedFiles(repositoryRoot).sort();
|
|
4414
|
-
const dirtyWorktreeHash = hashDirtyWorktree(repositoryRoot, dirtyFiles);
|
|
4415
|
-
const navigation = this.next(repositoryRoot, task.id);
|
|
4416
|
-
const action = String(navigation.action);
|
|
4417
|
-
const stepId = typeof navigation.stepId === 'string' ? navigation.stepId : null;
|
|
4418
|
-
const step = task.steps.find((candidate) => candidate.id === stepId) ?? null;
|
|
4419
|
-
const terminal = readRemediationEvents(this.store, task)
|
|
4420
|
-
.filter((event) => event.stepId === stepId && event.failureKind === 'checks-failed').at(-1);
|
|
4421
|
-
const matchingYields = readTaskHandoffEvents(this.store, task).filter((event) => terminal && matchesTerminalCorrectiveYield(event, terminal, task.id));
|
|
4422
|
-
const yielded = matchingYields.length === 1 ? matchingYields[0] : null;
|
|
4423
|
-
const assessment = this.assessCurrentRootCauseReplanDirtyCarryover(repositoryRoot, task, stepId ?? undefined);
|
|
4424
|
-
const missingClaimOnly = assessment.blockers.length === 1
|
|
4425
|
-
&& assessment.blockers[0].startsWith('Corrective carryover requires the retained claimed C1');
|
|
4426
|
-
const rollingWave = navigation.rollingWave;
|
|
4427
|
-
const sourceActionAllowed = (action === 'task plan-set' && step?.status === 'failed'
|
|
4428
|
-
&& rollingWave?.state === 'root-cause-replan-required' && !assessment.applicable && c1.state === 'none')
|
|
4429
|
-
|| (action === 'task handoff-prepare' && step?.status === 'planned' && missingClaimOnly && c1.state === 'none')
|
|
4430
|
-
|| (action === 'task claim' && step?.status === 'planned' && missingClaimOnly
|
|
4431
|
-
&& c1.state === 'pending' && c1.targetActor === yielded?.actor);
|
|
4432
|
-
const blockers = [
|
|
4433
|
-
...(PACKAGE_VERSION !== targetVersion ? [`Runner must be the exact ${targetVersion} compatibility target.`] : []),
|
|
4434
|
-
...Object.entries(sourceSurfaces).filter(([, version]) => version !== sourceVersion)
|
|
4435
|
-
.map(([surface]) => `${surface} Workflow version must equal the exact ${sourceVersion} source.`),
|
|
4436
|
-
...observationPreflightBlockers(snapshot.observation),
|
|
4437
|
-
...(snapshot.leases.length > 0 ? ['No active or stale writer lease may exist before source recovery.'] : []),
|
|
4438
|
-
...(snapshot.observation.pendingProjectTransactions.length
|
|
4439
|
-
+ snapshot.observation.pendingMilestoneTransactions.length
|
|
4440
|
-
+ snapshot.observation.pendingTaskTransactions.length
|
|
4441
|
-
+ snapshot.observation.corruptTaskTransactions.length
|
|
4442
|
-
+ snapshot.observation.coreTaskOperations.length > 0 ? ['No pending or corrupt transaction or Core operation may exist.'] : []),
|
|
4443
|
-
...(!recorded || recorded.stepId === stepId
|
|
4444
|
-
|| task.steps.find((candidate) => candidate.id === recorded.stepId)?.status !== 'completed'
|
|
4445
|
-
? ['A different, verified completed Step must own the historical carryover binding.'] : []),
|
|
4446
|
-
...(!sourceActionAllowed ? ['No exact source Plan replacement or original-Worker handoff action is eligible.'] : []),
|
|
4447
|
-
...(!(task.status === 'ready' || (action === 'task plan-set' && task.status === 'needs_fix'))
|
|
4448
|
-
|| task.workspaceOwner !== 'local'
|
|
4449
|
-
|| currentBranch(repositoryRoot) !== task.taskBranch
|
|
4450
|
-
|| task.steps.some((candidate) => candidate.status === 'in_progress')
|
|
4451
|
-
? ['The Task must retain its local, no-running-Step source recovery boundary.'] : []),
|
|
4452
|
-
...(!step || dirtyFiles.length === 0
|
|
4453
|
-
|| dirtyFiles.some((file) => !pathAllowed(file, step.allowedWrites) || pathAllowed(file, step.forbiddenScope))
|
|
4454
|
-
? ['Retained dirty files must remain inside the exact current Step scope.'] : []),
|
|
4455
|
-
...(!terminal || terminal.failureEvidence.completionCommit !== currentHead
|
|
4456
|
-
|| terminal.failureEvidence.dirtyWorktreeHash !== dirtyWorktreeHash
|
|
4457
|
-
? ['The terminal repeated failure HEAD and dirty-byte binding must remain unchanged.'] : []),
|
|
4458
|
-
...(!yielded || !terminal || yielded.planHash === null
|
|
4459
|
-
|| !Number.isFinite(Date.parse(yielded.recordedAt))
|
|
4460
|
-
|| Date.parse(yielded.recordedAt) < Date.parse(terminal.recordedAt)
|
|
4461
|
-
? ['The terminal failure must retain its exact completed original-Worker corrective yield.'] : []),
|
|
4462
|
-
];
|
|
4463
|
-
let candidateBinding = null;
|
|
4464
|
-
if (action === 'task plan-set') {
|
|
4465
|
-
if (!candidate) {
|
|
4466
|
-
blockers.push('Source Plan replacement requires candidate Plan and independent Plan Risk Audit inputs.');
|
|
4467
|
-
}
|
|
4468
|
-
else {
|
|
4469
|
-
try {
|
|
4470
|
-
validatePlan(candidate.plan);
|
|
4471
|
-
const retained = deriveCompletedStepCarryover(task, candidate.plan.steps);
|
|
4472
|
-
if (task.steps.some((completed) => completed.status === 'completed' && !retained.has(completed.id))) {
|
|
4473
|
-
throw new Error('Candidate must preserve every completed Step definition and its execution authority exactly.');
|
|
4474
|
-
}
|
|
4475
|
-
const correctiveStep = candidate.plan.steps.find((candidateStep) => candidateStep.id === stepId);
|
|
4476
|
-
if (!correctiveStep || dirtyFiles.some((file) => !pathAllowed(file, correctiveStep.allowedWrites) || pathAllowed(file, correctiveStep.forbiddenScope))) {
|
|
4477
|
-
throw new Error('Candidate must retain the exact failed Step and confine every dirty path to its replacement scope.');
|
|
4478
|
-
}
|
|
4479
|
-
if (correctiveStep.dependencies.some((dependency) => !retained.has(dependency))) {
|
|
4480
|
-
throw new Error('Candidate corrective Step dependencies must retain completed execution authority.');
|
|
4481
|
-
}
|
|
4482
|
-
if (candidate.plan.objective !== task.planObjective
|
|
4483
|
-
|| canonicalJsonStringify(candidate.plan.requirements) !== canonicalJsonStringify(task.requirements)
|
|
4484
|
-
|| canonicalJsonStringify(candidate.plan.acceptance) !== canonicalJsonStringify(task.acceptance)) {
|
|
4485
|
-
throw new Error('Candidate cannot change Task objective, requirements or acceptance.');
|
|
4486
|
-
}
|
|
4487
|
-
const map = this.assertKnowledgeMapBinding(repositoryRoot, task.projectId, candidate.plan.knowledgeMapRevision, candidate.plan.knowledgeMapHash);
|
|
4488
|
-
this.validateKnowledgeTargets(map, candidate.plan);
|
|
4489
|
-
const prepared = prepareCorrectiveReplanCandidate(task, candidate.plan);
|
|
4490
|
-
if (!task.planHash || normalizePlanKnowledgeBinding(prepared.planBytes, 'source recovery candidate').semanticHash
|
|
4491
|
-
=== normalizePlanKnowledgeBinding(readVerifiedPlanArtifact(this.store.taskRoot(task.projectId, task.id), task.planHash, task.planHash), 'current failed Plan').semanticHash) {
|
|
4492
|
-
throw new Error('Candidate must change the failed Plan semantics before source recovery.');
|
|
4493
|
-
}
|
|
4494
|
-
if (!['approved', 'approved-with-rationale'].includes(candidate.planRiskAudit.decision)) {
|
|
4495
|
-
throw new Error('Source recovery candidate requires a positive independent Plan Risk Audit.');
|
|
4496
|
-
}
|
|
4497
|
-
const auditTask = {
|
|
4498
|
-
...task, status: 'awaiting_execution_authorization', planHash: prepared.planHash,
|
|
4499
|
-
knowledgeMapRevision: candidate.plan.knowledgeMapRevision,
|
|
4500
|
-
knowledgeMapHash: candidate.plan.knowledgeMapHash,
|
|
4501
|
-
steps: candidate.plan.steps.map((candidateStep) => retained.get(candidateStep.id) ?? {
|
|
4502
|
-
...candidateStep, status: 'planned', evidence: null, failurePause: null,
|
|
4503
|
-
}),
|
|
4504
|
-
};
|
|
4505
|
-
validatePlanRiskAuditCandidate(this.store, auditTask, candidate.planRiskAudit);
|
|
4506
|
-
candidateBinding = {
|
|
4507
|
-
candidatePlanHash: prepared.planHash,
|
|
4508
|
-
candidateInputHash: sha256Hex(canonicalJsonStringify(candidate.plan)),
|
|
4509
|
-
riskAuditInputHash: sha256Hex(canonicalJsonStringify(candidate.planRiskAudit)),
|
|
4510
|
-
};
|
|
4511
|
-
}
|
|
4512
|
-
catch (error) {
|
|
4513
|
-
blockers.push(error instanceof Error ? error.message : String(error));
|
|
4514
|
-
}
|
|
4515
|
-
}
|
|
4516
|
-
}
|
|
4517
|
-
try {
|
|
4518
|
-
validateTaskHistory(repositoryRoot, task);
|
|
4519
|
-
this.assertTaskKnowledgeBinding(repositoryRoot, task);
|
|
4520
|
-
const sourceAudit = [...readPlanRiskAuditEvents(this.store, task)].reverse().find((event) => event.taskRevision <= (terminal?.failureEvidence.executionTaskRevision ?? -1));
|
|
4521
|
-
if (!sourceAudit || !yielded?.planHash || !task.planHash
|
|
4522
|
-
|| normalizePlanKnowledgeBinding(readVerifiedPlanArtifact(this.store.taskRoot(task.projectId, task.id), sourceAudit.planHash, task.planHash), 'terminal source').semanticHash
|
|
4523
|
-
!== normalizePlanKnowledgeBinding(readVerifiedPlanArtifact(this.store.taskRoot(task.projectId, task.id), yielded.planHash, task.planHash), 'terminal corrective yield').semanticHash) {
|
|
4524
|
-
throw new Error('Terminal failure and exact corrective yield must share a verified source Plan.');
|
|
4525
|
-
}
|
|
4526
|
-
}
|
|
4527
|
-
catch (error) {
|
|
4528
|
-
blockers.push(error instanceof Error ? error.message : String(error));
|
|
4529
|
-
}
|
|
4530
|
-
const binding = {
|
|
4531
|
-
taskId: task.id, taskRevision: task.revision, stepId, briefHash: task.briefHash,
|
|
4532
|
-
planHash: task.planHash, planRiskAuditEventHash: readCurrentPlanRiskAudit(this.store, task)?.audit.eventHash ?? null,
|
|
4533
|
-
sourceHeadCommit: currentHead, dirtyFiles, dirtyWorktreeHash,
|
|
4534
|
-
completedStepsHash: hashCompletedSteps(task), historicalCarryoverHash: recorded?.compatibilityHash ?? null,
|
|
4535
|
-
terminalRemediationEventHash: terminal?.eventHash ?? null,
|
|
4536
|
-
correctiveYieldEventHash: yielded?.eventHash ?? null, originalWorker: yielded?.actor ?? null,
|
|
4537
|
-
c1Posture: c1.state,
|
|
4538
|
-
candidateBinding,
|
|
4539
|
-
};
|
|
4540
|
-
return {
|
|
4541
|
-
readOnly: true, eligible: false, sourceVersion, targetVersion, ...binding,
|
|
4542
|
-
action: null, transportAllowed: false,
|
|
4543
|
-
...(action === 'task plan-set' ? { taskPlanContract: navigation.taskPlanContract } : {}),
|
|
4544
|
-
compatibilityHash: sha256Hex(canonicalJsonStringify(binding)),
|
|
4545
|
-
sourceRecovery: blockers.length === 0 ? {
|
|
4546
|
-
eligible: true, action,
|
|
4547
|
-
...(action === 'task plan-set' ? { ...candidateBinding } : {}),
|
|
4548
|
-
...(action === 'task handoff-prepare' ? { targetActor: yielded.actor } : {}),
|
|
4549
|
-
...(action === 'task claim' ? { actor: yielded.actor } : {}),
|
|
4550
|
-
instruction: 'Execute only this exact action with the installed source package; run source status then next and repeat this read-only preflight. Ordinary Plan Risk Audit and execution authorization retain their normal gates.',
|
|
4551
|
-
} : null,
|
|
4552
|
-
blockers,
|
|
4553
|
-
};
|
|
4279
|
+
return this.inspectDependencyProvenanceCandidate(snapshot, task);
|
|
4554
4280
|
}
|
|
4555
4281
|
historicalStepProvenanceRecoveryPreflight(repository, taskId) {
|
|
4556
4282
|
const snapshot = this.observationSnapshot(repository);
|
|
@@ -4621,7 +4347,7 @@ export class WorkflowService {
|
|
|
4621
4347
|
])],
|
|
4622
4348
|
};
|
|
4623
4349
|
}
|
|
4624
|
-
return this.inspectDependencyProvenanceCandidate(snapshot, task, null,
|
|
4350
|
+
return this.inspectDependencyProvenanceCandidate(snapshot, task, null, {
|
|
4625
4351
|
activeStepId: assessment.activeStepId,
|
|
4626
4352
|
planHash: task.planHash,
|
|
4627
4353
|
dirtyFiles: assessment.dirtyFiles,
|
|
@@ -4720,289 +4446,15 @@ export class WorkflowService {
|
|
|
4720
4446
|
invalidatedStepIds: assessment.invalidatedSteps.map((step) => step.stepId),
|
|
4721
4447
|
});
|
|
4722
4448
|
}
|
|
4723
|
-
downstreamProofReplanUpdatePreflight(repository, taskId) {
|
|
4724
|
-
const snapshot = this.observationSnapshot(repository);
|
|
4725
|
-
if (snapshot.observation.kind !== 'assessment') {
|
|
4726
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream-proof replan update preflight requires a coherent observation boundary.', {
|
|
4727
|
-
observation: snapshot.observation,
|
|
4728
|
-
});
|
|
4729
|
-
}
|
|
4730
|
-
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
4731
|
-
if (!task)
|
|
4732
|
-
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
4733
|
-
const currentPlanRiskAuditExists = readPlanRiskAuditEvents(this.store, task).some((event) => event.taskRevision === task.revision && event.planHash === task.planHash);
|
|
4734
|
-
const assessment = assessDownstreamProofReplanRecovery(this.store, snapshot.identity.repositoryRoot, task, { currentPlanRiskAuditExists });
|
|
4735
|
-
const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
|
|
4736
|
-
const sourceVersion = '2.0.0-beta.12.6';
|
|
4737
|
-
const versionSurfaces = {
|
|
4738
|
-
declared: versions.declared,
|
|
4739
|
-
locked: versions.locked,
|
|
4740
|
-
installed: versions.installed,
|
|
4741
|
-
currentBranch: versions.currentBranch,
|
|
4742
|
-
baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
|
|
4743
|
-
};
|
|
4744
|
-
const blockers = [
|
|
4745
|
-
...assessment.blockers,
|
|
4746
|
-
...(String(PACKAGE_VERSION) === sourceVersion ? ['Recovery runner must be newer than the beta.12.6 source package.'] : []),
|
|
4747
|
-
...Object.entries(versionSurfaces)
|
|
4748
|
-
.filter(([, version]) => version !== sourceVersion)
|
|
4749
|
-
.map(([surface]) => `${surface} Workflow version must equal the stranded source ${sourceVersion}.`),
|
|
4750
|
-
...(snapshot.observation.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
|
|
4751
|
-
...(snapshot.observation.pendingProjectTransactions.length > 0
|
|
4752
|
-
|| snapshot.observation.pendingMilestoneTransactions.length > 0
|
|
4753
|
-
|| snapshot.observation.pendingTaskTransactions.length > 0
|
|
4754
|
-
? ['No pending Workflow transaction may exist.'] : []),
|
|
4755
|
-
...(snapshot.observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
|
|
4756
|
-
...(snapshot.observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
|
|
4757
|
-
];
|
|
4758
|
-
return {
|
|
4759
|
-
readOnly: true,
|
|
4760
|
-
eligible: assessment.applicable && assessment.eligible && blockers.length === 0,
|
|
4761
|
-
action: assessment.applicable && assessment.eligible && blockers.length === 0
|
|
4762
|
-
? 'update downstream-proof-replan-transport'
|
|
4763
|
-
: null,
|
|
4764
|
-
projectId: task.projectId,
|
|
4765
|
-
taskId: task.id,
|
|
4766
|
-
taskRevision: task.revision,
|
|
4767
|
-
sourceVersion,
|
|
4768
|
-
targetVersion: PACKAGE_VERSION,
|
|
4769
|
-
sourceHeadCommit: assessment.invalidation?.preservedHeadCommit ?? null,
|
|
4770
|
-
taskBranch: task.taskBranch,
|
|
4771
|
-
baseBranch: task.baseBranch,
|
|
4772
|
-
dirtyFiles: assessment.compatibility?.dirtyFiles ?? [],
|
|
4773
|
-
dirtyWorktreeHash: assessment.compatibility?.dirtyWorktreeHash ?? null,
|
|
4774
|
-
requiredTransport: [
|
|
4775
|
-
'Commit only package.json and package-lock.json on the active Milestone base.',
|
|
4776
|
-
'Commit only the same dependency files on the Task branch while preserving all product bytes.',
|
|
4777
|
-
'Install the exact target package, then follow fresh next to dependency registration.',
|
|
4778
|
-
],
|
|
4779
|
-
blockers,
|
|
4780
|
-
};
|
|
4781
|
-
}
|
|
4782
|
-
planIntegrityUpdatePreflight(repository, taskId) {
|
|
4783
|
-
const snapshot = this.observationSnapshot(repository);
|
|
4784
|
-
if (snapshot.observation.kind !== 'assessment') {
|
|
4785
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Plan-integrity update preflight requires a coherent observation boundary.', {
|
|
4786
|
-
observation: snapshot.observation,
|
|
4787
|
-
});
|
|
4788
|
-
}
|
|
4789
|
-
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
4790
|
-
if (!task)
|
|
4791
|
-
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
4792
|
-
const assessments = task.steps
|
|
4793
|
-
.filter((step) => step.status === 'failed')
|
|
4794
|
-
.map((step) => assessPlanIntegrityRecovery(this.store, snapshot.identity.repositoryRoot, task, step.id))
|
|
4795
|
-
.filter((assessment) => assessment.applicable);
|
|
4796
|
-
const assessment = assessments.length === 1 ? assessments[0] : null;
|
|
4797
|
-
const adoptedCompatibility = assessment
|
|
4798
|
-
? null
|
|
4799
|
-
: this.postRebindCheckSupportCompatibility(snapshot, task);
|
|
4800
|
-
const dirtyCarryoverAssessment = assessment || adoptedCompatibility
|
|
4801
|
-
? null
|
|
4802
|
-
: this.assessPostRebindCheckSupportDirtyCarryover(snapshot.identity.repositoryRoot, task);
|
|
4803
|
-
const dirtyCarryoverCompatibility = dirtyCarryoverAssessment?.eligible
|
|
4804
|
-
&& dirtyCarryoverAssessment.compatibility
|
|
4805
|
-
? {
|
|
4806
|
-
...dirtyCarryoverAssessment.compatibility,
|
|
4807
|
-
planHash: task.planHash,
|
|
4808
|
-
mode: 'post-rebind-dirty-carryover',
|
|
4809
|
-
}
|
|
4810
|
-
: null;
|
|
4811
|
-
const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
|
|
4812
|
-
const versionSurfaces = {
|
|
4813
|
-
declared: versions.declared,
|
|
4814
|
-
locked: versions.locked,
|
|
4815
|
-
installed: versions.installed,
|
|
4816
|
-
currentBranch: versions.currentBranch,
|
|
4817
|
-
baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
|
|
4818
|
-
};
|
|
4819
|
-
const sourceVersion = versionSurfaces.declared;
|
|
4820
|
-
const sourceBetaVersion = parseWorkflowBetaVersion(sourceVersion);
|
|
4821
|
-
const targetBetaVersion = parseWorkflowBetaVersion(PACKAGE_VERSION);
|
|
4822
|
-
const sourceIsEligibleBeta12 = sourceBetaVersion?.train === 12
|
|
4823
|
-
&& sourceBetaVersion.patch !== null
|
|
4824
|
-
&& sourceBetaVersion.patch >= 11;
|
|
4825
|
-
const targetIsNewer = sourceBetaVersion !== null
|
|
4826
|
-
&& targetBetaVersion !== null
|
|
4827
|
-
&& (targetBetaVersion.train > sourceBetaVersion.train
|
|
4828
|
-
|| (targetBetaVersion.train === sourceBetaVersion.train
|
|
4829
|
-
&& targetBetaVersion.patch !== null
|
|
4830
|
-
&& sourceBetaVersion.patch !== null
|
|
4831
|
-
&& targetBetaVersion.patch > sourceBetaVersion.patch));
|
|
4832
|
-
const blockers = [
|
|
4833
|
-
...(assessments.length > 1 ? ['Exactly one failed Step may expose a Plan-integrity conflict.'] : []),
|
|
4834
|
-
...(assessment?.blockers ?? (adoptedCompatibility || dirtyCarryoverCompatibility
|
|
4835
|
-
? []
|
|
4836
|
-
: (dirtyCarryoverAssessment?.blockers.length
|
|
4837
|
-
? dirtyCarryoverAssessment.blockers
|
|
4838
|
-
: ['No eligible Plan-integrity conflict, post-rebind scope adoption, or dirty carryover was found.']))),
|
|
4839
|
-
...(!sourceIsEligibleBeta12 || !targetIsNewer
|
|
4840
|
-
? [`Declared Workflow source ${sourceVersion ?? 'missing'} must be an exact beta.12 patch at or after beta.12.11 and older than runner ${PACKAGE_VERSION}.`]
|
|
4841
|
-
: []),
|
|
4842
|
-
...Object.entries(versionSurfaces)
|
|
4843
|
-
.filter(([, version]) => version !== sourceVersion)
|
|
4844
|
-
.map(([surface, version]) => `${surface} Workflow version ${version ?? 'missing'} must equal the declared source ${sourceVersion ?? 'missing'}.`),
|
|
4845
|
-
...(snapshot.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
|
|
4846
|
-
...observationPreflightBlockers(snapshot.observation),
|
|
4847
|
-
];
|
|
4848
|
-
return {
|
|
4849
|
-
readOnly: true,
|
|
4850
|
-
eligible: Boolean(assessment?.eligible || adoptedCompatibility || dirtyCarryoverCompatibility) && blockers.length === 0,
|
|
4851
|
-
action: (assessment?.eligible || adoptedCompatibility || dirtyCarryoverCompatibility) && blockers.length === 0
|
|
4852
|
-
? 'update plan-integrity-transport'
|
|
4853
|
-
: null,
|
|
4854
|
-
projectId: task.projectId,
|
|
4855
|
-
taskId: task.id,
|
|
4856
|
-
taskRevision: task.revision,
|
|
4857
|
-
stepId: assessment?.stepId ?? adoptedCompatibility?.stepId ?? dirtyCarryoverCompatibility?.stepId ?? null,
|
|
4858
|
-
sourceVersion,
|
|
4859
|
-
targetVersion: PACKAGE_VERSION,
|
|
4860
|
-
taskBranch: task.taskBranch,
|
|
4861
|
-
baseBranch: task.baseBranch,
|
|
4862
|
-
dirtyFiles: assessment?.evidence?.changedFiles
|
|
4863
|
-
?? adoptedCompatibility?.dirtyFiles
|
|
4864
|
-
?? dirtyCarryoverCompatibility?.dirtyFiles
|
|
4865
|
-
?? [],
|
|
4866
|
-
conflictHash: assessment?.evidence?.conflictHash
|
|
4867
|
-
?? adoptedCompatibility?.conflictHash
|
|
4868
|
-
?? dirtyCarryoverCompatibility?.conflictHash
|
|
4869
|
-
?? null,
|
|
4870
|
-
compatibilityMode: adoptedCompatibility?.mode
|
|
4871
|
-
?? dirtyCarryoverCompatibility?.mode
|
|
4872
|
-
?? 'failed-step-conflict',
|
|
4873
|
-
requiredTransport: [
|
|
4874
|
-
'Commit only package.json and package-lock.json on the active Milestone base.',
|
|
4875
|
-
'Fast-forward or apply only that dependency commit to the Task branch while preserving all product bytes.',
|
|
4876
|
-
'Install the exact target package, then follow fresh next to Plan-integrity dependency registration.',
|
|
4877
|
-
],
|
|
4878
|
-
blockers,
|
|
4879
|
-
};
|
|
4880
|
-
}
|
|
4881
|
-
planIntegrityDependencyRecoveryPreflight(repository, taskId) {
|
|
4882
|
-
const snapshot = this.observationSnapshot(repository);
|
|
4883
|
-
if (snapshot.observation.kind !== 'assessment') {
|
|
4884
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Plan-integrity dependency recovery requires a coherent observation boundary.', {
|
|
4885
|
-
observation: snapshot.observation,
|
|
4886
|
-
});
|
|
4887
|
-
}
|
|
4888
|
-
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
4889
|
-
if (!task)
|
|
4890
|
-
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
4891
|
-
const assessmentBlockers = [];
|
|
4892
|
-
const assessments = task.steps
|
|
4893
|
-
.filter((step) => step.status === 'failed')
|
|
4894
|
-
.flatMap((step) => {
|
|
4895
|
-
try {
|
|
4896
|
-
return [assessPlanIntegrityRecovery(this.store, snapshot.identity.repositoryRoot, task, step.id)];
|
|
4897
|
-
}
|
|
4898
|
-
catch (error) {
|
|
4899
|
-
if (error instanceof WorkflowError && error.code === 'STATE_CORRUPT') {
|
|
4900
|
-
assessmentBlockers.push(`Plan-integrity compatibility cannot be assessed until the existing remediation history is repaired: ${error.message}`);
|
|
4901
|
-
return [];
|
|
4902
|
-
}
|
|
4903
|
-
throw error;
|
|
4904
|
-
}
|
|
4905
|
-
})
|
|
4906
|
-
.filter((assessment) => assessment.applicable && assessment.eligible && assessment.evidence);
|
|
4907
|
-
const assessment = assessments.length === 1 ? assessments[0] : null;
|
|
4908
|
-
const adoptedCompatibility = assessment?.evidence
|
|
4909
|
-
? null
|
|
4910
|
-
: this.postRebindCheckSupportCompatibility(snapshot, task);
|
|
4911
|
-
const dirtyCarryoverCompatibility = assessment?.evidence || adoptedCompatibility
|
|
4912
|
-
? null
|
|
4913
|
-
: this.carriedPostRebindCheckSupportDirtyCarryover(snapshot, task);
|
|
4914
|
-
const carriedCompatibility = !assessment?.evidence && task.planHash
|
|
4915
|
-
? this.carriedPlanIntegrityUpdateCompatibility(snapshot, task)
|
|
4916
|
-
: null;
|
|
4917
|
-
if ((!assessment?.evidence
|
|
4918
|
-
&& !carriedCompatibility
|
|
4919
|
-
&& !adoptedCompatibility
|
|
4920
|
-
&& !dirtyCarryoverCompatibility) || !task.planHash) {
|
|
4921
|
-
const ordinary = this.inspectDependencyProvenanceCandidate(snapshot, task);
|
|
4922
|
-
return {
|
|
4923
|
-
...ordinary,
|
|
4924
|
-
eligible: false,
|
|
4925
|
-
action: null,
|
|
4926
|
-
planIntegrity: null,
|
|
4927
|
-
blockers: [...new Set([
|
|
4928
|
-
...ordinary.blockers,
|
|
4929
|
-
...assessmentBlockers,
|
|
4930
|
-
'The dependency candidate must preserve one exact eligible Plan-integrity conflict.',
|
|
4931
|
-
])],
|
|
4932
|
-
};
|
|
4933
|
-
}
|
|
4934
|
-
const compatibility = carriedCompatibility
|
|
4935
|
-
?? adoptedCompatibility
|
|
4936
|
-
?? dirtyCarryoverCompatibility
|
|
4937
|
-
?? {
|
|
4938
|
-
stepId: assessment.stepId,
|
|
4939
|
-
planHash: task.planHash,
|
|
4940
|
-
conflictHash: assessment.evidence.conflictHash,
|
|
4941
|
-
dirtyFiles: [...assessment.evidence.changedFiles],
|
|
4942
|
-
dirtyWorktreeHash: hashDirtyWorktree(snapshot.identity.repositoryRoot, assessment.evidence.changedFiles),
|
|
4943
|
-
remediationEventIds: assessment.evidence.kind === 'missing-npm-script-outside-allowed-writes'
|
|
4944
|
-
? [assessment.evidence.remediationEventId]
|
|
4945
|
-
: assessment.evidence.remediationEvents.map((event) => event.eventId),
|
|
4946
|
-
};
|
|
4947
|
-
return this.inspectDependencyProvenanceCandidate(snapshot, task, null, null, null, compatibility);
|
|
4948
|
-
}
|
|
4949
|
-
downstreamProofReplanDependencyRecoveryPreflight(repository, taskId) {
|
|
4950
|
-
const snapshot = this.observationSnapshot(repository);
|
|
4951
|
-
if (snapshot.observation.kind !== 'assessment') {
|
|
4952
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream-proof replan dependency recovery requires a coherent observation boundary.', {
|
|
4953
|
-
observation: snapshot.observation,
|
|
4954
|
-
});
|
|
4955
|
-
}
|
|
4956
|
-
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
4957
|
-
if (!task)
|
|
4958
|
-
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
4959
|
-
let candidateParent = null;
|
|
4960
|
-
try {
|
|
4961
|
-
candidateParent = runGit(snapshot.identity.repositoryRoot, ['rev-parse', 'HEAD^']);
|
|
4962
|
-
}
|
|
4963
|
-
catch {
|
|
4964
|
-
candidateParent = null;
|
|
4965
|
-
}
|
|
4966
|
-
const currentPlanRiskAuditExists = readPlanRiskAuditEvents(this.store, task).some((event) => event.taskRevision === task.revision && event.planHash === task.planHash);
|
|
4967
|
-
const assessment = candidateParent
|
|
4968
|
-
? assessDownstreamProofReplanRecovery(this.store, snapshot.identity.repositoryRoot, task, {
|
|
4969
|
-
historyBoundary: 'HEAD^',
|
|
4970
|
-
preservedHeadCommit: candidateParent,
|
|
4971
|
-
currentPlanRiskAuditExists,
|
|
4972
|
-
})
|
|
4973
|
-
: null;
|
|
4974
|
-
if (!assessment?.applicable || !assessment.eligible || !assessment.compatibility) {
|
|
4975
|
-
const ordinary = this.inspectDependencyProvenanceCandidate(snapshot, task);
|
|
4976
|
-
return {
|
|
4977
|
-
...ordinary,
|
|
4978
|
-
eligible: false,
|
|
4979
|
-
action: null,
|
|
4980
|
-
activeDownstreamProof: null,
|
|
4981
|
-
downstreamProofReplan: null,
|
|
4982
|
-
blockers: [...new Set([
|
|
4983
|
-
...ordinary.blockers,
|
|
4984
|
-
...(assessment?.blockers ?? []),
|
|
4985
|
-
'The Task must preserve the exact beta.12.6 downstream-proof invalidation and accidental knowledge-rebind boundary.',
|
|
4986
|
-
])],
|
|
4987
|
-
};
|
|
4988
|
-
}
|
|
4989
|
-
return this.inspectDependencyProvenanceCandidate(snapshot, task, null, assessment.compatibility);
|
|
4990
|
-
}
|
|
4991
4449
|
recoverDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
|
|
4992
4450
|
return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, false);
|
|
4993
4451
|
}
|
|
4994
4452
|
recoverActiveDownstreamProofDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
|
|
4995
4453
|
return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, true);
|
|
4996
4454
|
}
|
|
4997
|
-
recoverDownstreamProofReplanDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
|
|
4998
|
-
return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, 'downstream-replan');
|
|
4999
|
-
}
|
|
5000
4455
|
recoverHistoricalStepDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
|
|
5001
4456
|
return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, 'historical-step');
|
|
5002
4457
|
}
|
|
5003
|
-
recoverPlanIntegrityDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
|
|
5004
|
-
return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, 'plan-integrity');
|
|
5005
|
-
}
|
|
5006
4458
|
recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, recoveryMode) {
|
|
5007
4459
|
const normalizedActor = actor.trim();
|
|
5008
4460
|
const normalizedReason = reason.trim();
|
|
@@ -5015,15 +4467,9 @@ export class WorkflowService {
|
|
|
5015
4467
|
const currentHead = headCommit(context.identity.repositoryRoot);
|
|
5016
4468
|
const prior = current.dependencyProvenanceRecoveries?.find((record) => record.commitSha === currentHead
|
|
5017
4469
|
&& record.packageVersion === PACKAGE_VERSION
|
|
5018
|
-
&& (recoveryMode === '
|
|
5019
|
-
? Boolean(record.
|
|
5020
|
-
:
|
|
5021
|
-
? Boolean(record.historicalStepProvenance)
|
|
5022
|
-
: recoveryMode === 'plan-integrity'
|
|
5023
|
-
? Boolean(record.planIntegrity)
|
|
5024
|
-
: recoveryMode === false && record.rootCauseReplan
|
|
5025
|
-
? true
|
|
5026
|
-
: Boolean(record.activeDownstreamProof) === recoveryMode));
|
|
4470
|
+
&& (recoveryMode === 'historical-step'
|
|
4471
|
+
? Boolean(record.historicalStepProvenance)
|
|
4472
|
+
: Boolean(record.activeDownstreamProof) === recoveryMode));
|
|
5027
4473
|
if (prior && current.systemCommits.includes(currentHead))
|
|
5028
4474
|
return current;
|
|
5029
4475
|
assertExpectedRevision(current, expectedRevision);
|
|
@@ -5031,34 +4477,17 @@ export class WorkflowService {
|
|
|
5031
4477
|
const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
|
|
5032
4478
|
if (!task)
|
|
5033
4479
|
throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
|
|
5034
|
-
const preflight = recoveryMode === '
|
|
5035
|
-
? this.
|
|
5036
|
-
: recoveryMode
|
|
5037
|
-
? this.
|
|
5038
|
-
:
|
|
5039
|
-
? this.planIntegrityDependencyRecoveryPreflight(repository, taskId)
|
|
5040
|
-
: recoveryMode
|
|
5041
|
-
? this.activeDownstreamProofDependencyRecoveryPreflight(repository, taskId)
|
|
5042
|
-
: this.dependencyProvenanceRecoveryPreflight(repository, taskId);
|
|
4480
|
+
const preflight = recoveryMode === 'historical-step'
|
|
4481
|
+
? this.historicalStepDependencyRecoveryPreflight(repository, taskId)
|
|
4482
|
+
: recoveryMode
|
|
4483
|
+
? this.activeDownstreamProofDependencyRecoveryPreflight(repository, taskId)
|
|
4484
|
+
: this.dependencyProvenanceRecoveryPreflight(repository, taskId);
|
|
5043
4485
|
if (!preflight.eligible || !preflight.commitSha || !preflight.parentCommitSha) {
|
|
5044
4486
|
throw new WorkflowError('GIT_PRECONDITION_FAILED', 'The bounded dependency provenance recovery preflight failed.', {
|
|
5045
4487
|
taskId,
|
|
5046
4488
|
blockers: preflight.blockers,
|
|
5047
4489
|
});
|
|
5048
4490
|
}
|
|
5049
|
-
if (preflight.rootCauseReplan) {
|
|
5050
|
-
const posture = readTaskC1Posture(this.store, task);
|
|
5051
|
-
if (posture.state !== 'claimed'
|
|
5052
|
-
|| posture.claimant !== preflight.rootCauseReplan.claimant
|
|
5053
|
-
|| normalizedActor !== posture.claimant) {
|
|
5054
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Corrective carryover dependency recovery requires the exact retained C1 claimant actor.', {
|
|
5055
|
-
taskId,
|
|
5056
|
-
actor: normalizedActor,
|
|
5057
|
-
requiredActor: preflight.rootCauseReplan.claimant,
|
|
5058
|
-
posture: posture.state,
|
|
5059
|
-
});
|
|
5060
|
-
}
|
|
5061
|
-
}
|
|
5062
4491
|
const now = this.now();
|
|
5063
4492
|
const recoveredRecords = preflight.dependencyCommits.map((dependencyCommit, index) => {
|
|
5064
4493
|
const isTerminalCommit = index === preflight.dependencyCommits.length - 1;
|
|
@@ -5075,18 +4504,9 @@ export class WorkflowService {
|
|
|
5075
4504
|
...(isTerminalCommit && preflight.activeDownstreamProof
|
|
5076
4505
|
? { activeDownstreamProof: preflight.activeDownstreamProof }
|
|
5077
4506
|
: {}),
|
|
5078
|
-
...(isTerminalCommit && preflight.downstreamProofReplan
|
|
5079
|
-
? { downstreamProofReplan: preflight.downstreamProofReplan }
|
|
5080
|
-
: {}),
|
|
5081
4507
|
...(isTerminalCommit && preflight.historicalStepProvenance
|
|
5082
4508
|
? { historicalStepProvenance: preflight.historicalStepProvenance }
|
|
5083
4509
|
: {}),
|
|
5084
|
-
...(isTerminalCommit && preflight.planIntegrity
|
|
5085
|
-
? { planIntegrity: preflight.planIntegrity }
|
|
5086
|
-
: {}),
|
|
5087
|
-
...(isTerminalCommit && preflight.rootCauseReplan
|
|
5088
|
-
? { rootCauseReplan: preflight.rootCauseReplan }
|
|
5089
|
-
: {}),
|
|
5090
4510
|
};
|
|
5091
4511
|
});
|
|
5092
4512
|
const nextTask = {
|
|
@@ -5102,62 +4522,7 @@ export class WorkflowService {
|
|
|
5102
4522
|
...recoveredRecords,
|
|
5103
4523
|
],
|
|
5104
4524
|
};
|
|
5105
|
-
|
|
5106
|
-
return context.store.writeTask({ ...nextTask, revision: task.revision }, expectedRevision);
|
|
5107
|
-
}
|
|
5108
|
-
const currentKnowledgeMap = this.store.readKnowledgeMap(task.projectId);
|
|
5109
|
-
const scannedKnowledgeMap = scanProjectKnowledge(context.identity.repositoryRoot, task.projectId, now);
|
|
5110
|
-
const inspectedKnowledgeMap = inspectKnowledgeMap(currentKnowledgeMap, scannedKnowledgeMap);
|
|
5111
|
-
let refreshedKnowledgeMap = null;
|
|
5112
|
-
if (inspectedKnowledgeMap.status !== 'active' || !inspectedKnowledgeMap.approval) {
|
|
5113
|
-
const knowledgeRefresh = inspectDependencyKnowledgeRefresh(context.identity.repositoryRoot, task.baseBranch, currentKnowledgeMap, inspectedKnowledgeMap, preflight.planIntegrity.dirtyFiles);
|
|
5114
|
-
if (!knowledgeRefresh.eligible) {
|
|
5115
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Plan-integrity dependency recovery cannot bypass unrelated Project Knowledge drift.', {
|
|
5116
|
-
taskId,
|
|
5117
|
-
dependencyKnowledgeRefresh: knowledgeRefresh,
|
|
5118
|
-
});
|
|
5119
|
-
}
|
|
5120
|
-
const reconciled = reconcileKnowledgeMap(currentKnowledgeMap, scannedKnowledgeMap);
|
|
5121
|
-
const approved = applyKnowledgeSelections(reconciled, [], normalizedActor, now);
|
|
5122
|
-
if (!approved.approval) {
|
|
5123
|
-
throw new WorkflowError('STATE_CORRUPT', 'Plan-integrity dependency Knowledge refresh produced no approval evidence.');
|
|
5124
|
-
}
|
|
5125
|
-
refreshedKnowledgeMap = {
|
|
5126
|
-
...approved,
|
|
5127
|
-
revision: currentKnowledgeMap.revision + 2,
|
|
5128
|
-
createdAt: currentKnowledgeMap.createdAt,
|
|
5129
|
-
updatedAt: now.toISOString(),
|
|
5130
|
-
approval: { ...approved.approval, authorizationMode: 'corrective-derived' },
|
|
5131
|
-
};
|
|
5132
|
-
}
|
|
5133
|
-
const projectRoot = this.store.projectRoot(task.projectId);
|
|
5134
|
-
const relative = (absolute) => path.relative(projectRoot, absolute).split(path.sep).join('/');
|
|
5135
|
-
applyProjectTransaction({
|
|
5136
|
-
projectRoot,
|
|
5137
|
-
projectId: task.projectId,
|
|
5138
|
-
transactionId: `tx-${createEntityId('TASK', now.getTime()).toLowerCase()}`,
|
|
5139
|
-
kind: 'plan-integrity-dependency-recovery',
|
|
5140
|
-
now,
|
|
5141
|
-
targets: [
|
|
5142
|
-
...(refreshedKnowledgeMap
|
|
5143
|
-
? [{ path: relative(path.join(projectRoot, 'knowledge-map.json')), content: `${JSON.stringify(refreshedKnowledgeMap, null, 2)}\n` }]
|
|
5144
|
-
: []),
|
|
5145
|
-
{
|
|
5146
|
-
path: relative(path.join(this.store.taskRoot(task.projectId, task.id), 'state.json')),
|
|
5147
|
-
content: `${JSON.stringify(nextTask, null, 2)}\n`,
|
|
5148
|
-
},
|
|
5149
|
-
],
|
|
5150
|
-
validate: () => {
|
|
5151
|
-
const saved = this.store.readTask(task.projectId, task.id);
|
|
5152
|
-
if (saved.revision !== nextTask.revision || saved.systemCommits.at(-1) !== preflight.commitSha) {
|
|
5153
|
-
throw new WorkflowError('STATE_CORRUPT', 'Plan-integrity dependency recovery Task readback failed.', { taskId });
|
|
5154
|
-
}
|
|
5155
|
-
if (refreshedKnowledgeMap && this.store.readKnowledgeMap(task.projectId).revision !== refreshedKnowledgeMap.revision) {
|
|
5156
|
-
throw new WorkflowError('STATE_CORRUPT', 'Plan-integrity dependency Knowledge refresh readback failed.', { taskId });
|
|
5157
|
-
}
|
|
5158
|
-
},
|
|
5159
|
-
});
|
|
5160
|
-
return this.store.readTask(task.projectId, task.id);
|
|
4525
|
+
return context.store.writeTask({ ...nextTask, revision: task.revision }, expectedRevision);
|
|
5161
4526
|
}
|
|
5162
4527
|
milestoneProgress(repository, milestoneId) {
|
|
5163
4528
|
const snapshot = this.observationSnapshot(repository);
|
|
@@ -5225,109 +4590,6 @@ export class WorkflowService {
|
|
|
5225
4590
|
tasks,
|
|
5226
4591
|
};
|
|
5227
4592
|
}
|
|
5228
|
-
alpha6StrictReviewRescuePreflight(repository) {
|
|
5229
|
-
const context = this.#mutationContext(repository);
|
|
5230
|
-
const dependency = inspectLocalDependency(context.identity.repositoryRoot);
|
|
5231
|
-
const tasks = context.store.listTasks(context.identity.projectId);
|
|
5232
|
-
const leases = context.locks.list();
|
|
5233
|
-
const now = this.now().getTime();
|
|
5234
|
-
const activeLeases = leases.filter((lease) => Date.parse(lease.expiresAt) > now);
|
|
5235
|
-
const staleLeases = leases.filter((lease) => Date.parse(lease.expiresAt) <= now);
|
|
5236
|
-
const dirty = changedFiles(context.identity.repositoryRoot);
|
|
5237
|
-
const currentHead = headCommit(context.identity.repositoryRoot);
|
|
5238
|
-
const pendingCandidates = [];
|
|
5239
|
-
const malformedCandidates = [];
|
|
5240
|
-
for (const task of tasks) {
|
|
5241
|
-
if (task.status !== 'in_progress')
|
|
5242
|
-
continue;
|
|
5243
|
-
const planRiskAudit = task.planHash ? readCurrentPlanRiskAudit(this.store, task) : null;
|
|
5244
|
-
if (!planRiskAudit)
|
|
5245
|
-
continue;
|
|
5246
|
-
for (const step of task.steps) {
|
|
5247
|
-
if (step.status !== 'in_progress' || !step.evidence)
|
|
5248
|
-
continue;
|
|
5249
|
-
if (!isStepReviewRequired(task, step.id, planRiskAudit.reviewRequiredStepIds))
|
|
5250
|
-
continue;
|
|
5251
|
-
try {
|
|
5252
|
-
const cycle = buildCurrentStrictStepReviewCycle(this.store, task, step.id);
|
|
5253
|
-
if (cycle.resolution !== 'pending')
|
|
5254
|
-
continue;
|
|
5255
|
-
validateTaskHistory(context.identity.repositoryRoot, task);
|
|
5256
|
-
let knowledgeBindingStale = false;
|
|
5257
|
-
try {
|
|
5258
|
-
const storedKnowledgeMap = this.store.readKnowledgeMap(context.identity.projectId);
|
|
5259
|
-
const knowledgeMap = inspectKnowledgeMap(storedKnowledgeMap, scanProjectKnowledge(context.identity.repositoryRoot, context.identity.projectId, this.now()));
|
|
5260
|
-
knowledgeBindingStale = knowledgeMap.status !== 'active'
|
|
5261
|
-
|| task.knowledgeMapRevision !== knowledgeMap.revision
|
|
5262
|
-
|| task.knowledgeMapHash !== hashKnowledgeMap(knowledgeMap);
|
|
5263
|
-
}
|
|
5264
|
-
catch {
|
|
5265
|
-
knowledgeBindingStale = true;
|
|
5266
|
-
}
|
|
5267
|
-
pendingCandidates.push({
|
|
5268
|
-
taskId: task.id,
|
|
5269
|
-
taskRevision: task.revision,
|
|
5270
|
-
stepId: step.id,
|
|
5271
|
-
completionCommit: step.evidence.commitSha,
|
|
5272
|
-
currentHead,
|
|
5273
|
-
knowledgeBindingStale,
|
|
5274
|
-
});
|
|
5275
|
-
}
|
|
5276
|
-
catch (error) {
|
|
5277
|
-
malformedCandidates.push(error instanceof Error ? error.message : String(error));
|
|
5278
|
-
}
|
|
5279
|
-
}
|
|
5280
|
-
}
|
|
5281
|
-
const candidate = pendingCandidates.length === 1 ? pendingCandidates[0] : null;
|
|
5282
|
-
const candidateLeaseIds = new Set(candidate ? [candidate.taskId] : []);
|
|
5283
|
-
const blockers = [
|
|
5284
|
-
...(dependency.declared !== '2.0.0-alpha.6'
|
|
5285
|
-
? ['Project package.json must declare codex-workflow-v2 exactly at 2.0.0-alpha.6.']
|
|
5286
|
-
: []),
|
|
5287
|
-
...(dirty.length > 0 ? ['Repository checkout is not clean.'] : []),
|
|
5288
|
-
...(activeLeases.length > 0 ? ['At least one writer lease is active.'] : []),
|
|
5289
|
-
...(pendingCandidates.length !== 1
|
|
5290
|
-
? [`Expected exactly one pending alpha.6 strict review; found ${pendingCandidates.length}.`]
|
|
5291
|
-
: []),
|
|
5292
|
-
...(candidate && candidate.currentHead !== candidate.completionCommit
|
|
5293
|
-
? ['HEAD does not match the pending strict review completion commit.']
|
|
5294
|
-
: []),
|
|
5295
|
-
...(staleLeases.some((lease) => !candidateLeaseIds.has(lease.entityId))
|
|
5296
|
-
? ['A stale writer lease exists outside the rescue Task.']
|
|
5297
|
-
: []),
|
|
5298
|
-
...malformedCandidates.map((message) => `Pending strict review state is invalid: ${message}`),
|
|
5299
|
-
];
|
|
5300
|
-
const eligible = blockers.length === 0 && candidate !== null;
|
|
5301
|
-
const requiredActions = eligible
|
|
5302
|
-
? [
|
|
5303
|
-
...staleLeases.map((lease) => ({
|
|
5304
|
-
action: 'locks repair',
|
|
5305
|
-
entityId: lease.entityId,
|
|
5306
|
-
})),
|
|
5307
|
-
{
|
|
5308
|
-
action: 'task step-review',
|
|
5309
|
-
taskId: candidate.taskId,
|
|
5310
|
-
stepId: candidate.stepId,
|
|
5311
|
-
expectedRevision: candidate.taskRevision,
|
|
5312
|
-
},
|
|
5313
|
-
]
|
|
5314
|
-
: [];
|
|
5315
|
-
return {
|
|
5316
|
-
eligible,
|
|
5317
|
-
mutationFree: true,
|
|
5318
|
-
runnerPackageVersion: PACKAGE_VERSION,
|
|
5319
|
-
requiredProjectPackageVersion: '2.0.0-alpha.6',
|
|
5320
|
-
declaredProjectPackageVersion: dependency.declared,
|
|
5321
|
-
projectId: context.identity.projectId,
|
|
5322
|
-
repositoryRoot: context.identity.repositoryRoot,
|
|
5323
|
-
clean: dirty.length === 0,
|
|
5324
|
-
activeLeases: activeLeases.map((lease) => toPublicWriterLease(lease, new Date(now))),
|
|
5325
|
-
staleLeases: staleLeases.map((lease) => toPublicWriterLease(lease, new Date(now))),
|
|
5326
|
-
candidate,
|
|
5327
|
-
blockers,
|
|
5328
|
-
requiredActions,
|
|
5329
|
-
};
|
|
5330
|
-
}
|
|
5331
4593
|
scanKnowledgeMap(repository) {
|
|
5332
4594
|
const { identity } = this.#mutationContext(repository);
|
|
5333
4595
|
const scanned = scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now());
|
|
@@ -5379,7 +4641,7 @@ export class WorkflowService {
|
|
|
5379
4641
|
if (!allowAtomicTaskRefresh && current.status === 'active' && current.approval) {
|
|
5380
4642
|
const milestonesById = new Map(this.listMilestonesCurrent(identity.projectId).map((milestone) => [milestone.id, milestone]));
|
|
5381
4643
|
const inspected = inspectKnowledgeMap(current, scanned);
|
|
5382
|
-
const refreshOwner = this.findDelegatedContextRefreshOwner(identity.projectId, this.store.listTasks(identity.projectId), milestonesById, current, inspected);
|
|
4644
|
+
const refreshOwner = this.findDelegatedContextRefreshOwner(identity.projectId, this.store.listTasks(identity.projectId), milestonesById, current, inspected, identity.repositoryRoot);
|
|
5383
4645
|
if (refreshOwner) {
|
|
5384
4646
|
throw new WorkflowError('TRANSITION_BLOCKED', 'Standalone Project Knowledge reconcile would strand an authorized Task context refresh.', {
|
|
5385
4647
|
taskId: refreshOwner.task.id,
|
|
@@ -5394,10 +4656,13 @@ export class WorkflowService {
|
|
|
5394
4656
|
return createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, graphKind, mode, this.now());
|
|
5395
4657
|
}
|
|
5396
4658
|
graphStatus(repository, graphKind, mode = 'moderate') {
|
|
5397
|
-
|
|
4659
|
+
// Graph diagnostics must not register a virgin project or implicitly recover journals.
|
|
4660
|
+
// Surface the same structural boundary as status/next without loading or mutating Tasks.
|
|
4661
|
+
const identity = resolveRepositoryIdentity(repository);
|
|
4662
|
+
const { observation } = inspectWorkflowObservationBoundary({ store: this.store, identity, now: this.now() });
|
|
5398
4663
|
const request = createGraphRefreshRequest(identity.repositoryRoot, identity.projectId, graphKind, mode, this.now());
|
|
5399
4664
|
const binding = this.readGraphBindingOrNull(identity.projectId, graphKind);
|
|
5400
|
-
return { request, binding: binding ? inspectGraphBinding(binding, request) : null };
|
|
4665
|
+
return { request, binding: binding ? inspectGraphBinding(binding, request) : null, observation };
|
|
5401
4666
|
}
|
|
5402
4667
|
bindGraph(repository, expectedRevision, request, evidence) {
|
|
5403
4668
|
const { identity } = this.#mutationContext(repository);
|
|
@@ -5426,10 +4691,25 @@ export class WorkflowService {
|
|
|
5426
4691
|
return explainChange(input, repository, effectiveTask, navigation);
|
|
5427
4692
|
}
|
|
5428
4693
|
next(repository, taskId = null) {
|
|
5429
|
-
return withObservedRouteMetadata(this.nextFromStatus(repository, taskId, this.statusFromSnapshot(this.observationSnapshot(repository))));
|
|
4694
|
+
return withWorkflowBlockerRoute(withObservedRouteMetadata(this.nextFromStatus(repository, taskId, this.statusFromSnapshot(this.observationSnapshot(repository)))), PACKAGE_VERSION);
|
|
5430
4695
|
}
|
|
5431
4696
|
nextFromStatus(repository, taskId, status) {
|
|
5432
4697
|
if (status.observation.kind === 'blocked') {
|
|
4698
|
+
// Observation stays write-free. Only an absent or proven virgin registration
|
|
4699
|
+
// may resume initialization; retained or unknown state remains blocked.
|
|
4700
|
+
if (taskId === null && status.observation.missingRegistration
|
|
4701
|
+
&& isVirginRegistrationRoot(this.store.projectRoot(status.projectId), this.store.root, resolveRepositoryIdentity(repository), PACKAGE_VERSION)
|
|
4702
|
+
&& status.observation.reasonCodes.every(code => code === 'PROJECT_REGISTRATION_MISSING' || code === 'ADOPTION_MISSING')) {
|
|
4703
|
+
return {
|
|
4704
|
+
scope: 'project', projectId: status.projectId,
|
|
4705
|
+
status: 'initialization-required', action: 'project-memory scan',
|
|
4706
|
+
observation: status.observation,
|
|
4707
|
+
navigation: {
|
|
4708
|
+
kind: 'executable', command: 'project-memory scan', cataloged: false,
|
|
4709
|
+
bindings: { projectId: status.projectId },
|
|
4710
|
+
},
|
|
4711
|
+
};
|
|
4712
|
+
}
|
|
5433
4713
|
return {
|
|
5434
4714
|
scope: 'project',
|
|
5435
4715
|
projectId: status.projectId,
|
|
@@ -5437,7 +4717,16 @@ export class WorkflowService {
|
|
|
5437
4717
|
action: 'blocked',
|
|
5438
4718
|
observation: status.observation,
|
|
5439
4719
|
navigation: {
|
|
5440
|
-
kind:
|
|
4720
|
+
kind: status.observation.pendingProjectTransactions.some(transaction => transaction.ownerActive)
|
|
4721
|
+
|| status.observation.reasonCodes.includes('TASK_OPERATION_IN_PROGRESS') ? 'wait' : 'blocked',
|
|
4722
|
+
...(status.observation.pendingProjectTransactions.some(transaction => transaction.ownerActive) ? {
|
|
4723
|
+
reason: 'project-transaction-in-progress',
|
|
4724
|
+
waitFor: 'The current transaction owner must finish or exit before recovery; obtain fresh status and next afterward.',
|
|
4725
|
+
} : status.observation.reasonCodes.includes('TASK_OPERATION_IN_PROGRESS') ? {
|
|
4726
|
+
reason: 'core-operation-in-progress',
|
|
4727
|
+
waitFor: 'The active Core operation must finish or its lease expire; obtain fresh status and next afterward.',
|
|
4728
|
+
operations: status.observation.coreTaskOperations,
|
|
4729
|
+
} : {}),
|
|
5441
4730
|
reasonCodes: status.observation.reasonCodes,
|
|
5442
4731
|
availableRepairTransitionId: null,
|
|
5443
4732
|
},
|
|
@@ -5505,7 +4794,18 @@ export class WorkflowService {
|
|
|
5505
4794
|
currentEventType: status.adoption.currentPosture?.eventType ?? null,
|
|
5506
4795
|
};
|
|
5507
4796
|
if (status.adoption.status !== 'applied') {
|
|
4797
|
+
const adoptionApprovals = this.listDelegations(status.projectId).flatMap(grant => {
|
|
4798
|
+
try {
|
|
4799
|
+
return [assertDelegationCanAuthorize(grant, 'project.adopt', grant.delegate, { kind: 'project', id: status.projectId }, this.now())];
|
|
4800
|
+
}
|
|
4801
|
+
catch (error) {
|
|
4802
|
+
if (error instanceof WorkflowError && error.code === 'TRANSITION_BLOCKED')
|
|
4803
|
+
return [];
|
|
4804
|
+
throw error;
|
|
4805
|
+
}
|
|
4806
|
+
});
|
|
5508
4807
|
return {
|
|
4808
|
+
...(adoptionApprovals.length ? { delegatedApprovalOptions: adoptionApprovals } : {}),
|
|
5509
4809
|
scope: 'project',
|
|
5510
4810
|
projectId: status.projectId,
|
|
5511
4811
|
status: status.adoption.status,
|
|
@@ -5514,7 +4814,7 @@ export class WorkflowService {
|
|
|
5514
4814
|
observation: status.observation,
|
|
5515
4815
|
navigation: status.adoption.safe
|
|
5516
4816
|
? {
|
|
5517
|
-
kind: 'human-gate',
|
|
4817
|
+
kind: adoptionApprovals.length ? 'delegated' : 'human-gate',
|
|
5518
4818
|
transitionId: null,
|
|
5519
4819
|
cataloged: false,
|
|
5520
4820
|
command: 'state adoption-apply',
|
|
@@ -5536,6 +4836,49 @@ export class WorkflowService {
|
|
|
5536
4836
|
}
|
|
5537
4837
|
const withAdoption = (value) => ({
|
|
5538
4838
|
...value,
|
|
4839
|
+
delegatedMilestoneAutonomyOptions: status.milestones.flatMap(milestone => {
|
|
4840
|
+
if (!['active', 'awaiting_execution_authorization'].includes(milestone.status) || !milestone.planHash)
|
|
4841
|
+
return [];
|
|
4842
|
+
const previousContract = readMilestoneAutonomyEvents(this.store, status.projectId, milestone.id).at(-1);
|
|
4843
|
+
if (previousContract) {
|
|
4844
|
+
if (previousContract.semanticScopeHash !== milestoneSemanticScopeHash(milestone))
|
|
4845
|
+
return [];
|
|
4846
|
+
try {
|
|
4847
|
+
const previousGrant = this.store.readDelegation(status.projectId, previousContract.delegationGrantId);
|
|
4848
|
+
if (previousGrant.status === 'active' && Date.parse(previousGrant.expiresAt) > this.now().getTime())
|
|
4849
|
+
return [];
|
|
4850
|
+
}
|
|
4851
|
+
catch (error) {
|
|
4852
|
+
if (!(error instanceof WorkflowError) || error.code !== 'NOT_FOUND')
|
|
4853
|
+
throw error;
|
|
4854
|
+
}
|
|
4855
|
+
}
|
|
4856
|
+
try {
|
|
4857
|
+
this.requireActiveKnowledgeMap(repository, status.projectId);
|
|
4858
|
+
}
|
|
4859
|
+
catch (error) {
|
|
4860
|
+
if (error instanceof WorkflowError)
|
|
4861
|
+
return [];
|
|
4862
|
+
throw error;
|
|
4863
|
+
}
|
|
4864
|
+
return this.listDelegations(status.projectId).flatMap(grant => {
|
|
4865
|
+
if (grant.scope.kind !== 'milestone' || grant.scope.id !== milestone.id
|
|
4866
|
+
|| !['task.execution_authorize', 'task.final_accept', 'milestone.execution_authorize', 'milestone.final_accept']
|
|
4867
|
+
.every(transition => grant.transitions.some(value => value === transition)))
|
|
4868
|
+
return [];
|
|
4869
|
+
try {
|
|
4870
|
+
assertDelegationCanAuthorize(grant, 'milestone.execution_authorize', grant.delegate, milestone, this.now());
|
|
4871
|
+
return [{ action: 'milestone autonomy-bind', milestoneId: milestone.id,
|
|
4872
|
+
expectedRevision: milestone.revision, actor: grant.delegate, delegationGrantId: grant.id,
|
|
4873
|
+
issuesGrant: false, requiresExactHumanConfirmation: false }];
|
|
4874
|
+
}
|
|
4875
|
+
catch (error) {
|
|
4876
|
+
if (error instanceof WorkflowError && error.code === 'TRANSITION_BLOCKED')
|
|
4877
|
+
return [];
|
|
4878
|
+
throw error;
|
|
4879
|
+
}
|
|
4880
|
+
});
|
|
4881
|
+
}),
|
|
5539
4882
|
...(status.milestones.some((milestone) => milestone.status === 'active' && milestone.planHash)
|
|
5540
4883
|
? { activeMilestoneAutonomyOptions: status.milestones
|
|
5541
4884
|
.filter((milestone) => milestone.status === 'active' && milestone.planHash)
|
|
@@ -5574,6 +4917,46 @@ export class WorkflowService {
|
|
|
5574
4917
|
const inspectCurrentKnowledgeMap = () => inspectKnowledgeMap(this.store.readKnowledgeMap(status.projectId), scanProjectKnowledge(repositoryRoot, status.projectId, this.now()));
|
|
5575
4918
|
const milestonesById = new Map(status.milestones.map((milestone) => [milestone.id, milestone]));
|
|
5576
4919
|
const tasksById = new Map(status.tasks.map((task) => [task.id, task]));
|
|
4920
|
+
const incompleteScope = status.milestones.find((milestone) => !['accepted', 'cancelled'].includes(milestone.status)
|
|
4921
|
+
&& (taskId === null || tasksById.get(taskId)?.milestoneId === milestone.id)
|
|
4922
|
+
&& !assessMilestoneInitialAssembly({ store: this.store, projectId: status.projectId, milestone,
|
|
4923
|
+
listTasks: () => status.tasks }).eligible
|
|
4924
|
+
&& assessMilestoneScopeCompletion(milestone, status.tasks).length > 0);
|
|
4925
|
+
if (incompleteScope) {
|
|
4926
|
+
const lease = this.inspectWriterLease(repositoryRoot, incompleteScope.id);
|
|
4927
|
+
if (lease)
|
|
4928
|
+
return withAdoption({ scope: 'milestone', id: incompleteScope.id, entityId: incompleteScope.id,
|
|
4929
|
+
revision: incompleteScope.revision, action: lease.stale ? 'locks repair' : 'blocked', lease,
|
|
4930
|
+
navigation: { kind: lease.stale ? 'repair' : 'wait', reason: 'milestone-authority-lease' } });
|
|
4931
|
+
if (incompleteScope.status === 'awaiting_final_acceptance')
|
|
4932
|
+
return withAdoption({
|
|
4933
|
+
scope: 'milestone', id: incompleteScope.id, revision: incompleteScope.revision, action: 'blocked',
|
|
4934
|
+
navigation: { kind: 'blocked', reason: 'retained-final-scope-gap' },
|
|
4935
|
+
reason: 'Retained final acceptance state contains unclassified linked Tasks; no safe scope transition is available.',
|
|
4936
|
+
scopeCompletion: { orphanTaskIds: assessMilestoneScopeCompletion(incompleteScope, status.tasks), executionAvailable: false },
|
|
4937
|
+
});
|
|
4938
|
+
const event = readMilestoneAutonomyEvents(this.store, status.projectId, incompleteScope.id).at(-1);
|
|
4939
|
+
let autonomous = null;
|
|
4940
|
+
if (event) {
|
|
4941
|
+
try {
|
|
4942
|
+
const authority = requireActiveMilestoneAutonomy({ store: this.store, projectId: status.projectId,
|
|
4943
|
+
milestone: incompleteScope, actor: event.delegate, now: this.now() });
|
|
4944
|
+
autonomous = { actor: event.delegate, grantId: authority.grant.id };
|
|
4945
|
+
}
|
|
4946
|
+
catch (error) {
|
|
4947
|
+
if (!(error instanceof WorkflowError) || !['TRANSITION_BLOCKED', 'NOT_FOUND'].includes(error.code))
|
|
4948
|
+
throw error;
|
|
4949
|
+
}
|
|
4950
|
+
}
|
|
4951
|
+
return withAdoption({ scope: 'milestone', id: incompleteScope.id, status: incompleteScope.status,
|
|
4952
|
+
revision: incompleteScope.revision, action: autonomous ? 'milestone autonomy-evolve' : 'milestone scope-change-prepare',
|
|
4953
|
+
scopeCompletion: { orphanTaskIds: assessMilestoneScopeCompletion(incompleteScope, status.tasks),
|
|
4954
|
+
requiresCandidatePlan: true, executionAvailable: false, preservesExistingScopeAndDependencies: true },
|
|
4955
|
+
...(autonomous ? { autonomy: autonomous } : { scopeChangeBoundary: { state: 'human-decision-required',
|
|
4956
|
+
requiresCandidatePlan: true, prepareAction: 'milestone scope-change-prepare', applyAction: 'milestone scope-change-apply',
|
|
4957
|
+
requiresExplicitHumanConfirmation: true, confirmationCodeSource: 'scope-change-prepare-result',
|
|
4958
|
+
afterPreparation: 'await-human-confirmation-before-apply', applyRequiresSamePlanAndActor: true } }) });
|
|
4959
|
+
}
|
|
5577
4960
|
const requestedTask = taskId === null
|
|
5578
4961
|
? null
|
|
5579
4962
|
: status.tasks.find((task) => task.id === taskId) ?? null;
|
|
@@ -5615,23 +4998,27 @@ export class WorkflowService {
|
|
|
5615
4998
|
? [task]
|
|
5616
4999
|
: [];
|
|
5617
5000
|
}).at(0) ?? null;
|
|
5001
|
+
let prioritizedRefreshTask = null;
|
|
5002
|
+
if (!requestedTask) {
|
|
5003
|
+
let approved = null;
|
|
5004
|
+
try {
|
|
5005
|
+
approved = this.store.readKnowledgeMap(status.projectId);
|
|
5006
|
+
}
|
|
5007
|
+
catch (error) {
|
|
5008
|
+
if (!(error instanceof WorkflowError) || error.code !== 'NOT_FOUND')
|
|
5009
|
+
throw error;
|
|
5010
|
+
}
|
|
5011
|
+
if (approved?.status === 'active' && approved.approval) {
|
|
5012
|
+
const inspected = inspectCurrentKnowledgeMap();
|
|
5013
|
+
if (inspected.status === 'stale')
|
|
5014
|
+
prioritizedRefreshTask = this.findDelegatedContextRefreshOwner(status.projectId, status.tasks, milestonesById, approved, inspected, repositoryRoot)?.task ?? null;
|
|
5015
|
+
}
|
|
5016
|
+
}
|
|
5618
5017
|
const activeTask = requestedTask
|
|
5018
|
+
?? prioritizedRefreshTask
|
|
5619
5019
|
?? prioritizedRemediationTask
|
|
5620
5020
|
?? status.tasks.find((task) => taskIsActionableFromRepositoryNext(task, milestonesById, tasksById));
|
|
5621
5021
|
if (activeTask) {
|
|
5622
|
-
let pendingReviewDependencyRecovery = null;
|
|
5623
|
-
try {
|
|
5624
|
-
const reconstructed = pendingReviewSourceBinding(repositoryRoot, this.store, activeTask, runGit(repositoryRoot, ['rev-parse', 'HEAD^']), runGit(repositoryRoot, ['rev-parse', `${activeTask.baseBranch}^`]));
|
|
5625
|
-
pendingReviewDependencyRecovery = this.pendingReviewDependencyPreflight(repositoryRoot, activeTask.id, reconstructed);
|
|
5626
|
-
}
|
|
5627
|
-
catch { /* A nonmatching shape follows ordinary navigation without a compatibility bypass. */ }
|
|
5628
|
-
if (pendingReviewDependencyRecovery?.eligible) {
|
|
5629
|
-
return withAdoption({ scope: 'task', id: activeTask.id, status: activeTask.status,
|
|
5630
|
-
action: 'update pending-review-dependency-recover', revision: activeTask.revision,
|
|
5631
|
-
pendingReviewDependencyRecovery,
|
|
5632
|
-
sourceReceiptRequired: true,
|
|
5633
|
-
});
|
|
5634
|
-
}
|
|
5635
5022
|
const historicalStepProvenanceRecovery = this.historicalStepProvenanceRecoveryPreflight(repositoryRoot, activeTask.id);
|
|
5636
5023
|
if (historicalStepProvenanceRecovery.eligible) {
|
|
5637
5024
|
return withAdoption({
|
|
@@ -5659,22 +5046,6 @@ export class WorkflowService {
|
|
|
5659
5046
|
},
|
|
5660
5047
|
});
|
|
5661
5048
|
}
|
|
5662
|
-
const planIntegrityDependencyRecovery = this.planIntegrityDependencyRecoveryPreflight(repositoryRoot, activeTask.id);
|
|
5663
|
-
if (planIntegrityDependencyRecovery.eligible) {
|
|
5664
|
-
return withAdoption({
|
|
5665
|
-
scope: 'task',
|
|
5666
|
-
id: activeTask.id,
|
|
5667
|
-
status: activeTask.status,
|
|
5668
|
-
action: 'update plan-integrity-dependency-recover',
|
|
5669
|
-
revision: activeTask.revision,
|
|
5670
|
-
dependencyProvenanceRecovery: planIntegrityDependencyRecovery,
|
|
5671
|
-
compatibilityBoundary: {
|
|
5672
|
-
preservesWorktree: true,
|
|
5673
|
-
prerequisite: 'A newer package preflight approved the exact failed Step, remediation history, and dirty allowlist before dependency-only transport.',
|
|
5674
|
-
continuation: 'Register this dependency commit, then follow fresh next to Plan-integrity recovery and corrective replan.',
|
|
5675
|
-
},
|
|
5676
|
-
});
|
|
5677
|
-
}
|
|
5678
5049
|
const dependencyRecovery = this.dependencyProvenanceRecoveryPreflight(repositoryRoot, activeTask.id);
|
|
5679
5050
|
if (dependencyRecovery.eligible) {
|
|
5680
5051
|
return withAdoption({
|
|
@@ -5686,6 +5057,29 @@ export class WorkflowService {
|
|
|
5686
5057
|
dependencyProvenanceRecovery: dependencyRecovery,
|
|
5687
5058
|
});
|
|
5688
5059
|
}
|
|
5060
|
+
if (PACKAGE_VERSION === FAILED_CHECKPOINT_TARGET && activeTask.status === 'awaiting_execution_authorization'
|
|
5061
|
+
&& activeTask.workspaceOwner === 'external' && activeTask.steps.some(step => step.status === 'failed')) {
|
|
5062
|
+
const transport = inspectBoundedWorkflowDependencyCommit(repositoryRoot, headCommit(repositoryRoot));
|
|
5063
|
+
if (transport?.packageVersion === FAILED_CHECKPOINT_TARGET && !activeTask.systemCommits.includes(transport.commitSha)) {
|
|
5064
|
+
return withAdoption({ scope: 'task', id: activeTask.id, revision: activeTask.revision,
|
|
5065
|
+
action: 'update failed-checkpoint-transport-preflight',
|
|
5066
|
+
requiredInputs: ['--tarball <exact target artifact>', '--file <source receipt>'],
|
|
5067
|
+
reason: 'Verify the original failed-checkpoint receipt, native failure and retained product bytes before registering the dependency transport.',
|
|
5068
|
+
});
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
const navigationSourceHead = activeTask.authorizations.filter(item => item.kind === 'execution' && item.decision === 'approved').at(-1)?.headCommit;
|
|
5072
|
+
if (activeTask.status === 'ready' && navigationSourceHead
|
|
5073
|
+
&& activeTask.dependencyProvenanceRecoveries?.some(item => item.planIntegrity?.mode === 'navigation-fix-dirty-carryover')) {
|
|
5074
|
+
const transport = inspectBoundedWorkflowDependencyCommit(repositoryRoot, headCommit(repositoryRoot));
|
|
5075
|
+
if (transport?.packageVersion === PACKAGE_VERSION && transport.parentCommitSha === navigationSourceHead) {
|
|
5076
|
+
return withAdoption({ scope: 'task', id: activeTask.id, revision: activeTask.revision,
|
|
5077
|
+
action: 'update navigation-transport-preflight',
|
|
5078
|
+
requiredInputs: ['--tarball <exact target artifact>', '--file <source receipt>'],
|
|
5079
|
+
reason: 'Verify the preserved source receipt before registering navigation dependency transport. Missing evidence blocks recovery.',
|
|
5080
|
+
});
|
|
5081
|
+
}
|
|
5082
|
+
}
|
|
5689
5083
|
const activeDownstreamProofDependencyRecovery = this.activeDownstreamProofDependencyRecoveryPreflight(repositoryRoot, activeTask.id);
|
|
5690
5084
|
if (activeDownstreamProofDependencyRecovery.eligible) {
|
|
5691
5085
|
return withAdoption({
|
|
@@ -5702,22 +5096,6 @@ export class WorkflowService {
|
|
|
5702
5096
|
},
|
|
5703
5097
|
});
|
|
5704
5098
|
}
|
|
5705
|
-
const downstreamProofReplanDependencyRecovery = this.downstreamProofReplanDependencyRecoveryPreflight(repositoryRoot, activeTask.id);
|
|
5706
|
-
if (downstreamProofReplanDependencyRecovery.eligible) {
|
|
5707
|
-
return withAdoption({
|
|
5708
|
-
scope: 'task',
|
|
5709
|
-
id: activeTask.id,
|
|
5710
|
-
status: activeTask.status,
|
|
5711
|
-
action: 'update downstream-proof-replan-dependency-recover',
|
|
5712
|
-
revision: activeTask.revision,
|
|
5713
|
-
dependencyProvenanceRecovery: downstreamProofReplanDependencyRecovery,
|
|
5714
|
-
compatibilityBoundary: {
|
|
5715
|
-
preservesWorktree: true,
|
|
5716
|
-
prerequisite: 'A newer package preflight approved the exact beta.12.6 stranded replan transport before dependency-only commits were created.',
|
|
5717
|
-
continuation: 'Register this dependency commit, then run task downstream-proof-replan-recover.',
|
|
5718
|
-
},
|
|
5719
|
-
});
|
|
5720
|
-
}
|
|
5721
5099
|
const activeTaskMilestone = activeTask.milestoneId
|
|
5722
5100
|
? milestonesById.get(activeTask.milestoneId) ?? null
|
|
5723
5101
|
: null;
|
|
@@ -5741,54 +5119,6 @@ export class WorkflowService {
|
|
|
5741
5119
|
},
|
|
5742
5120
|
}
|
|
5743
5121
|
: value);
|
|
5744
|
-
const currentPlanRiskAuditExists = activeTask.planHash
|
|
5745
|
-
? readPlanRiskAuditEvents(this.store, activeTask).some((event) => event.taskRevision === activeTask.revision && event.planHash === activeTask.planHash)
|
|
5746
|
-
: false;
|
|
5747
|
-
const downstreamReplanRecovery = assessDownstreamProofReplanRecovery(this.store, repositoryRoot, activeTask, { currentPlanRiskAuditExists });
|
|
5748
|
-
if (downstreamReplanRecovery.applicable) {
|
|
5749
|
-
const environmentBlockers = [
|
|
5750
|
-
...(!downstreamReplanRecovery.dependencyRecoveryCommit
|
|
5751
|
-
? ['The exact beta.12.7 dependency compatibility must be registered before Task recovery.']
|
|
5752
|
-
: []),
|
|
5753
|
-
...(status.observation.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
|
|
5754
|
-
...(status.observation.pendingProjectTransactions.length > 0
|
|
5755
|
-
|| status.observation.pendingMilestoneTransactions.length > 0
|
|
5756
|
-
|| status.observation.pendingTaskTransactions.length > 0
|
|
5757
|
-
? ['No pending Workflow transaction may exist.'] : []),
|
|
5758
|
-
...(status.observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
|
|
5759
|
-
...(status.observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
|
|
5760
|
-
];
|
|
5761
|
-
return withAdoption(withMilestoneCancellation(downstreamReplanRecovery.eligible && environmentBlockers.length === 0
|
|
5762
|
-
? {
|
|
5763
|
-
scope: 'task',
|
|
5764
|
-
id: activeTask.id,
|
|
5765
|
-
status: activeTask.status,
|
|
5766
|
-
action: 'task downstream-proof-replan-recover',
|
|
5767
|
-
revision: activeTask.revision,
|
|
5768
|
-
downstreamProofReplanRecovery: {
|
|
5769
|
-
state: 'eligible',
|
|
5770
|
-
invalidationEventId: downstreamReplanRecovery.invalidation.eventId,
|
|
5771
|
-
sourcePlanHash: downstreamReplanRecovery.invalidation.planHash,
|
|
5772
|
-
reboundPlanHash: activeTask.planHash,
|
|
5773
|
-
dirtyFiles: downstreamReplanRecovery.compatibility.dirtyFiles,
|
|
5774
|
-
dirtyWorktreeHash: downstreamReplanRecovery.compatibility.dirtyWorktreeHash,
|
|
5775
|
-
dependencyRecoveryCommit: downstreamReplanRecovery.dependencyRecoveryCommit,
|
|
5776
|
-
boundedEffect: 'restore-needs-fix-and-return-same-task-to-plan-set',
|
|
5777
|
-
},
|
|
5778
|
-
}
|
|
5779
|
-
: {
|
|
5780
|
-
scope: 'task',
|
|
5781
|
-
id: activeTask.id,
|
|
5782
|
-
status: activeTask.status,
|
|
5783
|
-
action: 'doctor',
|
|
5784
|
-
blockedAction: 'task downstream-proof-replan-recover',
|
|
5785
|
-
revision: activeTask.revision,
|
|
5786
|
-
downstreamProofReplanRecovery: {
|
|
5787
|
-
state: 'blocked',
|
|
5788
|
-
blockers: [...downstreamReplanRecovery.blockers, ...environmentBlockers],
|
|
5789
|
-
},
|
|
5790
|
-
}));
|
|
5791
|
-
}
|
|
5792
5122
|
let activePlanRiskAudit = null;
|
|
5793
5123
|
let planRiskAuditRecovery = null;
|
|
5794
5124
|
const requiresReplacementPlan = activeTask.status === 'needs_fix'
|
|
@@ -6004,9 +5334,8 @@ export class WorkflowService {
|
|
|
6004
5334
|
if (taskNext?.action === 'task step-complete'
|
|
6005
5335
|
|| taskNext?.action === 'task step-review'
|
|
6006
5336
|
|| taskNext?.action === 'task downstream-proof-recover'
|
|
6007
|
-
|| taskNext?.action === 'task remediation-mode-recover'
|
|
6008
5337
|
|| (taskNext?.action === 'task writer-credential-replace'
|
|
6009
|
-
&& ['task step-complete', 'task step-review'
|
|
5338
|
+
&& ['task step-complete', 'task step-review'].includes(String(taskNext?.blockedAction)))
|
|
6010
5339
|
|| taskNext?.action === 'task plan-integrity-recover'
|
|
6011
5340
|
|| taskNext?.blockedAction === 'task plan-integrity-recover'
|
|
6012
5341
|
|| taskNext?.action === 'task corrective-yield'
|
|
@@ -6052,10 +5381,10 @@ export class WorkflowService {
|
|
|
6052
5381
|
const contextRefreshOptions = action === 'project-memory reconcile'
|
|
6053
5382
|
&& (taskNext?.action !== 'task plan-set' || rollingRootCausePlanSet)
|
|
6054
5383
|
&& refreshAssessment?.eligible
|
|
6055
|
-
? this.delegatedContextRefreshOptions(status.projectId, activeTask, knowledgeMap, refreshAssessment.mode)
|
|
5384
|
+
? this.delegatedContextRefreshOptions(status.projectId, activeTask, knowledgeMap, refreshAssessment.mode, repositoryRoot)
|
|
6056
5385
|
: [];
|
|
6057
5386
|
if (requestedTask && action === 'project-memory reconcile' && contextRefreshOptions.length === 0) {
|
|
6058
|
-
const refreshOwner = this.findDelegatedContextRefreshOwner(status.projectId, status.tasks, milestonesById, storedKnowledgeMap, knowledgeMap);
|
|
5387
|
+
const refreshOwner = this.findDelegatedContextRefreshOwner(status.projectId, status.tasks, milestonesById, storedKnowledgeMap, knowledgeMap, repositoryRoot);
|
|
6059
5388
|
if (refreshOwner && refreshOwner.task.id !== requestedTask.id) {
|
|
6060
5389
|
throw new WorkflowError('TRANSITION_BLOCKED', `Task ${requestedTask.id} cannot be selected while Task ${refreshOwner.task.id} requires an atomic context refresh.`, {
|
|
6061
5390
|
taskId: requestedTask.id,
|
|
@@ -6082,6 +5411,7 @@ export class WorkflowService {
|
|
|
6082
5411
|
contextRefresh: {
|
|
6083
5412
|
action: 'task context-refresh',
|
|
6084
5413
|
mode: refreshAssessment.mode,
|
|
5414
|
+
authority: (this.registeredPlanningContextTransport(activeTask, repositoryRoot) || readFailedStepPlanningRecovery(this.store, activeTask)) ? 'planning-only' : 'execution',
|
|
6085
5415
|
addedSources: refreshAssessment.addedSources,
|
|
6086
5416
|
options: contextRefreshOptions,
|
|
6087
5417
|
},
|
|
@@ -6186,6 +5516,24 @@ export class WorkflowService {
|
|
|
6186
5516
|
milestoneInitialAssembly: initialAssembly,
|
|
6187
5517
|
});
|
|
6188
5518
|
}
|
|
5519
|
+
// Only this Milestone's authority lease gates its own transitions.
|
|
5520
|
+
// Keep this outside validation admission: the executor already owns its lease there.
|
|
5521
|
+
const milestoneLease = this.inspectWriterLease(repositoryRoot, activeMilestone.id);
|
|
5522
|
+
if (milestoneLease) {
|
|
5523
|
+
return withAdoption({
|
|
5524
|
+
scope: 'milestone',
|
|
5525
|
+
id: activeMilestone.id,
|
|
5526
|
+
entityId: activeMilestone.id,
|
|
5527
|
+
status: activeMilestone.status,
|
|
5528
|
+
action: milestoneLease.stale ? 'locks repair' : 'blocked',
|
|
5529
|
+
...(!milestoneLease.stale ? { navigation: { kind: 'wait', reason: 'active-milestone-writer-lease' } } : {}),
|
|
5530
|
+
revision: activeMilestone.revision,
|
|
5531
|
+
lease: milestoneLease,
|
|
5532
|
+
reason: milestoneLease.stale
|
|
5533
|
+
? 'The expired Milestone authority lease requires explicit repair before another transition.'
|
|
5534
|
+
: 'Another writer owns this Milestone; wait for that operation to release its lease.',
|
|
5535
|
+
});
|
|
5536
|
+
}
|
|
6189
5537
|
if (activeMilestone.status === 'awaiting_final_acceptance') {
|
|
6190
5538
|
const root = this.store.milestoneRoot(activeMilestone.projectId, activeMilestone.id);
|
|
6191
5539
|
const evidence = JSON.parse(readFileSync(path.join(root, 'evidence.json'), 'utf8'));
|
|
@@ -6227,6 +5575,33 @@ export class WorkflowService {
|
|
|
6227
5575
|
...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
|
|
6228
5576
|
});
|
|
6229
5577
|
}
|
|
5578
|
+
let milestoneAction = nextForMilestone(activeMilestone);
|
|
5579
|
+
let validationBlock = null;
|
|
5580
|
+
if (activeMilestone.status === 'active') {
|
|
5581
|
+
try {
|
|
5582
|
+
this.assertMilestoneValidationReady(repositoryRoot, activeMilestone);
|
|
5583
|
+
milestoneAction = 'milestone validate';
|
|
5584
|
+
}
|
|
5585
|
+
catch (error) {
|
|
5586
|
+
if (!(error instanceof WorkflowError))
|
|
5587
|
+
throw error;
|
|
5588
|
+
if (error.code === 'GIT_PRECONDITION_FAILED') {
|
|
5589
|
+
milestoneAction = 'restore clean base, then milestone validate';
|
|
5590
|
+
validationBlock = { blockedAction: 'milestone validate', reason: error.message };
|
|
5591
|
+
}
|
|
5592
|
+
else if (error.code === 'TRANSITION_BLOCKED' && Array.isArray(error.details.taskIds)) {
|
|
5593
|
+
validationBlock = { blockedAction: 'milestone validate', incompleteTaskIds: error.details.taskIds };
|
|
5594
|
+
}
|
|
5595
|
+
else if (error.code === 'TRANSITION_BLOCKED') {
|
|
5596
|
+
milestoneAction = 'doctor';
|
|
5597
|
+
validationBlock = { blockedAction: 'milestone validate', reason: error.message };
|
|
5598
|
+
}
|
|
5599
|
+
else
|
|
5600
|
+
throw error;
|
|
5601
|
+
}
|
|
5602
|
+
}
|
|
5603
|
+
if (activeMilestone.status === 'blocked')
|
|
5604
|
+
milestoneAction = 'milestone scope-change-prepare';
|
|
6230
5605
|
const delegatedApprovalOptions = activeMilestone.status === 'awaiting_execution_authorization'
|
|
6231
5606
|
? this.delegatedApprovalOptions(status.projectId, 'milestone.execution_authorize', activeMilestone)
|
|
6232
5607
|
: [];
|
|
@@ -6234,7 +5609,24 @@ export class WorkflowService {
|
|
|
6234
5609
|
scope: 'milestone',
|
|
6235
5610
|
id: activeMilestone.id,
|
|
6236
5611
|
status: activeMilestone.status,
|
|
6237
|
-
action:
|
|
5612
|
+
action: milestoneAction,
|
|
5613
|
+
...(validationBlock ?? {}),
|
|
5614
|
+
...(activeMilestone.status === 'blocked' ? {
|
|
5615
|
+
options: [
|
|
5616
|
+
{ action: 'milestone scope-change-prepare', eligible: true, requiresCandidatePlan: true },
|
|
5617
|
+
{ action: 'milestone cancel', eligible: true, requiresExplicitCancellationIntent: true },
|
|
5618
|
+
],
|
|
5619
|
+
scopeChangeBoundary: {
|
|
5620
|
+
state: 'human-decision-required',
|
|
5621
|
+
requiresCandidatePlan: true,
|
|
5622
|
+
prepareAction: 'milestone scope-change-prepare',
|
|
5623
|
+
applyAction: 'milestone scope-change-apply',
|
|
5624
|
+
requiresExplicitHumanConfirmation: true,
|
|
5625
|
+
confirmationCodeSource: 'scope-change-prepare-result',
|
|
5626
|
+
afterPreparation: 'await-human-confirmation-before-apply',
|
|
5627
|
+
applyRequiresSamePlanAndActor: true,
|
|
5628
|
+
},
|
|
5629
|
+
} : {}),
|
|
6238
5630
|
revision: activeMilestone.revision,
|
|
6239
5631
|
...(activeMilestone.status === 'awaiting_execution_authorization' && activeMilestone.planHash
|
|
6240
5632
|
? {
|
|
@@ -6438,6 +5830,8 @@ export class WorkflowService {
|
|
|
6438
5830
|
return { milestone, authorization, scopeChange };
|
|
6439
5831
|
}
|
|
6440
5832
|
nextForTaskWithDelegation(projectId, repositoryRoot, task, planRiskAudit = null) {
|
|
5833
|
+
if (task.pendingBaseSyncValidation !== undefined)
|
|
5834
|
+
return this.taskGitNavigationOverride(task);
|
|
6441
5835
|
const planningRecovery = readFailedStepPlanningRecovery(this.store, task);
|
|
6442
5836
|
// A stopped rebound Plan can expose planning recovery, never an approval route.
|
|
6443
5837
|
const transition = planningRecovery ? null : task.status === 'awaiting_execution_authorization'
|
|
@@ -6454,49 +5848,44 @@ export class WorkflowService {
|
|
|
6454
5848
|
const correctiveDerivedAuthority = transition && correctiveDerivedActor
|
|
6455
5849
|
? this.currentCorrectiveDerivedTaskAuthority(projectId, task, correctiveDerivedActor)
|
|
6456
5850
|
: null;
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
if (planRiskAudit) {
|
|
6460
|
-
try {
|
|
6461
|
-
guardedCorrectivePosture = findCurrentGuardedRemediationPosture(this.store, task, planRiskAudit);
|
|
6462
|
-
}
|
|
6463
|
-
catch (error) {
|
|
6464
|
-
remediationModeRecovery = findRemediationModeRecoveryCandidate(this.store, task, { authorityMode: 'observe' });
|
|
6465
|
-
if (!remediationModeRecovery)
|
|
6466
|
-
throw error;
|
|
6467
|
-
}
|
|
6468
|
-
}
|
|
6469
|
-
let catalogCorrectiveReplanPosture = guardedCorrectivePosture?.posture === 'replan-required'
|
|
6470
|
-
? guardedCorrectivePosture
|
|
5851
|
+
const guardedCorrectivePosture = planRiskAudit
|
|
5852
|
+
? findCurrentGuardedRemediationPosture(this.store, task, planRiskAudit)
|
|
6471
5853
|
: null;
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
catch (error) {
|
|
6477
|
-
remediationModeRecovery = findRemediationModeRecoveryCandidate(this.store, task, { authorityMode: 'observe' });
|
|
6478
|
-
if (!remediationModeRecovery)
|
|
6479
|
-
throw error;
|
|
6480
|
-
}
|
|
5854
|
+
let catalogCorrectiveReplanPosture = guardedCorrectivePosture?.posture === 'replan-required'
|
|
5855
|
+
? guardedCorrectivePosture : null;
|
|
5856
|
+
if (planRiskAudit && guardedCorrectivePosture === null) {
|
|
5857
|
+
catalogCorrectiveReplanPosture = identifyCorrectiveReplanPosture(task, planRiskAudit.reviewRequiredStepIds, readCorrectiveDecisionEvents(this.store, task), readRemediationEvents(this.store, task));
|
|
6481
5858
|
}
|
|
6482
5859
|
const next = this.taskGitNavigationOverride(task) ?? nextForTask(task);
|
|
6483
5860
|
const nextStepId = typeof next.stepId === 'string' ? next.stepId : null;
|
|
6484
5861
|
const rootCauseStepId = nextStepId
|
|
6485
5862
|
?? task.steps.find((candidate) => candidate.status === 'planned')?.id
|
|
6486
5863
|
?? null;
|
|
5864
|
+
const hasDirtyFiles = changedFiles(repositoryRoot).length > 0;
|
|
5865
|
+
const nextStartPhase = stepStartPhase(task.steps.find(step => step.id === nextStepId)?.status, hasDirtyFiles);
|
|
5866
|
+
const rootCauseStartPhase = stepStartPhase(task.steps.find(step => step.id === rootCauseStepId)?.status, hasDirtyFiles);
|
|
6487
5867
|
const downstreamProofCarryover = next.action === 'task run'
|
|
6488
5868
|
&& nextStepId
|
|
6489
|
-
&&
|
|
5869
|
+
&& nextStartPhase === 'planned-dirty'
|
|
6490
5870
|
? assessDownstreamProofCarryover(this.store, repositoryRoot, task, nextStepId)
|
|
6491
5871
|
: null;
|
|
6492
5872
|
const planIntegrityDirtyCarryover = next.action === 'task run'
|
|
6493
5873
|
&& nextStepId
|
|
6494
|
-
&&
|
|
5874
|
+
&& nextStartPhase === 'planned-dirty'
|
|
6495
5875
|
? this.assessPostRebindCheckSupportDirtyCarryover(repositoryRoot, task, nextStepId)
|
|
6496
5876
|
: null;
|
|
6497
|
-
|
|
5877
|
+
const navigationDirtyCarryover = next.action === 'task run' && rootCauseStartPhase === 'planned-dirty' && rootCauseStepId
|
|
5878
|
+
? assessNavigationDirtyCarryover(this.store, repositoryRoot, task, rootCauseStepId)
|
|
5879
|
+
: null;
|
|
5880
|
+
const navigationDirtyOverlay = navigationDirtyCarryover?.applicable
|
|
5881
|
+
? navigationDirtyCarryover.eligible
|
|
5882
|
+
? { navigationDirtyCarryover: { state: 'validated', stepId: rootCauseStepId, dirtyFiles: navigationDirtyCarryover.compatibility?.dirtyFiles, dirtyWorktreeHash: navigationDirtyCarryover.compatibility?.dirtyWorktreeHash } }
|
|
5883
|
+
: { action: 'doctor', blockedAction: 'task run', navigationDirtyCarryover: { state: 'blocked', stepId: rootCauseStepId, blockers: navigationDirtyCarryover.blockers } }
|
|
5884
|
+
: {};
|
|
5885
|
+
let rootCauseReplanDirtyCarryover = !navigationDirtyCarryover?.applicable && ['task run', 'task handoff-prepare', 'task claim'].includes(String(next.action))
|
|
6498
5886
|
&& rootCauseStepId
|
|
6499
|
-
|
|
5887
|
+
// Match runStep: historical carryover admits a replacement start, never a failed retry.
|
|
5888
|
+
&& rootCauseStartPhase === 'planned-dirty'
|
|
6500
5889
|
? this.assessCurrentRootCauseReplanDirtyCarryover(repositoryRoot, task, rootCauseStepId)
|
|
6501
5890
|
: null;
|
|
6502
5891
|
if (rootCauseReplanDirtyCarryover?.eligible) {
|
|
@@ -6562,14 +5951,14 @@ export class WorkflowService {
|
|
|
6562
5951
|
}
|
|
6563
5952
|
const handoffPosture = readTaskC1Posture(this.store, task);
|
|
6564
5953
|
const failedStep = task.steps.find((step) => step.status === 'failed') ?? null;
|
|
6565
|
-
const failedStepRemediations = failedStep
|
|
5954
|
+
const failedStepRemediations = failedStep
|
|
6566
5955
|
? readRemediationEvents(this.store, task).filter((event) => event.stepId === failedStep.id)
|
|
6567
5956
|
: [];
|
|
6568
5957
|
const failedStepReviewRouting = failedStep
|
|
6569
5958
|
? latestFailedStepReviewRouting(this.store, task, failedStep.id)
|
|
6570
5959
|
: null;
|
|
6571
|
-
const planIntegrityAssessment =
|
|
6572
|
-
?
|
|
5960
|
+
const planIntegrityAssessment = planRiskAudit && failedStep
|
|
5961
|
+
? this.assessPlanIntegrityRecoveryAdmission(repositoryRoot, task, failedStep.id)
|
|
6573
5962
|
: null;
|
|
6574
5963
|
const downstreamProofAssessment = assessDownstreamProofRecovery(this.store, repositoryRoot, task);
|
|
6575
5964
|
const strictStepReviewStep = planRiskAudit
|
|
@@ -6628,12 +6017,13 @@ export class WorkflowService {
|
|
|
6628
6017
|
: {};
|
|
6629
6018
|
const correctiveReplanOverlay = (posture) => {
|
|
6630
6019
|
const evaluation = evaluateTaskCorrectiveReplanAvailability(task, posture);
|
|
6020
|
+
const replanDelegations = this.delegatedApprovalOptions(task.projectId, 'task.execution_authorize', task);
|
|
6631
6021
|
const writerLease = inspectRawWriterLease(this.store.lockRoot(task.projectId), task.id, this.store.root);
|
|
6632
6022
|
const command = handoffPosture.state === 'claimed'
|
|
6633
6023
|
? 'task corrective-yield'
|
|
6634
6024
|
: writerLease
|
|
6635
6025
|
? 'doctor'
|
|
6636
|
-
: 'task corrective-replan-prepare';
|
|
6026
|
+
: replanDelegations.length ? 'task corrective-replan-authorize' : 'task corrective-replan-prepare';
|
|
6637
6027
|
if (evaluation.allowed && handoffPosture.state === 'none' && writerLease) {
|
|
6638
6028
|
const navigation = correctiveReplanNavigation(evaluation);
|
|
6639
6029
|
return {
|
|
@@ -6664,16 +6054,19 @@ export class WorkflowService {
|
|
|
6664
6054
|
return evaluation.allowed
|
|
6665
6055
|
? {
|
|
6666
6056
|
action: command,
|
|
6057
|
+
...(replanDelegations.length ? { delegatedApprovalOptions: replanDelegations, correctiveReplanAuthority: { mode: 'delegated', candidateField: 'delegation', actorHash: 'sha256-utf8-grant-delegate', principalHash: 'sha256-utf8-grant-principal', grantTransition: 'task.execution_authorize' } } : {}),
|
|
6667
6058
|
blockedAction: 'task plan-set',
|
|
6668
6059
|
stepId: posture.stepId,
|
|
6669
|
-
navigation: { ...correctiveReplanNavigation(evaluation), command },
|
|
6060
|
+
navigation: { ...correctiveReplanNavigation(evaluation), kind: replanDelegations.length ? 'executable' : 'human-gate', command },
|
|
6670
6061
|
correctiveDecisionGate: {
|
|
6671
6062
|
attemptCount: posture.attemptCount,
|
|
6672
6063
|
decision: posture.correctiveDecision?.decision ?? null,
|
|
6673
6064
|
decisionEventId: posture.correctiveDecision?.eventId ?? null,
|
|
6674
6065
|
requiredAction: handoffPosture.state === 'claimed'
|
|
6675
6066
|
? 'Yield the exact C1 claim and writer lease before preparing replacement authority.'
|
|
6676
|
-
:
|
|
6067
|
+
: replanDelegations.length
|
|
6068
|
+
? 'Prepare an independently validated same-scope candidate with the eligible grant and actor; authorize it without Human confirmation, then execute its exact delegated receipt.'
|
|
6069
|
+
: 'No eligible execution grant: request issuance/expansion of grant for delegate mode; the explicit Human preparation route remains available outside delegate mode.',
|
|
6677
6070
|
},
|
|
6678
6071
|
}
|
|
6679
6072
|
: {
|
|
@@ -6727,28 +6120,10 @@ export class WorkflowService {
|
|
|
6727
6120
|
},
|
|
6728
6121
|
};
|
|
6729
6122
|
}
|
|
6730
|
-
if (
|
|
6731
|
-
guardedCorrectiveOverlay = {
|
|
6732
|
-
action: 'task remediation-mode-recover',
|
|
6733
|
-
stepId: remediationModeRecovery.remediation.stepId,
|
|
6734
|
-
remediationModeRecovery: {
|
|
6735
|
-
state: 'required',
|
|
6736
|
-
remediationEventId: remediationModeRecovery.remediation.eventId,
|
|
6737
|
-
remediationEventHash: remediationModeRecovery.remediation.eventHash,
|
|
6738
|
-
correctiveDecisionEventId: remediationModeRecovery.decision.eventId,
|
|
6739
|
-
correctiveDecisionRecoveryEventId: remediationModeRecovery.correctiveRecovery.eventId,
|
|
6740
|
-
attemptOrdinal: remediationModeRecovery.remediation.attemptOrdinal,
|
|
6741
|
-
fromMode: 'ordinary',
|
|
6742
|
-
toMode: 'corrective',
|
|
6743
|
-
requiredActor: remediationModeRecovery.actor,
|
|
6744
|
-
},
|
|
6745
|
-
};
|
|
6746
|
-
}
|
|
6747
|
-
if (!remediationModeRecovery && guardedCorrectivePosture === null && catalogCorrectiveReplanPosture) {
|
|
6123
|
+
if (guardedCorrectivePosture === null && catalogCorrectiveReplanPosture) {
|
|
6748
6124
|
guardedCorrectiveOverlay = correctiveReplanOverlay(catalogCorrectiveReplanPosture);
|
|
6749
6125
|
}
|
|
6750
|
-
if (
|
|
6751
|
-
&& guardedCorrectivePosture
|
|
6126
|
+
if (guardedCorrectivePosture
|
|
6752
6127
|
&& (!strictStepReviewStep || guardedCorrectivePosture.posture === 'replan-required')) {
|
|
6753
6128
|
switch (guardedCorrectivePosture.posture) {
|
|
6754
6129
|
case 'decision-required':
|
|
@@ -6929,62 +6304,42 @@ export class WorkflowService {
|
|
|
6929
6304
|
retainedImplementation: 'journal-recoverable-1-to-1',
|
|
6930
6305
|
diagnosticCode: 'STRUCTURAL_REPLACEMENT_REQUIRED',
|
|
6931
6306
|
structuralReplacementAvailable: false,
|
|
6307
|
+
productDecisionRequired: true,
|
|
6308
|
+
executionAvailable: false,
|
|
6309
|
+
decisionEffect: 'No decision authorizes structural replacement in this package.',
|
|
6310
|
+
productDecisionOptions: replacementMilestone && !['accepted', 'cancelled'].includes(replacementMilestone.status)
|
|
6311
|
+
? [{
|
|
6312
|
+
command: 'milestone cancel',
|
|
6313
|
+
milestoneId: replacementMilestone.id,
|
|
6314
|
+
expectedRevision: replacementMilestone.revision,
|
|
6315
|
+
reasonRequired: true,
|
|
6316
|
+
effect: 'Cancels the whole Milestone; does not create or authorize a replacement Task.',
|
|
6317
|
+
}]
|
|
6318
|
+
: [],
|
|
6932
6319
|
incidentDependencies,
|
|
6933
6320
|
discoveryCandidates: replacementDiscoveries.map((discovery) => ({
|
|
6934
6321
|
id: discovery.id,
|
|
6935
6322
|
revision: discovery.revision,
|
|
6936
6323
|
goal: discovery.goal,
|
|
6937
6324
|
})),
|
|
6938
|
-
requiredAction: '
|
|
6325
|
+
requiredAction: 'The owner must decide whether to stop this Task or revise the Milestone scope through supported transitions; structural replacement is unavailable.',
|
|
6939
6326
|
},
|
|
6940
6327
|
};
|
|
6941
6328
|
}
|
|
6942
6329
|
break;
|
|
6943
6330
|
case 'stop-escalate':
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
delegatedApprovalAllowed: false,
|
|
6957
|
-
},
|
|
6958
|
-
stopOverride: {
|
|
6959
|
-
state: 'eligible',
|
|
6960
|
-
policy: 'beta11-attempt4-stop-compatibility-v1',
|
|
6961
|
-
prepareCommand: 'task stop-override-prepare',
|
|
6962
|
-
applyCommand: 'task stop-override-apply',
|
|
6963
|
-
expectedRevision: task.revision,
|
|
6964
|
-
appendOnly: true,
|
|
6965
|
-
preservesOriginalDecision: true,
|
|
6966
|
-
requiredAction: 'Prepare with an explicit human actor and reason, then apply the exact confirmation code in a later user turn.',
|
|
6967
|
-
},
|
|
6968
|
-
};
|
|
6969
|
-
}
|
|
6970
|
-
catch (error) {
|
|
6971
|
-
guardedCorrectiveOverlay = {
|
|
6972
|
-
action: 'doctor',
|
|
6973
|
-
blockedAction: 'task stop-override-prepare',
|
|
6974
|
-
stepId: guardedCorrectivePosture.stepId,
|
|
6975
|
-
correctiveDecisionGate: {
|
|
6976
|
-
attemptCount: guardedCorrectivePosture.attemptCount,
|
|
6977
|
-
decision: guardedCorrectivePosture.correctiveDecision?.decision ?? null,
|
|
6978
|
-
decisionEventId: guardedCorrectivePosture.correctiveDecision?.eventId ?? null,
|
|
6979
|
-
humanApprovalRequired: true,
|
|
6980
|
-
},
|
|
6981
|
-
stopOverride: {
|
|
6982
|
-
state: 'blocked',
|
|
6983
|
-
policy: 'beta11-attempt4-stop-compatibility-v1',
|
|
6984
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
6985
|
-
},
|
|
6986
|
-
};
|
|
6987
|
-
}
|
|
6331
|
+
guardedCorrectiveOverlay = {
|
|
6332
|
+
action: 'doctor',
|
|
6333
|
+
blockedAction: 'task run',
|
|
6334
|
+
stepId: guardedCorrectivePosture.stepId,
|
|
6335
|
+
correctiveDecisionGate: {
|
|
6336
|
+
attemptCount: guardedCorrectivePosture.attemptCount,
|
|
6337
|
+
decision: guardedCorrectivePosture.correctiveDecision?.decision ?? null,
|
|
6338
|
+
decisionEventId: guardedCorrectivePosture.correctiveDecision?.eventId ?? null,
|
|
6339
|
+
humanApprovalRequired: true,
|
|
6340
|
+
},
|
|
6341
|
+
reason: 'The recorded stop decision blocks execution; legacy stop-override creation is no longer supported.',
|
|
6342
|
+
};
|
|
6988
6343
|
break;
|
|
6989
6344
|
default:
|
|
6990
6345
|
break;
|
|
@@ -7135,6 +6490,7 @@ export class WorkflowService {
|
|
|
7135
6490
|
}
|
|
7136
6491
|
: {}),
|
|
7137
6492
|
...mechanicalFeasibilityOverlay,
|
|
6493
|
+
...navigationDirtyOverlay,
|
|
7138
6494
|
...(downstreamProofCarryover?.applicable
|
|
7139
6495
|
? downstreamProofCarryover.eligible
|
|
7140
6496
|
? {
|
|
@@ -7203,10 +6559,11 @@ export class WorkflowService {
|
|
|
7203
6559
|
rootCauseReplanDirtyCarryover: {
|
|
7204
6560
|
state: 'awaiting-c1',
|
|
7205
6561
|
stepId: rootCauseStepId,
|
|
7206
|
-
requiredActor: defaultTaskWorkerActor(task.id),
|
|
6562
|
+
requiredActor: rootCauseReplanDirtyCarryover.requiredActor ?? defaultTaskWorkerActor(task.id),
|
|
7207
6563
|
},
|
|
7208
6564
|
}
|
|
7209
6565
|
: handoffPosture.state === 'pending'
|
|
6566
|
+
&& (!rootCauseReplanDirtyCarryover.requiredActor || handoffPosture.targetActor === rootCauseReplanDirtyCarryover.requiredActor)
|
|
7210
6567
|
? {}
|
|
7211
6568
|
: {
|
|
7212
6569
|
action: 'doctor',
|
|
@@ -7247,15 +6604,17 @@ export class WorkflowService {
|
|
|
7247
6604
|
// fix review. Advertise the existing append-only decision route without turning
|
|
7248
6605
|
// every ordinary repair into a mandatory replan or rewriting that review.
|
|
7249
6606
|
const correctiveDecisionOrdinal = failedStepRemediations.length + 1;
|
|
6607
|
+
const firstFailurePlanningObstruction = planningRecovery && failedStep
|
|
6608
|
+
? firstFailurePlanningObstructionEvidence(this.store, task, failedStep.id) : null;
|
|
7250
6609
|
const optionalCorrectiveDecision = (result.action === 'task run' || planningRecovery !== null)
|
|
7251
6610
|
&& (['needs_fix', 'ready'].includes(task.status) || planningRecovery !== null)
|
|
7252
6611
|
&& failedStep && task.planHash && planRiskAudit
|
|
7253
6612
|
&& planRiskAudit.reviewRequiredStepIds.includes(failedStep.id)
|
|
7254
6613
|
&& !task.steps.some(step => step.status === 'in_progress')
|
|
7255
|
-
&& failedStepRemediations.length >= 2
|
|
6614
|
+
&& (failedStepRemediations.length >= 2 || firstFailurePlanningObstruction !== null)
|
|
7256
6615
|
&& handoffPosture.state !== 'pending'
|
|
7257
6616
|
&& !readCorrectiveDecisionEvents(this.store, task).some(event => event.stepId === failedStep.id && event.triggeringAttemptCount === correctiveDecisionOrdinal)
|
|
7258
|
-
&&
|
|
6617
|
+
&& correctiveDecisionCheckoutEligible(this.store, repositoryRoot, task, failedStep.id)
|
|
7259
6618
|
? {
|
|
7260
6619
|
command: 'task corrective-decision',
|
|
7261
6620
|
state: 'eligible',
|
|
@@ -7271,12 +6630,13 @@ export class WorkflowService {
|
|
|
7271
6630
|
recommendedAuditor: defaultCorrectiveAuditorActor(task.id),
|
|
7272
6631
|
auditorMustDifferFromLatestStrictReviewer: true,
|
|
7273
6632
|
requiredActor: handoffPosture.state === 'claimed' ? handoffPosture.claimant : null,
|
|
7274
|
-
decisions:
|
|
7275
|
-
: ['
|
|
6633
|
+
decisions: firstFailurePlanningObstruction ? ['replan-required']
|
|
6634
|
+
: planningRecovery ? ['replan-required', 'split-required', 'stop-escalate']
|
|
6635
|
+
: ['continue-fix', 'replan-required', 'split-required', 'stop-escalate'],
|
|
7276
6636
|
authority: 'none',
|
|
7277
6637
|
preservesOriginalReview: true,
|
|
7278
6638
|
humanApprovalRequiredForDecision: false,
|
|
7279
|
-
|
|
6639
|
+
replanRequiresBoundAuthorization: true,
|
|
7280
6640
|
requiredAction: 'Only for newly assessed evidence: obtain an independent corrective audit, refresh next and existing writer authority, then record the decision. This option does not authorize Plan replacement or extra writes.',
|
|
7281
6641
|
}
|
|
7282
6642
|
: null;
|
|
@@ -7290,7 +6650,8 @@ export class WorkflowService {
|
|
|
7290
6650
|
const rootCauseAllowsPendingClaim = !rootCauseReplanDirtyCarryover?.applicable
|
|
7291
6651
|
|| (rootCauseReplanDirtyCarryover.blockers.length === 1
|
|
7292
6652
|
&& rootCauseReplanDirtyCarryover.blockers[0]?.startsWith('Corrective carryover requires the retained claimed C1'));
|
|
7293
|
-
if (handoffPosture.state === 'pending' && rootCauseAllowsPendingClaim
|
|
6653
|
+
if (handoffPosture.state === 'pending' && rootCauseAllowsPendingClaim
|
|
6654
|
+
&& (!rootCauseReplanDirtyCarryover?.requiredActor || handoffPosture.targetActor === rootCauseReplanDirtyCarryover.requiredActor)) {
|
|
7294
6655
|
return this.withTaskNavigationContracts(task, {
|
|
7295
6656
|
...resultWithCorrectiveOption,
|
|
7296
6657
|
action: 'task claim',
|
|
@@ -7323,28 +6684,66 @@ export class WorkflowService {
|
|
|
7323
6684
|
},
|
|
7324
6685
|
};
|
|
7325
6686
|
}
|
|
7326
|
-
|
|
6687
|
+
registeredPlanningContextTransport(task, repositoryRoot) {
|
|
6688
|
+
const failedTransport = repositoryRoot
|
|
6689
|
+
&& task.status === 'awaiting_execution_authorization'
|
|
6690
|
+
&& !task.steps.some(step => step.status === 'in_progress')
|
|
6691
|
+
&& !readPlanRiskAuditEvents(this.store, task).some(event => event.planHash === task.planHash && event.decision !== 'stop-escalate')
|
|
6692
|
+
&& readCorrectiveDecisionEvents(this.store, task).length === 0
|
|
6693
|
+
? task.dependencyProvenanceRecoveries?.find(record => record.failedCheckpoint
|
|
6694
|
+
&& task.steps.some(step => step.id === record.failedCheckpoint.stepId && step.status === 'failed'))
|
|
6695
|
+
: null;
|
|
6696
|
+
if (failedTransport?.failedCheckpoint && repositoryRoot) {
|
|
6697
|
+
const binding = failedTransport.failedCheckpoint;
|
|
6698
|
+
// Historical transports remain readable after later legitimate progress. Validate
|
|
6699
|
+
// their artifact first, but require current dirty bytes only for this exact checkpoint.
|
|
6700
|
+
readVerifiedPlanArtifact(this.store.taskRoot(task.projectId, task.id), binding.sourcePlanHash, task.planHash);
|
|
6701
|
+
if (binding.sourcePlanHash !== task.planHash)
|
|
6702
|
+
return null;
|
|
6703
|
+
if (readFailedCheckpointDirtyTransport(this.store, repositoryRoot, task, binding.stepId, binding.sourceHead, binding.sourceDirtyHash))
|
|
6704
|
+
return binding;
|
|
6705
|
+
}
|
|
6706
|
+
return null;
|
|
6707
|
+
}
|
|
6708
|
+
taskContextChangeBlocker(task, operation, repositoryRoot) {
|
|
6709
|
+
const blocked = (error) => ({ error, activeStep: false });
|
|
7327
6710
|
if (readCurrentPreExecutionReplan(this.store, task)) {
|
|
7328
|
-
|
|
6711
|
+
return blocked(new WorkflowError('TRANSITION_BLOCKED', `Task ${task.id} has a pending pre-execution replan; replace the implementation Plan before changing context.`, { taskId: task.id, requiredAction: 'task plan-set' }));
|
|
7329
6712
|
}
|
|
7330
6713
|
if (task.status === 'needs_fix' && !task.steps.some((step) => step.status === 'failed')) {
|
|
7331
|
-
|
|
6714
|
+
return blocked(new WorkflowError('TRANSITION_BLOCKED', `Task ${task.id} requires a replacement Step Plan; Project Knowledge cannot rebind or refresh the stale Plan.`, {
|
|
7332
6715
|
taskId: task.id,
|
|
7333
6716
|
requiredAction: 'Run task plan-set with the full unfinished remainder before requesting authorization.',
|
|
7334
|
-
});
|
|
6717
|
+
}));
|
|
7335
6718
|
}
|
|
6719
|
+
if (repositoryRoot && this.registeredPlanningContextTransport(task, repositoryRoot))
|
|
6720
|
+
return null;
|
|
7336
6721
|
const posture = this.taskTerminalCorrectivePosture(task);
|
|
7337
|
-
if (
|
|
7338
|
-
return;
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
}
|
|
6722
|
+
if (posture) {
|
|
6723
|
+
return blocked(new WorkflowError('TRANSITION_BLOCKED', `Task ${task.id} has terminal corrective decision ${posture.posture}; Project Knowledge cannot rebind or refresh this Task.`, {
|
|
6724
|
+
taskId: task.id,
|
|
6725
|
+
stepId: posture.stepId,
|
|
6726
|
+
decision: posture.posture,
|
|
6727
|
+
decisionEventId: posture.correctiveDecision?.eventId ?? null,
|
|
6728
|
+
requiredAction: posture.posture === 'split-required'
|
|
6729
|
+
? 'This Task cannot resume after split-required; no executable replacement route is available. Request a human decision without changing retained state.'
|
|
6730
|
+
: 'Escalate outside this Task; stop-escalate cannot be resumed or rebound.',
|
|
6731
|
+
}));
|
|
6732
|
+
}
|
|
6733
|
+
if (!taskCanRebindKnowledge(task)) {
|
|
6734
|
+
return blocked(new WorkflowError('TRANSITION_BLOCKED', operation === 'rebind'
|
|
6735
|
+
? `Cannot rebind Task Plan knowledge while Task is ${task.status}.`
|
|
6736
|
+
: `Cannot refresh Task context while Task is ${task.status}.`));
|
|
6737
|
+
}
|
|
6738
|
+
const activeStep = task.steps.find(step => step.status === 'in_progress');
|
|
6739
|
+
if (activeStep)
|
|
6740
|
+
return {
|
|
6741
|
+
activeStep: true,
|
|
6742
|
+
error: new WorkflowError('TRANSITION_BLOCKED', operation === 'rebind'
|
|
6743
|
+
? `Cannot rebind Task Plan knowledge while ${activeStep.id} is in progress.`
|
|
6744
|
+
: `Cannot refresh Task context while ${activeStep.id} is in progress.`),
|
|
6745
|
+
};
|
|
6746
|
+
return null;
|
|
7348
6747
|
}
|
|
7349
6748
|
stopOverrideContextRebindDecisionId(task, stepId, repositoryRoot) {
|
|
7350
6749
|
return readValidatedStopOverrideContextRebindIfApplicable(this.store, task, stepId, repositoryRoot)?.stopDecision.eventId ?? null;
|
|
@@ -7368,6 +6767,14 @@ export class WorkflowService {
|
|
|
7368
6767
|
taskTerminalCorrectivePosture(task) {
|
|
7369
6768
|
if (!task.planHash)
|
|
7370
6769
|
return null;
|
|
6770
|
+
if (task.status === 'awaiting_execution_authorization' && task.authorizations.length === 0
|
|
6771
|
+
&& task.baseCommit === null && task.taskBranch === null && task.workspaceOwner === null
|
|
6772
|
+
&& task.mergedCommit === null && task.systemCommits.length === 0
|
|
6773
|
+
&& task.resultHash === null && task.evidenceHash === null && task.review.status === 'pending'
|
|
6774
|
+
&& task.steps.every((step) => (step.status === 'planned' || (step.status === 'blocked' && step.dependencies.length > 0))
|
|
6775
|
+
&& step.evidence === null && !step.failurePause)
|
|
6776
|
+
&& readPlanRiskAuditEvents(this.store, task).length === 0)
|
|
6777
|
+
return null;
|
|
7371
6778
|
const planRiskAudit = readCurrentPlanningObstructionAudit(this.store, task);
|
|
7372
6779
|
if (!planRiskAudit)
|
|
7373
6780
|
return null;
|
|
@@ -7376,42 +6783,6 @@ export class WorkflowService {
|
|
|
7376
6783
|
? posture
|
|
7377
6784
|
: null;
|
|
7378
6785
|
}
|
|
7379
|
-
assertStopOverrideRepositoryBoundary(identity, task, observedLeases) {
|
|
7380
|
-
const blockers = [];
|
|
7381
|
-
const dirty = changedFiles(identity.repositoryRoot);
|
|
7382
|
-
if (dirty.length > 0)
|
|
7383
|
-
blockers.push('Repository checkout must be clean.');
|
|
7384
|
-
if (task.workspaceOwner !== 'local')
|
|
7385
|
-
blockers.push('Stop override is limited to a local Task workspace.');
|
|
7386
|
-
if (!task.taskBranch || currentBranch(identity.repositoryRoot) !== task.taskBranch) {
|
|
7387
|
-
blockers.push('The current branch must be the recorded Task branch.');
|
|
7388
|
-
}
|
|
7389
|
-
if (task.steps.some((step) => step.status === 'in_progress'))
|
|
7390
|
-
blockers.push('No Step may be in progress.');
|
|
7391
|
-
const leases = observedLeases
|
|
7392
|
-
?? new WriterLockManager(this.store.lockRoot(identity.projectId), this.now, this.store.root).list();
|
|
7393
|
-
if (leases.some((lease) => lease.entityId === task.id))
|
|
7394
|
-
blockers.push('No active or stale Task writer lease may exist.');
|
|
7395
|
-
const projectRoot = this.store.projectRoot(identity.projectId);
|
|
7396
|
-
const replanTransactions = inspectCorrectiveReplanTaskTransactions(projectRoot, this.store.root);
|
|
7397
|
-
const yieldTransactions = inspectCorrectiveYieldTaskTransactions(projectRoot, this.store.root);
|
|
7398
|
-
if (replanTransactions.pending.some((transaction) => transaction.taskId === task.id)
|
|
7399
|
-
|| replanTransactions.corrupt.some((transaction) => transaction.taskId === task.id)
|
|
7400
|
-
|| yieldTransactions.pending.some((transaction) => transaction.taskId === task.id)
|
|
7401
|
-
|| yieldTransactions.corrupt.some((transaction) => transaction.taskId === task.id)) {
|
|
7402
|
-
blockers.push('No pending or corrupt Task transaction may exist.');
|
|
7403
|
-
}
|
|
7404
|
-
if (listCoreTaskOperationMutexes(this.store.lockRoot(identity.projectId), this.store.root)
|
|
7405
|
-
.some((operation) => operation.entityId === task.id)) {
|
|
7406
|
-
blockers.push('No Core Task operation may exist.');
|
|
7407
|
-
}
|
|
7408
|
-
if (blockers.length > 0) {
|
|
7409
|
-
throw new WorkflowError('TRANSITION_BLOCKED', 'Stop override repository preconditions are not satisfied.', {
|
|
7410
|
-
taskId: task.id,
|
|
7411
|
-
blockers,
|
|
7412
|
-
});
|
|
7413
|
-
}
|
|
7414
|
-
}
|
|
7415
6786
|
delegatedApprovalOptions(projectId, transition, target) {
|
|
7416
6787
|
return this.listDelegations(projectId)
|
|
7417
6788
|
.filter((grant) => {
|
|
@@ -7434,7 +6805,21 @@ export class WorkflowService {
|
|
|
7434
6805
|
policyHash: grant.policyHash,
|
|
7435
6806
|
}));
|
|
7436
6807
|
}
|
|
7437
|
-
delegatedContextRefreshOptions(projectId, task, knowledgeMap, mode) {
|
|
6808
|
+
delegatedContextRefreshOptions(projectId, task, knowledgeMap, mode, repositoryRoot) {
|
|
6809
|
+
let planningRecovery;
|
|
6810
|
+
try {
|
|
6811
|
+
planningRecovery = this.registeredPlanningContextTransport(task, repositoryRoot)
|
|
6812
|
+
?? readFailedStepPlanningRecovery(this.store, task);
|
|
6813
|
+
if (!planningRecovery)
|
|
6814
|
+
readCurrentPlanRiskAudit(this.store, task);
|
|
6815
|
+
}
|
|
6816
|
+
catch (error) {
|
|
6817
|
+
if (error instanceof WorkflowError && error.code === 'TRANSITION_BLOCKED')
|
|
6818
|
+
return [];
|
|
6819
|
+
throw error;
|
|
6820
|
+
}
|
|
6821
|
+
if (planningRecovery && mode !== 'delegated-content-only')
|
|
6822
|
+
return [];
|
|
7438
6823
|
return this.listDelegations(projectId)
|
|
7439
6824
|
.filter((grant) => {
|
|
7440
6825
|
try {
|
|
@@ -7454,27 +6839,24 @@ export class WorkflowService {
|
|
|
7454
6839
|
grantId: grant.id,
|
|
7455
6840
|
principal: grant.principal,
|
|
7456
6841
|
delegate: grant.delegate,
|
|
7457
|
-
transition: 'task.execution_authorize',
|
|
7458
|
-
transitions: ['project_memory.approve', 'task.execution_authorize'],
|
|
6842
|
+
transition: planningRecovery ? 'project_memory.approve' : 'task.execution_authorize',
|
|
6843
|
+
transitions: planningRecovery ? ['project_memory.approve'] : ['project_memory.approve', 'task.execution_authorize'],
|
|
7459
6844
|
scope: grant.scope,
|
|
7460
6845
|
expiresAt: grant.expiresAt,
|
|
7461
6846
|
policyHash: grant.policyHash,
|
|
7462
6847
|
}));
|
|
7463
6848
|
}
|
|
7464
|
-
findDelegatedContextRefreshOwner(projectId, tasks, milestonesById, approved, inspected) {
|
|
6849
|
+
findDelegatedContextRefreshOwner(projectId, tasks, milestonesById, approved, inspected, repositoryRoot) {
|
|
7465
6850
|
const tasksById = new Map(tasks.map((task) => [task.id, task]));
|
|
7466
6851
|
for (const task of tasks) {
|
|
7467
6852
|
if (!taskIsActionableFromRepositoryNext(task, milestonesById, tasksById)
|
|
7468
|
-
|| !task.planHash
|
|
7469
|
-
|| !taskCanRebindKnowledge(task)
|
|
7470
|
-
|| (task.status === 'needs_fix' && !task.steps.some((step) => step.status === 'failed'))
|
|
7471
|
-
|| this.taskHasTerminalCorrectiveDecision(task))
|
|
6853
|
+
|| !task.planHash)
|
|
7472
6854
|
continue;
|
|
7473
6855
|
const assessment = assessDelegatedKnowledgeRefresh(approved, inspected, task, this.store.hashArtifact(this.store.taskRoot(projectId, task.id), 'plan.md'));
|
|
7474
6856
|
if (!assessment.eligible)
|
|
7475
6857
|
continue;
|
|
7476
|
-
const options = this.delegatedContextRefreshOptions(projectId, task, inspected, assessment.mode);
|
|
7477
|
-
if (options.length > 0)
|
|
6858
|
+
const options = this.delegatedContextRefreshOptions(projectId, task, inspected, assessment.mode, repositoryRoot);
|
|
6859
|
+
if (options.length > 0 && this.taskContextChangeBlocker(task, 'refresh', repositoryRoot) === null)
|
|
7478
6860
|
return { task, assessment, options };
|
|
7479
6861
|
}
|
|
7480
6862
|
return null;
|
|
@@ -7582,12 +6964,31 @@ export class WorkflowService {
|
|
|
7582
6964
|
: inspectMilestoneWithIntegrity(this.store, projectId, milestone.id, (taskId) => this.store.readTask(projectId, taskId), () => this.store.listTasks(projectId))));
|
|
7583
6965
|
}
|
|
7584
6966
|
taskGitNavigationOverride(task) {
|
|
6967
|
+
if (task.pendingBaseSyncValidation !== undefined) {
|
|
6968
|
+
const assessment = assessPendingBaseSyncValidation(task);
|
|
6969
|
+
return { scope: 'task', id: task.id, status: task.status, revision: task.revision,
|
|
6970
|
+
action: assessment.reason ? 'blocked' : 'task sync-base',
|
|
6971
|
+
pendingBaseSyncValidation: task.pendingBaseSyncValidation,
|
|
6972
|
+
...(assessment.reason ? { dirtyFiles: assessment.dirtyFiles,
|
|
6973
|
+
navigation: { kind: 'wait', reason: 'base-sync-validation-snapshot', waitFor: assessment.reason } } : {}),
|
|
6974
|
+
};
|
|
6975
|
+
}
|
|
7585
6976
|
if (task.status !== 'merging' || task.workspaceOwner !== 'local' || !task.baseCommit || !task.taskBranch) {
|
|
7586
6977
|
return null;
|
|
7587
6978
|
}
|
|
7588
6979
|
const currentBase = runGit(task.repositoryRoot, ['rev-parse', task.baseBranch]);
|
|
7589
6980
|
if (currentBase === task.baseCommit)
|
|
7590
6981
|
return null;
|
|
6982
|
+
const taskHead = runGit(task.repositoryRoot, ['rev-parse', task.taskBranch]);
|
|
6983
|
+
const conflict = currentBaseSyncConflict(this.store, task, taskHead, currentBase);
|
|
6984
|
+
if (conflict)
|
|
6985
|
+
return {
|
|
6986
|
+
scope: 'task', id: task.id, status: task.status, revision: task.revision, action: 'blocked',
|
|
6987
|
+
baseSyncConflict: conflict,
|
|
6988
|
+
navigation: { kind: 'wait', reason: 'base-sync-conflict', externalDecisionRequired: true,
|
|
6989
|
+
waitFor: 'The base owner resolves the conflict on the base branch without changing the reviewed Task head.',
|
|
6990
|
+
resume: 'After the base HEAD changes, obtain fresh status and next; synchronization still requires review and acceptance.' },
|
|
6991
|
+
};
|
|
7591
6992
|
return {
|
|
7592
6993
|
scope: 'task',
|
|
7593
6994
|
id: task.id,
|
|
@@ -7601,43 +7002,6 @@ export class WorkflowService {
|
|
|
7601
7002
|
},
|
|
7602
7003
|
};
|
|
7603
7004
|
}
|
|
7604
|
-
carriedPlanIntegrityUpdateCompatibility(snapshot, task) {
|
|
7605
|
-
let boundaryCommit = headCommit(snapshot.identity.repositoryRoot);
|
|
7606
|
-
let dependencyCommitCount = 0;
|
|
7607
|
-
while (!task.systemCommits.includes(boundaryCommit)) {
|
|
7608
|
-
const dependencyCommit = inspectBoundedWorkflowDependencyCommit(snapshot.identity.repositoryRoot, boundaryCommit);
|
|
7609
|
-
if (!dependencyCommit)
|
|
7610
|
-
return null;
|
|
7611
|
-
boundaryCommit = dependencyCommit.parentCommitSha;
|
|
7612
|
-
dependencyCommitCount += 1;
|
|
7613
|
-
}
|
|
7614
|
-
if (dependencyCommitCount === 0)
|
|
7615
|
-
return null;
|
|
7616
|
-
const dirtyFiles = changedFiles(snapshot.identity.repositoryRoot).sort();
|
|
7617
|
-
const candidates = (task.dependencyProvenanceRecoveries ?? []).filter((record) => {
|
|
7618
|
-
const compatibility = record.planIntegrity;
|
|
7619
|
-
if (!compatibility
|
|
7620
|
-
|| record.commitSha !== boundaryCommit
|
|
7621
|
-
|| !task.systemCommits.includes(record.commitSha)
|
|
7622
|
-
|| compatibility.planHash !== task.planHash
|
|
7623
|
-
|| canonicalJsonStringify([...compatibility.dirtyFiles].sort()) !== canonicalJsonStringify(dirtyFiles)
|
|
7624
|
-
|| compatibility.dirtyWorktreeHash !== hashDirtyWorktree(snapshot.identity.repositoryRoot, compatibility.dirtyFiles))
|
|
7625
|
-
return false;
|
|
7626
|
-
const step = task.steps.find((candidate) => candidate.id === compatibility.stepId);
|
|
7627
|
-
if (!step || step.status !== 'failed')
|
|
7628
|
-
return false;
|
|
7629
|
-
const remediationEventIds = readRemediationEvents(this.store, task)
|
|
7630
|
-
.filter((event) => event.stepId === compatibility.stepId)
|
|
7631
|
-
.map((event) => event.eventId);
|
|
7632
|
-
if (canonicalJsonStringify(compatibility.remediationEventIds) !== canonicalJsonStringify(remediationEventIds))
|
|
7633
|
-
return false;
|
|
7634
|
-
return readCorrectiveDecisionEvents(this.store, task).some((event) => event.planHash === task.planHash
|
|
7635
|
-
&& event.stepId === compatibility.stepId
|
|
7636
|
-
&& event.decision === 'replan-required')
|
|
7637
|
-
&& readTaskC1Posture(this.store, task).state === 'none';
|
|
7638
|
-
});
|
|
7639
|
-
return candidates.length === 1 ? structuredClone(candidates[0].planIntegrity) : null;
|
|
7640
|
-
}
|
|
7641
7005
|
postRebindCheckSupportCompatibility(snapshot, task) {
|
|
7642
7006
|
if (task.status !== 'awaiting_execution_authorization'
|
|
7643
7007
|
|| !task.planHash
|
|
@@ -7812,9 +7176,12 @@ export class WorkflowService {
|
|
|
7812
7176
|
blockers,
|
|
7813
7177
|
};
|
|
7814
7178
|
}
|
|
7815
|
-
inspectDependencyProvenanceCandidate(snapshot, task, activeDownstreamProof = null,
|
|
7179
|
+
inspectDependencyProvenanceCandidate(snapshot, task, activeDownstreamProof = null, historicalStepProvenance = null) {
|
|
7816
7180
|
const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
|
|
7817
7181
|
const now = this.now().getTime();
|
|
7182
|
+
const navigationCarryover = !activeDownstreamProof && !historicalStepProvenance && changedFiles(snapshot.identity.repositoryRoot).length > 0
|
|
7183
|
+
? assessNavigationDirtyCarryover(this.store, snapshot.identity.repositoryRoot, task, undefined, 'HEAD^')
|
|
7184
|
+
: null;
|
|
7818
7185
|
return inspectDependencyProvenanceRecovery(snapshot.identity.repositoryRoot, task, {
|
|
7819
7186
|
activeLeaseCount: snapshot.leases.filter((lease) => Date.parse(lease.expiresAt) > now).length,
|
|
7820
7187
|
staleLeaseCount: snapshot.leases.filter((lease) => Date.parse(lease.expiresAt) <= now).length,
|
|
@@ -7832,7 +7199,7 @@ export class WorkflowService {
|
|
|
7832
7199
|
currentBranch: versions.currentBranch,
|
|
7833
7200
|
baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
|
|
7834
7201
|
},
|
|
7835
|
-
}, activeDownstreamProof,
|
|
7202
|
+
}, activeDownstreamProof, historicalStepProvenance, navigationCarryover?.eligible ? navigationCarryover.compatibility : null);
|
|
7836
7203
|
}
|
|
7837
7204
|
assessCurrentRootCauseReplanDirtyCarryover(repositoryRoot, task, stepId, historyHead) {
|
|
7838
7205
|
const recordedRecovery = [...(task.dependencyProvenanceRecoveries ?? [])].reverse().find((record) => record.rootCauseReplan
|
|
@@ -7898,10 +7265,19 @@ export class WorkflowService {
|
|
|
7898
7265
|
}
|
|
7899
7266
|
const candidates = task.steps
|
|
7900
7267
|
.filter((step) => step.status === 'planned' && (!stepId || step.id === stepId))
|
|
7901
|
-
.flatMap((step) =>
|
|
7902
|
-
|
|
7903
|
-
.
|
|
7904
|
-
|
|
7268
|
+
.flatMap((step) => {
|
|
7269
|
+
const failures = readRemediationEvents(this.store, task)
|
|
7270
|
+
.filter((event) => event.stepId === step.id && event.failureKind === 'checks-failed');
|
|
7271
|
+
const terminal = failures.at(-1);
|
|
7272
|
+
if (!terminal)
|
|
7273
|
+
return [];
|
|
7274
|
+
const yields = readTaskHandoffEvents(this.store, task)
|
|
7275
|
+
.filter((event) => matchesTerminalCorrectiveYield(event, terminal, task.id));
|
|
7276
|
+
// Missing native boundary must remain visibly blocked, never fall through to task run.
|
|
7277
|
+
if (yields.length > 1)
|
|
7278
|
+
return [];
|
|
7279
|
+
return [assessRootCauseReplanDirtyCarryover(this.store, repositoryRoot, task, step.id, { historyHead: terminal.failureEvidence.completionCommit })];
|
|
7280
|
+
})
|
|
7905
7281
|
.filter((assessment) => assessment.applicable);
|
|
7906
7282
|
const eligible = candidates.filter((assessment) => assessment.eligible && assessment.compatibility);
|
|
7907
7283
|
if (eligible.length === 1)
|
|
@@ -7910,6 +7286,7 @@ export class WorkflowService {
|
|
|
7910
7286
|
applicable: candidates.length > 0,
|
|
7911
7287
|
eligible: false,
|
|
7912
7288
|
compatibility: null,
|
|
7289
|
+
...(candidates.length === 1 && candidates[0].requiredActor ? { requiredActor: candidates[0].requiredActor } : {}),
|
|
7913
7290
|
blockers: [...new Set([
|
|
7914
7291
|
...candidates.flatMap((assessment) => assessment.blockers),
|
|
7915
7292
|
...(eligible.length > 1 ? ['Corrective carryover history resolved to more than one eligible boundary.'] : []),
|
|
@@ -8885,7 +8262,6 @@ function writerTokenContractForAction(action, claimedC1Posture = false, existing
|
|
|
8885
8262
|
'task review-launch',
|
|
8886
8263
|
'task review-sealed-record',
|
|
8887
8264
|
'task corrective-decision-recover',
|
|
8888
|
-
'task remediation-mode-recover',
|
|
8889
8265
|
'task plan-integrity-recover',
|
|
8890
8266
|
'task corrective-decision',
|
|
8891
8267
|
'task corrective-yield',
|