codex-workflow-v2 2.0.0-beta.13.8 → 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 (281) hide show
  1. package/README.md +27 -388
  2. package/dist/reviewer-runtime-build.json +96 -32
  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/failed-step-planning-recovery.d.ts +10 -0
  19. package/dist/src/alpha6/failed-step-planning-recovery.js +93 -0
  20. package/dist/src/alpha6/failed-step-planning-recovery.js.map +1 -0
  21. package/dist/src/alpha6/literal-test-invocation.d.ts +2 -0
  22. package/dist/src/alpha6/literal-test-invocation.js +106 -0
  23. package/dist/src/alpha6/literal-test-invocation.js.map +1 -0
  24. package/dist/src/alpha6/milestone.d.ts +2 -0
  25. package/dist/src/alpha6/milestone.js +10 -1
  26. package/dist/src/alpha6/milestone.js.map +1 -1
  27. package/dist/src/alpha6/navigation-dirty-carryover.d.ts +11 -0
  28. package/dist/src/alpha6/navigation-dirty-carryover.js +130 -0
  29. package/dist/src/alpha6/navigation-dirty-carryover.js.map +1 -0
  30. package/dist/src/alpha6/plan-integrity.js +7 -5
  31. package/dist/src/alpha6/plan-integrity.js.map +1 -1
  32. package/dist/src/alpha6/plan-risk.d.ts +2 -0
  33. package/dist/src/alpha6/plan-risk.js +10 -3
  34. package/dist/src/alpha6/plan-risk.js.map +1 -1
  35. package/dist/src/alpha6/remediation.d.ts +6 -21
  36. package/dist/src/alpha6/remediation.js +161 -476
  37. package/dist/src/alpha6/remediation.js.map +1 -1
  38. package/dist/src/alpha6/root-cause-replan-carryover.d.ts +4 -1
  39. package/dist/src/alpha6/root-cause-replan-carryover.js +29 -5
  40. package/dist/src/alpha6/root-cause-replan-carryover.js.map +1 -1
  41. package/dist/src/beta1/project-transaction.d.ts +3 -1
  42. package/dist/src/beta1/project-transaction.js +6 -2
  43. package/dist/src/beta1/project-transaction.js.map +1 -1
  44. package/dist/src/checks/runner.d.ts +26 -0
  45. package/dist/src/checks/runner.js +223 -0
  46. package/dist/src/checks/runner.js.map +1 -0
  47. package/dist/src/checks/task-sync.d.ts +10 -0
  48. package/dist/src/checks/task-sync.js +41 -0
  49. package/dist/src/checks/task-sync.js.map +1 -0
  50. package/dist/src/checks/worker.d.ts +1 -0
  51. package/dist/src/checks/worker.js +154 -0
  52. package/dist/src/checks/worker.js.map +1 -0
  53. package/dist/src/cli-actions.d.ts +2 -2
  54. package/dist/src/cli-actions.js +3 -15
  55. package/dist/src/cli-actions.js.map +1 -1
  56. package/dist/src/cli.js +14 -98
  57. package/dist/src/cli.js.map +1 -1
  58. package/dist/src/contracts.d.ts +35 -32
  59. package/dist/src/dependency-provenance.d.ts +2 -2
  60. package/dist/src/dependency-provenance.js +12 -69
  61. package/dist/src/dependency-provenance.js.map +1 -1
  62. package/dist/src/domain/base-sync-conflict.d.ts +13 -0
  63. package/dist/src/domain/base-sync-conflict.js +32 -0
  64. package/dist/src/domain/base-sync-conflict.js.map +1 -0
  65. package/dist/src/domain/step-start-admission.d.ts +4 -0
  66. package/dist/src/domain/step-start-admission.js +9 -0
  67. package/dist/src/domain/step-start-admission.js.map +1 -0
  68. package/dist/src/domain/virgin-registration.d.ts +3 -0
  69. package/dist/src/domain/virgin-registration.js +65 -0
  70. package/dist/src/domain/virgin-registration.js.map +1 -0
  71. package/dist/src/errors.d.ts +1 -1
  72. package/dist/src/errors.js.map +1 -1
  73. package/dist/src/gateway-handshake.js +0 -8
  74. package/dist/src/gateway-handshake.js.map +1 -1
  75. package/dist/src/git.d.ts +3 -1
  76. package/dist/src/git.js +41 -14
  77. package/dist/src/git.js.map +1 -1
  78. package/dist/src/graph.js +25 -3
  79. package/dist/src/graph.js.map +1 -1
  80. package/dist/src/index.d.ts +1 -0
  81. package/dist/src/lifecycle/corrective-replan.js +3 -3
  82. package/dist/src/lifecycle/corrective-replan.js.map +1 -1
  83. package/dist/src/navigation-actions.d.ts +9 -0
  84. package/dist/src/navigation-actions.js +72 -0
  85. package/dist/src/navigation-actions.js.map +1 -0
  86. package/dist/src/navigation-update-artifact.d.ts +5 -0
  87. package/dist/src/navigation-update-artifact.js +227 -0
  88. package/dist/src/navigation-update-artifact.js.map +1 -0
  89. package/dist/src/navigation-update.d.ts +39 -0
  90. package/dist/src/navigation-update.js +82 -0
  91. package/dist/src/navigation-update.js.map +1 -0
  92. package/dist/src/observation.js +52 -29
  93. package/dist/src/observation.js.map +1 -1
  94. package/dist/src/observed-routes.js +5 -6
  95. package/dist/src/observed-routes.js.map +1 -1
  96. package/dist/src/pending-review-update.d.ts +0 -13
  97. package/dist/src/pending-review-update.js +1 -6
  98. package/dist/src/pending-review-update.js.map +1 -1
  99. package/dist/src/reviewer.d.ts +1 -1
  100. package/dist/src/reviewer.js +20 -17
  101. package/dist/src/reviewer.js.map +1 -1
  102. package/dist/src/runtime.d.ts +3 -0
  103. package/dist/src/runtime.js +6 -0
  104. package/dist/src/runtime.js.map +1 -0
  105. package/dist/src/state/corrective-replan-executor.d.ts +10 -0
  106. package/dist/src/state/corrective-replan-executor.js +37 -1
  107. package/dist/src/state/corrective-replan-executor.js.map +1 -1
  108. package/dist/src/state/corrective-replan-public-schema.js +15 -2
  109. package/dist/src/state/corrective-replan-public-schema.js.map +1 -1
  110. package/dist/src/state/corrective-replan-public.js +6 -5
  111. package/dist/src/state/corrective-replan-public.js.map +1 -1
  112. package/dist/src/state/corrective-replan-transaction.d.ts +1 -0
  113. package/dist/src/state/corrective-replan-transaction.js +12 -11
  114. package/dist/src/state/corrective-replan-transaction.js.map +1 -1
  115. package/dist/src/state/corrective-yield-executor.js +2 -2
  116. package/dist/src/state/corrective-yield-executor.js.map +1 -1
  117. package/dist/src/state/corrective-yield-transaction.d.ts +1 -0
  118. package/dist/src/state/corrective-yield-transaction.js +11 -10
  119. package/dist/src/state/corrective-yield-transaction.js.map +1 -1
  120. package/dist/src/state/lock.d.ts +4 -0
  121. package/dist/src/state/lock.js +21 -0
  122. package/dist/src/state/lock.js.map +1 -1
  123. package/dist/src/version.d.ts +1 -1
  124. package/dist/src/version.js +1 -1
  125. package/dist/src/version.js.map +1 -1
  126. package/dist/src/workflow-blocker-route.d.ts +8 -0
  127. package/dist/src/workflow-blocker-route.js +106 -0
  128. package/dist/src/workflow-blocker-route.js.map +1 -0
  129. package/dist/src/workflow.d.ts +48 -63
  130. package/dist/src/workflow.js +698 -1470
  131. package/dist/src/workflow.js.map +1 -1
  132. package/docs/autonomy-guardrails.md +26 -304
  133. package/docs/decisions.md +14 -112
  134. package/docs/development-flow.md +35 -238
  135. package/docs/project-memory.md +31 -50
  136. package/docs/release-app-evidence.md +190 -0
  137. package/docs/release.md +95 -363
  138. package/docs/updating-existing-project.md +19 -717
  139. package/package.json +11 -13
  140. package/plugins/codex-workflow-gateway/.codex-plugin/plugin.json +2 -2
  141. package/plugins/codex-workflow-gateway/references/chat-dispatch.md +73 -197
  142. package/plugins/codex-workflow-gateway/references/codebase-memory-routing.md +57 -0
  143. package/plugins/codex-workflow-gateway/references/protocol.md +42 -445
  144. package/plugins/codex-workflow-gateway/scripts/chat-dispatch.mjs +7 -1
  145. package/plugins/codex-workflow-gateway/scripts/chat-model-policy.mjs +10 -8
  146. package/plugins/codex-workflow-gateway/scripts/chat-registry.mjs +23 -5
  147. package/plugins/codex-workflow-gateway/skills/codex-workflow-gateway/SKILL.md +66 -740
  148. package/references/state-machine.md +4 -4
  149. package/roles/technical-planner.md +1 -1
  150. package/schemas/corrective-decision-event.schema.json +3 -1
  151. package/schemas/project-knowledge-map.schema.json +3 -1
  152. package/schemas/remediation-event.schema.json +10 -1
  153. package/schemas/task.schema.json +46 -2
  154. package/schemas/transition-payloads.schema.json +16 -1
  155. package/src/alpha6/adoption.ts +1123 -0
  156. package/src/alpha6/captured-check-evidence.ts +132 -0
  157. package/src/alpha6/check-support-anchor.ts +128 -0
  158. package/src/alpha6/component-owner.ts +168 -0
  159. package/src/alpha6/corrective-decision-boundary.ts +39 -0
  160. package/src/alpha6/downstream-proof.ts +520 -0
  161. package/src/alpha6/failed-step-planning-recovery.ts +88 -0
  162. package/src/alpha6/handoff.ts +1443 -0
  163. package/src/alpha6/journal.ts +473 -0
  164. package/src/alpha6/literal-test-invocation.ts +84 -0
  165. package/src/alpha6/mechanical-feasibility.ts +488 -0
  166. package/src/alpha6/milestone.ts +2192 -0
  167. package/src/alpha6/navigation-dirty-carryover.ts +154 -0
  168. package/src/alpha6/npm-check-contract.ts +47 -0
  169. package/src/alpha6/plan-integrity.ts +298 -0
  170. package/src/alpha6/plan-risk.ts +1480 -0
  171. package/src/alpha6/preexecution-replan.ts +187 -0
  172. package/src/alpha6/remediation-cause.ts +98 -0
  173. package/src/alpha6/remediation.ts +2438 -0
  174. package/src/alpha6/review.ts +1198 -0
  175. package/src/alpha6/root-cause-replan-carryover.ts +491 -0
  176. package/src/alpha6/store-sidecars.ts +335 -0
  177. package/src/alpha7/autonomy.ts +411 -0
  178. package/src/alpha7/corrective-recovery.ts +1332 -0
  179. package/src/artifacts.ts +130 -0
  180. package/src/beta1/project-transaction.ts +355 -0
  181. package/src/change-explanation.ts +153 -0
  182. package/src/checks/runner.ts +245 -0
  183. package/src/checks/task-sync.ts +41 -0
  184. package/src/checks/worker.ts +149 -0
  185. package/src/cli-actions.ts +107 -0
  186. package/src/cli.ts +1457 -0
  187. package/src/contracts.ts +1494 -0
  188. package/src/credential-output.ts +89 -0
  189. package/src/credential-transport.ts +215 -0
  190. package/src/delegation.ts +190 -0
  191. package/src/dependency-provenance.ts +472 -0
  192. package/src/diagnostics.ts +93 -0
  193. package/src/domain/base-sync-conflict.ts +35 -0
  194. package/src/domain/completed-step-carryover.ts +76 -0
  195. package/src/domain/discovery.ts +27 -0
  196. package/src/domain/plan-semantics.ts +58 -0
  197. package/src/domain/step-start-admission.ts +10 -0
  198. package/src/domain/validation.ts +177 -0
  199. package/src/domain/virgin-registration.ts +46 -0
  200. package/src/errors.ts +24 -0
  201. package/src/fs-utils.ts +61 -0
  202. package/src/gateway-handshake.ts +95 -0
  203. package/src/git.ts +183 -0
  204. package/src/graph.ts +342 -0
  205. package/src/historical-step-provenance.ts +136 -0
  206. package/src/index.ts +23 -0
  207. package/src/lifecycle/canonical-hash.ts +28 -0
  208. package/src/lifecycle/catalog.ts +202 -0
  209. package/src/lifecycle/compiler-inspection.ts +29 -0
  210. package/src/lifecycle/core-static-readiness.ts +132 -0
  211. package/src/lifecycle/corrective-replan-authority.ts +136 -0
  212. package/src/lifecycle/corrective-replan-binding-manifest.ts +51 -0
  213. package/src/lifecycle/corrective-replan-credential-core.ts +408 -0
  214. package/src/lifecycle/corrective-replan-credential-schema.ts +54 -0
  215. package/src/lifecycle/corrective-replan-credentials.ts +48 -0
  216. package/src/lifecycle/corrective-replan.ts +843 -0
  217. package/src/lifecycle/evaluator.ts +48 -0
  218. package/src/lifecycle/fingerprint.ts +488 -0
  219. package/src/lifecycle/immutable.ts +8 -0
  220. package/src/lifecycle/implementation-table.ts +118 -0
  221. package/src/lifecycle/index.ts +8 -0
  222. package/src/lifecycle/schema-artifact.ts +263 -0
  223. package/src/lifecycle/semantic-registry.ts +572 -0
  224. package/src/lifecycle/types.ts +838 -0
  225. package/src/memory.ts +273 -0
  226. package/src/migration.ts +161 -0
  227. package/src/navigation-actions.ts +70 -0
  228. package/src/navigation-update-artifact.ts +198 -0
  229. package/src/navigation-update.ts +121 -0
  230. package/src/observation.ts +225 -0
  231. package/src/observed-routes.ts +660 -0
  232. package/src/operational-contract.ts +125 -0
  233. package/src/pending-review-update.ts +175 -0
  234. package/src/repository.ts +99 -0
  235. package/src/reviewer.ts +1879 -0
  236. package/src/runtime.ts +6 -0
  237. package/src/state/corrective-replan-executor.ts +818 -0
  238. package/src/state/corrective-replan-public-schema.ts +83 -0
  239. package/src/state/corrective-replan-public.ts +908 -0
  240. package/src/state/corrective-replan-transaction.ts +949 -0
  241. package/src/state/corrective-yield-executor.ts +327 -0
  242. package/src/state/corrective-yield-transaction.ts +730 -0
  243. package/src/state/lock.ts +902 -0
  244. package/src/state/store.ts +567 -0
  245. package/src/transition-core.ts +330 -0
  246. package/src/ulid.ts +24 -0
  247. package/src/version.ts +2 -0
  248. package/src/workflow-blocker-route.ts +109 -0
  249. package/src/workflow.ts +10172 -0
  250. package/docs/alpha7.1-implementation-brief.md +0 -268
  251. package/docs/alpha7.2-corrective-context-refresh-brief.md +0 -484
  252. package/docs/alpha7.2.1-remediation-recovery-brief.md +0 -86
  253. package/docs/beta1-stabilization-brief.md +0 -165
  254. package/docs/beta11-plan-integrity-recovery-brief.md +0 -38
  255. package/docs/beta13.2-signal-review-recovery.md +0 -38
  256. package/docs/beta2-initial-assembly-navigation-brief.md +0 -616
  257. package/docs/change-model.md +0 -118
  258. package/docs/delegated-approval.md +0 -254
  259. package/docs/lifecycle/state-machine-stabilization.md +0 -641
  260. package/docs/pdf/README.md +0 -24
  261. package/docs/pdf/codex-workflow-v2-architecture-ru.pdf +0 -0
  262. package/docs/pdf/codex-workflow-v2-chat-only-guide-ru.pdf +0 -0
  263. package/docs/pdf/codex-workflow-v2-technical-reference-ru.pdf +0 -0
  264. package/docs/pdf/requirements.txt +0 -1
  265. package/docs/pdf/sources/codex-workflow-v2-architecture-ru.md +0 -478
  266. package/docs/pdf/sources/codex-workflow-v2-chat-only-guide-ru.md +0 -508
  267. package/docs/pdf/sources/codex-workflow-v2-technical-reference-ru.md +0 -778
  268. package/docs/pending-review-update.md +0 -15
  269. package/docs/problem-briefs/01-pre-implementation-integrity.md +0 -482
  270. package/docs/problem-briefs/02-minimal-step-integrity.md +0 -411
  271. package/docs/problem-briefs/03-minimal-agent-context-integrity.md +0 -358
  272. package/docs/problem-briefs/04-task-dependency-and-structural-replacement-integrity.md +0 -573
  273. package/docs/problem-briefs/BRIEF-TEMPLATE.md +0 -56
  274. package/docs/problem-briefs/README.md +0 -120
  275. package/docs/problem-briefs/evidence/p01-mechanical-feasibility-corpus.md +0 -90
  276. package/docs/problem-briefs/evidence/signal-v4-pre-m3-replay.md +0 -246
  277. package/docs/split-required-recovery.md +0 -47
  278. package/docs/stable-release-defect-register.md +0 -730
  279. package/docs/validation-report.md +0 -182
  280. package/scripts/generate-pdf-docs.py +0 -524
  281. package/scripts/run-pdf-docs.mjs +0 -62
