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
package/src/cli.ts CHANGED
@@ -70,6 +70,9 @@ interface CliHelpSpec {
70
70
 
71
71
  const CLI_HELP_SPECS: Record<string, CliHelpSpec> = {
72
72
  'update navigation-source-preflight': { requiredOptions: ['--id', '--tarball'], optionalOptions: ['--repo', '--json'] },
73
+ 'update failed-checkpoint-source-preflight': { requiredOptions: ['--id', '--tarball'], optionalOptions: ['--repo', '--json'] },
74
+ 'update failed-checkpoint-transport-preflight': { requiredOptions: ['--id', '--tarball', '--file'], optionalOptions: ['--repo', '--json'] },
75
+ 'update failed-checkpoint-recover': { requiredOptions: ['--id', '--tarball', '--file', '--expected-revision', '--actor', '--reason'], optionalOptions: ['--repo', '--json'] },
73
76
  'update navigation-transport-preflight': { requiredOptions: ['--id', '--tarball', '--file'], optionalOptions: ['--repo', '--json'] },
74
77
  'update navigation-recover': { requiredOptions: ['--id', '--tarball', '--file', '--expected-revision', '--actor', '--reason'], optionalOptions: ['--repo', '--json'] },
75
78
  'change explain': { requiredOptions: ['--task', '--file'], optionalOptions: ['--repo', '--json'] },
@@ -113,6 +116,14 @@ const CLI_HELP_SPECS: Record<string, CliHelpSpec> = {
113
116
  requiredOptions: ['--id'],
114
117
  optionalOptions: ['--repo', '--json'],
115
118
  },
119
+ 'state adoption-apply': {
120
+ requiredOptions: ['--actor', '--confirmation-code'],
121
+ optionalOptions: ['--delegation-grant', '--repo', '--json'],
122
+ },
123
+ 'milestone autonomy-bind': {
124
+ requiredOptions: ['--id', '--expected-revision', '--actor', '--delegation-grant'],
125
+ optionalOptions: ['--repo', '--json'],
126
+ },
116
127
  'milestone authorize': {
117
128
  requiredOptions: ['--id', '--expected-revision'],
118
129
  optionalOptions: ['--actor', '--reason', '--delegation-grant', '--repo', '--json'],
@@ -172,6 +183,10 @@ const CLI_HELP_SPECS: Record<string, CliHelpSpec> = {
172
183
  requiredOptions: ['--id', '--transaction-id'],
173
184
  optionalOptions: ['--repo', '--json'],
174
185
  },
186
+ 'task corrective-replan-authorize': {
187
+ requiredOptions: ['--id', '--file'],
188
+ optionalOptions: ['--repo', '--json'],
189
+ },
175
190
  'task corrective-replan-prepare': {
176
191
  requiredOptions: ['--id', '--file'],
177
192
  optionalOptions: ['--repo', '--json'],
@@ -567,6 +582,16 @@ async function dispatch(args: ParsedArguments): Promise<unknown> {
567
582
  if (noun === 'update' && action === 'navigation-source-preflight') {
568
583
  return workflow.navigationUpdateSourcePreflight(repo, required(args, 'id'), required(args, 'tarball'));
569
584
  }
585
+ if (noun === 'update' && action === 'failed-checkpoint-source-preflight') {
586
+ return workflow.failedCheckpointUpdateSourcePreflight(repo, required(args, 'id'), required(args, 'tarball'));
587
+ }
588
+ if (noun === 'update' && action === 'failed-checkpoint-transport-preflight') {
589
+ return workflow.failedCheckpointUpdateTransportPreflight(repo, required(args, 'id'), required(args, 'tarball'), jsonFile(args));
590
+ }
591
+ if (noun === 'update' && action === 'failed-checkpoint-recover') {
592
+ return workflow.recoverFailedCheckpointUpdate(repo, required(args, 'id'), revision(args), required(args, 'tarball'),
593
+ jsonFile(args), required(args, 'actor'), required(args, 'reason'));
594
+ }
570
595
  if (noun === 'update' && action === 'navigation-transport-preflight') {
571
596
  return workflow.navigationUpdateTransportPreflight(repo, required(args, 'id'), required(args, 'tarball'), jsonFile(args));
572
597
  }
@@ -685,6 +710,7 @@ async function dispatch(args: ParsedArguments): Promise<unknown> {
685
710
  repo,
686
711
  required(args, 'actor'),
687
712
  required(args, 'confirmation-code'),
713
+ option(args, 'delegation-grant') ?? null,
688
714
  );
689
715
  }
690
716
  if (noun === 'milestone' && action === 'recover') {
@@ -1087,6 +1113,9 @@ async function dispatch(args: ParsedArguments): Promise<unknown> {
1087
1113
  if (noun === 'task' && action === 'corrective-yield-recover') {
1088
1114
  return workflow.recoverCorrectiveYield(repo, required(args, 'id'), required(args, 'transaction-id'));
1089
1115
  }
1116
+ if (noun === 'task' && action === 'corrective-replan-authorize') {
1117
+ return workflow.authorizeDelegatedCorrectiveReplan(repo, required(args, 'id'), jsonFile<CorrectiveReplanPublicCandidate>(args));
1118
+ }
1090
1119
  if (noun === 'task' && action === 'corrective-replan-prepare') {
1091
1120
  return workflow.prepareCorrectiveReplan(
1092
1121
  repo,
@@ -1339,6 +1368,10 @@ async function dispatch(args: ParsedArguments): Promise<unknown> {
1339
1368
  required(args, 'actor'),
1340
1369
  );
1341
1370
  }
1371
+ if (noun === 'milestone' && action === 'autonomy-bind') {
1372
+ return workflow.bindMilestoneAutonomy(repo, required(args, 'id'), revision(args),
1373
+ required(args, 'actor'), required(args, 'delegation-grant'));
1374
+ }
1342
1375
  if (noun === 'milestone' && action === 'authorize') {
1343
1376
  return workflow.authorizeMilestone(
1344
1377
  repo,
package/src/contracts.ts CHANGED
@@ -39,6 +39,7 @@ export type AuthorizationKind = 'execution' | 'final_acceptance' | 'exception';
39
39
  export type AuthorizationMode = 'human' | 'delegated' | 'corrective-derived';
40
40
  export type DelegationStatus = 'active' | 'revoked';
41
41
  export type DelegationTransition =
42
+ | 'project.adopt'
42
43
  | 'project_memory.approve'
43
44
  | 'task.execution_authorize'
44
45
  | 'task.final_accept'
@@ -734,8 +735,23 @@ export interface CorrectiveDecisionEvent extends SidecarEventRecord {
734
735
  coveredFindingIds: string[];
735
736
  coveredEventIds: string[];
736
737
  planHash: string;
737
- recoveryBasis?: 'plan-integrity';
738
+ recoveryBasis?: 'plan-integrity' | 'planning-obstruction';
738
739
  planIntegrityEvidence?: PlanIntegrityConflictEvidence;
740
+ planningObstructionEvidence?: {
741
+ kind: 'first-failure-rebound-planning-obstruction';
742
+ taskRevision: number;
743
+ sourcePlanHash: string;
744
+ baselinePlanHash: string;
745
+ baselineAuditEventId: string;
746
+ baselineAuditEventHash: string;
747
+ obstructionAuditEventId: string;
748
+ obstructionAuditEventHash: string;
749
+ priorAuthorizationHash: string;
750
+ remediationEventId: string;
751
+ remediationEventHash: string;
752
+ knowledgeRebindCount: number;
753
+ knowledgeRebindHash: string;
754
+ };
739
755
  }
740
756
 
741
757
  export interface CorrectiveDecisionRecoveryEvent extends SidecarEventRecord {
@@ -935,6 +951,7 @@ export interface AdoptionRepositoryIdentity {
935
951
  }
936
952
 
937
953
  export interface AdoptionPostureEvent extends SidecarEventRecord {
954
+ delegatedAuthorization?: DelegatedAuthorizationEvidence;
938
955
  projectId: string;
939
956
  eventType: AdoptionPostureEventType;
940
957
  actor: string | null;
@@ -1175,6 +1192,7 @@ export interface DependencyProvenanceRecoveryRecord {
1175
1192
  historicalStepProvenance?: HistoricalStepDependencyCompatibility;
1176
1193
  planIntegrity?: PlanIntegrityUpdateCompatibility;
1177
1194
  rootCauseReplan?: RootCauseReplanDirtyCarryoverCompatibility;
1195
+ failedCheckpoint?: import('./failed-checkpoint-update.js').FailedCheckpointUpdateBinding;
1178
1196
  }
1179
1197
 
1180
1198
  export interface RootCauseReplanDirtyCarryoverCompatibility {
package/src/delegation.ts CHANGED
@@ -12,6 +12,7 @@ import type {
12
12
  import { WorkflowError } from './errors.js';
13
13
 
14
14
  const TRANSITIONS: readonly DelegationTransition[] = [
15
+ 'project.adopt',
15
16
  'project_memory.approve',
16
17
  'task.execution_authorize',
17
18
  'task.final_accept',
@@ -19,7 +20,7 @@ const TRANSITIONS: readonly DelegationTransition[] = [
19
20
  'milestone.final_accept',
20
21
  ];
21
22
 
22
- type DelegationTarget = TaskState | MilestoneState | ProjectKnowledgeMap;
23
+ type DelegationTarget = TaskState | MilestoneState | ProjectKnowledgeMap | { kind: 'project'; id: string; milestoneId?: null };
23
24
 
24
25
  export function prepareDelegationGrantGate(
25
26
  projectId: string,
@@ -64,6 +65,9 @@ export function normalizeDelegationPolicy(
64
65
  });
65
66
  }
66
67
  transitions.sort();
68
+ if (transitions.includes('project.adopt') && scope.kind !== 'project') {
69
+ throw new WorkflowError('INVALID_ARGUMENT', 'project.adopt requires project scope.');
70
+ }
67
71
  if (transitions.includes('project_memory.approve') && scope.kind !== 'project') {
68
72
  throw new WorkflowError(
69
73
  'INVALID_ARGUMENT',
@@ -0,0 +1,203 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import type { TaskState } from './contracts.js';
5
+ import type { FileStateStore } from './state/store.js';
6
+ import { canonicalJsonStringify, sha256Hex } from './alpha6/store-sidecars.js';
7
+ import { readCorrectiveDecisionEvents, readRemediationEvents } from './alpha6/remediation.js';
8
+ import { readTaskC1Posture, readTaskHandoffEvents } from './alpha6/handoff.js';
9
+ import { readPlanRiskAuditEvents } from './alpha6/plan-risk.js';
10
+ import { verifiedHistoricalStepReviewBindings } from './alpha6/review.js';
11
+ import { readVerifiedPlanArtifact } from './domain/plan-semantics.js';
12
+ import { pathAllowed } from './domain/validation.js';
13
+ import { validateTaskHistory } from './git.js';
14
+ import { inspectBoundedWorkflowDependencyCommit, workflowDependencyVersionAt } from './dependency-provenance.js';
15
+ import { inspectWorkflowVersions } from './diagnostics.js';
16
+ import { changedFiles, currentBranch, headCommit, runGit } from './repository.js';
17
+ import { PACKAGE_VERSION } from './version.js';
18
+
19
+ export const FAILED_CHECKPOINT_SOURCE = '2.0.0-beta.14';
20
+ export const FAILED_CHECKPOINT_TARGET = '2.0.0-beta.14.1';
21
+ const hash = (value: unknown) => sha256Hex(canonicalJsonStringify(value));
22
+ function require(condition: unknown, message: string): asserts condition {
23
+ if (!condition) throw new Error(`FAILED_CHECKPOINT_UPDATE_BLOCKED: ${message}`);
24
+ }
25
+ export interface FailedCheckpointUpdateReceipt {
26
+ protocol: 'failed-checkpoint-update-source-v1';
27
+ projectId: string; taskId: string; revision: number; issuedAt: string;
28
+ taskHash: string; artifactsHash: string; knowledgeHash: string; milestoneHash: string | null;
29
+ sourceHead: string; sourceBaseHead: string; taskBranch: string; baseBranch: string;
30
+ sourceVersion: string; targetVersion: string; actor: string; stepId: string;
31
+ dirtyFiles: string[]; sourceDirtyHash: string; remediationEventId: string; remediationEventHash: string;
32
+ artifact: { sha256: string; runtimeHash: string };
33
+ }
34
+ export interface FailedCheckpointUpdateBinding {
35
+ protocol: 'failed-checkpoint-update-v1';
36
+ sourceHead: string; sourcePlanHash: string; stepId: string;
37
+ sourceDirtyHash: string; targetDirtyHash: string; dirtyFiles: string[];
38
+ remediationEventId: string; remediationEventHash: string;
39
+ sourceReceiptHash: string; artifactHash: string;
40
+ }
41
+ function filesHash(root: string): string {
42
+ if (!existsSync(root)) return hash([]);
43
+ const visit = (directory: string): Array<[string, string]> => readdirSync(directory).sort().flatMap(name => {
44
+ const file = path.join(directory, name); const info = lstatSync(file);
45
+ require(!info.isSymbolicLink() && (info.isFile() || info.isDirectory()), 'State artifacts must be regular files.');
46
+ return info.isDirectory() ? visit(file) : [[path.relative(root, file), sha256Hex(readFileSync(file))]];
47
+ });
48
+ return hash(visit(root));
49
+ }
50
+ /** Reverse only the dependency string, retaining every other byte (including whitespace and product scripts). */
51
+ function sourceManifestBytes(content: Buffer): Buffer {
52
+ const text = content.toString('utf8'); const manifest = JSON.parse(text);
53
+ require(manifest.devDependencies?.['codex-workflow-v2'] === FAILED_CHECKPOINT_TARGET
54
+ && !manifest.dependencies?.['codex-workflow-v2'], 'Target working manifest must have one exact dev dependency.');
55
+ const key = /("codex-workflow-v2"\s*:\s*)"2\.0\.0-beta\.14\.1"/g;
56
+ require([...text.matchAll(key)].length === 1, 'Working manifest must have one unambiguous literal target pin.');
57
+ return Buffer.from(text.replace(key, `$1"${FAILED_CHECKPOINT_SOURCE}"`));
58
+ }
59
+ /** Same byte framing as native failure evidence; no normalization of product content. */
60
+ export function failedCheckpointDirtyHash(repository: string, files: readonly string[], transported = false): string {
61
+ const digest = createHash('sha256');
62
+ for (const file of [...files].sort()) {
63
+ const absolute = path.resolve(repository, file); const relative = path.relative(repository, absolute);
64
+ require(relative && !relative.startsWith('..') && !path.isAbsolute(relative), 'Dirty path escapes checkout.');
65
+ require(existsSync(absolute) && lstatSync(absolute).isFile() && !lstatSync(absolute).isSymbolicLink(),
66
+ 'This checkpoint supports retained regular files only.');
67
+ let bytes: Buffer = readFileSync(absolute);
68
+ if (transported && file === 'package.json') bytes = sourceManifestBytes(bytes);
69
+ digest.update(`path\0${file}\0file\0`); digest.update(bytes); digest.update('\0');
70
+ }
71
+ return digest.digest('hex');
72
+ }
73
+ function assertPins(repository: string, commit: string, version: string): void {
74
+ const manifest = JSON.parse(runGit(repository, ['show', `${commit}:package.json`]));
75
+ const lock = JSON.parse(runGit(repository, ['show', `${commit}:package-lock.json`]));
76
+ require(manifest.devDependencies?.['codex-workflow-v2'] === version && !manifest.dependencies?.['codex-workflow-v2']
77
+ && workflowDependencyVersionAt(repository, commit) === version
78
+ && lock.packages?.['']?.devDependencies?.['codex-workflow-v2'] === version
79
+ && !lock.packages?.['']?.dependencies?.['codex-workflow-v2']
80
+ && lock.packages?.['node_modules/codex-workflow-v2']?.version === version,
81
+ 'Exact source manifest and lock bindings are required.');
82
+ }
83
+ export function assertFailedCheckpointInstalled(repository: string, task: TaskState, version: string): void {
84
+ const versions = inspectWorkflowVersions(repository, [task.baseBranch]);
85
+ require([versions.declared, versions.locked, versions.installed, versions.currentBranch,
86
+ ...versions.milestoneBases.map(item => item.version)].every(value => value === version),
87
+ `Every installed and Git version surface must equal ${version}.`);
88
+ }
89
+ export function failedCheckpointUpdateSource(repository: string, store: FileStateStore, task: TaskState,
90
+ artifact: FailedCheckpointUpdateReceipt['artifact'],
91
+ sourceHead = headCommit(repository), sourceBaseHead = runGit(repository, ['rev-parse', task.baseBranch]),
92
+ transported = false): FailedCheckpointUpdateReceipt {
93
+ require(PACKAGE_VERSION === FAILED_CHECKPOINT_TARGET, 'Only the beta.14 to beta.14.1 repair is supported.');
94
+ require(task.status === 'awaiting_execution_authorization' && task.workspaceOwner === 'external',
95
+ 'Expected an external failed Task awaiting its new Plan audit.');
96
+ require(readCorrectiveDecisionEvents(store, task).length === 0,
97
+ 'A checkpoint with corrective decision history requires its own supported boundary.');
98
+ require(task.taskBranch && currentBranch(repository) === task.taskBranch, 'Expected the recorded Task branch.');
99
+ const failed = task.steps.filter(step => step.status === 'failed');
100
+ require(failed.length === 1 && !task.steps.some(step => step.status === 'in_progress'), 'Exactly one paused failed Step is required.');
101
+ const step = failed[0]!;
102
+ require(step.failurePause && step.evidence === null, 'Native failed-check evidence is required.');
103
+ require(task.planHash && task.briefHash, 'Current Brief and Plan are required.');
104
+ const taskRoot = store.taskRoot(task.projectId, task.id);
105
+ require(store.hashArtifact(taskRoot, 'brief.md') === task.briefHash && store.hashArtifact(taskRoot, 'plan.md') === task.planHash,
106
+ 'Brief and Plan artifacts must match state.');
107
+ readVerifiedPlanArtifact(taskRoot, task.planHash, task.planHash);
108
+ const currentAudit = readPlanRiskAuditEvents(store, task).at(-1);
109
+ require(!currentAudit || currentAudit.planHash !== task.planHash
110
+ || currentAudit.decision === 'stop-escalate', 'The checkpoint must await its audit or retain a planning obstruction.');
111
+ assertPins(repository, sourceHead, FAILED_CHECKPOINT_SOURCE); assertPins(repository, sourceBaseHead, FAILED_CHECKPOINT_SOURCE);
112
+ validateTaskHistory(repository, task, sourceHead);
113
+ const completed = task.steps.filter(item => item.status === 'completed');
114
+ const reviews = verifiedHistoricalStepReviewBindings(store, task);
115
+ require(completed.length > 0 && completed.every(item => item.evidence
116
+ && reviews.some(review => review.stepId === item.id && review.commitSha === item.evidence!.commitSha)),
117
+ 'Every retained completed Step must have immutable execution and independent review evidence.');
118
+ require(step.dependencies.every(id => completed.some(item => item.id === id)), 'Failed Step dependencies must be complete.');
119
+ const events = readRemediationEvents(store, task).filter(event => event.stepId === step.id && event.failureKind === 'checks-failed');
120
+ require(events.length === 1, 'This checkpoint supports exactly the first native check failure.');
121
+ const terminal = events.at(-1);
122
+ require(terminal && terminal.failureEvidence.completionCommit === sourceHead
123
+ && terminal.failureEvidence.dirtyWorktreeHash === step.failurePause.dirtyWorktreeHash,
124
+ 'Native terminal failure must bind source HEAD and retained dirty bytes.');
125
+ const dirtyFiles = changedFiles(repository).sort();
126
+ require(dirtyFiles.length > 0 && !dirtyFiles.includes('package-lock.json')
127
+ && JSON.stringify(dirtyFiles) === JSON.stringify([...step.failurePause.dirtyFiles].sort()),
128
+ 'Dirty paths must equal the native failure; dirty lockfile transport is unsupported.');
129
+ require(dirtyFiles.every(file => pathAllowed(file, step.allowedWrites) && !pathAllowed(file, step.forbiddenScope)),
130
+ 'Retained dirty files must be inside the failed Step scope.');
131
+ const sourceDirtyHash = failedCheckpointDirtyHash(repository, dirtyFiles, transported);
132
+ require(sourceDirtyHash === terminal.failureEvidence.dirtyWorktreeHash, 'Product bytes changed since native failure.');
133
+ const posture = readTaskC1Posture(store, task);
134
+ const claim = readTaskHandoffEvents(store, task).filter(event => event.eventType === 'claim').at(-1);
135
+ require(posture.state === 'claimed' && claim && posture.claimant === claim.actor && claim.briefHash === task.briefHash,
136
+ 'Original immutable Worker claim is required.');
137
+ return { protocol: 'failed-checkpoint-update-source-v1', projectId: task.projectId, taskId: task.id,
138
+ revision: task.revision, issuedAt: task.updatedAt, taskHash: hash(task), artifactsHash: filesHash(taskRoot),
139
+ knowledgeHash: hash(store.readKnowledgeMap(task.projectId)),
140
+ milestoneHash: task.milestoneId ? hash(store.readMilestone(task.projectId, task.milestoneId)) : null,
141
+ sourceHead, sourceBaseHead, taskBranch: task.taskBranch, baseBranch: task.baseBranch,
142
+ sourceVersion: FAILED_CHECKPOINT_SOURCE, targetVersion: FAILED_CHECKPOINT_TARGET, actor: claim.actor,
143
+ stepId: step.id, dirtyFiles, sourceDirtyHash, remediationEventId: terminal.eventId,
144
+ remediationEventHash: terminal.eventHash, artifact };
145
+ }
146
+ export function inspectFailedCheckpointTransport(repository: string, store: FileStateStore, task: TaskState,
147
+ receipt: FailedCheckpointUpdateReceipt, artifact: FailedCheckpointUpdateReceipt['artifact']) {
148
+ require(receipt?.protocol === 'failed-checkpoint-update-source-v1', 'Missing or foreign source receipt.');
149
+ const targetHead = headCommit(repository); const base = runGit(repository, ['rev-parse', task.baseBranch]);
150
+ const target = inspectBoundedWorkflowDependencyCommit(repository, targetHead);
151
+ const baseTransport = inspectBoundedWorkflowDependencyCommit(repository, base);
152
+ require([targetHead, base].every(commit => runGit(repository, ['rev-list', '--parents', '-n', '1', commit]).split(' ').length === 2),
153
+ 'Transport commits must have exactly one parent.');
154
+ require(target?.parentCommitSha === receipt.sourceHead && target.packageVersion === FAILED_CHECKPOINT_TARGET
155
+ && baseTransport?.parentCommitSha === receipt.sourceBaseHead && baseTransport.packageVersion === FAILED_CHECKPOINT_TARGET,
156
+ 'Exactly one dependency-only transport is required on each bound branch.');
157
+ const reconstructed = failedCheckpointUpdateSource(repository, store, task, artifact,
158
+ target.parentCommitSha, baseTransport.parentCommitSha, true);
159
+ require(canonicalJsonStringify(receipt) === canonicalJsonStringify(reconstructed),
160
+ 'Source receipt no longer matches state, artifacts, product bytes, history or tarball.');
161
+ assertFailedCheckpointInstalled(repository, task, FAILED_CHECKPOINT_TARGET);
162
+ const binding: FailedCheckpointUpdateBinding = { protocol: 'failed-checkpoint-update-v1',
163
+ sourceHead: receipt.sourceHead, sourcePlanHash: task.planHash!, stepId: receipt.stepId,
164
+ sourceDirtyHash: receipt.sourceDirtyHash, targetDirtyHash: failedCheckpointDirtyHash(repository, receipt.dirtyFiles),
165
+ dirtyFiles: receipt.dirtyFiles, remediationEventId: receipt.remediationEventId,
166
+ remediationEventHash: receipt.remediationEventHash, sourceReceiptHash: hash(receipt), artifactHash: artifact.sha256 };
167
+ return { receipt: reconstructed, target, baseTransport: base, targetHead, binding };
168
+ }
169
+
170
+ /** Revalidate the registered byte-preserving transport when a later corrective gate reads native failure evidence.
171
+ * Returns no authority and never changes the original failure record or its hash. */
172
+ export function readFailedCheckpointDirtyTransport(store: FileStateStore, repository: string, task: TaskState,
173
+ stepId: string, sourceHead: string, sourceDirtyHash: string) {
174
+ const records = (task.dependencyProvenanceRecoveries ?? []).filter(record => {
175
+ const binding = (record as typeof record & { failedCheckpoint?: FailedCheckpointUpdateBinding }).failedCheckpoint;
176
+ return binding?.stepId === stepId && binding.sourceHead === sourceHead && binding.sourceDirtyHash === sourceDirtyHash;
177
+ });
178
+ if (records.length === 0) return null;
179
+ require(records.length === 1, 'Registered failed checkpoint is ambiguous.');
180
+ const record = records[0]!;
181
+ const binding = (record as typeof record & { failedCheckpoint: FailedCheckpointUpdateBinding }).failedCheckpoint;
182
+ require(task.planHash, 'Current Plan is required to validate the historical checkpoint.');
183
+ readVerifiedPlanArtifact(store.taskRoot(task.projectId, task.id), binding.sourcePlanHash, task.planHash);
184
+ require(binding.protocol === 'failed-checkpoint-update-v1' && task.systemCommits.includes(record.commitSha)
185
+ && record.packageVersion === FAILED_CHECKPOINT_TARGET && record.packageName === 'codex-workflow-v2'
186
+ && record.parentCommitSha === sourceHead && JSON.stringify(record.files) === JSON.stringify(['package-lock.json','package.json']),
187
+ 'Registered failed checkpoint dependency record is invalid.');
188
+ const inspected = inspectBoundedWorkflowDependencyCommit(repository, record.commitSha);
189
+ require(inspected?.parentCommitSha === sourceHead && inspected.packageVersion === FAILED_CHECKPOINT_TARGET,
190
+ 'Registered failed checkpoint no longer identifies a dependency-only commit.');
191
+ require(headCommit(repository) === record.commitSha, 'Dirty checkpoint requires its exact transported HEAD.');
192
+ const terminal = readRemediationEvents(store, task).filter(event => event.stepId === stepId && event.failureKind === 'checks-failed').at(-1);
193
+ require(terminal?.eventId === binding.remediationEventId && terminal.eventHash === binding.remediationEventHash
194
+ && terminal.failureEvidence.completionCommit === sourceHead && terminal.failureEvidence.dirtyWorktreeHash === sourceDirtyHash,
195
+ 'Registered failed checkpoint does not bind the terminal native failure.');
196
+ const dirty = changedFiles(repository).sort();
197
+ require(JSON.stringify(dirty) === JSON.stringify(binding.dirtyFiles)
198
+ && failedCheckpointDirtyHash(repository, dirty, true) === sourceDirtyHash
199
+ && failedCheckpointDirtyHash(repository, dirty) === binding.targetDirtyHash,
200
+ 'Retained product bytes changed after the registered dependency transport.');
201
+ return { sourceHead, sourceDirtyHash, targetHead: record.commitSha, targetDirtyHash: binding.targetDirtyHash,
202
+ dirtyFiles: [...dirty], sourceReceiptHash: binding.sourceReceiptHash };
203
+ }
@@ -28,7 +28,7 @@ export interface CorrectiveReplanCredentialCorePorts {
28
28
  readonly validationEventTailSource: { readPlanValidationTail(taskId: string): unknown };
29
29
  }
30
30
  export interface CorrectiveReplanCredentialKernel {
31
- issuePlanValidation(input: unknown): Result<JsonRecord>; issueHumanChallenge(input: unknown): Result<JsonRecord>; confirmHuman(input: unknown): Result<JsonRecord>; issueReceipt(input: unknown): Result<JsonRecord>;
31
+ issuePlanValidation(input: unknown): Result<JsonRecord>; issueHumanChallenge(input: unknown): Result<JsonRecord>; issueDelegatedChallenge(input: unknown): Result<JsonRecord>; confirmHuman(input: unknown): Result<JsonRecord>; confirmDelegated(input: unknown): Result<JsonRecord>; issueReceipt(input: unknown): Result<JsonRecord>;
32
32
  verifyReceipt(input: unknown): Verification<JsonRecord>; materializeStoredEvent(input: unknown): Result<JsonRecord>; verifyStoredEvent(input: unknown): Verification<JsonRecord>;
33
33
  }
34
34
  export interface CredentialFunctionBinding { readonly id: string; readonly version: 1; readonly kind: 'materializer' | 'verifier';
@@ -66,6 +66,8 @@ export function createCorrectiveReplanCredentialKernel(ports: CorrectiveReplanCr
66
66
  return deepFreeze({
67
67
  issuePlanValidation: (input: unknown) => totalResult(() => issuePlanValidation(issuer, input)),
68
68
  issueHumanChallenge: (input: unknown) => totalResult(() => issueHumanChallenge(issuer, input)),
69
+ issueDelegatedChallenge: (input: unknown) => totalResult(() => issueDelegatedChallenge(issuer, input)),
70
+ confirmDelegated: (input: unknown) => totalResult(() => confirmDelegated(issuer, input)),
69
71
  confirmHuman: (input: unknown) => totalResult(() => confirmHuman(issuer, input)),
70
72
  issueReceipt: (input: unknown) => totalResult(() => issueReceipt(issuer, input)),
71
73
  verifyReceipt: (input: unknown) => totalVerification(() => verifyReceipt(issuer, input)),
@@ -110,10 +112,11 @@ const issuePlanValidation = (issuer: unknown, input: unknown): Result<JsonRecord
110
112
  passed: true, findings: sortedFindings, dispositions: sortedDispositions, validatedAtMs: now, expiresAtMs: now + TTL };
111
113
  const planValidationHash = hashCanonical(validation); const values: JsonRecord = { ...clone(base), planValidationHash };
112
114
  if (!canonicalManifestValues(values, true)) return rejected('credential.binding.values-invalid', '/binding/values', 'binding');
115
+ const delegated = snapshot.delegatedAuthority;
113
116
  const capability = { id: 'capability.task.corrective-replan', version: 1,
114
- issuer: { kind: 'human', identityHash: values.humanPrincipalIdentityHash }, source: 'human-confirmation',
117
+ issuer: { kind: delegated ? 'delegate' : 'human', identityHash: delegated ? values.actorIdentityHash : values.humanPrincipalIdentityHash }, source: delegated ? 'delegation-grant' : 'human-confirmation',
115
118
  scope: { transitionId: values.transitionId, taskId: values.taskId, candidatePlanHash: values.candidatePlanHash,
116
- manifestSha256: correctiveReplanBindingManifest.manifestSha256 }, delegation: 'forbidden' };
119
+ manifestSha256: correctiveReplanBindingManifest.manifestSha256 }, delegation: delegated ?? 'forbidden' };
117
120
  const bindingBase = { schemaVersion: 1, manifestRef: { id: correctiveReplanBindingManifest.id, version: 1 },
118
121
  manifestSha256: correctiveReplanBindingManifest.manifestSha256, values, capability };
119
122
  const binding = { ...bindingBase, bindingSha256: hashCanonical(bindingBase) };
@@ -132,11 +135,12 @@ const issuePlanValidation = (issuer: unknown, input: unknown): Result<JsonRecord
132
135
  return accepted({ handle: handle as PlanValidationHandle, binding, attestation });
133
136
  } finally { if (!committed && kernel.preparations.get(preparationKey)?.generation === generation) kernel.preparations.delete(preparationKey); }
134
137
  };
135
- const issueHumanChallenge = (issuer: unknown, input: unknown): Result<JsonRecord> => {
138
+ const issueHumanChallenge = (issuer: unknown, input: unknown, delegated = false): Result<JsonRecord> => {
136
139
  const kernel = kernels.get(object(issuer)); if (!kernel) return rejected('credential.provenance.invalid', '/', 'provenance');
137
140
  if (!exactKeys(input, ['planValidation'])) return rejected('credential.envelope.invalid', '/', 'schema');
138
141
  const handle = object((input as JsonRecord).planValidation); const plan = kernel.plans.get(handle);
139
142
  if (!plan) return rejected('credential.provenance.invalid', '/planValidation', 'provenance');
143
+ if (Boolean(plan.snapshot.delegatedAuthority) !== delegated) return rejected('credential.authority.mode-mismatch', '/planValidation', 'authority');
140
144
  if (!activePreparation(kernel, plan)) return rejected('credential.preparation.superseded', '/planValidation', 'provenance');
141
145
  if (kernel.challenged.has(handle)) return rejected('credential.provenance.invalid', '/planValidation', 'provenance');
142
146
  const now = safeTime(kernel.ports.now()); if (now === null) return rejected('credential.time.invalid', '/coreClock', 'time');
@@ -151,8 +155,11 @@ const issueHumanChallenge = (issuer: unknown, input: unknown): Result<JsonRecord
151
155
  const challenge = deepFreeze({ ...core, challengeHash: hashCanonical(core) }); const challengeHandle = opaque();
152
156
  kernel.challenges.set(challengeHandle, deepFreeze({ plan, challenge })); kernel.challenged.add(handle);
153
157
  return accepted({ handle: challengeHandle as HumanChallengeHandle,
154
- delivery: { kind: 'corrective-replan-human-challenge-delivery', version: 1, challengeId, confirmationCode: code, expiresAtMs } });
158
+ delivery: delegated
159
+ ? { kind: 'corrective-replan-delegation-binding', version: 1, challengeId, expiresAtMs }
160
+ : { kind: 'corrective-replan-human-challenge-delivery', version: 1, challengeId, confirmationCode: code, expiresAtMs } });
155
161
  };
162
+ const issueDelegatedChallenge = (issuer: unknown, input: unknown): Result<JsonRecord> => issueHumanChallenge(issuer, input, true);
156
163
  const confirmHuman = (issuer: unknown, input: unknown): Result<JsonRecord> => {
157
164
  const kernel = kernels.get(object(issuer)); if (!kernel) return rejected('credential.provenance.invalid', '/', 'provenance');
158
165
  if (!exactKeys(input, ['challenge', 'confirmation'])) return rejected('credential.envelope.invalid', '/', 'schema');
@@ -162,6 +169,7 @@ const confirmHuman = (issuer: unknown, input: unknown): Result<JsonRecord> => {
162
169
  if (!challenge) return rejected('credential.provenance.invalid', '/challenge', 'provenance');
163
170
  if (!activePreparation(kernel, challenge.plan)) return rejected('credential.preparation.superseded', '/challenge', 'provenance');
164
171
  if (kernel.confirmed.has(challengeHandle)) return rejected('credential.provenance.invalid', '/challenge', 'provenance');
172
+ if (challenge.plan.snapshot.delegatedAuthority) return rejected('credential.authority.mode-mismatch', '/challenge', 'authority');
165
173
  const candidate = confirmation as JsonRecord; const now = safeTime(kernel.ports.now());
166
174
  if (now === null) return rejected('credential.time.invalid', '/coreClock', 'time');
167
175
  const validatedAt = number(challenge.plan.attestation.validatedAtMs); const issuedAt = number(challenge.challenge.issuedAtMs);
@@ -176,6 +184,24 @@ const confirmHuman = (issuer: unknown, input: unknown): Result<JsonRecord> => {
176
184
  const handle = opaque(); kernel.confirmations.set(handle, deepFreeze({ challenge, projection })); kernel.confirmed.add(challengeHandle);
177
185
  return accepted({ handle: handle as VerifiedConfirmationHandle, projection });
178
186
  };
187
+ // The authority comes exclusively from the Core snapshot, never from caller assertions.
188
+ const confirmDelegated = (issuer: unknown, input: unknown): Result<JsonRecord> => {
189
+ const kernel = kernels.get(object(issuer));
190
+ if (!kernel || !exactKeys(input, ['challenge'])) return rejected('credential.provenance.invalid', '/', 'provenance');
191
+ const handle = object((input as JsonRecord).challenge); const challenge = kernel.challenges.get(handle);
192
+ if (!challenge || !challenge.plan.snapshot.delegatedAuthority || kernel.confirmed.has(handle))
193
+ return rejected('credential.authority.mode-mismatch', '/challenge', 'authority');
194
+ const freshness = receiptFreshnessIssue(kernel, challenge.plan);
195
+ if (freshness) return rejected(freshness.issueId, freshness.path, freshness.reasonClass);
196
+ const now = safeTime(kernel.ports.now());
197
+ if (now === null || now < number(challenge.challenge.issuedAtMs) || now >= number(challenge.challenge.expiresAtMs))
198
+ return rejected('credential.delegation.expired', '/challenge', 'time');
199
+ const projection = { challengeId: challenge.challenge.challengeId, challengeHash: challenge.challenge.challengeHash,
200
+ bindingSha256: challenge.plan.binding.bindingSha256, delegatedAuthority: challenge.plan.snapshot.delegatedAuthority,
201
+ confirmedAtMs: now };
202
+ const confirmation = opaque(); kernel.confirmations.set(confirmation, deepFreeze({ challenge, projection })); kernel.confirmed.add(handle);
203
+ return accepted({ handle: confirmation as VerifiedConfirmationHandle, projection });
204
+ };
179
205
  const issueReceipt = (issuer: unknown, input: unknown): Result<JsonRecord> => {
180
206
  const kernel = kernels.get(object(issuer)); if (!kernel) return rejected('credential.provenance.invalid', '/', 'provenance');
181
207
  if (!exactKeys(input, ['planValidation', 'confirmation'])) return rejected('credential.envelope.invalid', '/', 'schema');
@@ -211,8 +237,10 @@ const issueReceipt = (issuer: unknown, input: unknown): Result<JsonRecord> => {
211
237
  expiresAtMs: plan.attestation.expiresAtMs };
212
238
  const base = { schemaVersion: 1, receiptId, domain: 'codex-workflow/corrective-replan-receipt/v1',
213
239
  binding: plan.binding, validation, audit: { riskAuditHash: values.riskAuditHash, riskAuditorIdentityHash: values.riskAuditorIdentityHash },
214
- human: { challengeId: confirmation.projection.challengeId, challengeHash: confirmation.projection.challengeHash,
215
- humanPrincipalIdentityHash: confirmation.projection.humanPrincipalIdentityHash, confirmedAtMs: confirmation.projection.confirmedAtMs },
240
+ ...(plan.snapshot.delegatedAuthority
241
+ ? { delegated: { authority: plan.snapshot.delegatedAuthority, authorizedAtMs: confirmation.projection.confirmedAtMs } }
242
+ : { human: { challengeId: confirmation.projection.challengeId, challengeHash: confirmation.projection.challengeHash,
243
+ humanPrincipalIdentityHash: confirmation.projection.humanPrincipalIdentityHash, confirmedAtMs: confirmation.projection.confirmedAtMs } }),
216
244
  supersession: { priorGrantHash: snapshotGrant.grantHash, disposition: 'superseded' },
217
245
  issuedAtMs: now, expiresAtMs };
218
246
  const withDomain = { ...base, domainHash: hashCanonical(base) }; const receipt = deepFreeze({ ...withDomain, receiptHash: hashCanonical(withDomain) });
@@ -266,11 +294,12 @@ const verifyStoredEvent = (issuer: unknown, input: unknown): Verification<JsonRe
266
294
  if (!same(candidate, recordValue.event)) return verificationRejected('credential.event.mismatch', '/candidate', 'integrity');
267
295
  return verified(recordValue.event);
268
296
  };
269
- const functions = [issuePlanValidation, issueHumanChallenge, confirmHuman, issueReceipt, verifyReceipt, materializeStoredEvent, verifyStoredEvent];
297
+ const functions = [issuePlanValidation, issueHumanChallenge, confirmHuman, issueReceipt, verifyReceipt, materializeStoredEvent, verifyStoredEvent, issueDelegatedChallenge, confirmDelegated];
270
298
  const ids = ['credential-materializer.corrective-replan.plan-validation-attestation', 'credential-materializer.corrective-replan.expected-human-challenge',
271
299
  'credential-verifier.corrective-replan.human-confirmation', 'credential-materializer.corrective-replan.receipt',
272
300
  'credential-verifier.corrective-replan.receipt', 'credential-materializer.corrective-replan.stored-plan-validation-event',
273
- 'credential-verifier.corrective-replan.stored-plan-validation-event'] as const;
301
+ 'credential-verifier.corrective-replan.stored-plan-validation-event',
302
+ 'credential-materializer.corrective-replan.delegated-binding', 'credential-verifier.corrective-replan.delegated-authorization'] as const;
274
303
  export const correctiveReplanCredentialFunctionBindings: readonly CredentialFunctionBinding[] = deepFreeze(ids.map((id, index) => ({ id, version: 1,
275
304
  kind: id.includes('materializer') ? 'materializer' as const : 'verifier' as const, implementationId: `implementation.${id}` as const,
276
305
  implementationVersion: 1, wiring: 'invoked', callable: functions[index]! })));
@@ -289,7 +318,7 @@ function receiptFreshnessIssue(kernel: KernelRecord, plan: PlanRecord): Issue |
289
318
  return loaded.snapshotBindingHash === plan.snapshotBindingHash ? null : issue('credential.receipt.snapshot-stale', '/snapshotBindingHash', 'binding');
290
319
  }
291
320
  function snapshotIssue(snapshot: JsonRecord, taskId: unknown): Issue | null {
292
- if (!exactKeys(snapshot, ['snapshotVersion', 'bindingManifest', 'worktree', 'baseValues', 'validationExpectation', 'priorPlanAuthorization']) || snapshot.snapshotVersion !== 2)
321
+ if (!exactKeys(snapshot, ['snapshotVersion', 'bindingManifest', 'worktree', 'baseValues', 'validationExpectation', 'priorPlanAuthorization', ...(snapshot.delegatedAuthority ? ['delegatedAuthority'] : [])]) || snapshot.snapshotVersion !== 2)
293
322
  return issue('credential.snapshot.invalid', '/snapshot', 'schema');
294
323
  const base = record(snapshot.baseValues); const expected = record(snapshot.validationExpectation);
295
324
  if (base.taskId !== taskId || base.transitionId !== 'task.plan.corrective-replan' || base.capabilityId !== 'capability.task.corrective-replan'
@@ -151,7 +151,7 @@ const decisionGuardMetadata = guardMetadata(
151
151
  );
152
152
  const attemptGuardMetadata = guardMetadata(
153
153
  'task.corrective-replan.attempt-threshold',
154
- 'The decision is bound either to the bounded first-failure Plan-integrity route or to the ordinary two-failure threshold.',
154
+ 'The decision binds a proven first-failure Plan-integrity or rebound planning-obstruction route, or the ordinary two-failure threshold.',
155
155
  'availability',
156
156
  'observation',
157
157
  ['field.corrective-decision-triggering-attempt-count', 'field.remediation-attempt-count'],
@@ -193,7 +193,7 @@ const planValidationPassedGuardMetadata = guardMetadata(
193
193
  'execution', 'candidate', ['field.plan-validation-passed'], ['input.corrective-plan-validation'], 'candidate-blocker',
194
194
  );
195
195
  const receiptValidGuardMetadata = guardMetadata(
196
- 'task.corrective-replan.receipt-valid', 'The Human-confirmed receipt matches every current execution binding.',
196
+ 'task.corrective-replan.receipt-valid', 'The Human-confirmed or explicitly delegated receipt matches every current execution binding.',
197
197
  'execution', 'authority', ['field.receipt-valid'], ['input.corrective-execution-receipt'], 'authority-blocker',
198
198
  );
199
199
  const riskAuditPassedGuardMetadata = guardMetadata(
@@ -264,12 +264,16 @@ const guards: readonly TransitionGuard<CorrectiveReplanSnapshot, CorrectiveRepla
264
264
  && decision?.triggeringAttemptCount === 2
265
265
  && decision.recoveryBasis === 'plan-integrity'
266
266
  && decision.planIntegrityEvidence !== undefined;
267
- const passed = ordinaryThreshold || boundedPlanIntegrity;
267
+ const boundedPlanningObstruction = snapshot.remediationAttemptCount === 1
268
+ && decision?.triggeringAttemptCount === 2
269
+ && decision.recoveryBasis === 'planning-obstruction'
270
+ && decision.planningObstructionEvidence !== undefined;
271
+ const passed = ordinaryThreshold || boundedPlanIntegrity || boundedPlanningObstruction;
268
272
  return verdict(
269
273
  attemptGuardMetadata,
270
274
  passed,
271
275
  'ATTEMPT_BINDING_INVALID',
272
- 'Corrective replan requires either one bounded Plan-integrity failure or two ordinary failed attempts, with an exact next-ordinal decision.',
276
+ 'Corrective replan requires one bounded Plan-integrity/planning-obstruction failure or two ordinary failed attempts, with an exact next-ordinal decision.',
273
277
  );
274
278
  },
275
279
  },
@@ -347,18 +351,18 @@ const metadata: TransitionDefinitionMetadata = {
347
351
  eligibleRoles: ['delivery-coordinator', 'technical-planner'],
348
352
  actor: {
349
353
  identity: v1('identity.actor'),
350
- principalBasis: v1('principal-basis.human-confirmation'),
354
+ principalBasis: v1('principal-basis.bound-corrective-authority'),
351
355
  required: true,
352
356
  },
353
357
  principal: {
354
358
  identity: v1('identity.human-principal'),
355
- principalBasis: v1('principal-basis.human-confirmation'),
359
+ principalBasis: v1('principal-basis.bound-corrective-authority'),
356
360
  issuer: v1('identity.human-principal'),
357
- allowedKinds: ['human'],
361
+ allowedKinds: ['human', 'delegation-grant'],
358
362
  },
359
363
  capabilities: [{ capability: v1('capability.task.corrective-replan'), source: 'credential', required: true }],
360
364
  credentials: [v1('credential-policy.corrective-execution-receipt')],
361
- delegation: v1('delegation.forbidden'),
365
+ delegation: v1('delegation.corrective-execution-exact'),
362
366
  ownership: v1('ownership.corrective-replan-clear'),
363
367
  executionBoundaries: ['human', 'coordinator', 'workflow-core'],
364
368
  separationOfDuty: [
@@ -232,15 +232,16 @@ export const transitionSemanticRegistry: SemanticRegistry = deepFreeze(validateS
232
232
  identities,
233
233
  identityComparators,
234
234
  principalBases: [
235
+ { id: 'principal-basis.bound-corrective-authority', version: 1, description: 'Core verifies an exact Human confirmation or a current task.execution_authorize grant for unchanged corrective scope.', principalKind: 'workflow-core', authoritySource: 'workflow-core-derived' },
235
236
  { id: 'principal-basis.human-confirmation', version: 1, description: 'Exact Human confirmation authority basis.', principalKind: 'human', authoritySource: 'human-confirmation' },
236
237
  ],
237
238
  capabilities: [{
238
- id: 'capability.task.corrective-replan', version: 1, description: 'Human-issued capability for one exact replacement candidate.',
239
+ id: 'capability.task.corrective-replan', version: 1, description: 'Human-confirmed or exact execution-grant capability for one replacement candidate.',
239
240
  transitionId: 'task.plan.corrective-replan', issuer: v1('identity.human-principal'),
240
241
  scope: v1('projection.corrective-replan.execution-receipt'), exactCandidateBound: true,
241
242
  }],
242
243
  credentialPolicies: [{
243
- id: 'credential-policy.corrective-execution-receipt', version: 1, description: 'Exact state-bound Human-confirmed receipt.',
244
+ id: 'credential-policy.corrective-execution-receipt', version: 1, description: 'Exact state-bound receipt with distinct Human or delegated authority evidence.',
244
245
  credentialKind: 'execution-receipt', schema: v1('schema.corrective-replan-receipt'),
245
246
  verifier: v1('credential-verifier.corrective-replan.receipt'), issuer: v1('identity.human-principal'),
246
247
  presentation: { mode: 'explicit-input', input: v1('input.corrective-execution-receipt') },
@@ -251,6 +252,7 @@ export const transitionSemanticRegistry: SemanticRegistry = deepFreeze(validateS
251
252
  credentialVerifiers: [{ id: 'credential-verifier.corrective-replan.receipt', version: 1, description: 'The invoked Core credential kernel recomputes receipt and all current bindings.', verifierKind: 'state-bound-sha256' }],
252
253
  clocks: [{ id: 'clock.workflow-core-utc', version: 1, description: 'Workflow Core injected UTC clock.', clockKind: 'workflow-core-utc' }],
253
254
  delegationPolicies: [
255
+ { id: 'delegation.corrective-execution-exact', version: 1, description: 'An explicit live task.execution_authorize grant may authorize a same-scope corrective candidate; independent validation and unchanged acceptance remain mandatory.', mode: 'allowed-exact', scopeBound: true, expiryRequired: true, redelegationAllowed: false },
254
256
  { id: 'delegation.forbidden', version: 1, description: 'Delegation is forbidden.', mode: 'forbidden', scopeBound: false, expiryRequired: false, redelegationAllowed: false },
255
257
  ],
256
258
  ownershipPolicies: [
@@ -114,6 +114,9 @@ const OBSERVED_ROUTE_INVENTORY: readonly ObservedRouteDefinition[] = [
114
114
  diagnostic('update navigation-source-preflight'),
115
115
  diagnostic('update navigation-transport-preflight'),
116
116
  executable('update navigation-recover', 'recoverNavigationUpdate'),
117
+ diagnostic('update failed-checkpoint-source-preflight'),
118
+ diagnostic('update failed-checkpoint-transport-preflight'),
119
+ executable('update failed-checkpoint-recover', 'recoverFailedCheckpointUpdate'),
117
120
  executable('task historical-step-provenance-recover', 'recoverHistoricalStepProvenance'),
118
121
  executable('update historical-step-dependency-recover', 'recoverHistoricalStepDependencyProvenance'),
119
122
  executable('update dependency-provenance-recover', 'recoverDependencyProvenance'),
@@ -134,6 +137,7 @@ const OBSERVED_ROUTE_INVENTORY: readonly ObservedRouteDefinition[] = [
134
137
  executable('task corrective-decision', 'recordStepCorrectiveDecision'),
135
138
  executable('task corrective-yield', 'executeCorrectiveYield'),
136
139
  executable('task corrective-replan-prepare', 'prepareCorrectiveReplan'),
140
+ executable('task corrective-replan-authorize', 'authorizeDelegatedCorrectiveReplan'),
137
141
  ],
138
142
  successorWitness: {
139
143
  kind: 'reachable', routeIds: ['observed.task-lifecycle'],
@@ -231,6 +235,7 @@ const OBSERVED_ROUTE_INVENTORY: readonly ObservedRouteDefinition[] = [
231
235
  executable('milestone plan-set', 'setMilestonePlan'),
232
236
  executable('milestone scope-change-prepare', 'prepareMilestoneScopeChange'),
233
237
  executable('milestone autonomy-evolve', 'evolveMilestonePlanAutonomously'),
238
+ executable('milestone autonomy-bind', 'bindMilestoneAutonomy'),
234
239
  executable('milestone authorize', 'authorizeMilestone'),
235
240
  executable('milestone validate', 'validateMilestone'),
236
241
  ],
@@ -638,6 +638,7 @@ function materializeExecutionEvent(
638
638
  planRiskAuditEventHash: riskEvent.eventHash,
639
639
  actorIdentityHash: values.actorIdentityHash,
640
640
  humanPrincipalIdentityHash: values.humanPrincipalIdentityHash,
641
+ ...(receipt.delegated ? { delegatedAuthority: get(receipt, '/delegated/authority') } : {}),
641
642
  priorGrantHash: get(receipt, '/supersession/priorGrantHash'),
642
643
  taskStateSha256,
643
644
  planBytesSha256,