codex-workflow-v2 2.0.0-beta.12.6 → 2.0.0-beta.12.8

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.
Files changed (35) hide show
  1. package/README.md +21 -3
  2. package/dist/src/alpha6/downstream-proof.d.ts +35 -1
  3. package/dist/src/alpha6/downstream-proof.js +310 -2
  4. package/dist/src/alpha6/downstream-proof.js.map +1 -1
  5. package/dist/src/alpha6/plan-risk.js +2 -2
  6. package/dist/src/alpha6/plan-risk.js.map +1 -1
  7. package/dist/src/cli.js +38 -3
  8. package/dist/src/cli.js.map +1 -1
  9. package/dist/src/contracts.d.ts +30 -1
  10. package/dist/src/dependency-provenance.d.ts +2 -2
  11. package/dist/src/dependency-provenance.js +26 -13
  12. package/dist/src/dependency-provenance.js.map +1 -1
  13. package/dist/src/version.d.ts +1 -1
  14. package/dist/src/version.js +1 -1
  15. package/dist/src/workflow.d.ts +9 -2
  16. package/dist/src/workflow.js +380 -33
  17. package/dist/src/workflow.js.map +1 -1
  18. package/docs/autonomy-guardrails.md +7 -0
  19. package/docs/development-flow.md +9 -0
  20. package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
  21. package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
  22. package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
  23. package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +25 -5
  24. package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +22 -7
  25. package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +33 -25
  26. package/docs/release.md +9 -1
  27. package/docs/updating-existing-project.md +24 -0
  28. package/docs/validation-report.md +88 -96
  29. package/package.json +2 -1
  30. package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +1 -1
  31. package/plugins/codex-workflow-gateway/references/protocol.md +6 -0
  32. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +25 -0
  33. package/schemas/downstream-proof-invalidation-event.schema.json +1 -0
  34. package/schemas/downstream-proof-replan-recovery-event.schema.json +61 -0
  35. package/schemas/task.schema.json +34 -0
@@ -11,9 +11,9 @@ import { WorkflowError } from './errors.js';
11
11
  import { bindGraphEvidence, createGraphRefreshRequest, fallbackGraphBinding, inspectGraphBinding, validateGraphRequestCurrent, } from './graph.js';
12
12
  import { inspectLocalDependency, inspectWorkflowVersions } from './diagnostics.js';
13
13
  import { appendTaskClaim, appendTaskHandback, appendTaskHandoff, appendTaskWriterCredentialReplacement, assertTaskClaimAllowed, assertTaskMutationAllowedByC1, defaultTaskWorkerActor, prepareTaskCorrectiveYield, readTaskC1Posture, } from './alpha6/handoff.js';
14
- import { assessDownstreamProofRecovery, downstreamProofInvalidationSidecar, prepareDownstreamProofInvalidation, readDownstreamProofInvalidations, } from './alpha6/downstream-proof.js';
14
+ import { assessDownstreamProofCarryover, assessDownstreamProofReplanRecovery, assessDownstreamProofRecovery, downstreamProofInvalidationSidecar, downstreamProofReplanRecoverySidecar, prepareDownstreamProofInvalidation, prepareDownstreamProofReplanRecovery, readDownstreamProofInvalidations, readDownstreamProofReplanRecoveries, } from './alpha6/downstream-proof.js';
15
15
  import { applyAdoptionPosture, assertAdoptionBaselinePreserved, buildAdoptionPreparation, initializeProjectRegistrationAdoption, readCurrentAdoptionPosture, } from './alpha6/adoption.js';
16
- import { appendCurrentPlanRiskAudit, appendReboundPlanRiskAudit, readCurrentPlanRiskAudit, validatePlanRiskAuditCandidate, } from './alpha6/plan-risk.js';
16
+ import { appendCurrentPlanRiskAudit, appendReboundPlanRiskAudit, readCurrentPlanRiskAudit, readPlanRiskAuditEvents, validatePlanRiskAuditCandidate, } from './alpha6/plan-risk.js';
17
17
  import { appendCorrectiveDecisionEvent, appendPlanIntegrityRecoveryDecision, appendRemediationEvent, appendRemediationModeRecovery, appendStopEscalateOverride, assertCorrectiveAuditorIndependence, assertGuardedRemediationAttemptAllowed, defaultCorrectiveAuditorActor, findCurrentGuardedRemediationPosture, findRemediationModeRecoveryCandidate, findFailedGuardedStepRequiringCorrectiveDecision, readGuardedRemediationPostureForStep, readCorrectiveDecisionEvents, readRemediationEvents, prepareStopEscalateOverride, stopEscalateOverrideDecisionEventIds, } from './alpha6/remediation.js';
18
18
  import { assessPlanIntegrityRecovery, defaultPlanIntegrityRecoveryActor, } from './alpha6/plan-integrity.js';
