codex-workflow-v2 2.0.0-beta.13.9 → 2.0.0-beta.14

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 (271) hide show
  1. package/README.md +27 -388
  2. package/dist/reviewer-runtime-build.json +89 -29
  3. package/dist/src/alpha6/adoption.d.ts +2 -0
  4. package/dist/src/alpha6/adoption.js +22 -0
  5. package/dist/src/alpha6/adoption.js.map +1 -1
  6. package/dist/src/alpha6/captured-check-evidence.d.ts +51 -0
  7. package/dist/src/alpha6/captured-check-evidence.js +152 -0
  8. package/dist/src/alpha6/captured-check-evidence.js.map +1 -0
  9. package/dist/src/alpha6/component-owner.d.ts +15 -1
  10. package/dist/src/alpha6/component-owner.js +44 -2
  11. package/dist/src/alpha6/component-owner.js.map +1 -1
  12. package/dist/src/alpha6/corrective-decision-boundary.d.ts +4 -0
  13. package/dist/src/alpha6/corrective-decision-boundary.js +47 -0
  14. package/dist/src/alpha6/corrective-decision-boundary.js.map +1 -0
  15. package/dist/src/alpha6/downstream-proof.d.ts +1 -25
  16. package/dist/src/alpha6/downstream-proof.js +0 -171
  17. package/dist/src/alpha6/downstream-proof.js.map +1 -1
  18. package/dist/src/alpha6/literal-test-invocation.d.ts +2 -0
  19. package/dist/src/alpha6/literal-test-invocation.js +106 -0
  20. package/dist/src/alpha6/literal-test-invocation.js.map +1 -0
  21. package/dist/src/alpha6/milestone.d.ts +2 -0
  22. package/dist/src/alpha6/milestone.js +10 -1
  23. package/dist/src/alpha6/milestone.js.map +1 -1
  24. package/dist/src/alpha6/navigation-dirty-carryover.d.ts +11 -0
  25. package/dist/src/alpha6/navigation-dirty-carryover.js +130 -0
  26. package/dist/src/alpha6/navigation-dirty-carryover.js.map +1 -0
  27. package/dist/src/alpha6/plan-integrity.js +7 -5
  28. package/dist/src/alpha6/plan-integrity.js.map +1 -1
  29. package/dist/src/alpha6/remediation.d.ts +6 -21
  30. package/dist/src/alpha6/remediation.js +161 -476
  31. package/dist/src/alpha6/remediation.js.map +1 -1
  32. package/dist/src/alpha6/root-cause-replan-carryover.d.ts +4 -1
  33. package/dist/src/alpha6/root-cause-replan-carryover.js +29 -5
  34. package/dist/src/alpha6/root-cause-replan-carryover.js.map +1 -1
  35. package/dist/src/beta1/project-transaction.d.ts +3 -1
  36. package/dist/src/beta1/project-transaction.js +6 -2
  37. package/dist/src/beta1/project-transaction.js.map +1 -1
  38. package/dist/src/checks/runner.d.ts +26 -0
  39. package/dist/src/checks/runner.js +223 -0
  40. package/dist/src/checks/runner.js.map +1 -0
  41. package/dist/src/checks/task-sync.d.ts +10 -0
  42. package/dist/src/checks/task-sync.js +41 -0
  43. package/dist/src/checks/task-sync.js.map +1 -0
  44. package/dist/src/checks/worker.d.ts +1 -0
  45. package/dist/src/checks/worker.js +154 -0
  46. package/dist/src/checks/worker.js.map +1 -0
  47. package/dist/src/cli-actions.d.ts +2 -2
  48. package/dist/src/cli-actions.js +3 -15
  49. package/dist/src/cli-actions.js.map +1 -1
  50. package/dist/src/cli.js +14 -98
  51. package/dist/src/cli.js.map +1 -1
  52. package/dist/src/contracts.d.ts +35 -32
  53. package/dist/src/dependency-provenance.d.ts +2 -2
  54. package/dist/src/dependency-provenance.js +12 -69
  55. package/dist/src/dependency-provenance.js.map +1 -1
  56. package/dist/src/domain/base-sync-conflict.d.ts +13 -0
  57. package/dist/src/domain/base-sync-conflict.js +32 -0
  58. package/dist/src/domain/base-sync-conflict.js.map +1 -0
  59. package/dist/src/domain/step-start-admission.d.ts +4 -0
  60. package/dist/src/domain/step-start-admission.js +9 -0
  61. package/dist/src/domain/step-start-admission.js.map +1 -0
  62. package/dist/src/domain/virgin-registration.d.ts +3 -0
  63. package/dist/src/domain/virgin-registration.js +65 -0
  64. package/dist/src/domain/virgin-registration.js.map +1 -0
  65. package/dist/src/errors.d.ts +1 -1
  66. package/dist/src/errors.js.map +1 -1
  67. package/dist/src/gateway-handshake.js +0 -8
  68. package/dist/src/gateway-handshake.js.map +1 -1
  69. package/dist/src/git.d.ts +3 -1
  70. package/dist/src/git.js +41 -14
  71. package/dist/src/git.js.map +1 -1
  72. package/dist/src/graph.js +25 -3
  73. package/dist/src/graph.js.map +1 -1
  74. package/dist/src/index.d.ts +1 -0
  75. package/dist/src/navigation-actions.d.ts +9 -0
  76. package/dist/src/navigation-actions.js +72 -0
  77. package/dist/src/navigation-actions.js.map +1 -0
  78. package/dist/src/navigation-update-artifact.d.ts +5 -0
  79. package/dist/src/navigation-update-artifact.js +227 -0
  80. package/dist/src/navigation-update-artifact.js.map +1 -0
  81. package/dist/src/navigation-update.d.ts +39 -0
  82. package/dist/src/navigation-update.js +82 -0
  83. package/dist/src/navigation-update.js.map +1 -0
  84. package/dist/src/observation.js +52 -29
  85. package/dist/src/observation.js.map +1 -1
  86. package/dist/src/observed-routes.js +5 -6
  87. package/dist/src/observed-routes.js.map +1 -1
  88. package/dist/src/pending-review-update.d.ts +0 -13
  89. package/dist/src/pending-review-update.js +1 -6
  90. package/dist/src/pending-review-update.js.map +1 -1
  91. package/dist/src/reviewer.d.ts +1 -1
  92. package/dist/src/reviewer.js +20 -17
  93. package/dist/src/reviewer.js.map +1 -1
  94. package/dist/src/runtime.d.ts +3 -0
  95. package/dist/src/runtime.js +6 -0
  96. package/dist/src/runtime.js.map +1 -0
  97. package/dist/src/state/corrective-replan-executor.d.ts +10 -0
  98. package/dist/src/state/corrective-replan-executor.js +37 -1
  99. package/dist/src/state/corrective-replan-executor.js.map +1 -1
  100. package/dist/src/state/corrective-replan-public-schema.js +15 -2
  101. package/dist/src/state/corrective-replan-public-schema.js.map +1 -1
  102. package/dist/src/state/corrective-replan-public.js +1 -1
  103. package/dist/src/state/corrective-replan-public.js.map +1 -1
  104. package/dist/src/state/corrective-replan-transaction.d.ts +1 -0
  105. package/dist/src/state/corrective-replan-transaction.js +12 -11
  106. package/dist/src/state/corrective-replan-transaction.js.map +1 -1
  107. package/dist/src/state/corrective-yield-transaction.d.ts +1 -0
  108. package/dist/src/state/corrective-yield-transaction.js +11 -10
  109. package/dist/src/state/corrective-yield-transaction.js.map +1 -1
  110. package/dist/src/state/lock.d.ts +4 -0
  111. package/dist/src/state/lock.js +21 -0
  112. package/dist/src/state/lock.js.map +1 -1
  113. package/dist/src/version.d.ts +1 -1
  114. package/dist/src/version.js +1 -1
  115. package/dist/src/version.js.map +1 -1
  116. package/dist/src/workflow-blocker-route.d.ts +8 -0
  117. package/dist/src/workflow-blocker-route.js +106 -0
  118. package/dist/src/workflow-blocker-route.js.map +1 -0
  119. package/dist/src/workflow.d.ts +48 -63
  120. package/dist/src/workflow.js +666 -1460
  121. package/dist/src/workflow.js.map +1 -1
  122. package/docs/autonomy-guardrails.md +26 -304
  123. package/docs/decisions.md +14 -112
  124. package/docs/development-flow.md +35 -238
  125. package/docs/project-memory.md +31 -50
  126. package/docs/release-app-evidence.md +190 -0
  127. package/docs/release.md +95 -388
  128. package/docs/updating-existing-project.md +19 -717
  129. package/package.json +11 -13
  130. package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +2 -2
  131. package/plugins/codex-workflow-gateway/references/chat-dispatch.md +73 -197
  132. package/plugins/codex-workflow-gateway/references/codebase-memory-routing.md +57 -0
  133. package/plugins/codex-workflow-gateway/references/protocol.md +42 -445
  134. package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +7 -1
  135. package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +10 -8
  136. package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +23 -5
  137. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +66 -757
  138. package/references/state-machine.md +4 -4
  139. package/roles/technical-planner.md +1 -1
  140. package/schemas/corrective-decision-event.schema.json +3 -1
  141. package/schemas/project-knowledge-map.schema.json +3 -1
  142. package/schemas/remediation-event.schema.json +10 -1
  143. package/schemas/task.schema.json +46 -2
  144. package/schemas/transition-payloads.schema.json +16 -1
  145. package/src/alpha6/adoption.ts +1123 -0
  146. package/src/alpha6/captured-check-evidence.ts +132 -0
  147. package/src/alpha6/check-support-anchor.ts +128 -0
  148. package/src/alpha6/component-owner.ts +168 -0
  149. package/src/alpha6/corrective-decision-boundary.ts +39 -0
  150. package/src/alpha6/downstream-proof.ts +520 -0
  151. package/src/alpha6/failed-step-planning-recovery.ts +88 -0
  152. package/src/alpha6/handoff.ts +1443 -0
  153. package/src/alpha6/journal.ts +473 -0
  154. package/src/alpha6/literal-test-invocation.ts +84 -0
  155. package/src/alpha6/mechanical-feasibility.ts +488 -0
  156. package/src/alpha6/milestone.ts +2192 -0
  157. package/src/alpha6/navigation-dirty-carryover.ts +154 -0
  158. package/src/alpha6/npm-check-contract.ts +47 -0
  159. package/src/alpha6/plan-integrity.ts +298 -0
  160. package/src/alpha6/plan-risk.ts +1480 -0
  161. package/src/alpha6/preexecution-replan.ts +187 -0
  162. package/src/alpha6/remediation-cause.ts +98 -0
  163. package/src/alpha6/remediation.ts +2438 -0
  164. package/src/alpha6/review.ts +1198 -0
  165. package/src/alpha6/root-cause-replan-carryover.ts +491 -0
  166. package/src/alpha6/store-sidecars.ts +335 -0
  167. package/src/alpha7/autonomy.ts +411 -0
  168. package/src/alpha7/corrective-recovery.ts +1332 -0
  169. package/src/artifacts.ts +130 -0
  170. package/src/beta1/project-transaction.ts +355 -0
  171. package/src/change-explanation.ts +153 -0
  172. package/src/checks/runner.ts +245 -0
  173. package/src/checks/task-sync.ts +41 -0
  174. package/src/checks/worker.ts +149 -0
  175. package/src/cli-actions.ts +107 -0
  176. package/src/cli.ts +1457 -0
  177. package/src/contracts.ts +1494 -0
  178. package/src/credential-output.ts +89 -0
  179. package/src/credential-transport.ts +215 -0
  180. package/src/delegation.ts +190 -0
  181. package/src/dependency-provenance.ts +472 -0
  182. package/src/diagnostics.ts +93 -0
  183. package/src/domain/base-sync-conflict.ts +35 -0
  184. package/src/domain/completed-step-carryover.ts +76 -0
  185. package/src/domain/discovery.ts +27 -0
  186. package/src/domain/plan-semantics.ts +58 -0
  187. package/src/domain/step-start-admission.ts +10 -0
  188. package/src/domain/validation.ts +177 -0
  189. package/src/domain/virgin-registration.ts +46 -0
  190. package/src/errors.ts +24 -0
  191. package/src/fs-utils.ts +61 -0
  192. package/src/gateway-handshake.ts +95 -0
  193. package/src/git.ts +183 -0
  194. package/src/graph.ts +342 -0
  195. package/src/historical-step-provenance.ts +136 -0
  196. package/src/index.ts +23 -0
  197. package/src/lifecycle/canonical-hash.ts +28 -0
  198. package/src/lifecycle/catalog.ts +202 -0
  199. package/src/lifecycle/compiler-inspection.ts +29 -0
  200. package/src/lifecycle/core-static-readiness.ts +132 -0
  201. package/src/lifecycle/corrective-replan-authority.ts +136 -0
  202. package/src/lifecycle/corrective-replan-binding-manifest.ts +51 -0
  203. package/src/lifecycle/corrective-replan-credential-core.ts +408 -0
  204. package/src/lifecycle/corrective-replan-credential-schema.ts +54 -0
  205. package/src/lifecycle/corrective-replan-credentials.ts +48 -0
  206. package/src/lifecycle/corrective-replan.ts +843 -0
  207. package/src/lifecycle/evaluator.ts +48 -0
  208. package/src/lifecycle/fingerprint.ts +488 -0
  209. package/src/lifecycle/immutable.ts +8 -0
  210. package/src/lifecycle/implementation-table.ts +118 -0
  211. package/src/lifecycle/index.ts +8 -0
  212. package/src/lifecycle/schema-artifact.ts +263 -0
  213. package/src/lifecycle/semantic-registry.ts +572 -0
  214. package/src/lifecycle/types.ts +838 -0
  215. package/src/memory.ts +273 -0
  216. package/src/migration.ts +161 -0
  217. package/src/navigation-actions.ts +70 -0
  218. package/src/navigation-update-artifact.ts +198 -0
  219. package/src/navigation-update.ts +121 -0
  220. package/src/observation.ts +225 -0
  221. package/src/observed-routes.ts +660 -0
  222. package/src/operational-contract.ts +125 -0
  223. package/src/pending-review-update.ts +175 -0
  224. package/src/repository.ts +99 -0
  225. package/src/reviewer.ts +1879 -0
  226. package/src/runtime.ts +6 -0
  227. package/src/state/corrective-replan-executor.ts +818 -0
  228. package/src/state/corrective-replan-public-schema.ts +83 -0
  229. package/src/state/corrective-replan-public.ts +908 -0
  230. package/src/state/corrective-replan-transaction.ts +949 -0
  231. package/src/state/corrective-yield-executor.ts +327 -0
  232. package/src/state/corrective-yield-transaction.ts +730 -0
  233. package/src/state/lock.ts +902 -0
  234. package/src/state/store.ts +567 -0
  235. package/src/transition-core.ts +330 -0
  236. package/src/ulid.ts +24 -0
  237. package/src/version.ts +2 -0
  238. package/src/workflow-blocker-route.ts +109 -0
  239. package/src/workflow.ts +10172 -0
  240. package/docs/alpha7.1-implementation-brief.md +0 -268
  241. package/docs/alpha7.2-corrective-context-refresh-brief.md +0 -484
  242. package/docs/alpha7.2.1-remediation-recovery-brief.md +0 -86
  243. package/docs/beta1-stabilization-brief.md +0 -165
  244. package/docs/beta11-plan-integrity-recovery-brief.md +0 -38
  245. package/docs/beta13.2-signal-review-recovery.md +0 -38
  246. package/docs/beta2-initial-assembly-navigation-brief.md +0 -616
  247. package/docs/change-model.md +0 -118
  248. package/docs/delegated-approval.md +0 -254
  249. package/docs/lifecycle/state-machine-stabilization.md +0 -641
  250. package/docs/pdf/README.md +0 -24
  251. package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
  252. package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
  253. package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
  254. package/docs/pdf/requirements.txt +0 -1
  255. package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +0 -478
  256. package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +0 -506
  257. package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +0 -778
  258. package/docs/pending-review-update.md +0 -15
  259. package/docs/problem-briefs/01-pre-implementation-integrity.md +0 -482
  260. package/docs/problem-briefs/02-minimal-step-integrity.md +0 -411
  261. package/docs/problem-briefs/03-minimal-agent-context-integrity.md +0 -358
  262. package/docs/problem-briefs/04-task-dependency-and-structural-replacement-integrity.md +0 -573
  263. package/docs/problem-briefs/BRIEF-TEMPLATE.md +0 -56
  264. package/docs/problem-briefs/README.md +0 -120
  265. package/docs/problem-briefs/evidence/p01-mechanical-feasibility-corpus.md +0 -90
  266. package/docs/problem-briefs/evidence/signal-v4-pre-m3-replay.md +0 -246
  267. package/docs/split-required-recovery.md +0 -47
  268. package/docs/stable-release-defect-register.md +0 -730
  269. package/docs/validation-report.md +0 -182
  270. package/scripts/generate-pdf-docs.py +0 -524
  271. package/scripts/run-pdf-docs.mjs +0 -62
