codex-workflow-v2 2.0.0-beta.12.5 → 2.0.0-beta.12.7

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 (39) hide show
  1. package/README.md +29 -3
  2. package/dist/src/alpha6/downstream-proof.d.ts +41 -2
  3. package/dist/src/alpha6/downstream-proof.js +360 -5
  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 +57 -1
  8. package/dist/src/cli.js.map +1 -1
  9. package/dist/src/contracts.d.ts +44 -1
  10. package/dist/src/dependency-provenance.d.ts +9 -2
  11. package/dist/src/dependency-provenance.js +49 -5
  12. package/dist/src/dependency-provenance.js.map +1 -1
  13. package/dist/src/git.d.ts +1 -1
  14. package/dist/src/git.js +4 -2
  15. package/dist/src/git.js.map +1 -1
  16. package/dist/src/version.d.ts +1 -1
  17. package/dist/src/version.js +1 -1
  18. package/dist/src/workflow.d.ts +11 -1
  19. package/dist/src/workflow.js +434 -28
  20. package/dist/src/workflow.js.map +1 -1
  21. package/docs/autonomy-guardrails.md +7 -0
  22. package/docs/development-flow.md +16 -0
  23. package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
  24. package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
  25. package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
  26. package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +26 -6
  27. package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +27 -7
  28. package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +34 -12
  29. package/docs/release.md +11 -1
  30. package/docs/updating-existing-project.md +61 -2
  31. package/docs/validation-report.md +92 -83
  32. package/package.json +2 -1
  33. package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +1 -1
  34. package/plugins/codex-workflow-gateway/references/protocol.md +12 -1
  35. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +33 -1
  36. package/schemas/downstream-proof-invalidation-event.schema.json +1 -0
  37. package/schemas/downstream-proof-replan-recovery-event.schema.json +61 -0
  38. package/schemas/task.schema.json +56 -0
  39. package/scripts/generate-pdf-docs.py +13 -2
@@ -2,7 +2,7 @@ import { appendFileSync, existsSync, lstatSync, readFileSync, readdirSync } from
2
2
  import { createHash } from 'node:crypto';
3
3
  import path from 'node:path';
4
4
  import { PROTOCOL_VERSION, STATE_SCHEMA_VERSION, } from './contracts.js';
5
- import { inspectDependencyProvenanceRecovery } from './dependency-provenance.js';
5
+ import { inspectDependencyProvenanceRecovery, } from './dependency-provenance.js';
6
6
  import { renderBrief, renderPlan, renderResult } from './artifacts.js';
7
7
  import { assertDelegationCanAuthorize, hashDelegationPolicy, prepareDelegationGrantGate, } from './delegation.js';
8
8
  import { assessDiscovery, mergeUnique } from './domain/discovery.js';
@@ -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));
@@ -2907,7 +3005,154 @@ export class WorkflowService {
2907
3005
  throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
2908
3006
  return this.inspectDependencyProvenanceCandidate(snapshot, task);
2909
3007
  }