19
19
  import { assertMechanicalFeasibilityFresh, evaluateMechanicalFeasibility, hashMechanicalFeasibilityEvidence, } from './alpha6/mechanical-feasibility.js';
@@ -281,6 +281,92 @@ export class WorkflowService {
281
281
  invalidation: invalidation.event,
282
282
  };
283
283
  }
284
+ recoverDownstreamProofReplan(repository, taskId, expectedTaskRevision, actor, reason) {
285
+ const context = this.#mutationContext(repository);
286
+ const task = this.store.readTask(context.identity.projectId, taskId);
287
+ assertExpectedRevision(task, expectedTaskRevision);
288
+ // This repair intentionally runs before Project Knowledge refresh: the exact
289
+ // dependency-only transport can make the current map stale, while the
290
+ // recovery assessment still binds the stranded Task to its recorded rebind.
291
+ const observation = this.observationSnapshot(repository).observation;
292
+ if (observation.kind !== 'assessment') {
293
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream-proof replan recovery requires a coherent observation boundary.', {
294
+ observation,
295
+ });
296
+ }
297
+ const currentPlanRiskAuditExists = readPlanRiskAuditEvents(this.store, task).some((event) => event.taskRevision === task.revision && event.planHash === task.planHash);
298
+ const assessment = assessDownstreamProofReplanRecovery(this.store, context.identity.repositoryRoot, task, { currentPlanRiskAuditExists });
299
+ const environmentBlockers = [
300
+ ...(!assessment.dependencyRecoveryCommit
301
+ ? ['The exact beta.12.7 dependency compatibility must be registered before Task recovery.']
302
+ : []),
303
+ ...(observation.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
304
+ ...(observation.pendingProjectTransactions.length > 0
305
+ || observation.pendingMilestoneTransactions.length > 0
306
+ || observation.pendingTaskTransactions.length > 0
307
+ ? ['No pending Workflow transaction may exist.'] : []),
308
+ ...(observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
309
+ ...(observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
310
+ ];
311
+ if (!assessment.applicable || !assessment.eligible || environmentBlockers.length > 0) {
312
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Task is not eligible for bounded downstream-proof replan recovery.', {
313
+ taskId,
314
+ blockers: [...assessment.blockers, ...environmentBlockers],
315
+ });
316
+ }
317
+ const now = this.now();
318
+ const resultTask = {
319
+ ...task,
320
+ revision: task.revision + 1,
321
+ updatedAt: now.toISOString(),
322
+ status: 'needs_fix',
323
+ authorizations: task.authorizations.map((authorization) => authorization.kind === 'execution' && authorization.decision === 'approved'
324
+ ? { ...authorization, decision: 'superseded' }
325
+ : authorization),
326
+ review: emptyReview(),
327
+ resultHash: null,
328
+ evidenceHash: null,
329
+ blockReason: `${assessment.invalidation.activeStepId} downstream-proof recovery requires replacement of the full unfinished Step Plan before execution can resume.`,
330
+ };
331
+ const recovery = prepareDownstreamProofReplanRecovery(this.store, resultTask, assessment, { previousTaskRevision: task.revision, actor, reason }, now);
332
+ const projectRoot = this.store.projectRoot(task.projectId);
333
+ const relative = (absolute) => path.relative(projectRoot, absolute).split(path.sep).join('/');
334
+ const taskRoot = this.store.taskRoot(task.projectId, task.id);
335
+ applyProjectTransaction({
336
+ projectRoot,
337
+ projectId: task.projectId,
338
+ transactionId: `tx-${createEntityId('TASK', now.getTime()).toLowerCase()}`,
339
+ kind: 'downstream-proof-replan-recovery',
340
+ now,
341
+ targets: [
342
+ {
343
+ path: relative(path.join(taskRoot, downstreamProofReplanRecoverySidecar())),
344
+ content: recovery.postimage,
345
+ },
346
+ { path: relative(path.join(taskRoot, 'state.json')), content: `${JSON.stringify(resultTask, null, 2)}\n` },
347
+ ],
348
+ validate: () => {
349
+ const saved = this.store.readTask(task.projectId, task.id);
350
+ const events = readDownstreamProofReplanRecoveries(this.store, saved);
351
+ if (saved.revision !== resultTask.revision
352
+ || saved.status !== 'needs_fix'
353
+ || saved.planHash !== task.planHash
354
+ || events.at(-1)?.eventHash !== recovery.event.eventHash
355
+ || headCommit(context.identity.repositoryRoot) !== recovery.event.preservedHeadCommit
356
+ || JSON.stringify(changedFiles(context.identity.repositoryRoot).sort())
357
+ !== JSON.stringify(recovery.event.dirtyFiles)) {
358
+ throw new WorkflowError('STATE_CORRUPT', 'Downstream-proof replan recovery transaction readback failed.', {
359
+ taskId,
360
+ });
361
+ }
362
+ validateTaskHistory(context.identity.repositoryRoot, saved);
363
+ },
364
+ });
365
+ return {
366
+ task: this.store.readTask(task.projectId, task.id),
367
+ recovery: recovery.event,
368
+ };
369
+ }
284
370
  correctiveReplanController(repository) {
285
371
  const identity = resolveRepositoryIdentity(repository);
286
372
  return createCorrectiveReplanPublicController({
@@ -1177,7 +1263,10 @@ export class WorkflowService {
1177
1263
  const overriddenStopDecisionEventIds = stopEscalateOverrideDecisionEventIds(this.store, task);
1178
1264
  const currentHardBlockingDecisions = currentPlanCorrectiveDecisions.filter((event) => event.decision === 'split-required'
1179
1265
  || (event.decision === 'stop-escalate' && !overriddenStopDecisionEventIds.has(event.eventId)));
1180
- const currentPlanRiskAudit = task.status === 'needs_fix' || currentReplanDecisions.length > 0
1266
+ const downstreamReplanRecovered = readDownstreamProofReplanRecoveries(this.store, task)
1267
+ .some((event) => event.resultTaskRevision === task.revision && event.reboundPlanHash === task.planHash);
1268
+ const currentPlanRiskAudit = (task.status === 'needs_fix' || currentReplanDecisions.length > 0)
1269
+ && !downstreamReplanRecovered
1181
1270
  ? readCurrentPlanRiskAudit(this.store, task)
1182
1271
  : null;
1183
1272
  const classifiedCorrectivePosture = currentReplanDecisions.length > 0 && currentPlanRiskAudit
@@ -1946,9 +2035,18 @@ export class WorkflowService {
1946
2035
  }
1947
2036
  const knowledgeMap = this.assertTaskKnowledgeBinding(context.identity.repositoryRoot, task);
1948
2037
  const dirty = changedFiles(context.identity.repositoryRoot);
1949
- if (step.status === 'planned' && dirty.length > 0 && !resumesCorrectiveReplan) {
2038
+ const downstreamProofCarryover = step.status === 'planned' && dirty.length > 0
2039
+ ? assessDownstreamProofCarryover(this.store, context.identity.repositoryRoot, task, stepId)
2040
+ : null;
2041
+ if (step.status === 'planned'
2042
+ && dirty.length > 0
2043
+ && !resumesCorrectiveReplan
2044
+ && !downstreamProofCarryover?.eligible) {
1950
2045
  throw new WorkflowError('GIT_PRECONDITION_FAILED', `${stepId} must start from a clean checkout.`, {
1951
2046
  changedFiles: dirty,
2047
+ ...(downstreamProofCarryover?.applicable
2048
+ ? { downstreamProofCarryoverBlockers: downstreamProofCarryover.blockers }
2049
+ : {}),
1952
2050
  });
1953
2051
  }
1954
2052
  const dirtyOutsideStep = dirty.filter((file) => !pathAllowed(file, step.allowedWrites));
@@ -2635,16 +2733,23 @@ export class WorkflowService {
2635
2733
  assertCorrectiveAuditorIndependence(this.store, task, stepId, correctiveAudit.auditor);
2636
2734
  return appendCorrectiveDecisionEvent(this.store, task, stepId, correctiveAudit, planRiskAudit, task.planHash, this.now());
2637
2735
  }
2638
- setTaskResult(repository, taskId, expectedRevision, summary, limitations) {
2639
- const { identity } = this.#mutationContext(repository);
2736
+ setTaskResult(repository, taskId, expectedRevision, summary, limitations, writerToken, actor) {
2737
+ const context = this.#mutationContext(repository);
2738
+ const { identity } = context;
2640
2739
  const task = this.store.readTask(identity.projectId, taskId);
2641
2740
  assertExpectedRevision(task, expectedRevision);
2642
2741
  if (task.status !== 'validating' || task.review.status !== 'passed') {
2643
2742
  throw new WorkflowError('TRANSITION_BLOCKED', 'Result requires a passing independent review.');
2644
2743
  }
2645
2744
  this.assertReviewFresh(identity.repositoryRoot, task);
2745
+ const lease = context.locks.heartbeat(taskId, writerToken);
2746
+ this.assertTaskMutationAllowedByHandoff(task, actor ?? null, lease.token);
2646
2747
  const root = this.store.taskRoot(task.projectId, task.id);
2647
2748
  const stagedResult = this.store.stageArtifact(root, 'result.md', renderResult(task, summary, limitations));
2749
+ // Human final acceptance is an intentionally unbounded pause. Release the
2750
+ // execution lease before entering that gate so elapsed human time cannot
2751
+ // turn a successful Task into a repository-wide stale-lock repair route.
2752
+ context.locks.release(taskId, lease.token);
2648
2753
  const saved = this.store.writeTask({ ...task, status: 'awaiting_final_acceptance', resultHash: stagedResult.hash, blockReason: null }, expectedRevision);
2649
2754
  this.store.publishArtifact(stagedResult);
2650
2755
  return saved;
@@ -2944,13 +3049,117 @@ export class WorkflowService {
2944
3049
  invalidatedStepIds: assessment.invalidatedSteps.map((step) => step.stepId),
2945
3050
  });
2946
3051
  }
3052
+ downstreamProofReplanUpdatePreflight(repository, taskId) {
3053
+ const snapshot = this.observationSnapshot(repository);
3054
+ if (snapshot.observation.kind !== 'assessment') {
3055
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream-proof replan update preflight requires a coherent observation boundary.', {
3056
+ observation: snapshot.observation,
3057
+ });
3058
+ }
3059
+ const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
3060
+ if (!task)
3061
+ throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
3062
+ const currentPlanRiskAuditExists = readPlanRiskAuditEvents(this.store, task).some((event) => event.taskRevision === task.revision && event.planHash === task.planHash);
3063
+ const assessment = assessDownstreamProofReplanRecovery(this.store, snapshot.identity.repositoryRoot, task, { currentPlanRiskAuditExists });
3064
+ const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
3065
+ const sourceVersion = '2.0.0-beta.12.6';
3066
+ const versionSurfaces = {
3067
+ declared: versions.declared,
3068
+ locked: versions.locked,
3069
+ installed: versions.installed,
3070
+ currentBranch: versions.currentBranch,
3071
+ baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
3072
+ };
3073
+ const blockers = [
3074
+ ...assessment.blockers,
3075
+ ...(String(PACKAGE_VERSION) === sourceVersion ? ['Recovery runner must be newer than the beta.12.6 source package.'] : []),
3076
+ ...Object.entries(versionSurfaces)
3077
+ .filter(([, version]) => version !== sourceVersion)
3078
+ .map(([surface]) => `${surface} Workflow version must equal the stranded source ${sourceVersion}.`),
3079
+ ...(snapshot.observation.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
3080
+ ...(snapshot.observation.pendingProjectTransactions.length > 0
3081
+ || snapshot.observation.pendingMilestoneTransactions.length > 0
3082
+ || snapshot.observation.pendingTaskTransactions.length > 0
3083
+ ? ['No pending Workflow transaction may exist.'] : []),
3084
+ ...(snapshot.observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
3085
+ ...(snapshot.observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
3086
+ ];
3087
+ return {
3088
+ readOnly: true,
3089
+ eligible: assessment.applicable && assessment.eligible && blockers.length === 0,
3090
+ action: assessment.applicable && assessment.eligible && blockers.length === 0
3091
+ ? 'update downstream-proof-replan-transport'
3092
+ : null,
3093
+ projectId: task.projectId,
3094
+ taskId: task.id,
3095
+ taskRevision: task.revision,
3096
+ sourceVersion,
3097
+ targetVersion: PACKAGE_VERSION,
3098
+ sourceHeadCommit: assessment.invalidation?.preservedHeadCommit ?? null,
3099
+ taskBranch: task.taskBranch,
3100
+ baseBranch: task.baseBranch,
3101
+ dirtyFiles: assessment.compatibility?.dirtyFiles ?? [],
3102
+ dirtyWorktreeHash: assessment.compatibility?.dirtyWorktreeHash ?? null,
3103
+ requiredTransport: [
3104
+ 'Commit only package.json and package-lock.json on the active Milestone base.',
3105
+ 'Commit only the same dependency files on the Task branch while preserving all product bytes.',
3106
+ 'Install the exact target package, then follow fresh next to dependency registration.',
3107
+ ],
3108
+ blockers,
3109
+ };
3110
+ }
3111
+ downstreamProofReplanDependencyRecoveryPreflight(repository, taskId) {
3112
+ const snapshot = this.observationSnapshot(repository);
3113
+ if (snapshot.observation.kind !== 'assessment') {
3114
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream-proof replan dependency recovery requires a coherent observation boundary.', {
3115
+ observation: snapshot.observation,
3116
+ });
3117
+ }
3118
+ const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
3119
+ if (!task)
3120
+ throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
3121
+ let candidateParent = null;
3122
+ try {
3123
+ candidateParent = runGit(snapshot.identity.repositoryRoot, ['rev-parse', 'HEAD^']);
3124
+ }
3125
+ catch {
3126
+ candidateParent = null;
3127
+ }
3128
+ const currentPlanRiskAuditExists = readPlanRiskAuditEvents(this.store, task).some((event) => event.taskRevision === task.revision && event.planHash === task.planHash);
3129
+ const assessment = candidateParent
3130
+ ? assessDownstreamProofReplanRecovery(this.store, snapshot.identity.repositoryRoot, task, {
3131
+ historyBoundary: 'HEAD^',
3132
+ preservedHeadCommit: candidateParent,
3133
+ currentPlanRiskAuditExists,
3134
+ })
3135
+ : null;
3136
+ if (!assessment?.applicable || !assessment.eligible || !assessment.compatibility) {
3137
+ const ordinary = this.inspectDependencyProvenanceCandidate(snapshot, task);
3138
+ return {
3139
+ ...ordinary,
3140
+ eligible: false,
3141
+ action: null,
3142
+ activeDownstreamProof: null,
3143
+ downstreamProofReplan: null,
3144
+ blockers: [...new Set([
3145
+ ...ordinary.blockers,
3146
+ ...(assessment?.blockers ?? []),
3147
+ 'The Task must preserve the exact beta.12.6 downstream-proof invalidation and accidental knowledge-rebind boundary.',
3148
+ ])],
3149
+ };
3150
+ }
3151
+ return this.inspectDependencyProvenanceCandidate(snapshot, task, null, assessment.compatibility);
3152
+ }
2947
3153
  recoverDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
2948
3154
  return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, false);
2949
3155
  }
2950
3156
  recoverActiveDownstreamProofDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
2951
3157
  return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, true);
2952
3158
  }
