codex-workflow-v2 2.0.0-beta.2 → 2.0.0-beta.4

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 (128) hide show
  1. package/README.md +35 -2
  2. package/dist/src/alpha6/adoption.js +13 -3
  3. package/dist/src/alpha6/adoption.js.map +1 -1
  4. package/dist/src/alpha6/handoff.d.ts +21 -0
  5. package/dist/src/alpha6/handoff.js +100 -19
  6. package/dist/src/alpha6/handoff.js.map +1 -1
  7. package/dist/src/alpha6/milestone.d.ts +2 -0
  8. package/dist/src/alpha6/milestone.js +15 -0
  9. package/dist/src/alpha6/milestone.js.map +1 -1
  10. package/dist/src/alpha6/plan-risk.d.ts +1 -0
  11. package/dist/src/alpha6/plan-risk.js +6 -0
  12. package/dist/src/alpha6/plan-risk.js.map +1 -1
  13. package/dist/src/cli.js +188 -65
  14. package/dist/src/cli.js.map +1 -1
  15. package/dist/src/contracts.d.ts +111 -5
  16. package/dist/src/contracts.js +1 -1
  17. package/dist/src/credential-output.d.ts +10 -0
  18. package/dist/src/credential-output.js +73 -0
  19. package/dist/src/credential-output.js.map +1 -0
  20. package/dist/src/errors.d.ts +1 -1
  21. package/dist/src/errors.js.map +1 -1
  22. package/dist/src/index.d.ts +1 -1
  23. package/dist/src/index.js +1 -1
  24. package/dist/src/index.js.map +1 -1
  25. package/dist/src/lifecycle/canonical-hash.d.ts +6 -0
  26. package/dist/src/lifecycle/canonical-hash.js +24 -0
  27. package/dist/src/lifecycle/canonical-hash.js.map +1 -0
  28. package/dist/src/lifecycle/catalog.d.ts +26 -0
  29. package/dist/src/lifecycle/catalog.js +208 -0
  30. package/dist/src/lifecycle/catalog.js.map +1 -0
  31. package/dist/src/lifecycle/compiler-inspection.d.ts +48 -0
  32. package/dist/src/lifecycle/compiler-inspection.js +29 -0
  33. package/dist/src/lifecycle/compiler-inspection.js.map +1 -0
  34. package/dist/src/lifecycle/core-static-readiness.d.ts +6 -0
  35. package/dist/src/lifecycle/core-static-readiness.js +130 -0
  36. package/dist/src/lifecycle/core-static-readiness.js.map +1 -0
  37. package/dist/src/lifecycle/corrective-replan-authority.d.ts +53 -0
  38. package/dist/src/lifecycle/corrective-replan-authority.js +139 -0
  39. package/dist/src/lifecycle/corrective-replan-authority.js.map +1 -0
  40. package/dist/src/lifecycle/corrective-replan-binding-manifest.d.ts +21 -0
  41. package/dist/src/lifecycle/corrective-replan-binding-manifest.js +35 -0
  42. package/dist/src/lifecycle/corrective-replan-binding-manifest.js.map +1 -0
  43. package/dist/src/lifecycle/corrective-replan-credential-core.d.ts +86 -0
  44. package/dist/src/lifecycle/corrective-replan-credential-core.js +534 -0
  45. package/dist/src/lifecycle/corrective-replan-credential-core.js.map +1 -0
  46. package/dist/src/lifecycle/corrective-replan-credential-schema.d.ts +17 -0
  47. package/dist/src/lifecycle/corrective-replan-credential-schema.js +44 -0
  48. package/dist/src/lifecycle/corrective-replan-credential-schema.js.map +1 -0
  49. package/dist/src/lifecycle/corrective-replan-credentials.d.ts +47 -0
  50. package/dist/src/lifecycle/corrective-replan-credentials.js +103 -0
  51. package/dist/src/lifecycle/corrective-replan-credentials.js.map +1 -0
  52. package/dist/src/lifecycle/corrective-replan.d.ts +64 -0
  53. package/dist/src/lifecycle/corrective-replan.js +548 -0
  54. package/dist/src/lifecycle/corrective-replan.js.map +1 -0
  55. package/dist/src/lifecycle/evaluator.d.ts +2 -0
  56. package/dist/src/lifecycle/evaluator.js +35 -0
  57. package/dist/src/lifecycle/evaluator.js.map +1 -0
  58. package/dist/src/lifecycle/fingerprint.d.ts +31 -0
  59. package/dist/src/lifecycle/fingerprint.js +388 -0
  60. package/dist/src/lifecycle/fingerprint.js.map +1 -0
  61. package/dist/src/lifecycle/immutable.d.ts +1 -0
  62. package/dist/src/lifecycle/immutable.js +10 -0
  63. package/dist/src/lifecycle/immutable.js.map +1 -0
  64. package/dist/src/lifecycle/implementation-table.d.ts +29 -0
  65. package/dist/src/lifecycle/implementation-table.js +87 -0
  66. package/dist/src/lifecycle/implementation-table.js.map +1 -0
  67. package/dist/src/lifecycle/index.d.ts +4 -0
  68. package/dist/src/lifecycle/index.js +5 -0
  69. package/dist/src/lifecycle/index.js.map +1 -0
  70. package/dist/src/lifecycle/schema-artifact.d.ts +12 -0
  71. package/dist/src/lifecycle/schema-artifact.js +214 -0
  72. package/dist/src/lifecycle/schema-artifact.js.map +1 -0
  73. package/dist/src/lifecycle/semantic-registry.d.ts +6 -0
  74. package/dist/src/lifecycle/semantic-registry.js +489 -0
  75. package/dist/src/lifecycle/semantic-registry.js.map +1 -0
  76. package/dist/src/lifecycle/types.d.ts +718 -0
  77. package/dist/src/lifecycle/types.js +4 -0
  78. package/dist/src/lifecycle/types.js.map +1 -0
  79. package/dist/src/observation.d.ts +20 -0
  80. package/dist/src/observation.js +171 -0
  81. package/dist/src/observation.js.map +1 -0
  82. package/dist/src/state/corrective-replan-executor.d.ts +64 -0
  83. package/dist/src/state/corrective-replan-executor.js +598 -0
  84. package/dist/src/state/corrective-replan-executor.js.map +1 -0
  85. package/dist/src/state/corrective-replan-public-schema.d.ts +3 -0
  86. package/dist/src/state/corrective-replan-public-schema.js +43 -0
  87. package/dist/src/state/corrective-replan-public-schema.js.map +1 -0
  88. package/dist/src/state/corrective-replan-public.d.ts +95 -0
  89. package/dist/src/state/corrective-replan-public.js +618 -0
  90. package/dist/src/state/corrective-replan-public.js.map +1 -0
  91. package/dist/src/state/corrective-replan-transaction.d.ts +158 -0
  92. package/dist/src/state/corrective-replan-transaction.js +723 -0
  93. package/dist/src/state/corrective-replan-transaction.js.map +1 -0
  94. package/dist/src/state/corrective-yield-executor.d.ts +31 -0
  95. package/dist/src/state/corrective-yield-executor.js +236 -0
  96. package/dist/src/state/corrective-yield-executor.js.map +1 -0
  97. package/dist/src/state/corrective-yield-transaction.d.ts +97 -0
  98. package/dist/src/state/corrective-yield-transaction.js +591 -0
  99. package/dist/src/state/corrective-yield-transaction.js.map +1 -0
  100. package/dist/src/state/lock.d.ts +75 -3
  101. package/dist/src/state/lock.js +582 -30
  102. package/dist/src/state/lock.js.map +1 -1
  103. package/dist/src/state/store.d.ts +3 -0
  104. package/dist/src/state/store.js +65 -12
  105. package/dist/src/state/store.js.map +1 -1
  106. package/dist/src/transition-core.d.ts +67 -0
  107. package/dist/src/transition-core.js +318 -0
  108. package/dist/src/transition-core.js.map +1 -0
  109. package/dist/src/version.d.ts +1 -1
  110. package/dist/src/version.js +1 -1
  111. package/dist/src/workflow.d.ts +34 -22
  112. package/dist/src/workflow.js +486 -141
  113. package/dist/src/workflow.js.map +1 -1
  114. package/docs/lifecycle/state-machine-stabilization.md +641 -0
  115. package/docs/release.md +4 -2
  116. package/docs/updating-existing-project.md +9 -0
  117. package/package.json +4 -1
  118. package/schemas/adoption-posture-event.schema.json +1 -1
  119. package/schemas/context-envelope.schema.json +1 -1
  120. package/schemas/corrective-replan-credentials.private.schema.json +98 -0
  121. package/schemas/corrective-replan-execution-event.private.schema.json +53 -0
  122. package/schemas/corrective-replan-public.schema.json +142 -0
  123. package/schemas/corrective-replan-task-transaction.private.schema.json +151 -0
  124. package/schemas/corrective-yield-task-transaction.private.schema.json +98 -0
  125. package/schemas/remediation-event.schema.json +11 -1
  126. package/schemas/task-handoff-event.schema.json +1 -1
  127. package/schemas/transition-definition.schema.json +2609 -0
  128. package/schemas/transition-payloads.schema.json +140 -0