@@ -0,0 +1,245 @@
1
+ import { spawnSync, type SpawnSyncOptions } from 'node:child_process';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { mkdtempSync, writeFileSync, readFileSync, existsSync, lstatSync, rmSync } from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { performance } from 'node:perf_hooks';
8
+ export interface CheckExecutionOptions {
9
+ timeoutMs?: number;
10
+ totalTimeoutMs?: number;
11
+ cancellationFile?: string;
12
+ maxOutputBytes?: number;
13
+ graceMs?: number;
14
+ }
15
+ export interface CheckCommandResult {
16
+ command: string;
17
+ outcome: 'passed' | 'failed' | 'infra';
18
+ exitCode: number | null;
19
+ signal: string | null;
20
+ error: string | null;
21
+ errorCode: string | null;
22
+ errorMessage: string | null;
23
+ output: string;
24
+ stdoutTail: string;
25
+ stderrTail: string;
26
+ outputTruncated: boolean;
27
+ outputBytes: number;
28
+ durationMs: number;
29
+ cleanupComplete: boolean;
30
+ cleanupScope: 'owned-process-group';
31
+ cleanupEvidencePath: string | null;
32
+ }
33
+ const message = (error: unknown) => error instanceof Error ? error.message.slice(0, 1024) : 'Unknown runner error';
34
+ const sleep = (ms: number) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
35
+ const positive = (n: number | undefined, fallback: number, max: number) => {
36
+ const value = n ?? fallback;
37
+ if (!Number.isSafeInteger(value) || value <= 0 || value > max) {
38
+ throw new Error('Invalid check runner limit');
39
+ }
40
+ return value;
41
+ };
42
+ function alive(pid: number): boolean {
43
+ try {
44
+ process.kill(-pid, 0);
45
+ return true;
46
+ } catch (e) {
47
+ return (e as NodeJS.ErrnoException).code !== 'ESRCH';
48
+ }
49
+ }
50
+ function kill(pid: number, signal: NodeJS.Signals) {
51
+ try {
52
+ process.kill(-pid, signal);
53
+ } catch (e) {
54
+ if ((e as NodeJS.ErrnoException).code !== 'ESRCH') {
55
+ throw e;
56
+ }
57
+ }
58
+ }
59
+ export function runCheckCommands(repositoryRoot: string, commands: string[], options: CheckExecutionOptions = {}): CheckCommandResult[] {
60
+ const timeout = positive(options.timeoutMs, 120_000, 600_000);
61
+ const total = positive(options.totalTimeoutMs, 600_000, 600_000);
62
+ const grace = positive(options.graceMs, 500, 5_000);
63
+ const cap = positive(options.maxOutputBytes, 12_000, 12_000);
64
+ if (options.cancellationFile !== undefined && (typeof options.cancellationFile !== 'string' || !options.cancellationFile.trim())) {
65
+ throw new Error('Invalid cancellation file');
66
+ }
67
+ const cancellationFile = options.cancellationFile === undefined ? null : path.resolve(options.cancellationFile);
68
+ const deadline = process.hrtime.bigint() + BigInt(total) * 1_000_000n;
69
+ const results: CheckCommandResult[] = [];
70
+ for (const command of commands) {
71
+ const start = performance.now();
72
+ let error: string | null = null, exitCode: number | null = null, signal: string | null = null, cleanup = true;
73
+ let output = '', stdoutTail = '', stderrTail = '', outputTruncated = false, outputBytes = 0;
74
+ const now = process.hrtime.bigint();
75
+ const perCommandDeadline = now + BigInt(timeout) * 1_000_000n;
76
+ const commandDeadline = perCommandDeadline < deadline ? perCommandDeadline : deadline;
77
+ const budget = Number((commandDeadline - now) / 1_000_000n);
78
+ if (process.platform === 'win32' || budget <= 0 || cancellationFile && existsSync(cancellationFile)) {
79
+ results.push({
80
+ command,
81
+ outcome: 'infra',
82
+ exitCode: null,
83
+ signal: null,
84
+ errorCode: process.platform === 'win32' ? 'PROCESS_PLATFORM_UNSUPPORTED' : budget <= 0 ? 'CHECK_TOTAL_TIMEOUT' : 'CHECK_CANCELLED',
85
+ error: process.platform === 'win32' ? 'platform-unsupported' : budget <= 0 ? 'total-timeout' : 'cancelled',
86
+ output,
87
+ stdoutTail,
88
+ stderrTail,
89
+ outputTruncated,
90
+ outputBytes,
91
+ errorMessage: null,
92
+ durationMs: 0,
93
+ cleanupComplete: true,
94
+ cleanupScope: 'owned-process-group',
95
+ cleanupEvidencePath: null
96
+ });
97
+ break;
98
+ }
99
+ const directory = mkdtempSync(path.join(os.tmpdir(), 'workflow-check-')), nonce = randomUUID(), request = path.join(directory, 'request.json'), resultFile = path.join(directory, 'result.json');
100
+ let pid: number | undefined;
101
+ let errorCode: string | null = null;
102
+ let errorMessage: string | null = null;
103
+ try {
104
+ writeFileSync(request, JSON.stringify({
105
+ nonce,
106
+ command,
107
+ cwd: repositoryRoot,
108
+ deadlineNs: commandDeadline.toString(),
109
+ graceMs: grace,
110
+ cap,
111
+ parentPid: process.pid,
112
+ cancellationFile,
113
+ resultFile,
114
+ journalFile: path.join(directory, 'owner.json')
115
+ }), {
116
+ mode: 0o600
117
+ });
118
+ const child = spawnSync(process.execPath, [fileURLToPath(new URL('./worker.js', import.meta.url)), request], {
119
+ cwd: repositoryRoot,
120
+ env: process.env,
121
+ stdio: 'ignore',
122
+ detached: true,
123
+ timeout: budget + 2 * grace + 250,
124
+ killSignal: 'SIGKILL'
125
+ } as SpawnSyncOptions & {
126
+ detached: boolean;
127
+ });
128
+ pid = child.pid;
129
+ signal = child.signal;
130
+ errorMessage = child.error ? message(child.error) : null;
131
+ errorCode = (child.error as NodeJS.ErrnoException | undefined)?.code ?? null;
132
+ error = child.error ? ((child.error as NodeJS.ErrnoException).code === 'ETIMEDOUT' ? 'outer-timeout' : 'helper-launch-failed') : null;
133
+ if (existsSync(resultFile)) {
134
+ const stat = lstatSync(resultFile);
135
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 18 * cap + 8192) {
136
+ throw new Error('Invalid helper protocol size');
137
+ }
138
+ const r = JSON.parse(readFileSync(resultFile, 'utf8'));
139
+ const journalFile = path.join(directory, 'owner.json'), journalStat = lstatSync(journalFile);
140
+ if (!journalStat.isFile() || journalStat.isSymbolicLink() || journalStat.size > 1024) {
141
+ throw new Error('Invalid helper ownership');
142
+ }
143
+ const owner = JSON.parse(readFileSync(journalFile, 'utf8'));
144
+ if (owner.nonce !== nonce || owner.pid !== pid || owner.parentPid !== process.pid || owner.groupLeader !== true) {
145
+ throw new Error('Invalid helper ownership');
146
+ }
147
+ if (Object.keys(r).sort().join(',') !== 'errorCode,errorMessage,exitCode,finishedAtNs,nonce,output,outputBytes,outputTruncated,pid,protocol,reason,signal,stderrTail,stdoutTail' || r.pid !== pid || r.nonce !== nonce || r.protocol !== 1 || !['exit', 'timeout', 'cancelled', 'parent-exit', 'launch-failed'].includes(r.reason)
148
+ || !(r.exitCode === null || Number.isInteger(r.exitCode) && r.exitCode >= 0 && r.exitCode <= 255) || !(r.signal === null || typeof r.signal === 'string' && /^SIG[A-Z0-9]+$/.test(r.signal))
149
+ || !['output', 'stdoutTail', 'stderrTail'].every(k => typeof r[k] === 'string' && Buffer.byteLength(r[k]) <= cap + 3)
150
+ || !(r.errorCode === null || typeof r.errorCode === 'string' && /^[A-Z0-9_]{1,80}$/.test(r.errorCode))
151
+ || !(r.errorMessage === null || typeof r.errorMessage === 'string' && r.errorMessage.length <= 1024)
152
+ || (r.reason === 'exit' && r.exitCode === null && r.signal === null)
153
+ || typeof r.finishedAtNs !== 'string' || !/^[0-9]{1,30}$/.test(r.finishedAtNs)
154
+ || typeof r.outputTruncated !== 'boolean' || !Number.isSafeInteger(r.outputBytes) || r.outputBytes < 0) {
155
+ throw new Error('Invalid helper protocol');
156
+ }
157
+ ({
158
+ output,
159
+ stdoutTail,
160
+ stderrTail,
161
+ outputTruncated,
162
+ outputBytes,
163
+ exitCode
164
+ } = r);
165
+ signal = r.signal;
166
+ errorCode ??= r.errorCode;
167
+ errorMessage ??= r.errorMessage;
168
+ error ??= r.reason === 'exit' ? null : r.reason;
169
+ if (r.reason === 'exit' && BigInt(r.finishedAtNs) > commandDeadline) {
170
+ error ??= 'timeout';
171
+ }
172
+ if (r.reason === 'exit' && child.status !== 0) {
173
+ error ??= 'helper-failed';
174
+ }
175
+ if (r.reason === 'exit' && r.signal !== null) {
176
+ error ??= 'command-signalled';
177
+ }
178
+ if (r.reason === 'exit' && [126, 127].includes(r.exitCode)) {
179
+ error ??= 'shell-command-unavailable';
180
+ }
181
+ }
182
+ else {
183
+ error ??= 'helper-result-missing';
184
+ }
185
+ } catch (e) {
186
+ errorMessage ??= message(e);
187
+ error ??= 'helper-protocol-invalid';
188
+ errorCode ??= (e as NodeJS.ErrnoException).code ?? 'CHECK_HELPER_PROTOCOL_INVALID';
189
+ }
190
+ finally {
191
+ if (pid && pid > 0 && alive(pid)) {
192
+ error ??= 'descendants-remain';
193
+ try {
194
+ kill(pid, 'SIGTERM');
195
+ const end = performance.now() + grace;
196
+ while (alive(pid) && performance.now() < end) {
197
+ sleep(10);
198
+ }
199
+ if (alive(pid)) {
200
+ kill(pid, 'SIGKILL');
201
+ }
202
+ const killed = performance.now() + grace;
203
+ while (alive(pid) && performance.now() < killed) {
204
+ sleep(10);
205
+ }
206
+ cleanup = !alive(pid);
207
+ } catch (e) {
208
+ cleanup = false;
209
+ errorMessage ??= message(e);
210
+ }
211
+ }
212
+ if (!cleanup) {
213
+ error = 'cleanup-failed';
214
+ }
215
+ if (cleanup) {
216
+ rmSync(directory, {
217
+ recursive: true,
218
+ force: true
219
+ });
220
+ }
221
+ }
222
+ results.push({
223
+ command,
224
+ outcome: error ? 'infra' : exitCode === 0 ? 'passed' : 'failed',
225
+ exitCode,
226
+ signal,
227
+ error,
228
+ errorCode,
229
+ errorMessage,
230
+ output,
231
+ stdoutTail,
232
+ stderrTail,
233
+ outputTruncated,
234
+ outputBytes,
235
+ durationMs: Math.round(performance.now() - start),
236
+ cleanupComplete: cleanup,
237
+ cleanupScope: 'owned-process-group',
238
+ cleanupEvidencePath: cleanup ? null : directory
239
+ });
240
+ if (error) {
241
+ break;
242
+ }
243
+ }
244
+ return results;
245
+ }
@@ -0,0 +1,41 @@
1
+ import { createHash } from 'node:crypto';
2
+ import type { TaskState } from '../contracts.js';
3
+ import { WorkflowError } from '../errors.js';
4
+ import { changedFiles, currentBranch, headCommit } from '../repository.js';
5
+
6
+ export function taskSyncChecks(task: TaskState): string[] {
7
+ return [...new Set(task.steps.flatMap(step => step.checks))];
8
+ }
9
+ export function taskSyncChecksHash(task: TaskState): string {
10
+ return createHash('sha256').update(JSON.stringify(taskSyncChecks(task))).digest('hex');
11
+ }
12
+ export function assertNoPendingBaseSyncValidation(task: TaskState): void {
13
+ if (task.pendingBaseSyncValidation !== undefined) {
14
+ throw new WorkflowError('TRANSITION_BLOCKED', 'Resume task sync-base validation before changing this Task.', { taskId: task.id });
15
+ }
16
+ }
17
+ /** This checkpoint grants no authority: only the exact merged snapshot may be checked again. */
18
+ export function assessPendingBaseSyncValidation(task: TaskState): { dirtyFiles: string[]; reason: string | null } {
19
+ const pending = task.pendingBaseSyncValidation;
20
+ if (!pending || typeof pending !== 'object' || Array.isArray(pending)
21
+ || Object.keys(pending).sort().join(',') !== 'baseCommit,checksHash,headCommit,syncCommit'
22
+ || ![pending.baseCommit, pending.headCommit, pending.syncCommit].every(value => typeof value === 'string' && /^[a-f0-9]{40}$/.test(value))
23
+ || typeof pending.checksHash !== 'string' || !/^[a-f0-9]{64}$/.test(pending.checksHash)
24
+ || task.status !== 'validating' || task.workspaceOwner !== 'local' || !task.taskBranch
25
+ || task.baseCommit !== pending.baseCommit || pending.syncCommit !== pending.headCommit
26
+ || !task.systemCommits.includes(pending.syncCommit)
27
+ || task.evidenceHash !== null || task.resultHash !== null || task.review.status !== 'pending'
28
+ || task.authorizations.some(item => item.kind === 'final_acceptance' && item.decision === 'approved')) {
29
+ throw new WorkflowError('STATE_CORRUPT', 'Invalid pending base-sync validation checkpoint.', { taskId: task.id });
30
+ }
31
+ const dirtyFiles = changedFiles(task.repositoryRoot).sort();
32
+ const reason = taskSyncChecksHash(task) !== pending.checksHash ? 'The approved checks changed.'
33
+ : currentBranch(task.repositoryRoot) !== task.taskBranch ? 'Restore the exact Task branch.'
34
+ : headCommit(task.repositoryRoot) !== pending.headCommit ? 'The Task HEAD changed after synchronization.'
35
+ : dirtyFiles.length > 0 ? 'Validation left uncommitted files; preserve and resolve these changes before retrying.' : null;
36
+ return { dirtyFiles, reason };
37
+ }
38
+ export function assertPendingBaseSyncValidationReady(task: TaskState): void {
39
+ const assessment = assessPendingBaseSyncValidation(task);
40
+ if (assessment.reason) throw new WorkflowError('TRANSITION_BLOCKED', assessment.reason, { taskId: task.id, ...assessment });
41
+ }
@@ -0,0 +1,149 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { readFileSync, writeFileSync, renameSync, existsSync } from 'node:fs';
3
+ const request = JSON.parse(readFileSync(process.argv[2]!, 'utf8'));
4
+ const { nonce, command, cwd, deadlineNs, graceMs, cap, parentPid, resultFile, journalFile, cancellationFile } = request;
5
+ // Refuse to launch work unless this process really owns its dedicated POSIX group.
6
+ try {
7
+ process.kill(-process.pid, 0);
8
+ } catch {
9
+ process.exit(70);
10
+ }
11
+ writeFileSync(journalFile, JSON.stringify({
12
+ protocol: 1,
13
+ pid: process.pid,
14
+ parentPid,
15
+ nonce,
16
+ groupLeader: true
17
+ }), {
18
+ mode: 0o600
19
+ });
20
+ let out = Buffer.alloc(0);
21
+ let err = Buffer.alloc(0);
22
+ let combined = Buffer.alloc(0);
23
+ let bytes = 0;
24
+ let truncated = false;
25
+ let stopping = false;
26
+ let finished = false;
27
+ let errorCode: string | null = null, errorMessage: string | null = null;
28
+ let exitCode: number | null = null, signal: NodeJS.Signals | null = null;
29
+ const append = (data: Buffer, stderr: boolean) => {
30
+ bytes = Math.min(Number.MAX_SAFE_INTEGER, bytes + data.length);
31
+ const retain = (old: Buffer) => {
32
+ const joined = Buffer.concat([old, data]);
33
+ if (joined.length > cap) {
34
+ truncated = true;
35
+ }
36
+ return joined.subarray(Math.max(0, joined.length - cap));
37
+ };
38
+ combined = retain(combined);
39
+ if (stderr) {
40
+ err = retain(err);
41
+ }
42
+ else {
43
+ out = retain(out);
44
+ }
45
+ };
46
+ const text = (b: Buffer) => {
47
+ const decoded = Buffer.from(b.toString('utf8'));
48
+ return decoded.subarray(Math.max(0, decoded.length - cap)).toString('utf8');
49
+ };
50
+ function record(reason: string) {
51
+ const tmp = resultFile + '.tmp';
52
+ writeFileSync(tmp, JSON.stringify({
53
+ protocol: 1,
54
+ nonce,
55
+ pid: process.pid,
56
+ reason,
57
+ errorCode,
58
+ errorMessage,
59
+ exitCode,
60
+ finishedAtNs: process.hrtime.bigint().toString(),
61
+ signal,
62
+ output: text(combined),
63
+ stdoutTail: text(out),
64
+ stderrTail: text(err),
65
+ outputBytes: bytes,
66
+ outputTruncated: truncated
67
+ }), {
68
+ mode: 0o600
69
+ });
70
+ renameSync(tmp, resultFile);
71
+ }
72
+ function stop(reason: string) {
73
+ if (stopping || finished) {
74
+ return;
75
+ }
76
+ stopping = true;
77
+ try {
78
+ record(reason);
79
+ } catch {
80
+ }
81
+ try {
82
+ process.kill(-process.pid, 'SIGTERM');
83
+ } catch {
84
+ }
85
+ setTimeout(() => {
86
+ try {
87
+ record(reason);
88
+ } catch {
89
+ }
90
+ process.kill(-process.pid, 'SIGKILL');
91
+ }, graceMs);
92
+ }
93
+ process.on('SIGTERM', () => stop('cancelled'));
94
+ process.on('SIGINT', () => stop('cancelled'));
95
+ if (process.ppid !== parentPid) {
96
+ record('parent-exit');
97
+ process.exit(1);
98
+ }
99
+ const deadline = BigInt(deadlineNs);
100
+ if (cancellationFile && existsSync(cancellationFile)) {
101
+ record('cancelled');
102
+ process.exit(1);
103
+ }
104
+ if (process.hrtime.bigint() >= deadline) {
105
+ record('timeout');
106
+ process.exit(1);
107
+ }
108
+ const child = spawn('/bin/sh', ['-lc', command], {
109
+ cwd,
110
+ env: process.env,
111
+ stdio: ['ignore', 'pipe', 'pipe']
112
+ });
113
+ child.stdout.on('data', (b: Buffer) => append(b, false));
114
+ child.stderr.on('data', (b: Buffer) => append(b, true));
115
+ const timer = setTimeout(() => stop('timeout'), Math.max(0, Number((deadline - process.hrtime.bigint()) / 1_000_000n)));
116
+ const monitor = setInterval(() => {
117
+ if (process.ppid !== parentPid) {
118
+ stop('parent-exit');
119
+ }
120
+ else if (cancellationFile && existsSync(cancellationFile)) {
121
+ stop('cancelled');
122
+ }
123
+ }, 25);
124
+ child.on('exit', (code, s) => {
125
+ exitCode = code;
126
+ signal = s;
127
+ });
128
+ child.on('error', (error: NodeJS.ErrnoException) => {
129
+ errorCode = error.code ?? 'CHECK_SPAWN_FAILED';
130
+ errorMessage = error.message.slice(0, 1024);
131
+ if (stopping || finished) {
132
+ return;
133
+ }
134
+ finished = true;
135
+ clearTimeout(timer);
136
+ clearInterval(monitor);
137
+ record('launch-failed');
138
+ });
139
+ child.on('close', (code, s) => {
140
+ exitCode = code;
141
+ signal = s;
142
+ if (stopping || finished) {
143
+ return;
144
+ }
145
+ finished = true;
146
+ clearTimeout(timer);
147
+ clearInterval(monitor);
148
+ record('exit');
149
+ });
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Commands accepted by the packaged CLI. Keeping this registry outside the
3
+ * executable entry point lets read-only navigation metadata bind an
4
+ * advertised action to the real CLI surface without importing `cli.ts` and
5
+ * executing it.
6
+ */
7
+ const CLI_ACTIONS_LITERAL = {
8
+ gateway: ['handshake'],
9
+ change: ['explain'],
10
+ update: [
11
+ 'preflight',
12
+ 'navigation-source-preflight',
13
+ 'navigation-transport-preflight',
14
+ 'navigation-recover',
15
+ 'dependency-provenance-preflight',
16
+ 'dependency-provenance-recover',
17
+ 'historical-step-dependency-preflight',
18
+ 'historical-step-dependency-recover',
19
+ 'downstream-proof-dependency-preflight',
20
+ 'downstream-proof-dependency-recover',
21
+ ],
22
+ delegation: ['prepare', 'grant', 'list', 'show', 'revoke'],
23
+ 'project-memory': ['scan', 'show', 'status', 'approve', 'reconcile'],
24
+ state: ['migrate', 'adoption-prepare', 'adoption-apply'],
25
+ graph: ['prepare', 'status', 'refresh-request', 'bind', 'fallback'],
26
+ discovery: ['start', 'update', 'show', 'materialize'],
27
+ task: [
28
+ 'show',
29
+ 'replacement-materialize',
30
+ 'preexecution-replan',
31
+ 'plan-set',
32
+ 'knowledge-rebind',
33
+ 'context-refresh',
34
+ 'authorize',
35
+ 'plan-risk-audit',
36
+ 'start',
37
+ 'run',
38
+ 'step-complete',
39
+ 'submit',
40
+ 'review-record',
41
+ 'review-launch',
42
+ 'review-packet',
43
+ 'review-sealed-record',
44
+ 'step-review',
45
+ 'step-review-packet',
46
+ 'step-review-record',
47
+ 'corrective-decision-recover',
48
+ 'plan-integrity-recover',
49
+ 'downstream-proof-recover',
50
+ 'corrective-decision',
51
+ 'corrective-yield',
52
+ 'corrective-yield-recover',
53
+ 'corrective-replan-prepare',
54
+ 'corrective-replan-confirm',
55
+ 'corrective-replan-execute',
56
+ 'corrective-replan-recover',
57
+ 'historical-step-provenance-preflight',
58
+ 'historical-step-provenance-recover',
59
+ 'handoff',
60
+ 'handoff-prepare',
61
+ 'handoff-replace',
62
+ 'handoff-show',
63
+ 'claim',
64
+ 'writer-credential-replace',
65
+ 'handback',
66
+ 'handback-create',
67
+ 'result-set',
68
+ 'accept',
69
+ 'sync-base',
70
+ 'merge',
71
+ 'merge-confirm',
72
+ ],
73
+ milestone: [
74
+ 'recover',
75
+ 'show',
76
+ 'progress',
77
+ 'plan-set',
78
+ 'remediation-materialize',
79
+ 'scope-change-prepare',
80
+ 'scope-change-apply',
81
+ 'autonomy-prepare',
82
+ 'autonomy-grant',
83
+ 'autonomy-evolve',
84
+ 'authorize',
85
+ 'validate',
86
+ 'accept',
87
+ 'cancel',
88
+ ],
89
+ locks: ['inspect', 'repair'],
90
+ } as const;
91
+
92
+ export const CLI_ACTIONS: Readonly<Record<string, readonly string[]>> = CLI_ACTIONS_LITERAL;
93
+
94
+ export type CliNoun = keyof typeof CLI_ACTIONS_LITERAL;
95
+ export type CliCommand = {
96
+ [Noun in CliNoun]: `${Noun} ${typeof CLI_ACTIONS_LITERAL[Noun][number]}`;
97
+ }[CliNoun] | 'doctor' | 'status' | 'next';
98
+
99
+ export function isCliCommand(command: string): command is CliCommand {
100
+ if (command === 'doctor' || command === 'status' || command === 'next') return true;
101
+ const separator = command.indexOf(' ');
102
+ if (separator < 1) return false;
103
+ const noun = command.slice(0, separator) as CliNoun;
104
+ const action = command.slice(separator + 1);
105
+ const supported = CLI_ACTIONS[noun] as readonly string[] | undefined;
106
+ return supported?.includes(action) ?? false;
107
+ }