2953
- recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, activeDownstreamProof) {
3159
+ recoverDownstreamProofReplanDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
3160
+ return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, 'downstream-replan');
3161
+ }
3162
+ recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, recoveryMode) {
2954
3163
  const normalizedActor = actor.trim();
2955
3164
  const normalizedReason = reason.trim();
2956
3165
  if (!normalizedActor)
@@ -2962,7 +3171,9 @@ export class WorkflowService {
2962
3171
  const currentHead = headCommit(context.identity.repositoryRoot);
2963
3172
  const prior = current.dependencyProvenanceRecoveries?.find((record) => record.commitSha === currentHead
2964
3173
  && record.packageVersion === PACKAGE_VERSION
2965
- && Boolean(record.activeDownstreamProof) === activeDownstreamProof);
3174
+ && (recoveryMode === 'downstream-replan'
3175
+ ? Boolean(record.downstreamProofReplan)
3176
+ : Boolean(record.activeDownstreamProof) === recoveryMode));
2966
3177
  if (prior && current.systemCommits.includes(currentHead))
2967
3178
  return current;
2968
3179
  assertExpectedRevision(current, expectedRevision);
@@ -2970,9 +3181,11 @@ export class WorkflowService {
2970
3181
  const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
2971
3182
  if (!task)
2972
3183
  throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
2973
- const preflight = activeDownstreamProof
2974
- ? this.activeDownstreamProofDependencyRecoveryPreflight(repository, taskId)
2975
- : this.inspectDependencyProvenanceCandidate(snapshot, task);
3184
+ const preflight = recoveryMode === 'downstream-replan'
3185
+ ? this.downstreamProofReplanDependencyRecoveryPreflight(repository, taskId)
3186
+ : recoveryMode
3187
+ ? this.activeDownstreamProofDependencyRecoveryPreflight(repository, taskId)
3188
+ : this.inspectDependencyProvenanceCandidate(snapshot, task);
2976
3189
  if (!preflight.eligible || !preflight.commitSha || !preflight.parentCommitSha) {
2977
3190
  throw new WorkflowError('GIT_PRECONDITION_FAILED', 'The bounded dependency provenance recovery preflight failed.', {
2978
3191
  taskId,
@@ -2997,6 +3210,9 @@ export class WorkflowService {
2997
3210
  ...(preflight.activeDownstreamProof
2998
3211
  ? { activeDownstreamProof: preflight.activeDownstreamProof }
2999
3212
  : {}),
3213
+ ...(preflight.downstreamProofReplan
3214
+ ? { downstreamProofReplan: preflight.downstreamProofReplan }
3215
+ : {}),
3000
3216
  },
3001
3217
  ],
3002
3218
  }, expectedRevision);
@@ -3426,6 +3642,22 @@ export class WorkflowService {
3426
3642
  },
3427
3643
  });