@@ -0,0 +1,723 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { closeSync, constants, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, writeSync, } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { Ajv2020 } from 'ajv/dist/2020.js';
5
+ import { WorkflowError } from '../errors.js';
6
+ import { assertNoSymlinkPathComponents } from '../alpha6/store-sidecars.js';
7
+ import { deepFreeze } from '../lifecycle/immutable.js';
8
+ import { correctiveReplanBindingManifest } from '../lifecycle/corrective-replan-binding-manifest.js';
9
+ import { assertCoreTaskOperationHandle, } from './lock.js';
10
+ const HASH = /^[a-f0-9]{64}$/;
11
+ const PROJECT = /^[a-f0-9]{24}$/;
12
+ const TASK = /^TASK-[0-9A-HJKMNP-TV-Z]{26}$/;
13
+ const MAX_TARGET_BYTES = 256 * 1024 * 1024;
14
+ const schema = JSON.parse(readFileSync(new URL('../../../schemas/corrective-replan-task-transaction.private.schema.json', import.meta.url), 'utf8'));
15
+ const ajv = new Ajv2020({ allErrors: true, strict: true, formats: { 'date-time': true } });
16
+ if (!ajv.validateSchema(schema))
17
+ throw new TypeError(`Invalid private Task transaction schema: ${ajv.errorsText(ajv.errors)}`);
18
+ const validateSchema = ajv.compile(schema);
19
+ export const CORRECTIVE_REPLAN_TARGETS = deepFreeze([
20
+ { targetId: 'task-state', relativePath: 'state.json', writeMode: 'replace-exact' },
21
+ { targetId: 'plan', relativePath: 'plan.md', writeMode: 'replace-exact' },
22
+ { targetId: 'plan-validation', relativePath: 'plan-validations.jsonl', writeMode: 'append-exact' },
23
+ { targetId: 'plan-risk-audit', relativePath: 'plan-risk-audits.jsonl', writeMode: 'append-exact' },
24
+ { targetId: 'execution-evidence', relativePath: 'corrective-replan-executions.jsonl', writeMode: 'append-exact' },
25
+ ]);
26
+ export class CorrectiveReplanTaskTransactionStore {
27
+ taskRoot;
28
+ locksRoot;
29
+ trustedRoot;
30
+ now;
31
+ constructor(options) {
32
+ this.taskRoot = path.resolve(options.taskRoot);
33
+ this.locksRoot = path.resolve(options.locksRoot);
34
+ this.trustedRoot = path.resolve(options.trustedRoot);
35
+ this.now = options.now ?? (() => new Date());
36
+ assertTaskRoot(this.taskRoot, this.trustedRoot);
37
+ }
38
+ createIntent(handle, input) {
39
+ assertPlainExact(input, [
40
+ 'actorIdentityHash', 'bindingManifest', 'bindingSha256', 'catalogHash', 'executionKeyHash',
41
+ 'expectedTaskRevision', 'humanPrincipalIdentityHash', 'lifecycleEpoch', 'postimages', 'projectId', 'receiptHash',
42
+ 'resultTaskRevision', 'runtimeFingerprint', 'taskId', 'transitionId',
43
+ ], 'Corrective-replan intent');
44
+ assertHeader(input);
45
+ assertCoreTaskOperationHandle(this.locksRoot, this.trustedRoot, handle, this.now(), {
46
+ entityId: input.taskId,
47
+ operationId: input.executionKeyHash,
48
+ transitionId: input.transitionId,
49
+ });
50
+ const postimages = normalizePostimages(input.postimages);
51
+ const preimages = CORRECTIVE_REPLAN_TARGETS.map((target) => readTarget(this.taskRoot, target.relativePath));
52
+ validateTaskStatePair(preimages[0].content, postimages[0], input.projectId, input.taskId, input.expectedTaskRevision, input.resultTaskRevision);
53
+ if (postimages[1].byteLength === 0)
54
+ throw new WorkflowError('INVALID_ARGUMENT', 'Replacement Plan bytes must not be empty.');
55
+ const sidecars = [2, 3, 4].map((ordinal) => sidecarAnchors(preimages[ordinal].content, postimages[ordinal]));
56
+ const transactionRoot = this.transactionRoot(input.executionKeyHash);
57
+ if (existsSync(transactionRoot)) {
58
+ throw new WorkflowError('STATE_CONFLICT', 'Corrective-replan transaction already exists.', {
59
+ transactionId: input.executionKeyHash,
60
+ });
61
+ }
62
+ const targets = CORRECTIVE_REPLAN_TARGETS.map((target, ordinal) => ({
63
+ ...target,
64
+ ordinal,
65
+ preimage: snapshot(preimages[ordinal].content, ordinal),
66
+ postimage: {
67
+ contentSha256: sha256(postimages[ordinal]),
68
+ byteLength: postimages[ordinal].byteLength,
69
+ contentPath: `targets/${ordinal}.bin`,
70
+ },
71
+ sidecarAnchors: ordinal < 2 ? null : sidecars[ordinal - 2],
72
+ taskCas: ordinal === 0
73
+ ? { expectedRevision: input.expectedTaskRevision, resultRevision: input.resultTaskRevision }
74
+ : null,
75
+ }));
76
+ const admittedAt = this.now().toISOString();
77
+ const journal = {
78
+ schemaVersion: 1,
79
+ kind: 'corrective-replan-task-transaction',
80
+ transactionId: input.executionKeyHash,
81
+ projectId: input.projectId,
82
+ taskId: input.taskId,
83
+ transitionId: input.transitionId,
84
+ executionKeyHash: input.executionKeyHash,
85
+ runtimeFingerprint: input.runtimeFingerprint,
86
+ catalogHash: input.catalogHash,
87
+ lifecycleEpoch: input.lifecycleEpoch,
88
+ bindingManifest: {
89
+ id: correctiveReplanBindingManifest.id,
90
+ version: correctiveReplanBindingManifest.version,
91
+ manifestSha256: correctiveReplanBindingManifest.manifestSha256,
92
+ },
93
+ bindingSha256: input.bindingSha256,
94
+ receiptHash: input.receiptHash,
95
+ actorIdentityHash: input.actorIdentityHash,
96
+ humanPrincipalIdentityHash: input.humanPrincipalIdentityHash,
97
+ admittedAt,
98
+ intentSha256: '0'.repeat(64),
99
+ phase: 'intent',
100
+ phaseRevision: 1,
101
+ nextTargetOrdinal: 0,
102
+ targets,
103
+ phaseHistory: [],
104
+ completedResult: null,
105
+ updatedAt: admittedAt,
106
+ };
107
+ journal.intentSha256 = intentHash(journal);
108
+ journal.phaseHistory = [phaseEntry(0, 'intent', 0, admittedAt, null, journal.intentSha256)];
109
+ validateJournal(journal, this.taskRoot, false, input.executionKeyHash);
110
+ ensureDirectorySafe(path.dirname(transactionRoot), this.trustedRoot);
111
+ let createdTransactionRoot = false;
112
+ try {
113
+ mkdirSync(transactionRoot, { mode: 0o700 });
114
+ createdTransactionRoot = true;
115
+ mkdirSync(path.join(transactionRoot, 'preimages'), { mode: 0o700 });
116
+ mkdirSync(path.join(transactionRoot, 'targets'), { mode: 0o700 });
117
+ preimages.forEach(({ content }, ordinal) => {
118
+ if (content !== null)
119
+ writeNewDurable(path.join(transactionRoot, 'preimages', `${ordinal}.bin`), content);
120
+ });
121
+ postimages.forEach((content, ordinal) => writeNewDurable(path.join(transactionRoot, 'targets', `${ordinal}.bin`), content));
122
+ assertCoreTaskOperationHandle(this.locksRoot, this.trustedRoot, handle, this.now(), {
123
+ entityId: input.taskId, operationId: input.executionKeyHash, transitionId: input.transitionId,
124
+ });
125
+ writeNewDurable(this.journalPath(input.executionKeyHash), jsonBuffer(journal));
126
+ fsyncDirectory(transactionRoot);
127
+ return this.read(input.executionKeyHash);
128
+ }
129
+ catch (error) {
130
+ if (createdTransactionRoot && !existsSync(this.journalPath(input.executionKeyHash))) {
131
+ rmSync(transactionRoot, { recursive: true, force: true });
132
+ }
133
+ if (isNodeError(error) && error.code === 'EEXIST') {
134
+ throw new WorkflowError('STATE_CONFLICT', 'Corrective-replan transaction already exists.', {
135
+ transactionId: input.executionKeyHash,
136
+ });
137
+ }
138
+ throw error;
139
+ }
140
+ }
141
+ read(transactionId) {
142
+ assertHash(transactionId, 'Transaction id');
143
+ const file = this.journalPath(transactionId);
144
+ assertNoSymlinkPathComponents(file, { label: 'Task transaction journal', trustedRoot: this.trustedRoot, requireLeaf: true });
145
+ if (!existsSync(file))
146
+ throw new WorkflowError('NOT_FOUND', `Task transaction journal not found: ${transactionId}`);
147
+ let journal;
148
+ try {
149
+ journal = JSON.parse(readFileSync(file, 'utf8'));
150
+ }
151
+ catch {
152
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction journal is not valid JSON.');
153
+ }
154
+ validateJournal(journal, this.taskRoot, true, transactionId);
155
+ return deepFreeze(structuredClone(journal));
156
+ }
157
+ journalPath(transactionId) {
158
+ assertHash(transactionId, 'Transaction id');
159
+ return path.join(this.transactionRoot(transactionId), 'journal.json');
160
+ }
161
+ readOwnedPostimage(transactionId, requested) {
162
+ const journal = this.read(transactionId);
163
+ const target = journal.targets.find((row) => row.targetId === requested.targetId);
164
+ if (!target)
165
+ throw new WorkflowError('INVALID_ARGUMENT', 'Unknown corrective-replan target.');
166
+ return readOwnedContent(this.taskRoot, transactionId, target);
167
+ }
168
+ readOwnedPreimage(transactionId, requested) {
169
+ const journal = this.read(transactionId);
170
+ const target = journal.targets.find((row) => row.targetId === requested.targetId);
171
+ if (!target)
172
+ throw new WorkflowError('INVALID_ARGUMENT', 'Unknown corrective-replan target.');
173
+ return readOwnedPreimage(this.taskRoot, transactionId, target);
174
+ }
175
+ captureCurrentTarget(handle, input) {
176
+ assertCoreTaskOperationHandle(this.locksRoot, this.trustedRoot, handle, this.now(), {
177
+ entityId: input.taskId,
178
+ operationId: input.operationId,
179
+ transitionId: 'task.plan.corrective-replan',
180
+ });
181
+ const target = CORRECTIVE_REPLAN_TARGETS.find((candidate) => candidate.targetId === input.targetId);
182
+ if (!target)
183
+ throw new WorkflowError('INVALID_ARGUMENT', 'Unknown corrective-replan target.');
184
+ const content = readTarget(this.taskRoot, target.relativePath).content;
185
+ return content === null ? null : Buffer.from(content);
186
+ }
187
+ compareAndSetPhase(handle, transactionId, input) {
188
+ const current = this.read(transactionId);
189
+ assertCoreTaskOperationHandle(this.locksRoot, this.trustedRoot, handle, this.now(), {
190
+ entityId: current.taskId, operationId: current.executionKeyHash, transitionId: current.transitionId,
191
+ });
192
+ if (current.phase !== input.expectedPhase || current.phaseRevision !== input.expectedRevision) {
193
+ throw new WorkflowError('STATE_CONFLICT', 'Task transaction phase compare-and-set conflict.');
194
+ }
195
+ assertPhaseTransition(current, input.nextPhase, input.nextTargetOrdinal);
196
+ if (current.phase === 'applying' && input.nextPhase === 'applying' && input.nextTargetOrdinal > 0) {
197
+ assertCanonicalTargetMatches(this.taskRoot, current.targets[input.nextTargetOrdinal - 1]);
198
+ }
199
+ if (input.nextPhase === 'completed') {
200
+ assertHash(input.completedResultSha256, 'Completed Task transaction result hash');
201
+ assertCanonicalTargetsMatch(this.taskRoot, current);
202
+ }
203
+ else if (input.completedResultSha256 !== undefined) {
204
+ throw new WorkflowError('INVALID_ARGUMENT', 'Only a completed Task transaction may bind a result hash.');
205
+ }
206
+ const previous = current.phaseHistory.at(-1);
207
+ const recordedAt = this.now().toISOString();
208
+ const nextEntry = phaseEntry(current.phaseRevision, input.nextPhase, input.nextTargetOrdinal, recordedAt, previous.entryHash, current.intentSha256);
209
+ const next = {
210
+ ...structuredClone(current),
211
+ phase: input.nextPhase,
212
+ phaseRevision: current.phaseRevision + 1,
213
+ nextTargetOrdinal: input.nextTargetOrdinal,
214
+ phaseHistory: [...current.phaseHistory, nextEntry],
215
+ completedResult: input.nextPhase === 'completed'
216
+ ? { resultSha256: input.completedResultSha256 }
217
+ : current.completedResult,
218
+ updatedAt: recordedAt,
219
+ };
220
+ validateJournal(next, this.taskRoot, true, transactionId);
221
+ assertCoreTaskOperationHandle(this.locksRoot, this.trustedRoot, handle, this.now(), {
222
+ entityId: current.taskId, operationId: current.executionKeyHash, transitionId: current.transitionId,
223
+ });
224
+ writeAtomicDurable(this.journalPath(transactionId), jsonBuffer(next));
225
+ return this.read(transactionId);
226
+ }
227
+ applyTarget(handle, transactionId, targetId) {
228
+ const journal = this.read(transactionId);
229
+ if (journal.phase !== 'applying') {
230
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Target application requires an applying transaction.');
231
+ }
232
+ assertCoreTaskOperationHandle(this.locksRoot, this.trustedRoot, handle, this.now(), {
233
+ entityId: journal.taskId, operationId: journal.executionKeyHash, transitionId: journal.transitionId,
234
+ });
235
+ const target = journal.targets.find((candidate) => candidate.targetId === targetId);
236
+ if (!target || target.ordinal !== journal.nextTargetOrdinal) {
237
+ throw new WorkflowError('INVALID_ARGUMENT', 'Task transaction target is not the exact next target.');
238
+ }
239
+ const file = path.join(this.taskRoot, target.relativePath);
240
+ const current = readTarget(this.taskRoot, target.relativePath).content;
241
+ const currentHash = current === null ? null : sha256(current);
242
+ if (currentHash === target.postimage.contentSha256)
243
+ return 'confirmed';
244
+ if (currentHash !== target.preimage.contentSha256) {
245
+ throw new WorkflowError('STATE_CONFLICT', 'Task target found neither the exact preimage nor exact result.', {
246
+ targetId,
247
+ });
248
+ }
249
+ const result = readOwnedContent(this.taskRoot, transactionId, target);
250
+ assertCoreTaskOperationHandle(this.locksRoot, this.trustedRoot, handle, this.now(), {
251
+ entityId: journal.taskId, operationId: journal.executionKeyHash, transitionId: journal.transitionId,
252
+ });
253
+ writeAtomicDurable(file, result);
254
+ if (sha256(readFileSync(file)) !== target.postimage.contentSha256) {
255
+ throw new WorkflowError('STATE_CORRUPT', 'Task target readback mismatch.', { targetId });
256
+ }
257
+ return 'applied';
258
+ }
259
+ applyTaskStateCas(handle, transactionId) {
260
+ return this.applyTarget(handle, transactionId, 'task-state');
261
+ }
262
+ readCanonicalTarget(handle, transactionId, targetId) {
263
+ const journal = this.read(transactionId);
264
+ assertCoreTaskOperationHandle(this.locksRoot, this.trustedRoot, handle, this.now(), {
265
+ entityId: journal.taskId,
266
+ operationId: journal.executionKeyHash,
267
+ transitionId: journal.transitionId,
268
+ });
269
+ const target = journal.targets.find((candidate) => candidate.targetId === targetId);
270
+ if (!target)
271
+ throw new WorkflowError('INVALID_ARGUMENT', 'Unknown corrective-replan target.');
272
+ assertCanonicalTargetMatches(this.taskRoot, target);
273
+ return Buffer.from(readTarget(this.taskRoot, target.relativePath).content);
274
+ }
275
+ readCompletedTarget(transactionId, targetId) {
276
+ const journal = this.read(transactionId);
277
+ if (journal.phase !== 'completed') {
278
+ throw new WorkflowError('TASK_RECOVERY_REQUIRED', 'Task transaction is not completed.');
279
+ }
280
+ const target = journal.targets.find((candidate) => candidate.targetId === targetId);
281
+ if (!target)
282
+ throw new WorkflowError('INVALID_ARGUMENT', 'Unknown corrective-replan target.');
283
+ return Buffer.from(readTarget(this.taskRoot, target.relativePath).content);
284
+ }
285
+ transactionRoot(transactionId) {
286
+ assertHash(transactionId, 'Transaction id');
287
+ return path.join(this.taskRoot, '.transactions', transactionId);
288
+ }
289
+ }
290
+ export function inspectCorrectiveReplanTaskTransactions(projectRoot, trustedRoot) {
291
+ const resolvedProject = path.resolve(projectRoot);
292
+ const resolvedTrusted = path.resolve(trustedRoot);
293
+ if (!existsSync(resolvedTrusted) || !existsSync(resolvedProject)) {
294
+ return deepFreeze({ pending: [], corrupt: [] });
295
+ }
296
+ assertNoSymlinkPathComponents(resolvedProject, { label: 'Project transaction observation root', trustedRoot: resolvedTrusted });
297
+ const tasksRoot = path.join(resolvedProject, 'tasks');
298
+ if (!existsSync(tasksRoot))
299
+ return deepFreeze({ pending: [], corrupt: [] });
300
+ assertNoSymlinkPathComponents(tasksRoot, { label: 'Task transaction observation root', trustedRoot: resolvedTrusted, requireLeaf: true });
301
+ const pending = [];
302
+ const corrupt = [];
303
+ for (const taskEntry of readdirSync(tasksRoot, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
304
+ const taskPath = path.join(tasksRoot, taskEntry.name);
305
+ if (taskEntry.isSymbolicLink())
306
+ throw new WorkflowError('STATE_CORRUPT', 'Task observation root must not be a symlink.');
307
+ if (!taskEntry.isDirectory() || !TASK.test(taskEntry.name))
308
+ continue;
309
+ const transactionRoot = path.join(taskPath, '.transactions');
310
+ if (!existsSync(transactionRoot))
311
+ continue;
312
+ assertNoSymlinkPathComponents(transactionRoot, { label: 'Task transaction directory', trustedRoot: resolvedTrusted, requireLeaf: true });
313
+ for (const entry of readdirSync(transactionRoot, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
314
+ if (entry.isSymbolicLink())
315
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction entry must not be a symlink.');
316
+ if (!entry.isDirectory())
317
+ continue;
318
+ const transactionId = HASH.test(entry.name) ? entry.name : 'invalid-transaction-id';
319
+ if (!HASH.test(entry.name)) {
320
+ corrupt.push({ taskId: taskEntry.name, transactionId, reason: 'journal-invalid' });
321
+ continue;
322
+ }
323
+ try {
324
+ const store = new CorrectiveReplanTaskTransactionStore({
325
+ taskRoot: taskPath,
326
+ locksRoot: path.join(resolvedProject, 'locks'),
327
+ trustedRoot: resolvedTrusted,
328
+ });
329
+ const journal = store.read(entry.name);
330
+ if (journal.phase !== 'completed')
331
+ pending.push({
332
+ transitionId: journal.transitionId,
333
+ taskId: journal.taskId,
334
+ transactionId: journal.transactionId,
335
+ phase: journal.phase,
336
+ phaseRevision: journal.phaseRevision,
337
+ updatedAt: journal.updatedAt,
338
+ });
339
+ }
340
+ catch {
341
+ corrupt.push({ taskId: taskEntry.name, transactionId, reason: 'journal-invalid' });
342
+ }
343
+ }
344
+ }
345
+ return deepFreeze({
346
+ pending: pending.sort((a, b) => a.taskId.localeCompare(b.taskId) || a.transactionId.localeCompare(b.transactionId)),
347
+ corrupt: corrupt.sort((a, b) => a.taskId.localeCompare(b.taskId) || a.transactionId.localeCompare(b.transactionId)),
348
+ });
349
+ }
350
+ export function assertTaskTransactionBoundary(projectRoot, trustedRoot, taskId) {
351
+ const observed = inspectCorrectiveReplanTaskTransactions(projectRoot, trustedRoot);
352
+ const corrupt = taskId === undefined
353
+ ? observed.corrupt
354
+ : observed.corrupt.filter((transaction) => transaction.taskId === taskId);
355
+ if (corrupt.length > 0) {
356
+ throw new WorkflowError('STATE_CORRUPT', 'A Task transaction journal failed integrity validation.', {
357
+ transactions: corrupt,
358
+ });
359
+ }
360
+ const pending = taskId === undefined
361
+ ? observed.pending
362
+ : observed.pending.filter((transaction) => transaction.taskId === taskId);
363
+ if (pending.length > 0) {
364
+ throw new WorkflowError('TASK_RECOVERY_REQUIRED', 'A Task transaction requires explicit recovery.', {
365
+ taskIds: [...new Set(pending.map((transaction) => transaction.taskId))].sort(),
366
+ transactionIds: pending.map((transaction) => transaction.transactionId).sort(),
367
+ });
368
+ }
369
+ }
370
+ function assertHeader(input) {
371
+ if (!PROJECT.test(input.projectId) || !TASK.test(input.taskId) || input.transitionId !== 'task.plan.corrective-replan') {
372
+ throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-replan intent identity is invalid.');
373
+ }
374
+ [input.executionKeyHash, input.runtimeFingerprint, input.catalogHash, input.bindingManifest?.manifestSha256,
375
+ input.bindingSha256, input.receiptHash, input.actorIdentityHash, input.humanPrincipalIdentityHash]
376
+ .forEach((value) => assertHash(value, 'Corrective-replan binding hash'));
377
+ if (input.bindingManifest?.id !== correctiveReplanBindingManifest.id
378
+ || input.bindingManifest.version !== correctiveReplanBindingManifest.version
379
+ || input.bindingManifest.manifestSha256 !== correctiveReplanBindingManifest.manifestSha256) {
380
+ throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-replan binding manifest identity is invalid.');
381
+ }
382
+ if (!Number.isSafeInteger(input.lifecycleEpoch) || input.lifecycleEpoch < 1
383
+ || !Number.isSafeInteger(input.expectedTaskRevision) || input.expectedTaskRevision < 1
384
+ || input.resultTaskRevision !== input.expectedTaskRevision + 1) {
385
+ throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-replan revision or epoch is invalid.');
386
+ }
387
+ }
388
+ function normalizePostimages(value) {
389
+ assertPlainExact(value, ['executionEvidence', 'plan', 'planRiskAudit', 'planValidation', 'taskState'], 'Postimages');
390
+ return [value.taskState, value.plan, value.planValidation, value.planRiskAudit, value.executionEvidence].map((item) => {
391
+ if (!(item instanceof Uint8Array) || item.byteLength === 0 || item.byteLength > MAX_TARGET_BYTES) {
392
+ throw new WorkflowError('INVALID_ARGUMENT', 'Each corrective-replan postimage must contain bounded owned bytes.');
393
+ }
394
+ return Buffer.from(item);
395
+ });
396
+ }
397
+ function validateTaskStatePair(preimage, postimage, projectId, taskId, expectedTaskRevision, resultTaskRevision) {
398
+ if (!preimage)
399
+ throw new WorkflowError('STATE_CONFLICT', 'Corrective-replan Task preimage is missing.');
400
+ const before = parseJsonRecord(preimage, 'Task preimage');
401
+ const after = parseJsonRecord(postimage, 'Task postimage');
402
+ for (const value of [before, after]) {
403
+ if (value.kind !== 'task' || value.projectId !== projectId || value.id !== taskId) {
404
+ throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-replan Task bytes have the wrong identity.');
405
+ }
406
+ }
407
+ if (before.revision !== expectedTaskRevision || after.revision !== resultTaskRevision) {
408
+ throw new WorkflowError('STATE_CONFLICT', 'Corrective-replan Task bytes have the wrong revision CAS.');
409
+ }
410
+ }
411
+ function sidecarAnchors(preimage, result) {
412
+ const before = jsonLines(preimage);
413
+ const after = jsonLines(result);
414
+ if (after.length !== before.length + 1 || (preimage && !result.subarray(0, preimage.byteLength).equals(preimage))) {
415
+ throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-replan sidecar postimage must append exactly one event.');
416
+ }
417
+ const previousEventHash = before.length === 0 ? null : eventHash(before.at(-1));
418
+ const appended = after.at(-1);
419
+ const resultEventHash = eventHash(appended);
420
+ if (appended.previousEventHash !== previousEventHash) {
421
+ throw new WorkflowError('INVALID_ARGUMENT', 'Corrective-replan sidecar event does not bind the current tail.');
422
+ }
423
+ return { previousEventHash, resultEventHash, previousLineCount: before.length, resultLineCount: after.length };
424
+ }
425
+ function jsonLines(content) {
426
+ if (!content || content.byteLength === 0)
427
+ return [];
428
+ const text = content.toString('utf8');
429
+ if (!text.endsWith('\n'))
430
+ throw new WorkflowError('INVALID_ARGUMENT', 'Sidecar bytes must end with a newline.');
431
+ return text.slice(0, -1).split('\n').map((line) => parseJsonRecord(Buffer.from(line), 'Sidecar event'));
432
+ }
433
+ function eventHash(event) {
434
+ assertHash(event.eventHash, 'Sidecar eventHash');
435
+ return event.eventHash;
436
+ }
437
+ function validateJournal(value, taskRoot, verifyOwnedContent, expectedTransactionId) {
438
+ if (validateSchema(value) !== true)
439
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction journal schema is invalid.');
440
+ const journal = value;
441
+ assertStoredIso(journal.admittedAt);
442
+ assertStoredIso(journal.updatedAt);
443
+ if (journal.transactionId !== journal.executionKeyHash || journal.transactionId !== expectedTransactionId
444
+ || journal.taskId !== path.basename(taskRoot)) {
445
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction path identity mismatch.');
446
+ }
447
+ if (journal.bindingManifest.id !== correctiveReplanBindingManifest.id
448
+ || journal.bindingManifest.version !== correctiveReplanBindingManifest.version
449
+ || journal.bindingManifest.manifestSha256 !== correctiveReplanBindingManifest.manifestSha256) {
450
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction binding manifest mismatch.');
451
+ }
452
+ if (journal.intentSha256 !== intentHash(journal))
453
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction intent hash mismatch.');
454
+ CORRECTIVE_REPLAN_TARGETS.forEach((expected, ordinal) => {
455
+ const actual = journal.targets[ordinal];
456
+ if (!actual || actual.targetId !== expected.targetId || actual.ordinal !== ordinal
457
+ || actual.relativePath !== expected.relativePath || actual.writeMode !== expected.writeMode
458
+ || (ordinal === 0) !== (actual.taskCas !== null) || (ordinal >= 2) !== (actual.sidecarAnchors !== null)) {
459
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction target inventory mismatch.');
460
+ }
461
+ if (actual.preimage.exists !== (actual.preimage.contentPath !== null)
462
+ || actual.preimage.exists !== (actual.preimage.contentSha256 !== null)
463
+ || actual.preimage.contentPath !== (actual.preimage.exists ? `preimages/${ordinal}.bin` : null)) {
464
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction preimage ownership mismatch.');
465
+ }
466
+ if (verifyOwnedContent) {
467
+ const preimage = readOwnedPreimage(taskRoot, journal.transactionId, actual);
468
+ const postimage = readOwnedContent(taskRoot, journal.transactionId, actual);
469
+ if (ordinal === 0) {
470
+ validateTaskStatePair(preimage, postimage, journal.projectId, journal.taskId, actual.taskCas.expectedRevision, actual.taskCas.resultRevision);
471
+ }
472
+ if (ordinal >= 2) {
473
+ const anchors = sidecarAnchors(preimage, postimage);
474
+ if (JSON.stringify(anchors) !== JSON.stringify(actual.sidecarAnchors)) {
475
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction sidecar anchors mismatch.');
476
+ }
477
+ }
478
+ }
479
+ });
480
+ if (journal.phaseHistory.length !== journal.phaseRevision)
481
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction phase revision mismatch.');
482
+ journal.phaseHistory.forEach((entry, index) => {
483
+ assertStoredIso(entry.recordedAt);
484
+ const previous = index === 0 ? null : journal.phaseHistory[index - 1].entryHash;
485
+ if (index > 0 && entry.recordedAt < journal.phaseHistory[index - 1].recordedAt) {
486
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction phase time moved backwards.');
487
+ }
488
+ if (entry.sequence !== index || entry.previousEntryHash !== previous
489
+ || entry.entryHash !== phaseEntryHash(entry.sequence, entry.phase, entry.nextTargetOrdinal, entry.recordedAt, previous, journal.intentSha256)) {
490
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction phase hash chain mismatch.');
491
+ }
492
+ if (index === 0 && (entry.phase !== 'intent' || entry.nextTargetOrdinal !== 0 || entry.recordedAt !== journal.admittedAt)) {
493
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction phase history must start at intent.');
494
+ }
495
+ if (index > 0 && !isStoredPhaseTransition(journal.phaseHistory[index - 1], entry)) {
496
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction phase topology mismatch.');
497
+ }
498
+ });
499
+ const last = journal.phaseHistory.at(-1);
500
+ if (last.phase !== journal.phase || last.nextTargetOrdinal !== journal.nextTargetOrdinal || last.recordedAt !== journal.updatedAt) {
501
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction current phase projection mismatch.');
502
+ }
503
+ if ((journal.phase === 'completed') !== (journal.completedResult !== null)) {
504
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction completion result mismatch.');
505
+ }
506
+ if (journal.phaseRevision > 9)
507
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction phase cardinality mismatch.');
508
+ if (verifyOwnedContent && journal.phase === 'completed')
509
+ assertCanonicalTargetsMatch(taskRoot, journal);
510
+ }
511
+ function isStoredPhaseTransition(previous, next) {
512
+ return previous.phase === 'intent' && previous.nextTargetOrdinal === 0
513
+ && next.phase === 'applying' && next.nextTargetOrdinal === 0
514
+ || previous.phase === 'applying' && next.phase === 'applying'
515
+ && next.nextTargetOrdinal === previous.nextTargetOrdinal + 1
516
+ && next.nextTargetOrdinal <= CORRECTIVE_REPLAN_TARGETS.length
517
+ || previous.phase === 'applying' && previous.nextTargetOrdinal === CORRECTIVE_REPLAN_TARGETS.length
518
+ && next.phase === 'published-pending-readback' && next.nextTargetOrdinal === CORRECTIVE_REPLAN_TARGETS.length
519
+ || previous.phase === 'published-pending-readback' && next.phase === 'completed'
520
+ && next.nextTargetOrdinal === CORRECTIVE_REPLAN_TARGETS.length;
521
+ }
522
+ function assertPhaseTransition(current, next, ordinal) {
523
+ const allowed = current.phase === 'intent' && next === 'applying' && ordinal === 0
524
+ || current.phase === 'applying' && next === 'applying'
525
+ && ordinal === current.nextTargetOrdinal + 1 && ordinal <= CORRECTIVE_REPLAN_TARGETS.length
526
+ || current.phase === 'applying' && next === 'published-pending-readback'
527
+ && current.nextTargetOrdinal === CORRECTIVE_REPLAN_TARGETS.length
528
+ && ordinal === CORRECTIVE_REPLAN_TARGETS.length
529
+ || current.phase === 'published-pending-readback' && next === 'completed'
530
+ && ordinal === CORRECTIVE_REPLAN_TARGETS.length;
531
+ if (!allowed)
532
+ throw new WorkflowError('INVALID_ARGUMENT', 'Invalid Task transaction phase transition.');
533
+ }
534
+ function phaseEntry(sequence, phase, nextTargetOrdinal, recordedAt, previousEntryHash, intentSha256) {
535
+ assertIso(recordedAt, 'phase recordedAt');
536
+ return {
537
+ sequence, phase, nextTargetOrdinal, recordedAt, previousEntryHash,
538
+ entryHash: phaseEntryHash(sequence, phase, nextTargetOrdinal, recordedAt, previousEntryHash, intentSha256),
539
+ };
540
+ }
541
+ function phaseEntryHash(sequence, phase, nextTargetOrdinal, recordedAt, previousEntryHash, intentSha256) {
542
+ return sha256(Buffer.from(JSON.stringify({
543
+ domain: 'codex-workflow/corrective-replan-task-transaction-phase/v1',
544
+ intentSha256, sequence, phase, nextTargetOrdinal, recordedAt, previousEntryHash,
545
+ })));
546
+ }
547
+ function intentHash(journal) {
548
+ return sha256(Buffer.from(JSON.stringify({
549
+ domain: 'codex-workflow/corrective-replan-task-transaction-intent/v1',
550
+ schemaVersion: journal.schemaVersion,
551
+ kind: journal.kind,
552
+ transactionId: journal.transactionId,
553
+ projectId: journal.projectId,
554
+ taskId: journal.taskId,
555
+ transitionId: journal.transitionId,
556
+ executionKeyHash: journal.executionKeyHash,
557
+ runtimeFingerprint: journal.runtimeFingerprint,
558
+ catalogHash: journal.catalogHash,
559
+ lifecycleEpoch: journal.lifecycleEpoch,
560
+ bindingManifest: journal.bindingManifest,
561
+ bindingSha256: journal.bindingSha256,
562
+ receiptHash: journal.receiptHash,
563
+ actorIdentityHash: journal.actorIdentityHash,
564
+ humanPrincipalIdentityHash: journal.humanPrincipalIdentityHash,
565
+ admittedAt: journal.admittedAt,
566
+ targets: journal.targets,
567
+ })));
568
+ }
569
+ function readTarget(taskRoot, relativePath) {
570
+ const file = path.join(taskRoot, relativePath);
571
+ assertNoSymlinkPathComponents(file, { label: 'Corrective-replan target', trustedRoot: taskRoot });
572
+ if (!existsSync(file))
573
+ return { content: null };
574
+ const stat = lstatSync(file);
575
+ if (!stat.isFile() || stat.isSymbolicLink())
576
+ throw new WorkflowError('STATE_CORRUPT', 'Corrective-replan target must be a regular file.');
577
+ return { content: readFileSync(file) };
578
+ }
579
+ function snapshot(content, ordinal) {
580
+ return content === null
581
+ ? { exists: false, contentSha256: null, byteLength: 0, contentPath: null }
582
+ : { exists: true, contentSha256: sha256(content), byteLength: content.byteLength, contentPath: `preimages/${ordinal}.bin` };
583
+ }
584
+ function readOwnedPreimage(taskRoot, transactionId, target) {
585
+ if (!target.preimage.exists)
586
+ return null;
587
+ if (target.preimage.contentPath !== `preimages/${target.ordinal}.bin`) {
588
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction preimage path mismatch.');
589
+ }
590
+ const file = path.join(taskRoot, '.transactions', transactionId, target.preimage.contentPath);
591
+ assertNoSymlinkPathComponents(file, { label: 'Task transaction owned preimage', trustedRoot: taskRoot, requireLeaf: true });
592
+ if (!existsSync(file))
593
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction owned preimage is missing.');
594
+ const content = readFileSync(file);
595
+ if (content.byteLength !== target.preimage.byteLength || sha256(content) !== target.preimage.contentSha256) {
596
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction owned preimage digest mismatch.');
597
+ }
598
+ return content;
599
+ }
600
+ function assertCanonicalTargetsMatch(taskRoot, journal) {
601
+ for (const target of journal.targets) {
602
+ assertCanonicalTargetMatches(taskRoot, target);
603
+ }
604
+ }
605
+ function assertCanonicalTargetMatches(taskRoot, target) {
606
+ const current = readTarget(taskRoot, target.relativePath).content;
607
+ if (current === null || sha256(current) !== target.postimage.contentSha256) {
608
+ throw new WorkflowError('STATE_CONFLICT', 'Task transaction target readback mismatch.', {
609
+ targetId: target.targetId,
610
+ });
611
+ }
612
+ }
613
+ function readOwnedContent(taskRoot, transactionId, target) {
614
+ if (target.postimage.contentPath !== `targets/${target.ordinal}.bin`) {
615
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction owned-content path mismatch.');
616
+ }
617
+ const file = path.join(taskRoot, '.transactions', transactionId, target.postimage.contentPath);
618
+ assertNoSymlinkPathComponents(file, { label: 'Task transaction owned content', trustedRoot: taskRoot, requireLeaf: true });
619
+ if (!existsSync(file))
620
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction owned content is missing.');
621
+ const content = readFileSync(file);
622
+ if (content.byteLength !== target.postimage.byteLength || sha256(content) !== target.postimage.contentSha256) {
623
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction owned content digest mismatch.');
624
+ }
625
+ return content;
626
+ }
627
+ function parseJsonRecord(content, label) {
628
+ try {
629
+ const value = JSON.parse(content.toString('utf8'));
630
+ if (!value || typeof value !== 'object' || Array.isArray(value))
631
+ throw new TypeError('not an object');
632
+ return value;
633
+ }
634
+ catch {
635
+ throw new WorkflowError('INVALID_ARGUMENT', `${label} must contain one JSON object.`);
636
+ }
637
+ }
638
+ function assertPlainExact(value, keys, label) {
639
+ if (!value || typeof value !== 'object' || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype) {
640
+ throw new WorkflowError('INVALID_ARGUMENT', `${label} must be a plain object.`);
641
+ }
642
+ const actual = Object.keys(value).sort();
643
+ const expected = [...keys].sort();
644
+ if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
645
+ throw new WorkflowError('INVALID_ARGUMENT', `${label} has unknown or missing fields.`);
646
+ }
647
+ }
648
+ function assertTaskRoot(taskRoot, trustedRoot) {
649
+ if (!path.resolve(taskRoot).startsWith(`${path.resolve(trustedRoot)}${path.sep}`)) {
650
+ throw new WorkflowError('INVALID_ARGUMENT', 'Task transaction root is outside the trusted state root.');
651
+ }
652
+ assertNoSymlinkPathComponents(taskRoot, { label: 'Task transaction root', trustedRoot, requireLeaf: true });
653
+ }
654
+ function ensureDirectorySafe(directory, trustedRoot) {
655
+ if (!existsSync(directory))
656
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
657
+ assertNoSymlinkPathComponents(directory, { label: 'Task transaction directory', trustedRoot, requireLeaf: true });
658
+ }
659
+ function writeNewDurable(file, content) {
660
+ let descriptor = null;
661
+ try {
662
+ descriptor = openSync(file, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600);
663
+ writeSync(descriptor, content);
664
+ fsyncSync(descriptor);
665
+ }
666
+ finally {
667
+ if (descriptor !== null)
668
+ closeSync(descriptor);
669
+ }
670
+ }
671
+ function writeAtomicDurable(file, content) {
672
+ const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${randomUUID()}.tmp`);
673
+ let descriptor = null;
674
+ try {
675
+ descriptor = openSync(temporary, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 0o600);
676
+ writeSync(descriptor, content);
677
+ fsyncSync(descriptor);
678
+ closeSync(descriptor);
679
+ descriptor = null;
680
+ renameSync(temporary, file);
681
+ fsyncDirectory(path.dirname(file));
682
+ }
683
+ finally {
684
+ if (descriptor !== null)
685
+ closeSync(descriptor);
686
+ rmSync(temporary, { force: true });
687
+ }
688
+ }
689
+ function fsyncDirectory(directory) {
690
+ const descriptor = openSync(directory, 'r');
691
+ try {
692
+ fsyncSync(descriptor);
693
+ }
694
+ finally {
695
+ closeSync(descriptor);
696
+ }
697
+ }
698
+ function jsonBuffer(value) {
699
+ return Buffer.from(`${JSON.stringify(value, null, 2)}\n`, 'utf8');
700
+ }
701
+ function sha256(value) {
702
+ return createHash('sha256').update(value).digest('hex');
703
+ }
704
+ function assertHash(value, label) {
705
+ if (typeof value !== 'string' || !HASH.test(value))
706
+ throw new WorkflowError('INVALID_ARGUMENT', `${label} must be lowercase SHA-256.`);
707
+ }
708
+ function assertIso(value, label) {
709
+ const parsed = Date.parse(value);
710
+ if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== value) {
711
+ throw new WorkflowError('INVALID_ARGUMENT', `${label} must be a canonical UTC timestamp.`);
712
+ }
713
+ }
714
+ function assertStoredIso(value) {
715
+ const parsed = Date.parse(value);
716
+ if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== value) {
717
+ throw new WorkflowError('STATE_CORRUPT', 'Task transaction contains a noncanonical timestamp.');
718
+ }
719
+ }
720
+ function isNodeError(error) {
721
+ return error instanceof Error && 'code' in error;
722
+ }
723
+ //# sourceMappingURL=corrective-replan-transaction.js.map