codex-workflow-v2 2.0.0-beta.14 → 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.
Files changed (93) hide show
  1. package/README.md +3 -3
  2. package/dist/reviewer-runtime-build.json +24 -20
  3. package/dist/src/alpha6/adoption.d.ts +1 -1
  4. package/dist/src/alpha6/adoption.js +26 -8
  5. package/dist/src/alpha6/adoption.js.map +1 -1
  6. package/dist/src/alpha6/corrective-decision-boundary.js +9 -3
  7. package/dist/src/alpha6/corrective-decision-boundary.js.map +1 -1
  8. package/dist/src/alpha6/remediation.d.ts +2 -0
  9. package/dist/src/alpha6/remediation.js +68 -2
  10. package/dist/src/alpha6/remediation.js.map +1 -1
  11. package/dist/src/alpha7/autonomy.d.ts +9 -0
  12. package/dist/src/alpha7/autonomy.js +40 -1
  13. package/dist/src/alpha7/autonomy.js.map +1 -1
  14. package/dist/src/beta1/project-transaction.js +14 -4
  15. package/dist/src/beta1/project-transaction.js.map +1 -1
  16. package/dist/src/cli-actions.d.ts +3 -3
  17. package/dist/src/cli-actions.js +5 -0
  18. package/dist/src/cli-actions.js.map +1 -1
  19. package/dist/src/cli.js +31 -1
  20. package/dist/src/cli.js.map +1 -1
  21. package/dist/src/contracts.d.ts +19 -2
  22. package/dist/src/delegation.d.ts +5 -1
  23. package/dist/src/delegation.js +4 -0
  24. package/dist/src/delegation.js.map +1 -1
  25. package/dist/src/failed-checkpoint-update.d.ts +69 -0
  26. package/dist/src/failed-checkpoint-update.js +171 -0
  27. package/dist/src/failed-checkpoint-update.js.map +1 -0
  28. package/dist/src/lifecycle/corrective-replan-credential-core.d.ts +2 -0
  29. package/dist/src/lifecycle/corrective-replan-credential-core.js +45 -9
  30. package/dist/src/lifecycle/corrective-replan-credential-core.js.map +1 -1
  31. package/dist/src/lifecycle/corrective-replan.js +12 -8
  32. package/dist/src/lifecycle/corrective-replan.js.map +1 -1
  33. package/dist/src/lifecycle/semantic-registry.js +4 -2
  34. package/dist/src/lifecycle/semantic-registry.js.map +1 -1
  35. package/dist/src/observed-routes.js +5 -0
  36. package/dist/src/observed-routes.js.map +1 -1
  37. package/dist/src/state/corrective-replan-executor.js +1 -0
  38. package/dist/src/state/corrective-replan-executor.js.map +1 -1
  39. package/dist/src/state/corrective-replan-public.d.ts +9 -1
  40. package/dist/src/state/corrective-replan-public.js +49 -7
  41. package/dist/src/state/corrective-replan-public.js.map +1 -1
  42. package/dist/src/version.d.ts +1 -1
  43. package/dist/src/version.js +1 -1
  44. package/dist/src/version.js.map +1 -1
  45. package/dist/src/workflow-blocker-route.js +1 -1
  46. package/dist/src/workflow-blocker-route.js.map +1 -1
  47. package/dist/src/workflow.d.ts +40 -1
  48. package/dist/src/workflow.js +203 -33
  49. package/dist/src/workflow.js.map +1 -1
  50. package/docs/autonomy-guardrails.md +4 -4
  51. package/docs/development-flow.md +8 -6
  52. package/docs/project-memory.md +2 -2
  53. package/docs/release.md +7 -1
  54. package/docs/updating-existing-project.md +37 -0
  55. package/package.json +1 -1
  56. package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +1 -1
  57. package/plugins/codex-workflow-gateway/references/chat-dispatch.md +17 -5
  58. package/plugins/codex-workflow-gateway/references/protocol.md +39 -43
  59. package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +13 -3
  60. package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +18 -33
  61. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +15 -10
  62. package/references/state-machine.md +3 -1
  63. package/roles/delivery-coordinator.md +31 -11
  64. package/roles/scope-lead.md +1 -1
  65. package/roles/technical-planner.md +4 -1
  66. package/schemas/adoption-posture-event.schema.json +11 -0
  67. package/schemas/authorization-event.schema.json +1 -1
  68. package/schemas/corrective-decision-event.schema.json +23 -3
  69. package/schemas/corrective-replan-credentials.private.schema.json +8 -5
  70. package/schemas/corrective-replan-execution-event.private.schema.json +9 -0
  71. package/schemas/corrective-replan-public.schema.json +2 -1
  72. package/schemas/delegation-grant.schema.json +1 -1
  73. package/schemas/task.schema.json +18 -0
  74. package/schemas/transition-definition.schema.json +4 -0
  75. package/src/alpha6/adoption.ts +30 -7
  76. package/src/alpha6/corrective-decision-boundary.ts +9 -3
  77. package/src/alpha6/remediation.ts +69 -2
  78. package/src/alpha7/autonomy.ts +39 -1
  79. package/src/beta1/project-transaction.ts +13 -4
  80. package/src/cli-actions.ts +5 -0
  81. package/src/cli.ts +33 -0
  82. package/src/contracts.ts +19 -1
  83. package/src/delegation.ts +5 -1
  84. package/src/failed-checkpoint-update.ts +203 -0
  85. package/src/lifecycle/corrective-replan-credential-core.ts +39 -10
  86. package/src/lifecycle/corrective-replan.ts +12 -8
  87. package/src/lifecycle/semantic-registry.ts +4 -2
  88. package/src/observed-routes.ts +5 -0
  89. package/src/state/corrective-replan-executor.ts +1 -0
  90. package/src/state/corrective-replan-public.ts +50 -6
  91. package/src/version.ts +1 -1
  92. package/src/workflow-blocker-route.ts +1 -1
  93. package/src/workflow.ts +192 -22
@@ -13,9 +13,11 @@ import {
13
13
  } from '../alpha6/remediation.js';
14
14
  import { pathAllowed, validatePlan } from '../domain/validation.js';
15
15
  import { WorkflowError } from '../errors.js';
16
+ import { assertDelegationCanAuthorize } from '../delegation.js';
16
17
  import { hashKnowledgeMap } from '../memory.js';
17
18
  import { changedFiles, headCommit } from '../repository.js';
18
19
  import { hashDirtyWorktree } from '../alpha6/downstream-proof.js';