3428
3644
  }
3645
+ const downstreamProofReplanDependencyRecovery = this.downstreamProofReplanDependencyRecoveryPreflight(repositoryRoot, activeTask.id);
3646
+ if (downstreamProofReplanDependencyRecovery.eligible) {
3647
+ return withAdoption({
3648
+ scope: 'task',
3649
+ id: activeTask.id,
3650
+ status: activeTask.status,
3651
+ action: 'update downstream-proof-replan-dependency-recover',
3652
+ revision: activeTask.revision,
3653
+ dependencyProvenanceRecovery: downstreamProofReplanDependencyRecovery,
3654
+ compatibilityBoundary: {
3655
+ preservesWorktree: true,
3656
+ prerequisite: 'A newer package preflight approved the exact beta.12.6 stranded replan transport before dependency-only commits were created.',
3657
+ continuation: 'Register this dependency commit, then run task downstream-proof-replan-recover.',
3658
+ },
3659
+ });
3660
+ }
3429
3661
  const activeTaskMilestone = activeTask.milestoneId
3430
3662
  ? milestonesById.get(activeTask.milestoneId) ?? null
3431
3663
  : null;
@@ -3449,36 +3681,109 @@ export class WorkflowService {
3449
3681
  },
3450
3682
  }
3451
3683
  : value);
