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,730 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import {
3
+ closeSync,
4
+ constants,
5
+ existsSync,
6
+ fsyncSync,
7
+ lstatSync,
8
+ mkdirSync,
9
+ openSync,
10
+ readFileSync,
11
+ readdirSync,
12
+ renameSync,
13
+ rmSync,
14
+ writeSync,
15
+ } from 'node:fs';
16
+ import path from 'node:path';
17
+ import { Ajv2020 } from 'ajv/dist/2020.js';
18
+ import type { CorruptObservedTaskTransaction, ObservedTaskTransaction } from '../contracts.js';
19
+ import { WorkflowError } from '../errors.js';
20
+ import { assertNoSymlinkPathComponents, computeSidecarEventHash } from '../alpha6/store-sidecars.js';
21
+ import { hashCanonical } from '../lifecycle/fingerprint.js';
22
+ import { deepFreeze } from '../lifecycle/immutable.js';
23
+ import {
24
+ WriterLockManager,
25
+ assertCoreTaskOperationHandle,
26
+ type CoreTaskOperationHandle,
27
+ type WriterLeaseHandle,
28
+ } from './lock.js';
29
+
30
+ const HASH = /^[a-f0-9]{64}$/;
31
+ const TASK = /^TASK-[0-9A-HJKMNP-TV-Z]{26}$/;
32
+ const PROJECT = /^[a-f0-9]{24}$/;
33
+ const TRANSITION = 'task.coordination.corrective-yield' as const;
34
+ const schema = JSON.parse(readFileSync(
35
+ new URL('../../../schemas/corrective-yield-task-transaction.private.schema.json', import.meta.url),
36
+ 'utf8',
37
+ )) as object;
38
+ const ajv = new Ajv2020({
39
+ allErrors: true,
40
+ strict: true,
41
+ formats: {
42
+ 'date-time': {
43
+ type: 'string',
44
+ validate(value: string) {
45
+ const parsed = Date.parse(value);
46
+ return Number.isFinite(parsed) && new Date(parsed).toISOString() === value;
47
+ },
48
+ },
49
+ },
50
+ });
51
+ if (!ajv.validateSchema(schema)) throw new TypeError(`Invalid corrective-yield transaction schema: ${ajv.errorsText(ajv.errors)}`);
52
+ const validateSchema = ajv.compile(schema);
53
+
54
+ export type CorrectiveYieldTransactionPhase = 'intent' | 'applying' | 'published-pending-readback' | 'completed';
55
+
56
+ interface SnapshotRecord {
57
+ exists: boolean;
58
+ contentSha256: string | null;
59
+ byteLength: number;
60
+ contentPath: 'preimages/0.bin' | null;
61
+ }
62
+
63
+ export interface CorrectiveYieldTaskTransactionJournal {
64
+ schemaVersion: 1;
65
+ kind: 'corrective-yield-task-transaction';
66
+ transactionId: string;
67
+ projectId: string;
68
+ taskId: string;
69
+ transitionId: typeof TRANSITION;
70
+ taskStateSha256: string;
71
+ actor: string;
72
+ writerLeaseTokenSha256: string;
73
+ writerLeaseContentSha256: string;
74
+ writerLeaseExpiresAt: string;
75
+ admittedAt: string;
76
+ intentSha256: string;
77
+ phase: CorrectiveYieldTransactionPhase;
78
+ phaseRevision: number;
79
+ nextTargetOrdinal: number;
80
+ handoffTarget: {
81
+ targetId: 'handoff-event';
82
+ ordinal: 0;
83
+ relativePath: 'handoffs.jsonl';
84
+ preimage: SnapshotRecord;
85
+ postimage: { contentSha256: string; byteLength: number; contentPath: 'targets/0.bin' };
86
+ previousEventHash: string | null;
87
+ resultEventHash: string;
88
+ };
89
+ phaseHistory: Array<{
90
+ sequence: number;
91
+ phase: CorrectiveYieldTransactionPhase;
92
+ nextTargetOrdinal: number;
93
+ recordedAt: string;
94
+ previousEntryHash: string | null;
95
+ entryHash: string;
96
+ }>;
97
+ completedResult: { resultSha256: string } | null;
98
+ updatedAt: string;
99
+ }
100
+
101
+ export interface CreateCorrectiveYieldIntentInput {
102
+ projectId: string;
103
+ taskId: string;
104
+ transactionId: string;
105
+ taskStateSha256: string;
106
+ actor: string;
107
+ writerLeaseTokenSha256: string;
108
+ writerLeaseContentSha256: string;
109
+ writerLeaseExpiresAt: string;
110
+ handoffPostimage: Uint8Array;
111
+ }
112
+
113
+ export class CorrectiveYieldTaskTransactionStore {
114
+ private readonly taskRoot: string;
115
+ private readonly locksRoot: string;
116
+ private readonly trustedRoot: string;
117
+ private readonly now: () => Date;
118
+ private readonly writerLocks: WriterLockManager;
119
+
120
+ constructor(options: { taskRoot: string; locksRoot: string; trustedRoot: string; now?: () => Date }) {
121
+ this.taskRoot = path.resolve(options.taskRoot);
122
+ this.locksRoot = path.resolve(options.locksRoot);
123
+ this.trustedRoot = path.resolve(options.trustedRoot);
124
+ this.now = options.now ?? (() => new Date());
125
+ this.writerLocks = new WriterLockManager(this.locksRoot, this.now, this.trustedRoot);
126
+ assertTaskRoot(this.taskRoot, this.trustedRoot);
127
+ }
128
+
129
+ createIntent(writerHandle: WriterLeaseHandle, input: CreateCorrectiveYieldIntentInput): CorrectiveYieldTaskTransactionJournal {
130
+ assertExactRecord(input, [
131
+ 'actor', 'handoffPostimage', 'projectId', 'taskId', 'taskStateSha256', 'transactionId',
132
+ 'writerLeaseContentSha256', 'writerLeaseExpiresAt', 'writerLeaseTokenSha256',
133
+ ], 'Corrective-yield intent');
134
+ assertIntent(input);
135
+ const proof = this.writerLocks.proofBinding(writerHandle);
136
+ if (proof.entityId !== input.taskId || proof.owner !== input.actor
137
+ || proof.tokenSha256 !== input.writerLeaseTokenSha256
138
+ || proof.contentSha256 !== input.writerLeaseContentSha256
139
+ || proof.expiresAt !== input.writerLeaseExpiresAt) {
140
+ throw new WorkflowError('LOCK_TOKEN_INVALID', 'Writer lease proof does not match the corrective-yield intent.');
141
+ }
142
+ const stateBytes = readRequired(this.targetPath('state.json'), this.taskRoot);
143
+ if (sha256(stateBytes) !== input.taskStateSha256) {
144
+ throw new WorkflowError('STATE_CONFLICT', 'Corrective-yield Task snapshot changed before intent.');
145
+ }
146
+ const preimage = readOptional(this.targetPath('handoffs.jsonl'), this.taskRoot);
147
+ const postimage = Buffer.from(input.handoffPostimage);
148
+ const anchors = validateAppend(preimage, postimage, input.transactionId, input.actor, input.writerLeaseTokenSha256);
149
+ const root = this.transactionRoot(input.transactionId);
150
+ if (existsSync(root)) throw new WorkflowError('STATE_CONFLICT', 'Corrective-yield transaction already exists.');
151
+ const admittedAt = this.now().toISOString();
152
+ const journal: CorrectiveYieldTaskTransactionJournal = {
153
+ schemaVersion: 1,
154
+ kind: 'corrective-yield-task-transaction',
155
+ transactionId: input.transactionId,
156
+ projectId: input.projectId,
157
+ taskId: input.taskId,
158
+ transitionId: TRANSITION,
159
+ taskStateSha256: input.taskStateSha256,
160
+ actor: input.actor,
161
+ writerLeaseTokenSha256: input.writerLeaseTokenSha256,
162
+ writerLeaseContentSha256: input.writerLeaseContentSha256,
163
+ writerLeaseExpiresAt: input.writerLeaseExpiresAt,
164
+ admittedAt,
165
+ intentSha256: '0'.repeat(64),
166
+ phase: 'intent',
167
+ phaseRevision: 1,
168
+ nextTargetOrdinal: 0,
169
+ handoffTarget: {
170
+ targetId: 'handoff-event',
171
+ ordinal: 0,
172
+ relativePath: 'handoffs.jsonl',
173
+ preimage: snapshot(preimage),
174
+ postimage: { contentSha256: sha256(postimage), byteLength: postimage.byteLength, contentPath: 'targets/0.bin' },
175
+ ...anchors,
176
+ },
177
+ phaseHistory: [],
178
+ completedResult: null,
179
+ updatedAt: admittedAt,
180
+ };
181
+ journal.intentSha256 = intentHash(journal);
182
+ journal.phaseHistory = [phaseEntry(0, 'intent', 0, admittedAt, null, journal.intentSha256)];
183
+ validateJournal(journal, this.taskRoot, false);
184
+ ensureDirectorySafe(path.dirname(root), this.trustedRoot);
185
+ let created = false;
186
+ try {
187
+ mkdirSync(root, { mode: 0o700 });
188
+ created = true;
189
+ mkdirSync(path.join(root, 'preimages'), { mode: 0o700 });
190
+ mkdirSync(path.join(root, 'targets'), { mode: 0o700 });
191
+ if (preimage) writeNewDurable(path.join(root, 'preimages', '0.bin'), preimage);
192
+ writeNewDurable(path.join(root, 'targets', '0.bin'), postimage);
193
+ this.writerLocks.proofBinding(writerHandle);
194
+ writeNewDurable(this.journalPath(input.transactionId), jsonBytes(journal));
195
+ fsyncDirectory(root);
196
+ return this.read(input.transactionId);
197
+ } catch (error) {
198
+ if (created && !existsSync(this.journalPath(input.transactionId))) rmSync(root, { recursive: true, force: true });
199
+ throw error;
200
+ }
201
+ }
202
+
203
+ read(transactionId: string): CorrectiveYieldTaskTransactionJournal {
204
+ assertHash(transactionId, 'Corrective-yield transaction id');
205
+ const file = this.journalPath(transactionId);
206
+ assertNoSymlinkPathComponents(file, { label: 'Corrective-yield journal', trustedRoot: this.trustedRoot, requireLeaf: true });
207
+ if (!existsSync(file)) throw new WorkflowError('NOT_FOUND', `Corrective-yield journal not found: ${transactionId}`);
208
+ let value: unknown;
209
+ try { value = JSON.parse(readFileSync(file, 'utf8')); } catch { throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield journal is not valid JSON.'); }
210
+ validateJournal(value, this.taskRoot, true);
211
+ const journal = value as CorrectiveYieldTaskTransactionJournal;
212
+ if (journal.transactionId !== transactionId || journal.taskId !== path.basename(this.taskRoot)) {
213
+ throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield journal path identity mismatch.');
214
+ }
215
+ return deepFreeze(structuredClone(journal));
216
+ }
217
+
218
+ journalPath(transactionId: string): string {
219
+ assertHash(transactionId, 'Corrective-yield transaction id');
220
+ return path.join(this.transactionRoot(transactionId), 'journal.json');
221
+ }
222
+
223
+ advanceWithWriter(
224
+ writerHandle: WriterLeaseHandle,
225
+ transactionId: string,
226
+ expectedPhase: CorrectiveYieldTransactionPhase,
227
+ expectedRevision: number,
228
+ nextPhase: CorrectiveYieldTransactionPhase,
229
+ nextTargetOrdinal: number,
230
+ ): CorrectiveYieldTaskTransactionJournal {
231
+ const journal = this.read(transactionId);
232
+ const proof = this.writerLocks.proofBinding(writerHandle);
233
+ if (proof.entityId !== journal.taskId || proof.tokenSha256 !== journal.writerLeaseTokenSha256) {
234
+ throw new WorkflowError('LOCK_TOKEN_INVALID', 'Writer proof does not own the corrective-yield transaction.');
235
+ }
236
+ return this.advance(journal, expectedPhase, expectedRevision, nextPhase, nextTargetOrdinal);
237
+ }
238
+
239
+ advanceWithCore(
240
+ handle: CoreTaskOperationHandle,
241
+ transactionId: string,
242
+ expectedPhase: CorrectiveYieldTransactionPhase,
243
+ expectedRevision: number,
244
+ nextPhase: CorrectiveYieldTransactionPhase,
245
+ nextTargetOrdinal: number,
246
+ completedResultSha256?: string,
247
+ ): CorrectiveYieldTaskTransactionJournal {
248
+ const journal = this.read(transactionId);
249
+ assertCoreTaskOperationHandle(this.locksRoot, this.trustedRoot, handle, this.now(), {
250
+ entityId: journal.taskId,
251
+ operationId: journal.transactionId,
252
+ transitionId: journal.transitionId,
253
+ });
254
+ return this.advance(journal, expectedPhase, expectedRevision, nextPhase, nextTargetOrdinal, completedResultSha256);
255
+ }
256
+
257
+ applyHandoffWithWriter(writerHandle: WriterLeaseHandle, transactionId: string): 'applied' | 'confirmed' {
258
+ const journal = this.read(transactionId);
259
+ const proof = this.writerLocks.proofBinding(writerHandle);
260
+ if (proof.entityId !== journal.taskId || proof.tokenSha256 !== journal.writerLeaseTokenSha256) {
261
+ throw new WorkflowError('LOCK_TOKEN_INVALID', 'Writer proof does not own the corrective-yield transaction.');
262
+ }
263
+ return this.applyHandoff(journal);
264
+ }
265
+
266
+ applyHandoffWithCore(handle: CoreTaskOperationHandle, transactionId: string): 'applied' | 'confirmed' {
267
+ const journal = this.read(transactionId);
268
+ assertCoreTaskOperationHandle(this.locksRoot, this.trustedRoot, handle, this.now(), {
269
+ entityId: journal.taskId,
270
+ operationId: journal.transactionId,
271
+ transitionId: journal.transitionId,
272
+ });
273
+ return this.applyHandoff(journal);
274
+ }
275
+
276
+ domainReadback(handle: CoreTaskOperationHandle, transactionId: string): string {
277
+ const journal = this.read(transactionId);
278
+ assertCoreTaskOperationHandle(this.locksRoot, this.trustedRoot, handle, this.now(), {
279
+ entityId: journal.taskId,
280
+ operationId: journal.transactionId,
281
+ transitionId: journal.transitionId,
282
+ });
283
+ if (journal.phase !== 'published-pending-readback') {
284
+ throw new WorkflowError('TASK_RECOVERY_REQUIRED', 'Corrective-yield domain readback requires its published phase.');
285
+ }
286
+ assertCanonicalHandoff(this.taskRoot, journal);
287
+ assertTaskState(this.taskRoot, journal.taskStateSha256);
288
+ return resultHash(journal);
289
+ }
290
+
291
+ terminalReadback(transactionId: string): string {
292
+ const journal = this.read(transactionId);
293
+ if (journal.phase !== 'completed' || !journal.completedResult) {
294
+ throw new WorkflowError('TASK_RECOVERY_REQUIRED', 'Corrective-yield transaction is not terminal.');
295
+ }
296
+ assertCanonicalHandoffRetained(this.taskRoot, journal);
297
+ if (existsSync(path.join(this.locksRoot, `${journal.taskId}.json`))) {
298
+ throw new WorkflowError('STATE_CONFLICT', 'Corrective-yield terminal readback requires the writer/Core lock slot to be empty.');
299
+ }
300
+ const result = resultHash(journal);
301
+ if (result !== journal.completedResult.resultSha256) {
302
+ throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield completed result digest mismatch.');
303
+ }
304
+ return result;
305
+ }
306
+
307
+ handoffIsPostimage(journal: CorrectiveYieldTaskTransactionJournal): boolean {
308
+ const current = readOptional(this.targetPath('handoffs.jsonl'), this.taskRoot);
309
+ return current !== null && sha256(current) === journal.handoffTarget.postimage.contentSha256;
310
+ }
311
+
312
+ private applyHandoff(journal: CorrectiveYieldTaskTransactionJournal): 'applied' | 'confirmed' {
313
+ if (journal.phase !== 'applying' || journal.nextTargetOrdinal !== 0) {
314
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Corrective-yield handoff event is not the exact next target.');
315
+ }
316
+ assertTaskState(this.taskRoot, journal.taskStateSha256);
317
+ const file = this.targetPath('handoffs.jsonl');
318
+ const current = readOptional(file, this.taskRoot);
319
+ const currentHash = current ? sha256(current) : null;
320
+ if (currentHash === journal.handoffTarget.postimage.contentSha256) return 'confirmed';
321
+ if (currentHash !== journal.handoffTarget.preimage.contentSha256) {
322
+ throw new WorkflowError('STATE_CONFLICT', 'Task handoff target found neither exact preimage nor exact yield result.');
323
+ }
324
+ const owned = readOwned(this.taskRoot, journal);
325
+ writeAtomicDurable(file, owned);
326
+ assertCanonicalHandoff(this.taskRoot, journal);
327
+ return 'applied';
328
+ }
329
+
330
+ private advance(
331
+ current: CorrectiveYieldTaskTransactionJournal,
332
+ expectedPhase: CorrectiveYieldTransactionPhase,
333
+ expectedRevision: number,
334
+ nextPhase: CorrectiveYieldTransactionPhase,
335
+ nextTargetOrdinal: number,
336
+ completedResultSha256?: string,
337
+ ): CorrectiveYieldTaskTransactionJournal {
338
+ if (current.phase !== expectedPhase || current.phaseRevision !== expectedRevision) {
339
+ throw new WorkflowError('STATE_CONFLICT', 'Corrective-yield phase compare-and-set conflict.');
340
+ }
341
+ assertPhaseTransition(current, nextPhase, nextTargetOrdinal);
342
+ if (nextTargetOrdinal >= 1) assertCanonicalHandoff(this.taskRoot, current);
343
+ if (nextPhase === 'completed') assertHash(completedResultSha256, 'Corrective-yield completed result hash');
344
+ else if (completedResultSha256 !== undefined) throw new WorkflowError('INVALID_ARGUMENT', 'Only completed yield may bind a result.');
345
+ const recordedAt = this.now().toISOString();
346
+ const previous = current.phaseHistory.at(-1)!;
347
+ const entry = phaseEntry(current.phaseRevision, nextPhase, nextTargetOrdinal, recordedAt, previous.entryHash, current.intentSha256);
348
+ const next: CorrectiveYieldTaskTransactionJournal = {
349
+ ...structuredClone(current),
350
+ phase: nextPhase,
351
+ phaseRevision: current.phaseRevision + 1,
352
+ nextTargetOrdinal,
353
+ phaseHistory: [...current.phaseHistory, entry],
354
+ completedResult: nextPhase === 'completed' ? { resultSha256: completedResultSha256! } : current.completedResult,
355
+ updatedAt: recordedAt,
356
+ };
357
+ validateJournal(next, this.taskRoot, true);
358
+ writeAtomicDurable(this.journalPath(current.transactionId), jsonBytes(next));
359
+ return this.read(current.transactionId);
360
+ }
361
+
362
+ private transactionRoot(transactionId: string): string {
363
+ assertHash(transactionId, 'Corrective-yield transaction id');
364
+ return path.join(this.taskRoot, '.yield-transactions', transactionId);
365
+ }
366
+
367
+ private targetPath(relative: string): string {
368
+ return path.join(this.taskRoot, relative);
369
+ }
370
+ }
371
+
372
+ export function inspectCorrectiveYieldTaskTransactions(
373
+ projectRoot: string,
374
+ trustedRoot: string,
375
+ ): { pending: ObservedTaskTransaction[]; completed: ObservedTaskTransaction[]; corrupt: CorruptObservedTaskTransaction[] } {
376
+ const pending: ObservedTaskTransaction[] = [];
377
+ const completed: ObservedTaskTransaction[] = [];
378
+ const corrupt: CorruptObservedTaskTransaction[] = [];
379
+ const tasksRoot = path.join(path.resolve(projectRoot), 'tasks');
380
+ if (!existsSync(tasksRoot)) return deepFreeze({ pending, completed, corrupt });
381
+ assertNoSymlinkPathComponents(tasksRoot, { label: 'Corrective-yield observation root', trustedRoot, requireLeaf: true });
382
+ for (const task of readdirSync(tasksRoot, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
383
+ if (task.isSymbolicLink()) throw new WorkflowError('STATE_CORRUPT', 'Task observation root must not be a symlink.');
384
+ if (!task.isDirectory() || !TASK.test(task.name)) continue;
385
+ const taskRoot = path.join(tasksRoot, task.name);
386
+ const root = path.join(taskRoot, '.yield-transactions');
387
+ if (!existsSync(root)) continue;
388
+ assertNoSymlinkPathComponents(root, { label: 'Corrective-yield transaction root', trustedRoot, requireLeaf: true });
389
+ for (const entry of readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
390
+ const transactionId = HASH.test(entry.name) ? entry.name : 'invalid-transaction-id';
391
+ if (entry.isSymbolicLink() || !entry.isDirectory() || !HASH.test(entry.name)) {
392
+ corrupt.push({ taskId: task.name, transactionId, reason: 'journal-invalid' });
393
+ continue;
394
+ }
395
+ try {
396
+ const journal = new CorrectiveYieldTaskTransactionStore({
397
+ taskRoot,
398
+ locksRoot: path.join(projectRoot, 'locks'),
399
+ trustedRoot,
400
+ }).read(entry.name);
401
+ (journal.phase === 'completed' ? completed : pending).push({
402
+ transitionId: journal.transitionId,
403
+ taskId: journal.taskId,
404
+ transactionId: journal.transactionId,
405
+ phase: journal.phase,
406
+ phaseRevision: journal.phaseRevision,
407
+ updatedAt: journal.updatedAt,
408
+ });
409
+ } catch {
410
+ corrupt.push({ taskId: task.name, transactionId, reason: 'journal-invalid' });
411
+ }
412
+ }
413
+ }
414
+ return deepFreeze({
415
+ completed: completed.sort(orderObserved),
416
+ pending: pending.sort(orderObserved),
417
+ corrupt: corrupt.sort(orderObserved),
418
+ });
419
+ }
420
+
421
+ export function assertCorrectiveYieldTaskTransactionBoundary(
422
+ projectRoot: string,
423
+ trustedRoot: string,
424
+ taskId?: string,
425
+ ): void {
426
+ const observed = inspectCorrectiveYieldTaskTransactions(projectRoot, trustedRoot);
427
+ const corrupt = taskId ? observed.corrupt.filter((row) => row.taskId === taskId) : observed.corrupt;
428
+ if (corrupt.length > 0) throw new WorkflowError('STATE_CORRUPT', 'A corrective-yield journal failed integrity validation.', { transactions: corrupt });
429
+ const pending = taskId ? observed.pending.filter((row) => row.taskId === taskId) : observed.pending;
430
+ if (pending.length > 0) throw new WorkflowError('TASK_RECOVERY_REQUIRED', 'A corrective-yield transaction requires explicit recovery.', {
431
+ taskIds: [...new Set(pending.map((row) => row.taskId))].sort(),
432
+ transactionIds: pending.map((row) => row.transactionId).sort(),
433
+ });
434
+ }
435
+
436
+ function validateJournal(value: unknown, taskRoot: string, verifyOwned: boolean): asserts value is CorrectiveYieldTaskTransactionJournal {
437
+ if (validateSchema(value) !== true) throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield transaction schema is invalid.');
438
+ const journal = value as CorrectiveYieldTaskTransactionJournal;
439
+ if (journal.intentSha256 !== intentHash(journal)) throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield intent hash mismatch.');
440
+ if (journal.phaseHistory.length !== journal.phaseRevision) throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield phase revision mismatch.');
441
+ journal.phaseHistory.forEach((entry, index) => {
442
+ const previous = index === 0 ? null : journal.phaseHistory[index - 1]!.entryHash;
443
+ if (entry.sequence !== index || entry.previousEntryHash !== previous
444
+ || entry.entryHash !== phaseEntryHash(index, entry.phase, entry.nextTargetOrdinal, entry.recordedAt, previous, journal.intentSha256)
445
+ || (index > 0 && entry.recordedAt < journal.phaseHistory[index - 1]!.recordedAt)) {
446
+ throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield phase history is invalid.');
447
+ }
448
+ if (index === 0 && (entry.phase !== 'intent' || entry.nextTargetOrdinal !== 0)) {
449
+ throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield phase history must start at intent.');
450
+ }
451
+ if (index > 0) {
452
+ const prior = journal.phaseHistory[index - 1]!;
453
+ const legal = (prior.phase === 'intent' && entry.phase === 'applying' && prior.nextTargetOrdinal === 0 && entry.nextTargetOrdinal === 0)
454
+ || (prior.phase === 'applying' && entry.phase === 'applying' && entry.nextTargetOrdinal === prior.nextTargetOrdinal + 1 && entry.nextTargetOrdinal <= 2)
455
+ || (prior.phase === 'applying' && prior.nextTargetOrdinal === 2 && entry.phase === 'published-pending-readback' && entry.nextTargetOrdinal === 2)
456
+ || (prior.phase === 'published-pending-readback' && entry.phase === 'completed' && entry.nextTargetOrdinal === 2);
457
+ if (!legal) throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield phase topology is invalid.');
458
+ }
459
+ });
460
+ const last = journal.phaseHistory.at(-1)!;
461
+ if (last.phase !== journal.phase || last.nextTargetOrdinal !== journal.nextTargetOrdinal || last.recordedAt !== journal.updatedAt
462
+ || (journal.phase === 'completed') !== (journal.completedResult !== null)) {
463
+ throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield current phase projection is invalid.');
464
+ }
465
+ if (journal.phaseRevision > 6 || Date.parse(journal.admittedAt) >= Date.parse(journal.writerLeaseExpiresAt)) {
466
+ throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield timing or phase cardinality is invalid.');
467
+ }
468
+ const before = journal.handoffTarget.preimage;
469
+ if (before.exists !== (before.contentSha256 !== null) || before.exists !== (before.contentPath !== null)
470
+ || (before.exists ? before.byteLength < 1 : before.byteLength !== 0)) {
471
+ throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield preimage ownership is invalid.');
472
+ }
473
+ if (journal.completedResult && journal.completedResult.resultSha256 !== resultHash(journal)) {
474
+ throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield completed result formula mismatch.');
475
+ }
476
+ if (verifyOwned) {
477
+ const owned = readOwned(taskRoot, journal);
478
+ const preimage = readOwnedPreimage(taskRoot, journal);
479
+ const anchors = validateAppend(preimage, owned, journal.transactionId, journal.actor, journal.writerLeaseTokenSha256);
480
+ if (anchors.previousEventHash !== journal.handoffTarget.previousEventHash
481
+ || anchors.resultEventHash !== journal.handoffTarget.resultEventHash) {
482
+ throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield handoff anchors mismatch.');
483
+ }
484
+ if (journal.phase === 'completed') {
485
+ assertCanonicalHandoffRetained(taskRoot, journal);
486
+ }
487
+ }
488
+ }
489
+
490
+ function assertPhaseTransition(current: CorrectiveYieldTaskTransactionJournal, phase: CorrectiveYieldTransactionPhase, ordinal: number): void {
491
+ const allowed = (current.phase === 'intent' && phase === 'applying' && ordinal === 0)
492
+ || (current.phase === 'applying' && phase === 'applying' && ordinal === current.nextTargetOrdinal + 1 && ordinal <= 2)
493
+ || (current.phase === 'applying' && current.nextTargetOrdinal === 2 && phase === 'published-pending-readback' && ordinal === 2)
494
+ || (current.phase === 'published-pending-readback' && phase === 'completed' && ordinal === 2);
495
+ if (!allowed) throw new WorkflowError('INVALID_ARGUMENT', 'Invalid corrective-yield phase transition.');
496
+ }
497
+
498
+ function validateAppend(
499
+ before: Buffer | null,
500
+ after: Buffer,
501
+ transactionId: string,
502
+ actor: string,
503
+ tokenSha256: string,
504
+ ): { previousEventHash: string | null; resultEventHash: string } {
505
+ if ((before && !after.subarray(0, before.byteLength).equals(before)) || after.byteLength <= (before?.byteLength ?? 0)) {
506
+ throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-yield handoff postimage must append exact bytes.');
507
+ }
508
+ const beforeLines = jsonLines(before);
509
+ const afterLines = jsonLines(after);
510
+ if (afterLines.length !== beforeLines.length + 1) throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-yield must append exactly one handoff event.');
511
+ const event = afterLines.at(-1)!;
512
+ const previousEventHash = beforeLines.length === 0 ? null : eventHash(beforeLines.at(-1)!);
513
+ if (event.eventId !== `HOF-${transactionId}` || event.eventType !== 'yield' || event.actor !== actor
514
+ || event.writerLeaseTokenHash !== tokenSha256 || event.targetActor !== null || event.handback !== null
515
+ || event.previousEventHash !== previousEventHash) {
516
+ throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-yield event does not bind the exact claimant transaction.');
517
+ }
518
+ return { previousEventHash, resultEventHash: eventHash(event) };
519
+ }
520
+
521
+ function intentHash(journal: CorrectiveYieldTaskTransactionJournal): string {
522
+ const { intentSha256: _intent, phase: _phase, phaseRevision: _revision, nextTargetOrdinal: _ordinal,
523
+ phaseHistory: _history, completedResult: _completed, updatedAt: _updated, ...intent } = journal;
524
+ return hashCanonical({ domain: 'codex-workflow/corrective-yield-intent/v1', intent });
525
+ }
526
+
527
+ function resultHash(journal: CorrectiveYieldTaskTransactionJournal): string {
528
+ return hashCanonical({
529
+ domain: 'codex-workflow/corrective-yield-result/v1',
530
+ transactionId: journal.transactionId,
531
+ intentSha256: journal.intentSha256,
532
+ taskStateSha256: journal.taskStateSha256,
533
+ handoffSha256: journal.handoffTarget.postimage.contentSha256,
534
+ yieldEventHash: journal.handoffTarget.resultEventHash,
535
+ writerLeaseReleased: true,
536
+ });
537
+ }
538
+
539
+ function phaseEntry(
540
+ sequence: number,
541
+ phase: CorrectiveYieldTransactionPhase,
542
+ nextTargetOrdinal: number,
543
+ recordedAt: string,
544
+ previousEntryHash: string | null,
545
+ intentSha256: string,
546
+ ) {
547
+ return { sequence, phase, nextTargetOrdinal, recordedAt, previousEntryHash,
548
+ entryHash: phaseEntryHash(sequence, phase, nextTargetOrdinal, recordedAt, previousEntryHash, intentSha256) };
549
+ }
550
+
551
+ function phaseEntryHash(
552
+ sequence: number,
553
+ phase: CorrectiveYieldTransactionPhase,
554
+ nextTargetOrdinal: number,
555
+ recordedAt: string,
556
+ previousEntryHash: string | null,
557
+ intentSha256: string,
558
+ ): string {
559
+ return hashCanonical({ domain: 'codex-workflow/corrective-yield-phase/v1', sequence, phase, nextTargetOrdinal, recordedAt, previousEntryHash, intentSha256 });
560
+ }
561
+
562
+ function snapshot(content: Buffer | null): SnapshotRecord {
563
+ return content === null
564
+ ? { exists: false, contentSha256: null, byteLength: 0, contentPath: null }
565
+ : { exists: true, contentSha256: sha256(content), byteLength: content.byteLength, contentPath: 'preimages/0.bin' };
566
+ }
567
+
568
+ function assertIntent(input: CreateCorrectiveYieldIntentInput): void {
569
+ if (!PROJECT.test(input.projectId) || !TASK.test(input.taskId) || !HASH.test(input.transactionId)
570
+ || !HASH.test(input.taskStateSha256) || !HASH.test(input.writerLeaseTokenSha256)
571
+ || !HASH.test(input.writerLeaseContentSha256) || !input.actor.trim() || input.actor.length > 256
572
+ || !(input.handoffPostimage instanceof Uint8Array) || input.handoffPostimage.byteLength === 0
573
+ || input.handoffPostimage.byteLength > 1_048_576) {
574
+ throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-yield intent is invalid.');
575
+ }
576
+ assertIso(input.writerLeaseExpiresAt, 'Corrective-yield writer lease expiry');
577
+ }
578
+
579
+ function assertExactRecord(value: unknown, keys: string[], label: string): asserts value is Record<string, unknown> {
580
+ if (!value || typeof value !== 'object' || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype) {
581
+ throw new WorkflowError('INVALID_ARGUMENT', `${label} must be a plain object.`);
582
+ }
583
+ const actual = Object.keys(value).sort();
584
+ const expected = [...keys].sort();
585
+ if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
586
+ throw new WorkflowError('INVALID_ARGUMENT', `${label} has unknown or missing fields.`);
587
+ }
588
+ }
589
+
590
+ function assertCanonicalHandoff(taskRoot: string, journal: CorrectiveYieldTaskTransactionJournal): void {
591
+ const current = readOptional(path.join(taskRoot, journal.handoffTarget.relativePath), taskRoot);
592
+ if (!current || sha256(current) !== journal.handoffTarget.postimage.contentSha256) {
593
+ throw new WorkflowError('STATE_CONFLICT', 'Corrective-yield handoff target does not match its exact result.');
594
+ }
595
+ }
596
+
597
+ function assertCanonicalHandoffRetained(taskRoot: string, journal: CorrectiveYieldTaskTransactionJournal): void {
598
+ const current = readOptional(path.join(taskRoot, journal.handoffTarget.relativePath), taskRoot);
599
+ const owned = readOwned(taskRoot, journal);
600
+ if (!current || current.byteLength < owned.byteLength
601
+ || !current.subarray(0, owned.byteLength).equals(owned)) {
602
+ throw new WorkflowError('STATE_CONFLICT', 'Corrective-yield handoff result is not retained in canonical history.');
603
+ }
604
+ jsonLines(current);
605
+ }
606
+
607
+ function assertTaskState(taskRoot: string, expectedHash: string): void {
608
+ if (sha256(readRequired(path.join(taskRoot, 'state.json'), taskRoot)) !== expectedHash) {
609
+ throw new WorkflowError('STATE_CONFLICT', 'Task state changed across corrective-yield.');
610
+ }
611
+ }
612
+
613
+ function readOwned(taskRoot: string, journal: CorrectiveYieldTaskTransactionJournal): Buffer {
614
+ const file = path.join(taskRoot, '.yield-transactions', journal.transactionId, journal.handoffTarget.postimage.contentPath);
615
+ const content = readRequired(file, taskRoot);
616
+ if (content.byteLength !== journal.handoffTarget.postimage.byteLength || sha256(content) !== journal.handoffTarget.postimage.contentSha256) {
617
+ throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield owned postimage is corrupt.');
618
+ }
619
+ return content;
620
+ }
621
+
622
+ function readOwnedPreimage(taskRoot: string, journal: CorrectiveYieldTaskTransactionJournal): Buffer | null {
623
+ if (!journal.handoffTarget.preimage.exists) return null;
624
+ const file = path.join(taskRoot, '.yield-transactions', journal.transactionId, journal.handoffTarget.preimage.contentPath!);
625
+ const content = readRequired(file, taskRoot);
626
+ if (content.byteLength !== journal.handoffTarget.preimage.byteLength || sha256(content) !== journal.handoffTarget.preimage.contentSha256) {
627
+ throw new WorkflowError('STATE_CORRUPT', 'Corrective-yield owned preimage is corrupt.');
628
+ }
629
+ return content;
630
+ }
631
+
632
+ function jsonLines(content: Buffer | null): Array<Record<string, unknown>> {
633
+ if (!content) return [];
634
+ const text = content.toString('utf8');
635
+ if (!text.endsWith('\n')) throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-yield handoff bytes must end with newline.');
636
+ const events = text.slice(0, -1).split('\n').map((line) => {
637
+ const value = JSON.parse(line) as unknown;
638
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-yield handoff line is invalid.');
639
+ return value as Record<string, unknown>;
640
+ });
641
+ let previousEventHash: string | null = null;
642
+ for (const event of events) {
643
+ if (event.previousEventHash !== previousEventHash
644
+ || event.eventHash !== computeSidecarEventHash(event as never)) {
645
+ throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-yield handoff event chain is invalid.');
646
+ }
647
+ previousEventHash = eventHash(event);
648
+ }
649
+ return events;
650
+ }
651
+
652
+ function eventHash(event: Record<string, unknown>): string {
653
+ if (typeof event.eventHash !== 'string' || !HASH.test(event.eventHash)) throw new WorkflowError('INVALID_ARGUMENT', 'Handoff event hash is invalid.');
654
+ return event.eventHash;
655
+ }
656
+
657
+ function readOptional(file: string, trustedRoot: string): Buffer | null {
658
+ assertNoSymlinkPathComponents(file, { label: 'Corrective-yield target', trustedRoot, requireLeaf: true });
659
+ return existsSync(file) ? readFileSync(file) : null;
660
+ }
661
+
662
+ function readRequired(file: string, trustedRoot: string): Buffer {
663
+ const value = readOptional(file, trustedRoot);
664
+ if (!value) throw new WorkflowError('STATE_CORRUPT', `Corrective-yield required file is missing: ${file}`);
665
+ return value;
666
+ }
667
+
668
+ function writeNewDurable(file: string, content: Buffer): void {
669
+ let descriptor: number | null = null;
670
+ try {
671
+ descriptor = openSync(file, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600);
672
+ writeSync(descriptor, content);
673
+ fsyncSync(descriptor);
674
+ } finally { if (descriptor !== null) closeSync(descriptor); }
675
+ }
676
+
677
+ function writeAtomicDurable(file: string, content: Buffer): void {
678
+ const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${randomUUID()}.tmp`);
679
+ let descriptor: number | null = null;
680
+ try {
681
+ descriptor = openSync(temporary, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600);
682
+ writeSync(descriptor, content);
683
+ fsyncSync(descriptor);
684
+ closeSync(descriptor);
685
+ descriptor = null;
686
+ renameSync(temporary, file);
687
+ fsyncDirectory(path.dirname(file));
688
+ } finally {
689
+ if (descriptor !== null) closeSync(descriptor);
690
+ rmSync(temporary, { force: true });
691
+ }
692
+ }
693
+
694
+ function ensureDirectorySafe(directory: string, trustedRoot: string): void {
695
+ if (!existsSync(directory)) mkdirSync(directory, { recursive: true, mode: 0o700 });
696
+ assertNoSymlinkPathComponents(directory, { label: 'Corrective-yield transaction directory', trustedRoot, requireLeaf: true });
697
+ }
698
+
699
+ function fsyncDirectory(directory: string): void {
700
+ const descriptor = openSync(directory, 'r');
701
+ try { fsyncSync(descriptor); } finally { closeSync(descriptor); }
702
+ }
703
+
704
+ function assertTaskRoot(taskRoot: string, trustedRoot: string): void {
705
+ const resolvedTask = path.resolve(taskRoot);
706
+ const resolvedTrusted = path.resolve(trustedRoot);
707
+ if (!resolvedTask.startsWith(`${resolvedTrusted}${path.sep}`)) throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-yield Task root is outside trusted state.');
708
+ assertNoSymlinkPathComponents(resolvedTask, { label: 'Corrective-yield Task root', trustedRoot: resolvedTrusted, requireLeaf: true });
709
+ }
710
+
711
+ function assertHash(value: unknown, label: string): asserts value is string {
712
+ if (typeof value !== 'string' || !HASH.test(value)) throw new WorkflowError('INVALID_ARGUMENT', `${label} must be lowercase SHA-256.`);
713
+ }
714
+
715
+ function assertIso(value: string, label: string): void {
716
+ const parsed = Date.parse(value);
717
+ if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== value) throw new WorkflowError('INVALID_ARGUMENT', `${label} must be canonical UTC.`);
718
+ }
719
+
720
+ function jsonBytes(value: unknown): Buffer {
721
+ return Buffer.from(`${JSON.stringify(value, null, 2)}\n`, 'utf8');
722
+ }
723
+
724
+ function sha256(value: Buffer | string): string {
725
+ return createHash('sha256').update(value).digest('hex');
726
+ }
727
+
728
+ function orderObserved(left: { taskId: string; transactionId: string }, right: { taskId: string; transactionId: string }): number {
729
+ return left.taskId.localeCompare(right.taskId) || left.transactionId.localeCompare(right.transactionId);
730
+ }