20
+ import { readFailedCheckpointDirtyTransport } from '../failed-checkpoint-update.js';
19
21
  import { PACKAGE_VERSION } from '../version.js';
20
22
  import { GuardedTransitionCatalog } from '../lifecycle/catalog.js';
21
23
  import { correctiveReplanBindingManifest } from '../lifecycle/corrective-replan-binding-manifest.js';
@@ -92,6 +94,7 @@ export interface CorrectiveReplanValidationEvidence {
92
94
  }
93
95
 
94
96
  export interface CorrectiveReplanPublicCandidate {
97
+ readonly delegation?: { readonly grantId: string; readonly actor: string };
95
98
  readonly plan: TaskPlanInput;
96
99
  readonly validation: CorrectiveReplanValidationEvidence;
97
100
  readonly riskAudit: CorrectiveReplanRiskAuditInput;
@@ -99,7 +102,7 @@ export interface CorrectiveReplanPublicCandidate {
99
102
  }
100
103
 
101
104
  export interface CorrectiveReplanHumanGate {
102
- readonly kind: 'corrective-replan-human-gate';
105
+ readonly kind: 'corrective-replan-human-gate' | 'corrective-replan-delegated-binding';
103
106
  readonly version: 1;
104
107
  readonly taskId: string;
105
108
  readonly preparedAtMs: number;
@@ -136,6 +139,7 @@ export interface CorrectiveReplanPublicCredential {
136
139
  }
137
140
 
138
141
  export interface CorrectiveReplanPublicController {
142
+ authorizeDelegated(input: { taskId: string; candidate: CorrectiveReplanPublicCandidate }): CorrectiveReplanPublicCredential;
139
143
  prepare(input: { taskId: string; candidate: CorrectiveReplanPublicCandidate }): CorrectiveReplanHumanGateResult;
140
144
  confirm(input: {
141
145
  taskId: string;
@@ -192,6 +196,7 @@ export function createCorrectiveReplanPublicController(options: ControllerOption
192
196
  const prepare = (input: { taskId: string; candidate: CorrectiveReplanPublicCandidate }): CorrectiveReplanHumanGateResult => {
193
197
  const owned = ownRequest(input);
194
198
  assertPublicCandidate(owned.candidate);
199
+ if (owned.candidate.delegation) throw new WorkflowError('INVALID_ARGUMENT', 'Use corrective-replan-authorize for delegated candidates.');
195
200
  const preparedAtMs = now().getTime();
196
201
  const chain = buildChain(options, owned.taskId, owned.candidate, preparedAtMs);
197
202
  return assertCorrectiveReplanPublicOutput('humanGateResult', deepFreeze({
@@ -209,6 +214,7 @@ export function createCorrectiveReplanPublicController(options: ControllerOption
209
214
  }): CorrectiveReplanPublicCredential => {
210
215
  const owned = ownRequest(input);
211
216
  assertPublicCandidate(owned.candidate);
217
+ if (owned.candidate.delegation) throw new WorkflowError('INVALID_ARGUMENT', 'Delegated candidates cannot use Human confirmation.');
212
218
  assertGateTime(owned.gate, now().getTime());
213
219
  const chain = buildChain(options, owned.taskId, owned.candidate, owned.gate.preparedAtMs);
214
220
  if (!same(chain.gate, owned.gate)) throw new WorkflowError('STATE_CONFLICT', 'Corrective-replan Human gate is stale or malformed.');
@@ -237,6 +243,17 @@ export function createCorrectiveReplanPublicController(options: ControllerOption
237
243
  }));
238
244
  };
239
245
 
246
+ const authorizeDelegated = (input: { taskId: string; candidate: CorrectiveReplanPublicCandidate }): CorrectiveReplanPublicCredential => {
247
+ const owned = ownRequest(input); assertPublicCandidate(owned.candidate);
248
+ if (!owned.candidate.delegation) throw new WorkflowError('INVALID_ARGUMENT', 'Delegated corrective replan requires an explicit grant and actor.');
249
+ const chain = buildChain(options, owned.taskId, owned.candidate, now().getTime());
250
+ const confirmation = accepted(chain.kernel.confirmDelegated({ challenge: chain.challenge }), 'Delegated authorization was rejected.');
251
+ const receipt = accepted(chain.kernel.issueReceipt({ planValidation: chain.planValidation, confirmation: confirmation.handle }), 'Delegated receipt issuance was rejected.');
252
+ const event = accepted(chain.kernel.materializeStoredEvent({ receipt: receipt.handle }), 'Plan Validation event materialization was rejected.');
253
+ return assertCorrectiveReplanPublicOutput('credential', deepFreeze({ kind: 'corrective-replan-public-credential', version: 1,
254
+ gate: chain.gate, receipt: receipt.receipt as JsonRecord, planValidationEvent: event.event as JsonRecord }));
255
+ };
256
+
240
257
  const execute = (input: {
241
258
  taskId: string;
242
259
  candidate: CorrectiveReplanPublicCandidate;
@@ -292,7 +309,7 @@ export function createCorrectiveReplanPublicController(options: ControllerOption
292
309
  return assertCorrectiveReplanPublicOutput('executionResult', executor(owned.taskId, inertKernel()).recover({ taskId: owned.taskId, transactionId: owned.transactionId }));
293
310
  };
294
311
 
295
- return deepFreeze({ prepare, confirm, execute, recover });
312
+ return deepFreeze({ prepare, confirm, authorizeDelegated, execute, recover });
296
313
  }
297
314
 
298
315
  function assertCanonicalAdmission(
@@ -397,13 +414,15 @@ function buildChain(
397
414
  } },
398
415
  });
399
416
  const validation = accepted(kernel.issuePlanValidation({ taskId, draft: validationDraft(initialSnapshot, candidate.validation) }), 'Plan Validation issuance was rejected.');
400
- const challenge = accepted(kernel.issueHumanChallenge({ planValidation: validation.handle }), 'Human challenge issuance was rejected.');
417
+ const challenge = accepted(candidate.delegation
418
+ ? kernel.issueDelegatedChallenge({ planValidation: validation.handle })
419
+ : kernel.issueHumanChallenge({ planValidation: validation.handle }), 'Authorization binding issuance was rejected.');
401
420
  const delivery = record(challenge.delivery);
402
421
  const binding = record(validation.binding);
403
422
  const attestation = record(validation.attestation);
404
423
  const values = record(binding.values);
405
424
  const gateBase = {
406
- kind: 'corrective-replan-human-gate' as const,
425
+ kind: candidate.delegation ? 'corrective-replan-delegated-binding' as const : 'corrective-replan-human-gate' as const,
407
426
  version: 1 as const,
408
427
  taskId,
409
428
  preparedAtMs,
@@ -439,11 +458,11 @@ function confirmChain(
439
458
  const chain = buildChain(options, taskId, candidate, credential.gate.preparedAtMs, previousEventHash);
440
459
  if (!same(chain.gate, credential.gate)) throw new WorkflowError('STATE_CONFLICT', 'Corrective-replan Human gate no longer matches current state.');
441
460
  const receiptCandidate = record(credential.receipt);
442
- const confirmedAtMs = number(get(receiptCandidate, '/human/confirmedAtMs'));
461
+ const confirmedAtMs = number(get(receiptCandidate, candidate.delegation ? '/delegated/authorizedAtMs' : '/human/confirmedAtMs'));
443
462
  const issuedAtMs = number(receiptCandidate.issuedAtMs);
444
463
  if (!Number.isSafeInteger(confirmedAtMs) || confirmedAtMs !== issuedAtMs) throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-replan receipt time is invalid.');
445
464
  chain.clock.set(confirmedAtMs);
446
- const confirmation = accepted(chain.kernel.confirmHuman({
465
+ const confirmation = accepted(candidate.delegation ? chain.kernel.confirmDelegated({ challenge: chain.challenge }) : chain.kernel.confirmHuman({
447
466
  challenge: chain.challenge,
448
467
  confirmation: {
449
468
  kind: 'corrective-replan-human-confirmation', version: 1,
@@ -494,6 +513,7 @@ function trustedSnapshot(
494
513
  }
495
514
  const task = options.store.readTask(options.projectId, taskId);
496
515
  validateCandidate(task, candidate);
516
+ const delegatedAuthority = candidate.delegation ? delegatedReplanAuthority(options, task, candidate) : null;
497
517
  const taskRoot = options.store.taskRoot(options.projectId, taskId);
498
518
  if (options.store.hashArtifact(taskRoot, 'brief.md') !== task.briefHash
499
519
  || !task.planHash || options.store.hashArtifact(taskRoot, 'plan.md') !== task.planHash) {
@@ -537,6 +557,7 @@ function trustedSnapshot(
537
557
  const remediationTail = matchingRemediations.at(-1);
538
558
  if (!remediationTail || matchingRemediations.length !== posture.attemptCount) throw new WorkflowError('STATE_CORRUPT', 'Corrective-replan remediation tail is ambiguous.');
539
559
  assertCorrectiveReplanKnowledgeTransition(
560
+ options.store,
540
561
  options.repositoryRoot,
541
562
  task,
542
563
  posture.stepId,
@@ -601,6 +622,7 @@ function trustedSnapshot(
601
622
  });
602
623
  return {
603
624
  snapshotVersion: 2,
625
+ ...(delegatedAuthority ? { delegatedAuthority } : {}),
604
626
  bindingManifest: correctiveReplanBindingManifest,
605
627
  worktree,
606
628
  baseValues,
@@ -625,6 +647,7 @@ function trustedSnapshot(
625
647
  }
626
648
 
627
649
  function assertCorrectiveReplanKnowledgeTransition(
650
+ store: FileStateStore,
628
651
  repositoryRoot: string,
629
652
  task: TaskState,
630
653
  stepId: string,
@@ -634,6 +657,13 @@ function assertCorrectiveReplanKnowledgeTransition(
634
657
  targetKnowledgeHash: string,
635
658
  ): void {
636
659
  if (task.knowledgeMapRevision === targetKnowledgeRevision && task.knowledgeMapHash === targetKnowledgeHash) return;
660
+ const terminal = readRemediationEvents(store, task).filter(event => event.stepId === stepId).at(-1);
661
+ if (terminal?.failureEvidence.dirtyWorktreeHash) {
662
+ const transported = readFailedCheckpointDirtyTransport(store, repositoryRoot, task, stepId,
663
+ terminal.failureEvidence.completionCommit, terminal.failureEvidence.dirtyWorktreeHash);
664
+ if (transported && transported.targetHead === gitHead
665
+ && remediationEventIds.includes(terminal.eventId)) return;
666
+ }
637
667
  const compatibility = [...(task.dependencyProvenanceRecoveries ?? [])].reverse().find((record) =>
638
668
  record.commitSha === gitHead
639
669
  && record.packageVersion === PACKAGE_VERSION
@@ -712,6 +742,20 @@ function guardedStepWorktreeSnapshot(
712
742
  };
713
743
  }
714
744
 
745
+ function delegatedReplanAuthority(options: ControllerOptions, task: TaskState, candidate: CorrectiveReplanPublicCandidate): JsonRecord {
746
+ const request = candidate.delegation!;
747
+ const grant = options.store.readDelegation(options.projectId, request.grantId);
748
+ const evidence = assertDelegationCanAuthorize(grant, 'task.execution_authorize', request.actor, task, (options.now ?? (() => new Date()))());
749
+ // These are public actor identifiers, not user confirmation or vault credentials.
750
+ const identityHash = (actor: string) => createHash('sha256').update(actor).digest('hex');
751
+ if (candidate.authority.actorIdentityHash !== identityHash(grant.delegate)
752
+ || candidate.authority.humanPrincipalIdentityHash !== identityHash(grant.principal))
753
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Delegated corrective actor/principal hashes must match the exact grant actors (SHA256 UTF-8).');
754
+ if (candidate.plan.objective !== task.planObjective)
755
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Delegated corrective replan cannot change the approved objective; return to Discovery.');
756
+ return { ...evidence, expiresAt: grant.expiresAt };
757
+ }
758
+
715
759
  function validateCandidate(task: TaskState, candidate: CorrectiveReplanPublicCandidate): void {
716
760
  validatePlan(candidate.plan);
717
761
  for (const requirement of task.requirements) if (!candidate.plan.requirements.includes(requirement)) {
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export const PACKAGE_NAME = 'codex-workflow-v2';
2
- export const PACKAGE_VERSION = '2.0.0-beta.14';
2
+ export const PACKAGE_VERSION = '2.0.0-beta.14.1';
@@ -79,7 +79,7 @@ export function withWorkflowBlockerRoute(next: ObjectValue, packageVersion: stri
79
79
  projectName: 'codex-workflow-v2',
80
80
  chatType: 'workflow-blocker',
81
81
  title: patchVersion,
82
- modelRequest: { phase: 'implementation', complexity: 'complex', highRisk: false },
82
+ modelSelection: 'destination-default',
83
83
  requiredPacketFields: [
84
84
  'blockerId', 'packageVersion', 'sourceProject', 'sourceThreadId', 'source',
85
85
  'freshStatusHash', 'freshNextHash', 'repositoryState', 'expectedContinuation',
package/src/workflow.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { assessNavigationDirtyCarryover } from './alpha6/navigation-dirty-carryover.js';
2
2
  import { navigationUpdateSource, assertNavigationSourceInstalled, inspectNavigationTransport, type NavigationUpdateReceipt } from './navigation-update.js';
3
+ import { failedCheckpointUpdateSource, assertFailedCheckpointInstalled, inspectFailedCheckpointTransport, readFailedCheckpointDirtyTransport,
4
+ FAILED_CHECKPOINT_SOURCE, FAILED_CHECKPOINT_TARGET, type FailedCheckpointUpdateReceipt } from './failed-checkpoint-update.js';
3
5
  import { verifyNavigationUpdateArtifact } from './navigation-update-artifact.js';
4
6
  import type { CheckExecutionOptions } from './checks/runner.js';
5
7
  import { taskSyncChecks, taskSyncChecksHash, assertNoPendingBaseSyncValidation, assessPendingBaseSyncValidation, assertPendingBaseSyncValidationReady } from './checks/task-sync.js';
@@ -159,6 +161,7 @@ import {
159
161
  } from './alpha6/root-cause-replan-carryover.js';
160
162
  import {
161
163
  appendCorrectiveDecisionEvent,
164
+ firstFailurePlanningObstructionEvidence,
162
165
  appendPlanIntegrityRecoveryDecision,
163
166
  assertCorrectiveAuditorIndependence,
164
167
  assertGuardedRemediationAttemptAllowed,
@@ -226,6 +229,8 @@ import {
226
229
  } from './alpha6/review.js';
227
230
  import {
228
231
  applyMilestoneAutonomyContract,
232
+ bindMilestoneAutonomyToGrant,
233
+ milestoneSemanticScopeHash,
229
234
  assertAutonomousPlanEvolution,
230
235
  prepareMilestoneAutonomyContract,
231
236
  readMilestoneAutonomyEvents,
@@ -568,6 +573,10 @@ export class WorkflowService {
568
573
  };
569
574
  }
570
575
 
576
+ authorizeDelegatedCorrectiveReplan(repository: string, taskId: string, candidate: CorrectiveReplanPublicCandidate): CorrectiveReplanPublicCredential {
577
+ return this.correctiveReplanController(repository).authorizeDelegated({ taskId, candidate });
578
+ }
579
+
571
580
  prepareCorrectiveReplan(
572
581
  repository: string,
573
582
  taskId: string,
@@ -2022,6 +2031,19 @@ export class WorkflowService {
2022
2031
  }
2023
2032
  }
2024
2033
 
2034
+ bindMilestoneAutonomy(repository: string, milestoneId: string, expectedRevision: number,
2035
+ actor: string, grantId: string): ActiveMilestoneAutonomy {
2036
+ const { identity, locks } = this.#mutationContext(repository);
2037
+ const lease = locks.acquire(milestoneId, 'workflow-core:autonomy-binding');
2038
+ try {
2039
+ const milestone = this.readMilestoneCurrent(identity.projectId, milestoneId);
2040
+ assertExpectedRevision(milestone, expectedRevision);
2041
+ this.requireActiveKnowledgeMap(identity.repositoryRoot, identity.projectId);
2042
+ return bindMilestoneAutonomyToGrant({ store: this.store, projectId: identity.projectId,
2043
+ milestone, actor, grantId, now: this.now() });
2044
+ } finally { locks.release(milestoneId, lease.token); }
2045
+ }
2046
+
2025
2047
  evolveMilestonePlanAutonomously(
2026
2048
  repository: string,
2027
2049
  milestoneId: string,
@@ -2824,7 +2846,7 @@ export class WorkflowService {
2824
2846
  const { identity } = this.#mutationContext(repository);
2825
2847
  const task = this.store.readTask(identity.projectId, taskId);
2826
2848
  assertExpectedRevision(task, expectedRevision);
2827
- const contextBlocker = this.taskContextChangeBlocker(task, 'rebind');
2849
+ const contextBlocker = this.taskContextChangeBlocker(task, 'rebind', identity.repositoryRoot);
2828
2850
  // Preserve Plan-corruption precedence over the active-Step rejection.
2829
2851
  if (contextBlocker && !contextBlocker.activeStep) throw contextBlocker.error;
2830
2852
  if (
@@ -2935,7 +2957,7 @@ export class WorkflowService {
2935
2957
  const currentMap = this.store.readKnowledgeMap(identity.projectId);
2936
2958
  assertExpectedRevision(task, expectedTaskRevision);
2937
2959
  assertExpectedRevision(currentMap, expectedKnowledgeMapRevision);
2938
- const contextBlocker = this.taskContextChangeBlocker(task, 'refresh');
2960
+ const contextBlocker = this.taskContextChangeBlocker(task, 'refresh', identity.repositoryRoot);
2939
2961
  if (contextBlocker) throw contextBlocker.error;
2940
2962
 
2941
2963
  const inspectedMap = inspectKnowledgeMap(
@@ -2943,8 +2965,12 @@ export class WorkflowService {
2943
2965
  scanProjectKnowledge(identity.repositoryRoot, identity.projectId, this.now()),
2944
2966
  );
2945
2967
  const transitions: string[] = [];
2946
- const planningRecovery = readFailedStepPlanningRecovery(this.store, task);
2947
- const currentPlanRiskAudit = planningRecovery?.current ?? readCurrentPlanRiskAudit(this.store, task);
2968
+ const retainedAudits = JSON.stringify(readPlanRiskAuditEvents(this.store, task));
2969
+ const retainedRemediation = JSON.stringify(readRemediationEvents(this.store, task));
2970
+ const transport = this.registeredPlanningContextTransport(task, identity.repositoryRoot);
2971
+ const planningRecovery = transport ? null : readFailedStepPlanningRecovery(this.store, task);
2972
+ const planningOnly = Boolean(planningRecovery || transport);
2973
+ const currentPlanRiskAudit = transport ? null : planningRecovery?.current ?? readCurrentPlanRiskAudit(this.store, task);
2948
2974
  const currentPlanArtifactHash = task.planHash
2949
2975
  ? this.store.hashArtifact(this.store.taskRoot(identity.projectId, task.id), 'plan.md')
2950
2976
  : null;
@@ -2954,7 +2980,7 @@ export class WorkflowService {
2954
2980
  task,
2955
2981
  currentPlanArtifactHash,
2956
2982
  );
2957
- if (planningRecovery && refreshAssessment.mode !== 'delegated-content-only') {
2983
+ if (planningOnly && refreshAssessment.mode !== 'delegated-content-only') {
2958
2984
  throw new WorkflowError('TRANSITION_BLOCKED', 'Stopped planning context refresh permits content-only Knowledge drift.');
2959
2985
  }
2960
2986
 
@@ -3041,7 +3067,7 @@ export class WorkflowService {
3041
3067
  }
3042
3068
  transitions.push('task knowledge-rebind');
3043
3069
  // A stopped Plan may regain its Knowledge binding, never execution authority.
3044
- if (!planningRecovery) refreshedTask = this.authorizeTask(
3070
+ if (!planningOnly) refreshedTask = this.authorizeTask(
3045
3071
  repository,
3046
3072
  taskId,
3047
3073
  refreshedTask.revision,
@@ -3051,9 +3077,9 @@ export class WorkflowService {
3051
3077
  : 'Delegated content-only context refresh.',
3052
3078
  delegationGrantId,
3053
3079
  );
3054
- if (!planningRecovery) transitions.push('task execution authorize');
3080
+ if (!planningOnly) transitions.push('task execution authorize');
3055
3081
  return {
3056
- authority: planningRecovery ? 'planning-only' as const : 'execution' as const,
3082
+ authority: planningOnly ? 'planning-only' as const : 'execution' as const,
3057
3083
  mode: refreshAssessment.mode,
3058
3084
  transitions,
3059
3085
  addedSources: refreshAssessment.addedSources,
@@ -3063,10 +3089,21 @@ export class WorkflowService {
3063
3089
  },
3064
3090
  validate: (result) => {
3065
3091
  const currentTask = this.store.readTask(identity.projectId, task.id);
3066
- if (currentTask.revision !== result.task.revision || currentTask.status !== (planningRecovery ? 'awaiting_execution_authorization' : 'ready')) {
3092
+ if (currentTask.revision !== result.task.revision || currentTask.status !== (planningOnly ? 'awaiting_execution_authorization' : 'ready')) {
3067
3093
  throw new WorkflowError('STATE_CORRUPT', 'Context refresh transaction did not commit the expected Task posture.');
3068
3094
  }
3069
- if (planningRecovery) {
3095
+ if (transport) {
3096
+ if (!readFailedCheckpointDirtyTransport(this.store, identity.repositoryRoot, currentTask, transport.stepId, transport.sourceHead, transport.sourceDirtyHash)) {
3097
+ throw new WorkflowError('STATE_CORRUPT', 'Transport context proof disappeared.');
3098
+ }
3099
+ if (JSON.stringify(currentTask.steps) !== JSON.stringify(task.steps)
3100
+ || JSON.stringify(currentTask.authorizations) !== JSON.stringify(task.authorizations)
3101
+ || JSON.stringify(readPlanRiskAuditEvents(this.store, currentTask)) !== retainedAudits
3102
+ || JSON.stringify(readRemediationEvents(this.store, currentTask)) !== retainedRemediation) {
3103
+ throw new WorkflowError('STATE_CORRUPT', 'Transport context refresh changed retained evidence.');
3104
+ }
3105
+ }
3106
+ if (planningRecovery && !transport) {
3070
3107
  const rebound = readFailedStepPlanningRecovery(this.store, currentTask);
3071
3108
  if (!rebound || rebound.baseline.eventHash !== planningRecovery.baseline.eventHash
3072
3109
  || JSON.stringify(currentTask.steps) !== JSON.stringify(task.steps)
@@ -5400,6 +5437,7 @@ export class WorkflowService {
5400
5437
  repository: string,
5401
5438
  actor: string,
5402
5439
  confirmationCode: string,
5440
+ delegationGrantId: string | null = null,
5403
5441
  ): AdoptionPostureEvent {
5404
5442
  const context = this.#mutationContext(repository);
5405
5443
  const tasks = context.store.listTasks(context.identity.projectId);
@@ -5420,6 +5458,7 @@ export class WorkflowService {
5420
5458
  },
5421
5459
  actor,
5422
5460
  confirmationCode,
5461
+ delegationGrantId,
5423
5462
  );
5424
5463
  }
5425
5464
 
@@ -5525,6 +5564,51 @@ export class WorkflowService {
5525
5564
  return { snapshot, task, root: snapshot.identity.repositoryRoot };
5526
5565
  }
5527
5566
 
5567
+ failedCheckpointUpdateSourcePreflight(repository: string, taskId: string, tarball: string) {
5568
+ try {
5569
+ const { task, root } = this.navigationUpdateBoundary(repository, taskId);
5570
+ assertFailedCheckpointInstalled(root, task, FAILED_CHECKPOINT_SOURCE);
5571
+ const receipt = failedCheckpointUpdateSource(root, this.store, task, verifyNavigationUpdateArtifact(tarball));
5572
+ return { eligible: true, blockers: [] as string[], receipt };
5573
+ } catch (error) {
5574
+ return { eligible: false, blockers: [error instanceof Error ? error.message : String(error)], receipt: null };
5575
+ }
5576
+ }
5577
+
5578
+ failedCheckpointUpdateTransportPreflight(repository: string, taskId: string, tarball: string, receipt: FailedCheckpointUpdateReceipt) {
5579
+ try {
5580
+ const { task, root } = this.navigationUpdateBoundary(repository, taskId);
5581
+ const proof = inspectFailedCheckpointTransport(root, this.store, task, receipt,
5582
+ verifyNavigationUpdateArtifact(tarball, path.join(root, 'node_modules/codex-workflow-v2')));
5583
+ return { eligible: true, blockers: [] as string[], proof,
5584
+ action: 'update failed-checkpoint-recover', expectedRevision: task.revision, actor: receipt.actor };
5585
+ } catch (error) {
5586
+ return { eligible: false, blockers: [error instanceof Error ? error.message : String(error)], proof: null };
5587
+ }
5588
+ }
5589
+
5590
+ recoverFailedCheckpointUpdate(repository: string, taskId: string, expectedRevision: number,
5591
+ tarball: string, receipt: FailedCheckpointUpdateReceipt, actor: string, reason: string): TaskState {
5592
+ this.#mutationContext(repository);
5593
+ const { task, root } = this.navigationUpdateBoundary(repository, taskId);
5594
+ assertExpectedRevision(task, expectedRevision);
5595
+ if (!actor.trim() || actor !== receipt?.actor || !reason.trim()) {
5596
+ throw new WorkflowError('INVALID_ARGUMENT', 'Failed-checkpoint update requires the original claimant and an explicit reason.');
5597
+ }
5598
+ const proof = inspectFailedCheckpointTransport(root, this.store, task, receipt,
5599
+ verifyNavigationUpdateArtifact(tarball, path.join(root, 'node_modules/codex-workflow-v2')));
5600
+ const updated: TaskState = { ...task, status: 'awaiting_execution_authorization',
5601
+ systemCommits: [...task.systemCommits, proof.target.commitSha],
5602
+ dependencyProvenanceRecoveries: [...(task.dependencyProvenanceRecoveries ?? []), {
5603
+ ...proof.target, packageName: PACKAGE_NAME, files: ['package-lock.json', 'package.json'], actor,
5604
+ reason: `${reason.trim()} [failed-checkpoint-update ${receipt.artifact.sha256}]`,
5605
+ recordedAt: this.now().toISOString(), failedCheckpoint: proof.binding,
5606
+ }],
5607
+ };
5608
+ validateTaskHistory(root, updated);
5609
+ return this.store.writeTask(updated, expectedRevision);
5610
+ }
5611
+
5528
5612
  navigationUpdateSourcePreflight(repository: string, taskId: string, tarball: string) {
5529
5613
  try {
5530
5614
  const { task, root } = this.navigationUpdateBoundary(repository, taskId);
@@ -6061,6 +6145,7 @@ export class WorkflowService {
6061
6145
  milestonesById,
6062
6146
  current,
6063
6147
  inspected,
6148
+ identity.repositoryRoot,
6064
6149
  );
6065
6150
  if (refreshOwner) {
6066
6151
  throw new WorkflowError(
@@ -6259,7 +6344,16 @@ export class WorkflowService {
6259
6344
  currentEventType: status.adoption.currentPosture?.eventType ?? null,
6260
6345
  };
6261
6346
  if (status.adoption.status !== 'applied') {
6347
+ const adoptionApprovals = this.listDelegations(status.projectId).flatMap(grant => {
6348
+ try { return [assertDelegationCanAuthorize(grant, 'project.adopt', grant.delegate,
6349
+ { kind: 'project', id: status.projectId }, this.now())]; }
6350
+ catch (error) {
6351
+ if (error instanceof WorkflowError && error.code === 'TRANSITION_BLOCKED') return [];
6352
+ throw error;
6353
+ }
6354
+ });
6262
6355
  return {
6356
+ ...(adoptionApprovals.length ? { delegatedApprovalOptions: adoptionApprovals } : {}),
6263
6357
  scope: 'project',
6264
6358
  projectId: status.projectId,
6265
6359
  status: status.adoption.status,
@@ -6268,7 +6362,7 @@ export class WorkflowService {
6268
6362
  observation: status.observation,
6269
6363
  navigation: status.adoption.safe
6270
6364
  ? {
6271
- kind: 'human-gate',
6365
+ kind: adoptionApprovals.length ? 'delegated' : 'human-gate',
6272
6366
  transitionId: null,
6273
6367
  cataloged: false,
6274
6368
  command: 'state adoption-apply',
@@ -6290,6 +6384,36 @@ export class WorkflowService {
6290
6384
  }
6291
6385
  const withAdoption = (value: Record<string, unknown>): Record<string, unknown> => ({
6292
6386
  ...value,
6387
+ delegatedMilestoneAutonomyOptions: status.milestones.flatMap(milestone => {
6388
+ if (!['active', 'awaiting_execution_authorization'].includes(milestone.status) || !milestone.planHash) return [];
6389
+ const previousContract = readMilestoneAutonomyEvents(this.store, status.projectId, milestone.id).at(-1);
6390
+ if (previousContract) {
6391
+ if (previousContract.semanticScopeHash !== milestoneSemanticScopeHash(milestone)) return [];
6392
+ try {
6393
+ const previousGrant = this.store.readDelegation(status.projectId, previousContract.delegationGrantId);
6394
+ if (previousGrant.status === 'active' && Date.parse(previousGrant.expiresAt) > this.now().getTime()) return [];
6395
+ } catch (error) { if (!(error instanceof WorkflowError) || error.code !== 'NOT_FOUND') throw error; }
6396
+ }
6397
+ try { this.requireActiveKnowledgeMap(repository, status.projectId); }
6398
+ catch (error) {
6399
+ if (error instanceof WorkflowError) return [];
6400
+ throw error;
6401
+ }
6402
+ return this.listDelegations(status.projectId).flatMap(grant => {
6403
+ if (grant.scope.kind !== 'milestone' || grant.scope.id !== milestone.id
6404
+ || !['task.execution_authorize', 'task.final_accept', 'milestone.execution_authorize', 'milestone.final_accept']
6405
+ .every(transition => grant.transitions.some(value => value === transition))) return [];
6406
+ try {
6407
+ assertDelegationCanAuthorize(grant, 'milestone.execution_authorize', grant.delegate, milestone, this.now());
6408
+ return [{ action: 'milestone autonomy-bind', milestoneId: milestone.id,
6409
+ expectedRevision: milestone.revision, actor: grant.delegate, delegationGrantId: grant.id,
6410
+ issuesGrant: false, requiresExactHumanConfirmation: false }];
6411
+ } catch (error) {
6412
+ if (error instanceof WorkflowError && error.code === 'TRANSITION_BLOCKED') return [];
6413
+ throw error;
6414
+ }
6415
+ });
6416
+ }),
6293
6417
  ...(status.milestones.some((milestone) => milestone.status === 'active' && milestone.planHash)
6294
6418
  ? { activeMilestoneAutonomyOptions: status.milestones
6295
6419
  .filter((milestone) => milestone.status === 'active' && milestone.planHash)
@@ -6414,7 +6538,7 @@ export class WorkflowService {
6414
6538
  if (approved?.status === 'active' && approved.approval) {
6415
6539
  const inspected = inspectCurrentKnowledgeMap();
6416
6540
  if (inspected.status === 'stale') prioritizedRefreshTask = this.findDelegatedContextRefreshOwner(
6417
- status.projectId, status.tasks, milestonesById, approved, inspected)?.task ?? null;
6541
+ status.projectId, status.tasks, milestonesById, approved, inspected, repositoryRoot)?.task ?? null;
6418
6542
  }
6419
6543
  }
6420
6544
  const activeTask = requestedTask
@@ -6466,6 +6590,17 @@ export class WorkflowService {
6466
6590
  dependencyProvenanceRecovery: dependencyRecovery,
6467
6591
  });
6468
6592
  }
6593
+ if (PACKAGE_VERSION === FAILED_CHECKPOINT_TARGET && activeTask.status === 'awaiting_execution_authorization'
6594
+ && activeTask.workspaceOwner === 'external' && activeTask.steps.some(step => step.status === 'failed')) {
6595
+ const transport = inspectBoundedWorkflowDependencyCommit(repositoryRoot, headCommit(repositoryRoot));
6596
+ if (transport?.packageVersion === FAILED_CHECKPOINT_TARGET && !activeTask.systemCommits.includes(transport.commitSha)) {
6597
+ return withAdoption({ scope: 'task', id: activeTask.id, revision: activeTask.revision,
6598
+ action: 'update failed-checkpoint-transport-preflight',
6599
+ requiredInputs: ['--tarball <exact target artifact>', '--file <source receipt>'],
6600
+ reason: 'Verify the original failed-checkpoint receipt, native failure and retained product bytes before registering the dependency transport.',
6601
+ });
6602
+ }
6603
+ }
6469
6604
  const navigationSourceHead = activeTask.authorizations.filter(item => item.kind === 'execution' && item.decision === 'approved').at(-1)?.headCommit;
6470
6605
  if (activeTask.status === 'ready' && navigationSourceHead
6471
6606
  && activeTask.dependencyProvenanceRecoveries?.some(item => item.planIntegrity?.mode === 'navigation-fix-dirty-carryover')) {
@@ -6807,6 +6942,7 @@ export class WorkflowService {
6807
6942
  activeTask,
6808
6943
  knowledgeMap,
6809
6944
  refreshAssessment.mode,
6945
+ repositoryRoot,
6810
6946
  )
6811
6947
  : [];
6812
6948
  if (requestedTask && action === 'project-memory reconcile' && contextRefreshOptions.length === 0) {
@@ -6816,6 +6952,7 @@ export class WorkflowService {
6816
6952
  milestonesById,
6817
6953
  storedKnowledgeMap,
6818
6954
  knowledgeMap,
6955
+ repositoryRoot,
6819
6956
  );
6820
6957
  if (refreshOwner && refreshOwner.task.id !== requestedTask.id) {
6821
6958
  throw new WorkflowError(
@@ -6847,7 +6984,7 @@ export class WorkflowService {
6847
6984
  contextRefresh: {
6848
6985
  action: 'task context-refresh',
6849
6986
  mode: refreshAssessment!.mode,
6850
- authority: readFailedStepPlanningRecovery(this.store, activeTask) ? 'planning-only' : 'execution',
6987
+ authority: (this.registeredPlanningContextTransport(activeTask, repositoryRoot) || readFailedStepPlanningRecovery(this.store, activeTask)) ? 'planning-only' : 'execution',
6851
6988
  addedSources: refreshAssessment!.addedSources,
6852
6989
  options: contextRefreshOptions,
6853
6990
  },
@@ -7511,12 +7648,13 @@ export class WorkflowService {
7511
7648
  : {};
7512
7649
  const correctiveReplanOverlay = (posture: Readonly<CorrectiveReplanPosture>): Record<string, unknown> => {
7513
7650
  const evaluation = evaluateTaskCorrectiveReplanAvailability(task, posture);
7651
+ const replanDelegations = this.delegatedApprovalOptions(task.projectId, 'task.execution_authorize', task);
7514
7652
  const writerLease = inspectRawWriterLease(this.store.lockRoot(task.projectId), task.id, this.store.root);
7515
7653
  const command = handoffPosture.state === 'claimed'
7516
7654
  ? 'task corrective-yield'
7517
7655
  : writerLease
7518
7656
  ? 'doctor'
7519
- : 'task corrective-replan-prepare';
7657
+ : replanDelegations.length ? 'task corrective-replan-authorize' : 'task corrective-replan-prepare';
7520
7658
  if (evaluation.allowed && handoffPosture.state === 'none' && writerLease) {
7521
7659
  const navigation = correctiveReplanNavigation(evaluation);
7522
7660
  return {
@@ -7547,16 +7685,19 @@ export class WorkflowService {
7547
7685
  return evaluation.allowed
7548
7686
  ? {
7549
7687
  action: command,
7688
+ ...(replanDelegations.length ? { delegatedApprovalOptions: replanDelegations, correctiveReplanAuthority: { mode: 'delegated', candidateField: 'delegation', actorHash: 'sha256-utf8-grant-delegate', principalHash: 'sha256-utf8-grant-principal', grantTransition: 'task.execution_authorize' } } : {}),
7550
7689
  blockedAction: 'task plan-set',
7551
7690
  stepId: posture.stepId,
7552
- navigation: { ...correctiveReplanNavigation(evaluation), command },
7691
+ navigation: { ...correctiveReplanNavigation(evaluation), kind: replanDelegations.length ? 'executable' : 'human-gate', command },
7553
7692
  correctiveDecisionGate: {
7554
7693
  attemptCount: posture.attemptCount,
7555
7694
  decision: posture.correctiveDecision?.decision ?? null,
7556
7695
  decisionEventId: posture.correctiveDecision?.eventId ?? null,
7557
7696
  requiredAction: handoffPosture.state === 'claimed'
7558
7697
  ? 'Yield the exact C1 claim and writer lease before preparing replacement authority.'
7559
- : 'Prepare a state-bound candidate, confirm the Human gate, then execute its exact receipt.',
7698
+ : replanDelegations.length
7699
+ ? 'Prepare an independently validated same-scope candidate with the eligible grant and actor; authorize it without Human confirmation, then execute its exact delegated receipt.'
7700
+ : 'No eligible execution grant: request issuance/expansion of grant for delegate mode; the explicit Human preparation route remains available outside delegate mode.',
7560
7701
  },
7561
7702
  }
7562
7703
  : {
@@ -8114,12 +8255,14 @@ export class WorkflowService {
8114
8255
  // fix review. Advertise the existing append-only decision route without turning
8115
8256
  // every ordinary repair into a mandatory replan or rewriting that review.
8116
8257
  const correctiveDecisionOrdinal = failedStepRemediations.length + 1;
8258
+ const firstFailurePlanningObstruction = planningRecovery && failedStep
8259
+ ? firstFailurePlanningObstructionEvidence(this.store, task, failedStep.id) : null;
8117
8260
  const optionalCorrectiveDecision = (result.action === 'task run' || planningRecovery !== null)
8118
8261
  && (['needs_fix', 'ready'].includes(task.status) || planningRecovery !== null)
8119
8262
  && failedStep && task.planHash && planRiskAudit
8120
8263
  && planRiskAudit.reviewRequiredStepIds.includes(failedStep.id)
8121
8264
  && !task.steps.some(step => step.status === 'in_progress')
8122
- && failedStepRemediations.length >= 2
8265
+ && (failedStepRemediations.length >= 2 || firstFailurePlanningObstruction !== null)
8123
8266
  && handoffPosture.state !== 'pending'
8124
8267
  && !readCorrectiveDecisionEvents(this.store, task).some(event =>
8125
8268
  event.stepId === failedStep.id && event.triggeringAttemptCount === correctiveDecisionOrdinal)
@@ -8139,12 +8282,13 @@ export class WorkflowService {
8139
8282
  recommendedAuditor: defaultCorrectiveAuditorActor(task.id),
8140
8283
  auditorMustDifferFromLatestStrictReviewer: true,
8141
8284
  requiredActor: handoffPosture.state === 'claimed' ? handoffPosture.claimant : null,
8142
- decisions: planningRecovery ? ['replan-required', 'split-required', 'stop-escalate']
8285
+ decisions: firstFailurePlanningObstruction ? ['replan-required']
8286
+ : planningRecovery ? ['replan-required', 'split-required', 'stop-escalate']
8143
8287
  : ['continue-fix', 'replan-required', 'split-required', 'stop-escalate'],
8144
8288
  authority: 'none',
8145
8289
  preservesOriginalReview: true,
8146
8290
  humanApprovalRequiredForDecision: false,
8147
- replanRequiresExistingHumanGate: true,
8291
+ replanRequiresBoundAuthorization: true,
8148
8292
  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.',
8149
8293
  }
8150
8294
  : null;
@@ -8194,7 +8338,28 @@ export class WorkflowService {
8194
8338
  };
8195
8339
  }
8196
8340
 
8197
- private taskContextChangeBlocker(task: TaskState, operation: 'rebind' | 'refresh'): { error: WorkflowError; activeStep: boolean } | null {
8341
+ private registeredPlanningContextTransport(task: TaskState, repositoryRoot?: string) {
8342
+ const failedTransport = repositoryRoot
8343
+ && task.status === 'awaiting_execution_authorization'
8344
+ && !task.steps.some(step => step.status === 'in_progress')
8345
+ && !readPlanRiskAuditEvents(this.store, task).some(event => event.planHash === task.planHash && event.decision !== 'stop-escalate')
8346
+ && readCorrectiveDecisionEvents(this.store, task).length === 0
8347
+ ? task.dependencyProvenanceRecoveries?.find(record => record.failedCheckpoint
8348
+ && task.steps.some(step => step.id === record.failedCheckpoint!.stepId && step.status === 'failed'))
8349
+ : null;
8350
+ if (failedTransport?.failedCheckpoint && repositoryRoot) {
8351
+ const binding = failedTransport.failedCheckpoint;
8352
+ // Historical transports remain readable after later legitimate progress. Validate
8353
+ // their artifact first, but require current dirty bytes only for this exact checkpoint.
8354
+ readVerifiedPlanArtifact(this.store.taskRoot(task.projectId, task.id), binding.sourcePlanHash, task.planHash!);
8355
+ if (binding.sourcePlanHash !== task.planHash) return null;
8356
+ if (readFailedCheckpointDirtyTransport(this.store, repositoryRoot, task,
8357
+ binding.stepId, binding.sourceHead, binding.sourceDirtyHash)) return binding;
8358
+ }
8359
+ return null;
8360
+ }
8361
+
8362
+ private taskContextChangeBlocker(task: TaskState, operation: 'rebind' | 'refresh', repositoryRoot?: string): { error: WorkflowError; activeStep: boolean } | null {
8198
8363
  const blocked = (error: WorkflowError) => ({ error, activeStep: false });
8199
8364
  if (readCurrentPreExecutionReplan(this.store, task)) {
8200
8365
  return blocked(new WorkflowError(
@@ -8213,6 +8378,7 @@ export class WorkflowService {
8213
8378
  },
8214
8379
  ));
8215
8380
  }
8381
+ if (repositoryRoot && this.registeredPlanningContextTransport(task, repositoryRoot)) return null;
8216
8382
  const posture = this.taskTerminalCorrectivePosture(task);
8217
8383
  if (posture) {
8218
8384
  return blocked(new WorkflowError(
@@ -8330,10 +8496,12 @@ export class WorkflowService {
8330
8496
  task: TaskState,
8331
8497
  knowledgeMap: ProjectKnowledgeMap,
8332
8498
  mode: DelegatedKnowledgeRefreshMode,
8499
+ repositoryRoot?: string,
8333
8500
  ): Array<DelegatedApprovalOption & { transitions: DelegationTransition[] }> {
8334
8501
  let planningRecovery;
8335
8502
  try {
8336
- planningRecovery = readFailedStepPlanningRecovery(this.store, task);
8503
+ planningRecovery = this.registeredPlanningContextTransport(task, repositoryRoot)
8504
+ ?? readFailedStepPlanningRecovery(this.store, task);
8337
8505
  if (!planningRecovery) readCurrentPlanRiskAudit(this.store, task);
8338
8506
  } catch (error) {
8339
8507
  if (error instanceof WorkflowError && error.code === 'TRANSITION_BLOCKED') return [];
@@ -8391,6 +8559,7 @@ export class WorkflowService {
8391
8559
  milestonesById: Map<string, MilestoneState>,
8392
8560
  approved: ProjectKnowledgeMap,
8393
8561
  inspected: ProjectKnowledgeMap,
8562
+ repositoryRoot?: string,
8394
8563
  ): {
8395
8564
  task: TaskState;
8396
8565
  assessment: DelegatedKnowledgeRefreshAssessment;
@@ -8414,8 +8583,9 @@ export class WorkflowService {
8414
8583
  task,
8415
8584
  inspected,
8416
8585
  assessment.mode,
8586
+ repositoryRoot,
8417
8587
  );
8418
- if (options.length > 0 && this.taskContextChangeBlocker(task, 'refresh') === null) return { task, assessment, options };
8588
+ if (options.length > 0 && this.taskContextChangeBlocker(task, 'refresh', repositoryRoot) === null) return { task, assessment, options };
8419
8589
  }
8420
8590
  return null;
8421
8591
  }