3684
+ const currentPlanRiskAuditExists = activeTask.planHash
3685
+ ? readPlanRiskAuditEvents(this.store, activeTask).some((event) => event.taskRevision === activeTask.revision && event.planHash === activeTask.planHash)
3686
+ : false;
3687
+ const downstreamReplanRecovery = assessDownstreamProofReplanRecovery(this.store, repositoryRoot, activeTask, { currentPlanRiskAuditExists });
3688
+ if (downstreamReplanRecovery.applicable) {
3689
+ const environmentBlockers = [
3690
+ ...(!downstreamReplanRecovery.dependencyRecoveryCommit
3691
+ ? ['The exact beta.12.7 dependency compatibility must be registered before Task recovery.']
3692
+ : []),
3693
+ ...(status.observation.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
3694
+ ...(status.observation.pendingProjectTransactions.length > 0
3695
+ || status.observation.pendingMilestoneTransactions.length > 0
3696
+ || status.observation.pendingTaskTransactions.length > 0
3697
+ ? ['No pending Workflow transaction may exist.'] : []),
3698
+ ...(status.observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
3699
+ ...(status.observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
3700
+ ];
3701
+ return withAdoption(withMilestoneCancellation(downstreamReplanRecovery.eligible && environmentBlockers.length === 0
3702
+ ? {
3703
+ scope: 'task',
3704
+ id: activeTask.id,
3705
+ status: activeTask.status,
3706
+ action: 'task downstream-proof-replan-recover',
3707
+ revision: activeTask.revision,
3708
+ downstreamProofReplanRecovery: {
3709
+ state: 'eligible',
3710
+ invalidationEventId: downstreamReplanRecovery.invalidation.eventId,
3711
+ sourcePlanHash: downstreamReplanRecovery.invalidation.planHash,
3712
+ reboundPlanHash: activeTask.planHash,
3713
+ dirtyFiles: downstreamReplanRecovery.compatibility.dirtyFiles,
3714
+ dirtyWorktreeHash: downstreamReplanRecovery.compatibility.dirtyWorktreeHash,
3715
+ dependencyRecoveryCommit: downstreamReplanRecovery.dependencyRecoveryCommit,
3716
+ boundedEffect: 'restore-needs-fix-and-return-same-task-to-plan-set',
3717
+ },
3718
+ }
3719
+ : {
3720
+ scope: 'task',
3721
+ id: activeTask.id,
3722
+ status: activeTask.status,
3723
+ action: 'doctor',
3724
+ blockedAction: 'task downstream-proof-replan-recover',
3725
+ revision: activeTask.revision,
3726
+ downstreamProofReplanRecovery: {
3727
+ state: 'blocked',
3728
+ blockers: [...downstreamReplanRecovery.blockers, ...environmentBlockers],
3729
+ },
3730
+ }));
3731
+ }
3452
3732
  let activePlanRiskAudit = null;
3453
3733
  let planRiskAuditRecovery = null;
3454
- if (activeTask.planHash) {
3734
+ const requiresReplacementPlan = activeTask.status === 'needs_fix'
3735
+ && !activeTask.steps.some((step) => step.status === 'failed');
3736
+ if (activeTask.planHash && !requiresReplacementPlan) {
3455
3737
  try {
3456
3738
  activePlanRiskAudit = readCurrentPlanRiskAudit(this.store, activeTask);
3457
3739
  }
3458
3740
  catch (error) {
3459
3741
  if (!(error instanceof WorkflowError) || error.code !== 'TRANSITION_BLOCKED')
3460
3742
  throw error;
3461
- planRiskAuditRecovery = {
3462
- scope: 'task',
3463
- id: activeTask.id,
3464
- status: activeTask.status,
3465
- action: 'task plan-risk-audit',
3466
- revision: activeTask.revision,
3467
- planRiskAuditRecovery: {
3468
- state: 'required',
3469
- reason: error.message,
3470
- currentBriefHash: activeTask.briefHash,
3471
- currentPlanHash: activeTask.planHash,
3472
- knowledgeMapRevision: activeTask.knowledgeMapRevision,
3473
- knowledgeMapHash: activeTask.knowledgeMapHash,
3474
- },
3475
- };
3743
+ const blockingDecision = error.details.decision;
3744
+ planRiskAuditRecovery = blockingDecision === 'stop-escalate' || blockingDecision === 'split-required'
3745
+ ? {
3746
+ scope: 'task',
3747
+ id: activeTask.id,
3748
+ status: activeTask.status,
3749
+ action: blockingDecision === 'stop-escalate' ? 'task plan-set' : 'doctor',
3750
+ blockedAction: 'task authorize',
3751
+ revision: activeTask.revision,
3752
+ planRiskAuditDecision: {
3753
+ state: 'recorded-blocking',
3754
+ decision: blockingDecision,
3755
+ reason: error.message,
3756
+ requiredAction: blockingDecision === 'stop-escalate'
3757
+ ? 'Replace the rejected Step Plan before requesting fresh authorization.'
3758
+ : 'Resolve the required cross-Task split through an explicit Milestone topology decision.',
3759
+ },
3760
+ ...(blockingDecision === 'stop-escalate'
3761
+ ? { taskPlanContract: this.taskPlanContract(status.projectId, activeTask) }
3762
+ : {}),
3763
+ }
3764
+ : {
3765
+ scope: 'task',
3766
+ id: activeTask.id,
3767
+ status: activeTask.status,
3768
+ action: 'task plan-risk-audit',
3769
+ revision: activeTask.revision,
3770
+ planRiskAuditRecovery: {
3771
+ state: 'required',
3772
+ reason: error.message,
3773
+ currentBriefHash: activeTask.briefHash,
3774
+ currentPlanHash: activeTask.planHash,
3775
+ knowledgeMapRevision: activeTask.knowledgeMapRevision,
3776
+ knowledgeMapHash: activeTask.knowledgeMapHash,
3777
+ },
3778
+ };
3476
3779
  }
3477
3780
  }
3478
3781
  const taskNext = activeTask.status === 'planning'
3479
3782
  ? null
3480
- : planRiskAuditRecovery
3481
- ?? this.nextForTaskWithDelegation(status.projectId, repositoryRoot, activeTask, activePlanRiskAudit);
3783
+ : requiresReplacementPlan
3784
+ ? this.nextForTaskWithDelegation(status.projectId, repositoryRoot, activeTask, null)
3785
+ : planRiskAuditRecovery
3786
+ ?? this.nextForTaskWithDelegation(status.projectId, repositoryRoot, activeTask, activePlanRiskAudit);
3482
3787
  // Active Step work must outrank Project Knowledge refresh/rebind. Both completion and
3483
3788
  // strict review are executable while context refresh correctly remains forbidden.
3484
3789
  const hardStop = taskNext?.action === 'task corrective-decision'
@@ -3526,7 +3831,9 @@ export class WorkflowService {
3526
3831
  ? this.store.hashArtifact(this.store.taskRoot(status.projectId, activeTask.id), 'plan.md')
3527
3832
  : null)
3528
3833
  : null;
3529
- const contextRefreshOptions = action === 'project-memory reconcile' && refreshAssessment?.eligible
3834
+ const contextRefreshOptions = action === 'project-memory reconcile'
3835
+ && taskNext?.action !== 'task plan-set'
3836
+ && refreshAssessment?.eligible
3530
3837
  ? this.delegatedContextRefreshOptions(status.projectId, activeTask, knowledgeMap, refreshAssessment.mode)
3531
3838
  : [];
3532
3839
  if (requestedTask && action === 'project-memory reconcile' && contextRefreshOptions.length === 0) {
@@ -3574,6 +3881,9 @@ export class WorkflowService {
3574
3881
  }));
3575
3882
  }
3576
3883
  const knowledgeMapHash = hashKnowledgeMap(knowledgeMap);
3884
+ if (taskNext?.action === 'task plan-set') {
3885
+ return withAdoption(withMilestoneCancellation(taskNext));
3886
+ }
3577
3887
  if (taskCanRebindKnowledge(activeTask) &&
3578
3888
  activeTask.planHash &&
3579
3889
  (activeTask.knowledgeMapRevision !== knowledgeMap.revision ||
@@ -3909,6 +4219,12 @@ export class WorkflowService {
3909
4219
  }
3910
4220
  }
3911
4221
  const next = this.taskGitNavigationOverride(task) ?? nextForTask(task);
4222
+ const nextStepId = typeof next.stepId === 'string' ? next.stepId : null;
4223
+ const downstreamProofCarryover = next.action === 'task run'
4224
+ && nextStepId
4225
+ && changedFiles(repositoryRoot).length > 0
4226
+ ? assessDownstreamProofCarryover(this.store, repositoryRoot, task, nextStepId)
4227
+ : null;
3912
4228
  let mechanicalFeasibilityOverlay = {};
3913
4229
  if (task.status === 'awaiting_execution_authorization' && task.planHash) {
3914
4230
  try {
@@ -4431,6 +4747,29 @@ export class WorkflowService {
4431
4747
  }
4432
4748
  : {}),
4433
4749
  ...mechanicalFeasibilityOverlay,
4750
+ ...(downstreamProofCarryover?.applicable
4751
+ ? downstreamProofCarryover.eligible
4752
+ ? {
4753
+ downstreamProofCarryover: {
4754
+ state: 'validated',
4755
+ invalidationEventId: downstreamProofCarryover.invalidationEventId,
4756
+ stepId: nextStepId,
4757
+ dirtyFiles: downstreamProofCarryover.dirtyFiles,
4758
+ dirtyWorktreeHash: downstreamProofCarryover.dirtyWorktreeHash,
4759
+ meaning: 'The replacement Step may adopt exactly these preserved uncommitted bytes on task run.',
4760
+ },
4761
+ }
4762
+ : {
4763
+ action: 'doctor',
4764
+ blockedAction: 'task run',
4765
+ downstreamProofCarryover: {
4766
+ state: 'blocked',
4767
+ invalidationEventId: downstreamProofCarryover.invalidationEventId,
4768
+ stepId: nextStepId,
4769
+ blockers: downstreamProofCarryover.blockers,
4770
+ },
4771
+ }
4772
+ : {}),
4434
4773
  ...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
4435
4774
  };
4436
4775
  if (handoffPosture.state === 'pending') {
@@ -4467,6 +4806,12 @@ export class WorkflowService {
4467
4806
  };
4468
4807
  }
4469
4808
  assertTaskContextChangeAllowed(task) {
4809
+ if (task.status === 'needs_fix' && !task.steps.some((step) => step.status === 'failed')) {
4810
+ throw new WorkflowError('TRANSITION_BLOCKED', `Task ${task.id} requires a replacement Step Plan; Project Knowledge cannot rebind or refresh the stale Plan.`, {
4811
+ taskId: task.id,
4812
+ requiredAction: 'Run task plan-set with the full unfinished remainder before requesting authorization.',
4813
+ });
4814
+ }
4470
4815
  const posture = this.taskTerminalCorrectivePosture(task);
4471
4816
  if (!posture)
4472
4817
  return;
@@ -4588,6 +4933,7 @@ export class WorkflowService {
4588
4933
  if (!taskIsActionableFromRepositoryNext(task, milestonesById, tasksById)
4589
4934
  || !task.planHash
4590
4935
  || !taskCanRebindKnowledge(task)
4936
+ || (task.status === 'needs_fix' && !task.steps.some((step) => step.status === 'failed'))
4591
4937
  || this.taskHasTerminalCorrectiveDecision(task))
4592
4938
  continue;
4593
4939
  const assessment = assessDelegatedKnowledgeRefresh(approved, inspected, task, this.store.hashArtifact(this.store.taskRoot(projectId, task.id), 'plan.md'));
@@ -4717,7 +5063,7 @@ export class WorkflowService {
4717
5063
  },
4718
5064
  };
4719
5065
  }
4720
- inspectDependencyProvenanceCandidate(snapshot, task, activeDownstreamProof = null) {
5066
+ inspectDependencyProvenanceCandidate(snapshot, task, activeDownstreamProof = null, downstreamProofReplan = null) {
4721
5067
  const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
4722
5068
  const now = this.now().getTime();
4723
5069
  return inspectDependencyProvenanceRecovery(snapshot.identity.repositoryRoot, task, {
@@ -4737,7 +5083,7 @@ export class WorkflowService {
4737
5083
  currentBranch: versions.currentBranch,
4738
5084
  baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
4739
5085
  },
4740
- }, activeDownstreamProof);
5086
+ }, activeDownstreamProof, downstreamProofReplan);
4741
5087
  }
4742
5088
  withTaskNavigationContracts(task, next) {
4743
5089
  const action = typeof next.action === 'string' ? next.action : null;
@@ -4775,7 +5121,7 @@ export class WorkflowService {
4775
5121
  return activeLeaseOwner;
4776
5122
  const authorizationKind = ['task merge', 'task merge-confirm'].includes(action)
4777
5123
  ? 'final_acceptance'
4778
- : ['task step-complete', 'task downstream-proof-recover', 'task submit'].includes(action)
5124
+ : ['task step-complete', 'task downstream-proof-recover', 'task submit', 'task result-set'].includes(action)
4779
5125
  ? 'execution'
4780
5126
  : null;
4781
5127
  if (!authorizationKind)
@@ -5563,6 +5909,7 @@ function writerTokenContractForAction(action, claimedC1Posture = false) {
5563
5909
  'task step-complete',
5564
5910
  'task downstream-proof-recover',
5565
5911
  'task submit',
5912
+ 'task result-set',
5566
5913
  'task merge',
5567
5914
  'task merge-confirm',
5568
5915
  ];