3008
+ activeDownstreamProofDependencyRecoveryPreflight(repository, taskId) {
3009
+ const snapshot = this.observationSnapshot(repository);
3010
+ if (snapshot.observation.kind !== 'assessment') {
3011
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Active downstream-proof dependency recovery requires a coherent observation boundary.', {
3012
+ observation: snapshot.observation,
3013
+ });
3014
+ }
3015
+ const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
3016
+ if (!task)
3017
+ throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
3018
+ const assessment = assessDownstreamProofRecovery(this.store, snapshot.identity.repositoryRoot, task, { historyBoundary: 'HEAD^', preservedHeadCommit: headCommit(snapshot.identity.repositoryRoot) });
3019
+ if (!assessment.applicable
3020
+ || !assessment.eligible
3021
+ || !assessment.activeStepId
3022
+ || !assessment.dirtyWorktreeHash
3023
+ || !task.planHash) {
3024
+ const ordinary = this.inspectDependencyProvenanceCandidate(snapshot, task);
3025
+ return {
3026
+ ...ordinary,
3027
+ eligible: false,
3028
+ action: null,
3029
+ activeDownstreamProof: null,
3030
+ blockers: [...new Set([
3031
+ ...ordinary.blockers,
3032
+ ...assessment.blockers,
3033
+ 'The Task must have one eligible active downstream-proof predecessor invalidation at the candidate parent boundary.',
3034
+ ])],
3035
+ };
3036
+ }
3037
+ return this.inspectDependencyProvenanceCandidate(snapshot, task, {
3038
+ activeStepId: assessment.activeStepId,
3039
+ planHash: task.planHash,
3040
+ dirtyFiles: assessment.dirtyFiles,
3041
+ dirtyWorktreeHash: assessment.dirtyWorktreeHash,
3042
+ invalidatedStepIds: assessment.invalidatedSteps.map((step) => step.stepId),
3043
+ });
3044
+ }
3045
+ downstreamProofReplanUpdatePreflight(repository, taskId) {
3046
+ const snapshot = this.observationSnapshot(repository);
3047
+ if (snapshot.observation.kind !== 'assessment') {
3048
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream-proof replan update preflight requires a coherent observation boundary.', {
3049
+ observation: snapshot.observation,
3050
+ });
3051
+ }
3052
+ const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
3053
+ if (!task)
3054
+ throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
3055
+ const currentPlanRiskAuditExists = readPlanRiskAuditEvents(this.store, task).some((event) => event.taskRevision === task.revision && event.planHash === task.planHash);
3056
+ const assessment = assessDownstreamProofReplanRecovery(this.store, snapshot.identity.repositoryRoot, task, { currentPlanRiskAuditExists });
3057
+ const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
3058
+ const sourceVersion = '2.0.0-beta.12.6';
3059
+ const versionSurfaces = {
3060
+ declared: versions.declared,
3061
+ locked: versions.locked,
3062
+ installed: versions.installed,
3063
+ currentBranch: versions.currentBranch,
3064
+ baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
3065
+ };
3066
+ const blockers = [
3067
+ ...assessment.blockers,
3068
+ ...(String(PACKAGE_VERSION) === sourceVersion ? ['Recovery runner must be newer than the beta.12.6 source package.'] : []),
3069
+ ...Object.entries(versionSurfaces)
3070
+ .filter(([, version]) => version !== sourceVersion)
3071
+ .map(([surface]) => `${surface} Workflow version must equal the stranded source ${sourceVersion}.`),
3072
+ ...(snapshot.observation.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
3073
+ ...(snapshot.observation.pendingProjectTransactions.length > 0
3074
+ || snapshot.observation.pendingMilestoneTransactions.length > 0
3075
+ || snapshot.observation.pendingTaskTransactions.length > 0
3076
+ ? ['No pending Workflow transaction may exist.'] : []),
3077
+ ...(snapshot.observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
3078
+ ...(snapshot.observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
3079
+ ];
3080
+ return {
3081
+ readOnly: true,
3082
+ eligible: assessment.applicable && assessment.eligible && blockers.length === 0,
3083
+ action: assessment.applicable && assessment.eligible && blockers.length === 0
3084
+ ? 'update downstream-proof-replan-transport'
3085
+ : null,
3086
+ projectId: task.projectId,
3087
+ taskId: task.id,
3088
+ taskRevision: task.revision,
3089
+ sourceVersion,
3090
+ targetVersion: PACKAGE_VERSION,
3091
+ sourceHeadCommit: assessment.invalidation?.preservedHeadCommit ?? null,
3092
+ taskBranch: task.taskBranch,
3093
+ baseBranch: task.baseBranch,
3094
+ dirtyFiles: assessment.compatibility?.dirtyFiles ?? [],
3095
+ dirtyWorktreeHash: assessment.compatibility?.dirtyWorktreeHash ?? null,
3096
+ requiredTransport: [
3097
+ 'Commit only package.json and package-lock.json on the active Milestone base.',
3098
+ 'Commit only the same dependency files on the Task branch while preserving all product bytes.',
3099
+ 'Install the exact target package, then follow fresh next to dependency registration.',
3100
+ ],
3101
+ blockers,
3102
+ };
3103
+ }
3104
+ downstreamProofReplanDependencyRecoveryPreflight(repository, taskId) {
3105
+ const snapshot = this.observationSnapshot(repository);
3106
+ if (snapshot.observation.kind !== 'assessment') {
3107
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Downstream-proof replan dependency recovery requires a coherent observation boundary.', {
3108
+ observation: snapshot.observation,
3109
+ });
3110
+ }
3111
+ const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
3112
+ if (!task)
3113
+ throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
3114
+ let candidateParent = null;
3115
+ try {
3116
+ candidateParent = runGit(snapshot.identity.repositoryRoot, ['rev-parse', 'HEAD^']);
3117
+ }
3118
+ catch {
3119
+ candidateParent = null;
3120
+ }
3121
+ const currentPlanRiskAuditExists = readPlanRiskAuditEvents(this.store, task).some((event) => event.taskRevision === task.revision && event.planHash === task.planHash);
3122
+ const assessment = candidateParent
3123
+ ? assessDownstreamProofReplanRecovery(this.store, snapshot.identity.repositoryRoot, task, {
3124
+ historyBoundary: 'HEAD^',
3125
+ preservedHeadCommit: candidateParent,
3126
+ currentPlanRiskAuditExists,
3127
+ })
3128
+ : null;
3129
+ if (!assessment?.applicable || !assessment.eligible || !assessment.compatibility) {
3130
+ const ordinary = this.inspectDependencyProvenanceCandidate(snapshot, task);
3131
+ return {
3132
+ ...ordinary,
3133
+ eligible: false,
3134
+ action: null,
3135
+ activeDownstreamProof: null,
3136
+ downstreamProofReplan: null,
3137
+ blockers: [...new Set([
3138
+ ...ordinary.blockers,
3139
+ ...(assessment?.blockers ?? []),
3140
+ 'The Task must preserve the exact beta.12.6 downstream-proof invalidation and accidental knowledge-rebind boundary.',
3141
+ ])],
3142
+ };
3143
+ }
3144
+ return this.inspectDependencyProvenanceCandidate(snapshot, task, null, assessment.compatibility);
3145
+ }
2910
3146
  recoverDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