@@ -0,0 +1,154 @@
1
+ import type { PlanIntegrityUpdateCompatibility, TaskState } from '../contracts.js';
2
+ import { inspectBoundedWorkflowDependencyCommit } from '../dependency-provenance.js';
3
+ import { assertKnowledgeBindingOnlyPlanChange, readVerifiedPlanArtifact } from '../domain/plan-semantics.js';
4
+ import { pathAllowed } from '../domain/validation.js';
5
+ import { validateTaskHistory } from '../git.js';
6
+ import { changedFiles, currentBranch, gitIsAncestor, headCommit, runGit } from '../repository.js';
7
+ import type { FileStateStore } from '../state/store.js';
8
+ import { hashDirtyWorktree } from './downstream-proof.js';
9
+ import { readTaskC1Posture, readTaskHandoffEvents } from './handoff.js';
10
+ import { assertMechanicalFeasibilityFresh, hashMechanicalFeasibilityEvidence } from './mechanical-feasibility.js';
11
+ import { readCurrentPlanRiskAudit, readPlanRiskAuditEvents } from './plan-risk.js';
12
+ import { verifiedHistoricalStepReviewBindings } from './review.js';
13
+ import { readRemediationEvents } from './remediation.js';
14
+
15
+ export interface NavigationDirtyCarryoverAssessment {
16
+ applicable: boolean;
17
+ eligible: boolean;
18
+ compatibility: PlanIntegrityUpdateCompatibility | null;
19
+ blockers: string[];
20
+ requiredActor?: string;
21
+ }
22
+
23
+ /** Validate an existing native checkpoint; this never grants writes or execution authority. */
24
+ export function assessNavigationDirtyCarryover(
25
+ store: FileStateStore,
26
+ repositoryRoot: string,
27
+ task: TaskState,
28
+ stepId?: string,
29
+ historyBoundary = headCommit(repositoryRoot),
30
+ authorizationBoundary = headCommit(repositoryRoot),
31
+ ): NavigationDirtyCarryoverAssessment {
32
+ const records = (task.dependencyProvenanceRecoveries ?? []).filter(record =>
33
+ record.planIntegrity?.mode === 'navigation-fix-dirty-carryover'
34
+ && (!stepId || record.planIntegrity.stepId === stepId));
35
+ const record = records.at(-1);
36
+ if (!record) return { applicable: false, eligible: false, compatibility: null, blockers: [] };
37
+ const binding = record.planIntegrity!;
38
+ const blockers: string[] = [];
39
+ const require = (condition: unknown, message: string) => { if (!condition) blockers.push(message); };
40
+ let requiredActor: string | undefined;
41
+ try {
42
+ const step = task.steps.find(candidate => candidate.id === binding.stepId);
43
+ require(task.status === 'ready' && task.workspaceOwner === 'local', 'Navigation checkpoint requires a ready local Task.');
44
+ require(task.taskBranch && currentBranch(repositoryRoot) === task.taskBranch, 'Navigation checkpoint requires the owned Task branch.');
45
+ require(step?.status === 'planned' && step.evidence === null, 'The checkpoint Step must remain planned without evidence.');
46
+ require(!task.steps.some(candidate => candidate.status === 'in_progress'), 'No Step may be in progress.');
47
+ require(task.systemCommits.includes(record.commitSha)
48
+ && (task.dependencyProvenanceRecoveries ?? []).filter(item => item.commitSha === record.commitSha).length === 1
49
+ && record.packageName === 'codex-workflow-v2'
50
+ && JSON.stringify(record.files) === JSON.stringify(['package-lock.json', 'package.json']),
51
+ 'The checkpoint must have one exact registered Workflow dependency record.');
52
+ const inspected = inspectBoundedWorkflowDependencyCommit(repositoryRoot, record.commitSha);
53
+ require(inspected?.parentCommitSha === record.parentCommitSha && inspected?.packageVersion === record.packageVersion,
54
+ 'The checkpoint is not the exact recorded dependency-only commit.');
55
+ require(gitIsAncestor(repositoryRoot, record.commitSha, historyBoundary), 'The checkpoint must precede the assessed history boundary.');
56
+ validateTaskHistory(repositoryRoot, task, historyBoundary);
57
+ const suffix = runGit(repositoryRoot, ['rev-list', '--reverse', `${record.commitSha}..${historyBoundary}`]).split('\n').filter(Boolean);
58
+ let parent = record.commitSha;
59
+ for (const commit of suffix) {
60
+ const inspectedCommit = inspectBoundedWorkflowDependencyCommit(repositoryRoot, commit);
61
+ const matches = (task.dependencyProvenanceRecoveries ?? []).filter(item => item.commitSha === commit);
62
+ require(inspectedCommit?.parentCommitSha === parent && matches.length === 1
63
+ && matches[0]!.parentCommitSha === parent && matches[0]!.packageVersion === inspectedCommit?.packageVersion
64
+ && task.systemCommits.includes(commit), 'Checkpoint continuation requires exact registered dependency-only continuity.');
65
+ parent = commit;
66
+ }
67
+ const dirty = changedFiles(repositoryRoot).sort();
68
+ require(dirty.length > 0 && JSON.stringify(dirty) === JSON.stringify([...binding.dirtyFiles].sort())
69
+ && hashDirtyWorktree(repositoryRoot, dirty) === binding.dirtyWorktreeHash, 'Navigation checkpoint dirty paths or bytes changed.');
70
+ if (step) {
71
+ require(dirty.every(file => pathAllowed(file, step.allowedWrites) && !pathAllowed(file, step.forbiddenScope)),
72
+ 'Checkpoint dirty files must remain inside allowedWrites and outside forbiddenScope.');
73
+ require(step.dependencies.every(id => task.steps.find(item => item.id === id)?.status === 'completed'),
74
+ 'Checkpoint Step dependencies must be complete.');
75
+ }
76
+ const events = readRemediationEvents(store, task).filter(event => event.stepId === binding.stepId && event.failureKind === 'checks-failed');
77
+ require(events.length > 0 && JSON.stringify(events.map(event => event.eventId)) === JSON.stringify(binding.remediationEventIds),
78
+ 'Checkpoint remediation IDs must equal the complete ordered native Step check-failure history.');
79
+ const terminal = events.at(-1);
80
+ require(terminal?.failureEvidence.dirtyWorktreeHash === binding.dirtyWorktreeHash
81
+ && terminal?.failureEvidence.completionCommit === record.parentCommitSha,
82
+ 'The terminal native failure must bind the checkpoint parent and exact dirty bytes.');
83
+ require(terminal && Number.isFinite(Date.parse(record.recordedAt)) && Date.parse(record.recordedAt) >= Date.parse(terminal.recordedAt),
84
+ 'The checkpoint timestamp must follow its native failure.');
85
+ const root = store.taskRoot(task.projectId, task.id);
86
+ require(task.planHash && task.briefHash && store.hashArtifact(root, 'brief.md') === task.briefHash,
87
+ 'Current Brief authority does not match its artifact.');
88
+ if (!task.planHash) throw new Error('Current Plan is missing.');
89
+ let expected = binding.planHash;
90
+ readVerifiedPlanArtifact(root, expected, task.planHash);
91
+ const planHashes = [expected];
92
+ if (expected !== task.planHash) {
93
+ const start = task.knowledgeRebinds.findIndex(item => item.previousPlanHash === expected);
94
+ require(start >= 0, 'Checkpoint Plan requires an exact Knowledge-rebind chain.');
95
+ let previousTime = Number.NEGATIVE_INFINITY;
96
+ for (const item of start < 0 ? [] : task.knowledgeRebinds.slice(start)) {
97
+ require(item.previousPlanHash === expected && !planHashes.includes(item.reboundPlanHash)
98
+ && Number.isFinite(Date.parse(item.recordedAt)) && Date.parse(item.recordedAt) > previousTime,
99
+ 'Checkpoint Knowledge-rebind chain forks, cycles, or has invalid timestamps.');
100
+ assertKnowledgeBindingOnlyPlanChange(readVerifiedPlanArtifact(root, item.previousPlanHash, task.planHash),
101
+ readVerifiedPlanArtifact(root, item.reboundPlanHash, task.planHash));
102
+ expected = item.reboundPlanHash;
103
+ previousTime = Date.parse(item.recordedAt);
104
+ planHashes.push(expected);
105
+ }
106
+ }
107
+ require(expected === task.planHash, 'Checkpoint Plan chain does not terminate at the current Plan.');
108
+ const audit = readCurrentPlanRiskAudit(store, task);
109
+ require(audit && ['approved', 'approved-with-rationale'].includes(audit.audit.decision), 'Current approved Plan Risk Audit is required.');
110
+ const audits = readPlanRiskAuditEvents(store, task);
111
+ const reviews = verifiedHistoricalStepReviewBindings(store, task);
112
+ for (const completed of task.steps.filter(item => item.status === 'completed')) {
113
+ require(completed.evidence, 'Every completed Step must retain execution evidence.');
114
+ const guarded = audits.some(item => item.stepClassifications.some(classification => classification.stepId === completed.id && classification.reviewRequired));
115
+ if (guarded) require(completed.evidence && reviews.some(review => review.stepId === completed.id && review.commitSha === completed.evidence!.commitSha),
116
+ 'Completed guarded Step must retain verified strict-review evidence.');
117
+ }
118
+ const selected = planHashes.map(hash => audits.filter(item => item.planHash === hash));
119
+ require(selected.every(matches => matches.length === 1), 'Checkpoint Plan audit chain is missing or ambiguous.');
120
+ if (selected.every(matches => matches.length === 1)) {
121
+ const chain = selected.map(matches => matches[0]!);
122
+ require(chain.at(-1)?.eventHash === audit?.audit.eventHash, 'Checkpoint audit chain must reach the current audit.');
123
+ chain.forEach((item, index) => {
124
+ require(item.briefHash === task.briefHash, 'Checkpoint audit Brief authority changed.');
125
+ if (index) {
126
+ const before = chain[index - 1]!;
127
+ require(item.previousEventHash === before.eventHash && item.planner === before.planner
128
+ && item.auditor !== item.planner && item.decision === before.decision
129
+ && item.splitRationale === before.splitRationale
130
+ && JSON.stringify(item.stepClassifications) === JSON.stringify(before.stepClassifications),
131
+ 'Checkpoint Plan Risk Audit changed beyond its Knowledge binding.');
132
+ }
133
+ });
134
+ }
135
+ const authorization = [...task.authorizations].reverse().find(item => item.kind === 'execution' && item.decision === 'approved');
136
+ require(authorization && authorization.planHash === task.planHash && authorization.briefHash === task.briefHash
137
+ && authorization.headCommit === authorizationBoundary
138
+ && gitIsAncestor(repositoryRoot, authorizationBoundary, headCommit(repositoryRoot)), 'Current execution authorization must bind the exact Brief, Plan and HEAD.');
139
+ if (!authorization?.mechanicalFeasibility) throw new Error('Checkpoint requires current mechanical-feasibility evidence.');
140
+ require(authorization.evidenceHash === hashMechanicalFeasibilityEvidence(authorization.mechanicalFeasibility)
141
+ && authorization.headCommit === authorization.mechanicalFeasibility.evaluatedHead,
142
+ 'Mechanical-feasibility evidence hash or HEAD does not match authorization.');
143
+ assertMechanicalFeasibilityFresh(repositoryRoot, task, authorization.mechanicalFeasibility, task.briefHash!, task.planHash);
144
+ const posture = readTaskC1Posture(store, task);
145
+ const claim = readTaskHandoffEvents(store, task).filter(event => event.eventType === 'claim').at(-1);
146
+ requiredActor = claim?.actor;
147
+ require(posture.state === 'claimed' && claim && posture.claimant === claim.actor
148
+ && planHashes.includes(claim.planHash ?? '') && claim.briefHash === task.briefHash,
149
+ 'Checkpoint requires its original actor with a current immutable claimed C1 boundary.');
150
+ } catch (error) { blockers.push(error instanceof Error ? error.message : String(error)); }
151
+ return { applicable: true, eligible: blockers.length === 0,
152
+ compatibility: blockers.length ? null : { ...structuredClone(binding), planHash: task.planHash! },
153
+ blockers: [...new Set(blockers)], ...(requiredActor ? { requiredActor } : {}) };
154
+ }
@@ -0,0 +1,47 @@
1
+ import path from 'node:path';
2
+
3
+ const NPM_RUN_CHECK = /^npm\s+run(?:-script)?\s+([A-Za-z0-9._:-]+)(?:\s+--\s+(.+))?$/;
4
+ const SAFE_FORWARD_ARG = /^--[A-Za-z0-9._:-]+=[^\s]+$/;
5
+ const NODE_AFFECTED_SCRIPT = /^node\s+([A-Za-z0-9._/-]+\.(?:cjs|js|mjs))\s+affected$/;
6
+ const SELECTOR = /^(?:--base|--head|--files)=.+$/;
7
+
8
+ export interface ParsedNpmRunCheck {
9
+ script: string;
10
+ forwardedArguments: string[];
11
+ }
12
+
13
+ export interface RequiredSelectorContract {
14
+ runnerPath: string;
15
+ selectors: readonly ['--base', '--head', '--files'];
16
+ }
17
+
18
+ export function parseNpmRunCheck(command: string): ParsedNpmRunCheck | null {
19
+ const match = NPM_RUN_CHECK.exec(command.trim());
20
+ if (!match) return null;
21
+ const forwardedArguments = match[2]?.trim().split(/\s+/).filter(Boolean) ?? [];
22
+ if (forwardedArguments.some((argument) => !SAFE_FORWARD_ARG.test(argument))) return null;
23
+ return { script: match[1]!, forwardedArguments };
24
+ }
25
+
26
+ export function inspectRequiredSelectorContract(
27
+ scriptCommand: string,
28
+ readRunner: (runnerPath: string) => string | null,
29
+ ): RequiredSelectorContract | null {
30
+ const match = NODE_AFFECTED_SCRIPT.exec(scriptCommand.trim());
31
+ if (!match) return null;
32
+ const runnerPath = match[1]!;
33
+ if (path.posix.isAbsolute(runnerPath)
34
+ || runnerPath.split('/').some((segment) => segment === '.' || segment === '..')) return null;
35
+ const source = readRunner(runnerPath);
36
+ if (!source
37
+ || !source.includes('process.argv.slice(3)')
38
+ || !source.includes('--base')
39
+ || !source.includes('--head')
40
+ || !source.includes('--files')
41
+ || !source.includes('Affected mode requires')) return null;
42
+ return { runnerPath, selectors: ['--base', '--head', '--files'] };
43
+ }
44
+
45
+ export function hasRequiredSelector(arguments_: readonly string[]): boolean {
46
+ return arguments_.some((argument) => SELECTOR.test(argument));
47
+ }
@@ -0,0 +1,298 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { existsSync, lstatSync, readFileSync } from 'node:fs';
4
+ import path from 'node:path';
5
+ import type {
6
+ PlanIntegrityConflictEvidence,
7
+ PlanIntegrityConflictEvidenceV2,
8
+ PlanIntegrityConflictEvidenceV3,
9
+ PlanIntegrityConflictEvidenceV4,
10
+ TaskState,
11
+ } from '../contracts.js';
12
+ import { pathAllowed } from '../domain/validation.js';
13
+ import { WorkflowError } from '../errors.js';
14
+ import { hashCanonical } from '../lifecycle/fingerprint.js';
15
+ import { changedFiles, headCommit } from '../repository.js';
16
+ import type { FileStateStore } from '../state/store.js';
17
+ import {
18
+ hasRequiredSelector,
19
+ inspectRequiredSelectorContract,
20
+ parseNpmRunCheck,
21
+ } from './npm-check-contract.js';
22
+ import { inspectCheckSupportAnchors } from './check-support-anchor.js';
23
+ import { inspectFailedCheckComponentOwners, semanticBoundaryHash } from './component-owner.js';
24
+ import {
25
+ readCorrectiveDecisionEvents,
26
+ readRemediationEvents,
27
+ selectPlanIntegrityRemediations,
28
+ } from './remediation.js';
29
+
30
+ const ROOT_MANIFEST = 'package.json' as const;
31
+ const TASK_ID = /^TASK-[0-9A-HJKMNP-TV-Z]{26}$/;
32
+
33
+ export interface PlanIntegrityRecoveryAssessment {
34
+ readonly applicable: boolean;
35
+ readonly eligible: boolean;
36
+ readonly taskId: string;
37
+ readonly taskRevision: number;
38
+ readonly stepId: string;
39
+ readonly planHash: string | null;
40
+ readonly blockers: readonly string[];
41
+ readonly evidence: PlanIntegrityConflictEvidence | null;
42
+ }
43
+
44
+ export function defaultPlanIntegrityRecoveryActor(taskId: string): string {
45
+ if (!TASK_ID.test(taskId)) {
46
+ throw new WorkflowError('INVALID_ARGUMENT', `Cannot derive a Plan-integrity recovery actor from invalid Task ID ${taskId}.`);
47
+ }
48
+ return `agent:plan-integrity-recovery:${taskId.toLowerCase()}`;
49
+ }
50
+
51
+ export function assessPlanIntegrityRecovery(
52
+ store: FileStateStore,
53
+ repositoryRoot: string,
54
+ task: TaskState,
55
+ stepId: string,
56
+ ): PlanIntegrityRecoveryAssessment {
57
+ const base = {
58
+ taskId: task.id,
59
+ taskRevision: task.revision,
60
+ stepId,
61
+ planHash: task.planHash,
62
+ };
63
+ const step = task.steps.find((candidate) => candidate.id === stepId) ?? null;
64
+ if (!step || task.status !== 'needs_fix' || step.status !== 'failed' || !task.planHash) {
65
+ return { ...base, applicable: false, eligible: false, blockers: [], evidence: null };
66
+ }
67
+
68
+ const allRemediations = readRemediationEvents(store, task).filter((event) => event.stepId === stepId);
69
+ const decisions = readCorrectiveDecisionEvents(store, task).filter((event) => event.stepId === stepId);
70
+ const remediations = selectPlanIntegrityRemediations(store, task, allRemediations, decisions);
71
+ if (!remediations || remediations.some(event => event.failureKind !== 'checks-failed')) {
72
+ return { ...base, applicable: false, eligible: false, blockers: [], evidence: null };
73
+ }
74
+
75
+ const manifest = inspectRootManifest(repositoryRoot);
76
+ const runnerBlockers: string[] = [];
77
+ const conflicts: PlanIntegrityConflictEvidenceV2['conflicts'] = [];
78
+ for (const command of step.checks) {
79
+ const invocation = parseNpmRunCheck(command);
80
+ if (!invocation) continue;
81
+ const scriptCommand = manifest.scripts.get(invocation.script) ?? null;
82
+ if (scriptCommand === null) {
83
+ if (pathAllowed(ROOT_MANIFEST, step.allowedWrites)) continue;
84
+ conflicts.push({
85
+ reasonCode: 'MISSING_NPM_SCRIPT' as const,
86
+ script: invocation.script,
87
+ command: command.trim(),
88
+ scriptCommand: null,
89
+ runnerPath: null,
90
+ runnerHash: null,
91
+ });
92
+ continue;
93
+ }
94
+ const runnerInspections = new Map<string, ReturnType<typeof inspectRepositoryFile>>();
95
+ const selectorContract = inspectRequiredSelectorContract(scriptCommand, (runnerPath) => {
96
+ const inspection = inspectRepositoryFile(repositoryRoot, runnerPath);
97
+ runnerInspections.set(runnerPath, inspection);
98
+ return inspection.content;
99
+ });
100
+ if (!selectorContract || hasRequiredSelector(invocation.forwardedArguments)) continue;
101
+ const runnerInspection = runnerInspections.get(selectorContract.runnerPath) ?? null;
102
+ if (runnerInspection?.blocker) runnerBlockers.push(runnerInspection.blocker);
103
+ if (pathAllowed(ROOT_MANIFEST, step.allowedWrites)
104
+ || pathAllowed(selectorContract.runnerPath, step.allowedWrites)) continue;
105
+ conflicts.push({
106
+ reasonCode: 'REQUIRED_SELECTOR_MISSING' as const,
107
+ script: invocation.script,
108
+ command: command.trim(),
109
+ scriptCommand,
110
+ runnerPath: selectorContract.runnerPath,
111
+ runnerHash: runnerInspection?.hash ?? null,
112
+ });
113
+ }
114
+ conflicts.sort((left, right) => left.command.localeCompare(right.command) || left.reasonCode.localeCompare(right.reasonCode));
115
+ const supportConflicts: PlanIntegrityConflictEvidenceV3['conflicts'] = conflicts.length === 0
116
+ ? inspectCheckSupportAnchors({
117
+ step,
118
+ scripts: manifest.scripts,
119
+ readFile: (relativePath) => {
120
+ const inspected = inspectRepositoryFile(repositoryRoot, relativePath);
121
+ return { content: inspected.content, hash: inspected.hash };
122
+ },
123
+ pathExists: (relativePath) => gitPathExists(repositoryRoot, headCommit(repositoryRoot), relativePath),
124
+ }).map((conflict) => ({
125
+ ...conflict,
126
+ expectedOutputPaths: [...conflict.expectedOutputPaths],
127
+ anchoredMigrationNames: [...conflict.anchoredMigrationNames],
128
+ }))
129
+ : [];
130
+ const componentOwnerConflicts: PlanIntegrityConflictEvidenceV4['conflicts'] =
131
+ conflicts.length === 0 && supportConflicts.length === 0
132
+ ? inspectFailedCheckComponentOwners({
133
+ step,
134
+ scripts: manifest.scripts,
135
+ isTracked: (relativePath) => gitPathExists(repositoryRoot, headCommit(repositoryRoot), relativePath),
136
+ readFile: (relativePath) => {
137
+ const inspected = inspectRepositoryFile(repositoryRoot, relativePath);
138
+ return { content: inspected.content, hash: inspected.hash };
139
+ },
140
+ }).map(({ causeFingerprint: _causeFingerprint, ...conflict }) => conflict)
141
+ : [];
142
+ if (conflicts.length === 0 && supportConflicts.length === 0 && componentOwnerConflicts.length === 0) {
143
+ return { ...base, applicable: false, eligible: false, blockers: [], evidence: null };
144
+ }
145
+
146
+ if (remediations.length !== allRemediations.length && conflicts.length > 0)
147
+ return { ...base, applicable: false, eligible: false, blockers: [], evidence: null };
148
+
149
+ const files = [...changedFiles(repositoryRoot)].sort();
150
+ const outsideAllowedWrites = files.filter((file) => !pathAllowed(file, step.allowedWrites));
151
+ const forbiddenChanges = files.filter((file) => pathAllowed(file, step.forbiddenScope));
152
+ const blockers = [
153
+ ...(manifest.blocker ? [manifest.blocker] : []),
154
+ ...runnerBlockers,
155
+ ...(outsideAllowedWrites.length > 0
156
+ ? [`Changed files outside ${stepId} allowedWrites: ${outsideAllowedWrites.join(', ')}`]
157
+ : []),
158
+ ...(forbiddenChanges.length > 0
159
+ ? [`Changed files intersect ${stepId} forbiddenScope: ${forbiddenChanges.join(', ')}`]
160
+ : []),
161
+ ...supportConflicts.flatMap((conflict) => pathAllowed(conflict.supportPath, step.forbiddenScope)
162
+ ? [`Check-support authority ${conflict.supportPath} intersects ${stepId} forbiddenScope.`]
163
+ : []),
164
+ ...componentOwnerConflicts.flatMap((conflict) => pathAllowed(conflict.ownerPath, step.forbiddenScope)
165
+ ? [`Component owner ${conflict.ownerPath} intersects ${stepId} forbiddenScope.`]
166
+ : []),
167
+ ];
168
+ const stepDefinitionHash = hashCanonical({
169
+ id: step.id,
170
+ title: step.title,
171
+ objective: step.objective,
172
+ requirements: step.requirements,
173
+ dependencies: step.dependencies,
174
+ expectedOutputs: step.expectedOutputs,
175
+ allowedWrites: step.allowedWrites,
176
+ forbiddenScope: step.forbiddenScope,
177
+ checks: step.checks,
178
+ });
179
+ const remediationBindings = remediations.map((event) => ({
180
+ eventId: event.eventId,
181
+ eventHash: event.eventHash,
182
+ attemptOrdinal: event.attemptOrdinal,
183
+ }));
184
+ const evidenceBase = supportConflicts.length > 0 ? {
185
+ kind: 'check-support-anchor-outside-allowed-writes' as const,
186
+ taskRevision: task.revision,
187
+ remediationEvents: remediationBindings,
188
+ manifestPath: ROOT_MANIFEST,
189
+ manifestHash: manifest.hash,
190
+ gitHead: headCommit(repositoryRoot),
191
+ stepDefinitionHash,
192
+ conflicts: supportConflicts,
193
+ scopeExtensions: [...new Set(supportConflicts.map((conflict) => conflict.supportPath))].sort(),
194
+ changedFiles: files,
195
+ } : componentOwnerConflicts.length > 0 ? {
196
+ kind: 'failed-check-component-owner-outside-allowed-writes' as const,
197
+ taskRevision: task.revision,
198
+ remediationEvents: remediationBindings,
199
+ gitHead: headCommit(repositoryRoot),
200
+ stepDefinitionHash,
201
+ semanticBoundaryHash: semanticBoundaryHash(task),
202
+ failedCheckCauseFingerprint: step.failurePause!.failedChecks!.find((failure) =>
203
+ componentOwnerConflicts.some((conflict) => conflict.command === failure.command))!.causeFingerprint,
204
+ conflicts: componentOwnerConflicts,
205
+ scopeExtensions: [...new Set(componentOwnerConflicts.map((conflict) => conflict.ownerPath))].sort(),
206
+ changedFiles: files,
207
+ } : {
208
+ kind: 'non-executable-npm-check-outside-allowed-writes' as const,
209
+ taskRevision: task.revision,
210
+ remediationEvents: remediationBindings,
211
+ manifestPath: ROOT_MANIFEST,
212
+ manifestHash: manifest.hash,
213
+ gitHead: headCommit(repositoryRoot),
214
+ stepDefinitionHash,
215
+ conflicts,
216
+ changedFiles: files,
217
+ };
218
+ const evidence: PlanIntegrityConflictEvidence = {
219
+ ...evidenceBase,
220
+ conflictHash: hashCanonical({
221
+ domain: supportConflicts.length > 0
222
+ ? 'codex-workflow/plan-integrity-conflict/v3'
223
+ : componentOwnerConflicts.length > 0
224
+ ? 'codex-workflow/plan-integrity-conflict/v4'
225
+ : 'codex-workflow/plan-integrity-conflict/v2',
226
+ taskId: task.id,
227
+ planHash: task.planHash,
228
+ stepId,
229
+ evidence: evidenceBase,
230
+ }),
231
+ };
232
+ return {
233
+ ...base,
234
+ applicable: true,
235
+ eligible: blockers.length === 0,
236
+ blockers,
237
+ evidence,
238
+ };
239
+ }
240
+
241
+ function gitPathExists(repositoryRoot: string, commit: string, relativePath: string): boolean {
242
+ const result = spawnSync('git', ['-C', repositoryRoot, 'cat-file', '-e', `${commit}:${relativePath}`], {
243
+ encoding: 'utf8',
244
+ });
245
+ if (result.status === 0) return true;
246
+ if (result.status === 128) return false;
247
+ throw new WorkflowError('COMMAND_FAILED', `Unable to inspect ${relativePath} at ${commit}.`, {
248
+ stderr: result.stderr.trim(),
249
+ exitCode: result.status,
250
+ });
251
+ }
252
+
253
+ function inspectRootManifest(repositoryRoot: string): {
254
+ readonly scripts: ReadonlyMap<string, string>;
255
+ readonly hash: string | null;
256
+ readonly blocker: string | null;
257
+ } {
258
+ const file = path.join(repositoryRoot, ROOT_MANIFEST);
259
+ if (!existsSync(file)) return { scripts: new Map(), hash: null, blocker: null };
260
+ const stat = lstatSync(file);
261
+ if (!stat.isFile()) {
262
+ return { scripts: new Map(), hash: null, blocker: 'Root package.json is not a regular file.' };
263
+ }
264
+ const bytes = readFileSync(file);
265
+ const hash = createHash('sha256').update(bytes).digest('hex');
266
+ try {
267
+ const parsed = JSON.parse(bytes.toString('utf8')) as { scripts?: unknown };
268
+ if (parsed.scripts === undefined) return { scripts: new Map(), hash, blocker: null };
269
+ if (!parsed.scripts || typeof parsed.scripts !== 'object' || Array.isArray(parsed.scripts)) {
270
+ return { scripts: new Map(), hash, blocker: 'Root package.json scripts field is malformed.' };
271
+ }
272
+ return {
273
+ scripts: new Map(Object.entries(parsed.scripts)
274
+ .filter((entry): entry is [string, string] => typeof entry[1] === 'string')),
275
+ hash,
276
+ blocker: null,
277
+ };
278
+ } catch {
279
+ return { scripts: new Map(), hash, blocker: 'Root package.json is not valid JSON.' };
280
+ }
281
+ }
282
+
283
+ function inspectRepositoryFile(repositoryRoot: string, relativePath: string): {
284
+ content: string | null;
285
+ hash: string | null;
286
+ blocker: string | null;
287
+ } {
288
+ const file = path.join(repositoryRoot, relativePath);
289
+ if (!existsSync(file)) return { content: null, hash: null, blocker: `${relativePath} is missing.` };
290
+ const stat = lstatSync(file);
291
+ if (!stat.isFile()) return { content: null, hash: null, blocker: `${relativePath} is not a regular file.` };
292
+ const bytes = readFileSync(file);
293
+ return {
294
+ content: bytes.toString('utf8'),
295
+ hash: createHash('sha256').update(bytes).digest('hex'),
296
+ blocker: null,
297
+ };
298
+ }