3147
+ return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, false);
3148
+ }
3149
+ recoverActiveDownstreamProofDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
3150
+ return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, true);
3151
+ }
3152
+ recoverDownstreamProofReplanDependencyProvenance(repository, taskId, expectedRevision, actor, reason) {
3153
+ return this.recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, 'downstream-replan');
3154
+ }
3155
+ recoverDependencyProvenanceCandidate(repository, taskId, expectedRevision, actor, reason, recoveryMode) {
2911
3156
  const normalizedActor = actor.trim();
2912
3157
  const normalizedReason = reason.trim();
2913
3158
  if (!normalizedActor)
@@ -2917,7 +3162,11 @@ export class WorkflowService {
2917
3162
  const context = this.#mutationContext(repository);
2918
3163
  const current = context.store.readTask(context.identity.projectId, taskId);
2919
3164
  const currentHead = headCommit(context.identity.repositoryRoot);
2920
- const prior = current.dependencyProvenanceRecoveries?.find((record) => record.commitSha === currentHead && record.packageVersion === PACKAGE_VERSION);
3165
+ const prior = current.dependencyProvenanceRecoveries?.find((record) => record.commitSha === currentHead
3166
+ && record.packageVersion === PACKAGE_VERSION
3167
+ && (recoveryMode === 'downstream-replan'
3168
+ ? Boolean(record.downstreamProofReplan)
3169
+ : Boolean(record.activeDownstreamProof) === recoveryMode));
2921
3170
  if (prior && current.systemCommits.includes(currentHead))
2922
3171
  return current;
2923
3172
  assertExpectedRevision(current, expectedRevision);
@@ -2925,7 +3174,11 @@ export class WorkflowService {
2925
3174
  const task = snapshot.tasks.find((candidate) => candidate.id === taskId);
2926
3175
  if (!task)
2927
3176
  throw new WorkflowError('NOT_FOUND', `Task ${taskId} was not found in this repository.`);
2928
- const preflight = this.inspectDependencyProvenanceCandidate(snapshot, task);
3177
+ const preflight = recoveryMode === 'downstream-replan'
3178
+ ? this.downstreamProofReplanDependencyRecoveryPreflight(repository, taskId)
3179
+ : recoveryMode
3180
+ ? this.activeDownstreamProofDependencyRecoveryPreflight(repository, taskId)
3181
+ : this.inspectDependencyProvenanceCandidate(snapshot, task);
2929
3182
  if (!preflight.eligible || !preflight.commitSha || !preflight.parentCommitSha) {
2930
3183
  throw new WorkflowError('GIT_PRECONDITION_FAILED', 'The bounded dependency provenance recovery preflight failed.', {
2931
3184
  taskId,
@@ -2947,6 +3200,12 @@ export class WorkflowService {
2947
3200
  reason: normalizedReason,
2948
3201
  recordedAt: this.now().toISOString(),
2949
3202
  retainedHistoricalCommits: preflight.retainedHistoricalCommits,
3203
+ ...(preflight.activeDownstreamProof
3204
+ ? { activeDownstreamProof: preflight.activeDownstreamProof }
3205
+ : {}),
3206
+ ...(preflight.downstreamProofReplan
3207
+ ? { downstreamProofReplan: preflight.downstreamProofReplan }
3208
+ : {}),
2950
3209
  },
2951
3210
  ],
2952
3211
  }, expectedRevision);
@@ -3360,6 +3619,38 @@ export class WorkflowService {
3360
3619
  dependencyProvenanceRecovery: dependencyRecovery,
3361
3620
  });
3362
3621
  }
3622
+ const activeDownstreamProofDependencyRecovery = this.activeDownstreamProofDependencyRecoveryPreflight(repositoryRoot, activeTask.id);
3623
+ if (activeDownstreamProofDependencyRecovery.eligible) {
3624
+ return withAdoption({
3625
+ scope: 'task',
3626
+ id: activeTask.id,
3627
+ status: activeTask.status,
3628
+ action: 'update downstream-proof-dependency-recover',
3629
+ revision: activeTask.revision,
3630
+ dependencyProvenanceRecovery: activeDownstreamProofDependencyRecovery,
3631
+ compatibilityBoundary: {
3632
+ preservesWorktree: true,
3633
+ prerequisite: 'The active Milestone base and Task branch already contain only the exact dependency update commits.',
3634
+ continuation: 'Run task downstream-proof-recover after this provenance registration.',
3635
+ },
3636
+ });
3637
+ }
3638
+ const downstreamProofReplanDependencyRecovery = this.downstreamProofReplanDependencyRecoveryPreflight(repositoryRoot, activeTask.id);
3639
+ if (downstreamProofReplanDependencyRecovery.eligible) {
3640
+ return withAdoption({
3641
+ scope: 'task',
3642
+ id: activeTask.id,
3643
+ status: activeTask.status,
3644
+ action: 'update downstream-proof-replan-dependency-recover',
3645
+ revision: activeTask.revision,
3646
+ dependencyProvenanceRecovery: downstreamProofReplanDependencyRecovery,
3647
+ compatibilityBoundary: {
3648
+ preservesWorktree: true,
3649
+ prerequisite: 'A newer package preflight approved the exact beta.12.6 stranded replan transport before dependency-only commits were created.',
3650
+ continuation: 'Register this dependency commit, then run task downstream-proof-replan-recover.',
3651
+ },
3652
+ });
3653
+ }
3363
3654
  const activeTaskMilestone = activeTask.milestoneId
3364
3655
  ? milestonesById.get(activeTask.milestoneId) ?? null
3365
3656
  : null;
@@ -3383,36 +3674,109 @@ export class WorkflowService {
3383
3674
  },
3384
3675
  }
3385
3676
  : value);
3677
+ const currentPlanRiskAuditExists = activeTask.planHash
3678
+ ? readPlanRiskAuditEvents(this.store, activeTask).some((event) => event.taskRevision === activeTask.revision && event.planHash === activeTask.planHash)
3679
+ : false;
3680
+ const downstreamReplanRecovery = assessDownstreamProofReplanRecovery(this.store, repositoryRoot, activeTask, { currentPlanRiskAuditExists });
3681
+ if (downstreamReplanRecovery.applicable) {
3682
+ const environmentBlockers = [
3683
+ ...(!downstreamReplanRecovery.dependencyRecoveryCommit
3684
+ ? ['The exact beta.12.7 dependency compatibility must be registered before Task recovery.']
3685
+ : []),
3686
+ ...(status.observation.leases.length > 0 ? ['No active or stale Task writer lease may exist.'] : []),
3687
+ ...(status.observation.pendingProjectTransactions.length > 0
3688
+ || status.observation.pendingMilestoneTransactions.length > 0
3689
+ || status.observation.pendingTaskTransactions.length > 0
3690
+ ? ['No pending Workflow transaction may exist.'] : []),
3691
+ ...(status.observation.corruptTaskTransactions.length > 0 ? ['No corrupt Task transaction may exist.'] : []),
3692
+ ...(status.observation.coreTaskOperations.length > 0 ? ['No Core Task operation may exist.'] : []),
3693
+ ];
3694
+ return withAdoption(withMilestoneCancellation(downstreamReplanRecovery.eligible && environmentBlockers.length === 0
3695
+ ? {
3696
+ scope: 'task',
3697
+ id: activeTask.id,
3698
+ status: activeTask.status,
3699
+ action: 'task downstream-proof-replan-recover',
3700
+ revision: activeTask.revision,
3701
+ downstreamProofReplanRecovery: {
3702
+ state: 'eligible',
3703
+ invalidationEventId: downstreamReplanRecovery.invalidation.eventId,
3704
+ sourcePlanHash: downstreamReplanRecovery.invalidation.planHash,
3705
+ reboundPlanHash: activeTask.planHash,
3706
+ dirtyFiles: downstreamReplanRecovery.compatibility.dirtyFiles,
3707
+ dirtyWorktreeHash: downstreamReplanRecovery.compatibility.dirtyWorktreeHash,
3708
+ dependencyRecoveryCommit: downstreamReplanRecovery.dependencyRecoveryCommit,
3709
+ boundedEffect: 'restore-needs-fix-and-return-same-task-to-plan-set',
3710
+ },
3711
+ }
3712
+ : {
3713
+ scope: 'task',
3714
+ id: activeTask.id,
3715
+ status: activeTask.status,
3716
+ action: 'doctor',
3717
+ blockedAction: 'task downstream-proof-replan-recover',
3718
+ revision: activeTask.revision,
3719
+ downstreamProofReplanRecovery: {
3720
+ state: 'blocked',
3721
+ blockers: [...downstreamReplanRecovery.blockers, ...environmentBlockers],
3722
+ },
3723
+ }));
3724
+ }
3386
3725
  let activePlanRiskAudit = null;
3387
3726
  let planRiskAuditRecovery = null;
3388
- if (activeTask.planHash) {
3727
+ const requiresReplacementPlan = activeTask.status === 'needs_fix'
3728
+ && !activeTask.steps.some((step) => step.status === 'failed');
3729
+ if (activeTask.planHash && !requiresReplacementPlan) {
3389
3730
  try {
3390
3731
  activePlanRiskAudit = readCurrentPlanRiskAudit(this.store, activeTask);
3391
3732
  }
3392
3733
  catch (error) {
3393
3734
  if (!(error instanceof WorkflowError) || error.code !== 'TRANSITION_BLOCKED')
3394
3735
  throw error;
3395
- planRiskAuditRecovery = {
3396
- scope: 'task',
3397
- id: activeTask.id,
3398
- status: activeTask.status,
3399
- action: 'task plan-risk-audit',
3400
- revision: activeTask.revision,
3401
- planRiskAuditRecovery: {
3402
- state: 'required',
3403
- reason: error.message,
3404
- currentBriefHash: activeTask.briefHash,
3405
- currentPlanHash: activeTask.planHash,
3406
- knowledgeMapRevision: activeTask.knowledgeMapRevision,
3407
- knowledgeMapHash: activeTask.knowledgeMapHash,
3408
- },
3409
- };
3736
+ const blockingDecision = error.details.decision;
3737
+ planRiskAuditRecovery = blockingDecision === 'stop-escalate' || blockingDecision === 'split-required'
3738
+ ? {
3739
+ scope: 'task',
3740
+ id: activeTask.id,
3741
+ status: activeTask.status,
3742
+ action: blockingDecision === 'stop-escalate' ? 'task plan-set' : 'doctor',
3743
+ blockedAction: 'task authorize',
3744
+ revision: activeTask.revision,
3745
+ planRiskAuditDecision: {
3746
+ state: 'recorded-blocking',
3747
+ decision: blockingDecision,
3748
+ reason: error.message,
3749
+ requiredAction: blockingDecision === 'stop-escalate'
3750
+ ? 'Replace the rejected Step Plan before requesting fresh authorization.'
3751
+ : 'Resolve the required cross-Task split through an explicit Milestone topology decision.',
3752
+ },
3753
+ ...(blockingDecision === 'stop-escalate'
3754
+ ? { taskPlanContract: this.taskPlanContract(status.projectId, activeTask) }
3755
+ : {}),
3756
+ }
3757
+ : {
3758
+ scope: 'task',
3759
+ id: activeTask.id,
3760
+ status: activeTask.status,
3761
+ action: 'task plan-risk-audit',
3762
+ revision: activeTask.revision,
3763
+ planRiskAuditRecovery: {
3764
+ state: 'required',
3765
+ reason: error.message,
3766
+ currentBriefHash: activeTask.briefHash,
3767
+ currentPlanHash: activeTask.planHash,
3768
+ knowledgeMapRevision: activeTask.knowledgeMapRevision,
3769
+ knowledgeMapHash: activeTask.knowledgeMapHash,
3770
+ },
3771
+ };
3410
3772
  }
3411
3773
  }
3412
3774
  const taskNext = activeTask.status === 'planning'
3413
3775
  ? null
3414
- : planRiskAuditRecovery
3415
- ?? this.nextForTaskWithDelegation(status.projectId, repositoryRoot, activeTask, activePlanRiskAudit);
3776
+ : requiresReplacementPlan
3777
+ ? this.nextForTaskWithDelegation(status.projectId, repositoryRoot, activeTask, null)
3778
+ : planRiskAuditRecovery
3779
+ ?? this.nextForTaskWithDelegation(status.projectId, repositoryRoot, activeTask, activePlanRiskAudit);
3416
3780
  // Active Step work must outrank Project Knowledge refresh/rebind. Both completion and
3417
3781
  // strict review are executable while context refresh correctly remains forbidden.
3418
3782
  const hardStop = taskNext?.action === 'task corrective-decision'
@@ -3420,6 +3784,7 @@ export class WorkflowService {
3420
3784
  const terminalCorrectiveDecision = taskNext?.correctiveDecisionGate?.decision;
3421
3785
  if (taskNext?.action === 'task step-complete'
3422
3786
  || taskNext?.action === 'task step-review'
3787
+ || taskNext?.action === 'task downstream-proof-recover'
3423
3788
  || taskNext?.action === 'task remediation-mode-recover'
3424
3789
  || taskNext?.action === 'task plan-integrity-recover'
3425
3790
  || taskNext?.blockedAction === 'task plan-integrity-recover'
@@ -3459,7 +3824,9 @@ export class WorkflowService {
3459
3824
  ? this.store.hashArtifact(this.store.taskRoot(status.projectId, activeTask.id), 'plan.md')
3460
3825
  : null)
3461
3826
  : null;
3462
- const contextRefreshOptions = action === 'project-memory reconcile' && refreshAssessment?.eligible
3827
+ const contextRefreshOptions = action === 'project-memory reconcile'
3828
+ && taskNext?.action !== 'task plan-set'
3829
+ && refreshAssessment?.eligible
3463
3830
  ? this.delegatedContextRefreshOptions(status.projectId, activeTask, knowledgeMap, refreshAssessment.mode)
3464
3831
  : [];
3465
3832
  if (requestedTask && action === 'project-memory reconcile' && contextRefreshOptions.length === 0) {
@@ -3507,6 +3874,9 @@ export class WorkflowService {
3507
3874
  }));
3508
3875
  }
3509
3876
  const knowledgeMapHash = hashKnowledgeMap(knowledgeMap);
3877
+ if (taskNext?.action === 'task plan-set') {
3878
+ return withAdoption(withMilestoneCancellation(taskNext));
3879
+ }
3510
3880
  if (taskCanRebindKnowledge(activeTask) &&
3511
3881
  activeTask.planHash &&
3512
3882
  (activeTask.knowledgeMapRevision !== knowledgeMap.revision ||
@@ -3842,6 +4212,12 @@ export class WorkflowService {
3842
4212
  }
3843
4213
  }
3844
4214
  const next = this.taskGitNavigationOverride(task) ?? nextForTask(task);
4215
+ const nextStepId = typeof next.stepId === 'string' ? next.stepId : null;
4216
+ const downstreamProofCarryover = next.action === 'task run'
4217
+ && nextStepId
4218
+ && changedFiles(repositoryRoot).length > 0
4219
+ ? assessDownstreamProofCarryover(this.store, repositoryRoot, task, nextStepId)
4220
+ : null;
3845
4221
  let mechanicalFeasibilityOverlay = {};
3846
4222
  if (task.status === 'awaiting_execution_authorization' && task.planHash) {
3847
4223
  try {
@@ -4364,6 +4740,29 @@ export class WorkflowService {
4364
4740
  }
4365
4741
  : {}),
4366
4742
  ...mechanicalFeasibilityOverlay,
4743
+ ...(downstreamProofCarryover?.applicable
4744
+ ? downstreamProofCarryover.eligible
4745
+ ? {
4746
+ downstreamProofCarryover: {
4747
+ state: 'validated',
4748
+ invalidationEventId: downstreamProofCarryover.invalidationEventId,
4749
+ stepId: nextStepId,
4750
+ dirtyFiles: downstreamProofCarryover.dirtyFiles,
4751
+ dirtyWorktreeHash: downstreamProofCarryover.dirtyWorktreeHash,
4752
+ meaning: 'The replacement Step may adopt exactly these preserved uncommitted bytes on task run.',
4753
+ },
4754
+ }
4755
+ : {
4756
+ action: 'doctor',
4757
+ blockedAction: 'task run',
4758
+ downstreamProofCarryover: {
4759
+ state: 'blocked',
4760
+ invalidationEventId: downstreamProofCarryover.invalidationEventId,
4761
+ stepId: nextStepId,
4762
+ blockers: downstreamProofCarryover.blockers,
4763
+ },
4764
+ }
4765
+ : {}),
4367
4766
  ...(delegatedApprovalOptions.length > 0 ? { delegatedApprovalOptions } : {}),
4368
4767
  };
4369
4768
  if (handoffPosture.state === 'pending') {
@@ -4400,6 +4799,12 @@ export class WorkflowService {
4400
4799
  };
4401
4800
  }
4402
4801
  assertTaskContextChangeAllowed(task) {
4802
+ if (task.status === 'needs_fix' && !task.steps.some((step) => step.status === 'failed')) {
4803
+ throw new WorkflowError('TRANSITION_BLOCKED', `Task ${task.id} requires a replacement Step Plan; Project Knowledge cannot rebind or refresh the stale Plan.`, {
4804
+ taskId: task.id,
4805
+ requiredAction: 'Run task plan-set with the full unfinished remainder before requesting authorization.',
4806
+ });
4807
+ }
4403
4808
  const posture = this.taskTerminalCorrectivePosture(task);
4404
4809
  if (!posture)
4405
4810
  return;
@@ -4521,6 +4926,7 @@ export class WorkflowService {
4521
4926
  if (!taskIsActionableFromRepositoryNext(task, milestonesById, tasksById)
4522
4927
  || !task.planHash
4523
4928
  || !taskCanRebindKnowledge(task)
4929
+ || (task.status === 'needs_fix' && !task.steps.some((step) => step.status === 'failed'))
4524
4930
  || this.taskHasTerminalCorrectiveDecision(task))
4525
4931
  continue;
4526
4932
  const assessment = assessDelegatedKnowledgeRefresh(approved, inspected, task, this.store.hashArtifact(this.store.taskRoot(projectId, task.id), 'plan.md'));
@@ -4650,7 +5056,7 @@ export class WorkflowService {
4650
5056
  },
4651
5057
  };
4652
5058
  }
4653
- inspectDependencyProvenanceCandidate(snapshot, task) {
5059
+ inspectDependencyProvenanceCandidate(snapshot, task, activeDownstreamProof = null, downstreamProofReplan = null) {
4654
5060
  const versions = inspectWorkflowVersions(snapshot.identity.repositoryRoot, [task.baseBranch]);
4655
5061
  const now = this.now().getTime();
4656
5062
  return inspectDependencyProvenanceRecovery(snapshot.identity.repositoryRoot, task, {
@@ -4670,7 +5076,7 @@ export class WorkflowService {
4670
5076
  currentBranch: versions.currentBranch,
4671
5077
  baseBranch: versions.milestoneBases.find((base) => base.branch === task.baseBranch)?.version ?? null,
4672
5078
  },
4673
- });
5079
+ }, activeDownstreamProof, downstreamProofReplan);
4674
5080
  }
4675
5081
  withTaskNavigationContracts(task, next) {
4676
5082
  const action = typeof next.action === 'string' ? next.action : null;