@lsctech/polaris 0.1.0

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 (316) hide show
  1. package/README.md +87 -0
  2. package/dist/__mocks__/tool-server-linear.js +34 -0
  3. package/dist/cli/adopt-approve.js +60 -0
  4. package/dist/cli/adopt-cognition.js +199 -0
  5. package/dist/cli/adopt-instructions.js +143 -0
  6. package/dist/cli/adopt-rules.js +169 -0
  7. package/dist/cli/adopt-scan.js +25 -0
  8. package/dist/cli/adopt-smartdocs.js +192 -0
  9. package/dist/cli/adoption-inventory.js +350 -0
  10. package/dist/cli/adoption-plan.js +222 -0
  11. package/dist/cli/args.js +28 -0
  12. package/dist/cli/args.test.js +30 -0
  13. package/dist/cli/branding.js +41 -0
  14. package/dist/cli/graph.js +358 -0
  15. package/dist/cli/index.js +127 -0
  16. package/dist/cli/index.test.js +162 -0
  17. package/dist/cli/init-detect.js +61 -0
  18. package/dist/cli/init.js +492 -0
  19. package/dist/cli/init.test.js +259 -0
  20. package/dist/cli/librarian.js +32 -0
  21. package/dist/cli/medic.js +96 -0
  22. package/dist/cli/startup.test.js +23 -0
  23. package/dist/cli/tracker.js +130 -0
  24. package/dist/cli/tracker.test.js +126 -0
  25. package/dist/cli/version.js +10 -0
  26. package/dist/cli/version.test.js +11 -0
  27. package/dist/cli/worker.js +287 -0
  28. package/dist/cluster-state/fixtures/test-clusters.json +19 -0
  29. package/dist/cluster-state/store.js +302 -0
  30. package/dist/cluster-state/store.test.js +145 -0
  31. package/dist/cluster-state/types.js +2 -0
  32. package/dist/cognition/archive.js +158 -0
  33. package/dist/cognition/closeout-librarian-types.js +161 -0
  34. package/dist/cognition/cognition.test.js +623 -0
  35. package/dist/cognition/index.js +41 -0
  36. package/dist/cognition/librarian-dispatch.js +391 -0
  37. package/dist/cognition/librarian-dispatch.test.js +247 -0
  38. package/dist/cognition/librarian-packet.js +313 -0
  39. package/dist/cognition/librarian-types.js +57 -0
  40. package/dist/cognition/route-cognition-delta.js +350 -0
  41. package/dist/cognition/summary-delta.js +279 -0
  42. package/dist/cognition/validate.js +162 -0
  43. package/dist/config/defaults.js +98 -0
  44. package/dist/config/doctor.js +273 -0
  45. package/dist/config/index.js +14 -0
  46. package/dist/config/loader.js +58 -0
  47. package/dist/config/provider-detect.js +60 -0
  48. package/dist/config/provider-detect.test.js +119 -0
  49. package/dist/config/schema.js +2 -0
  50. package/dist/config/show.js +44 -0
  51. package/dist/config/show.test.js +42 -0
  52. package/dist/config/validator.js +675 -0
  53. package/dist/config/validator.test.js +77 -0
  54. package/dist/docs/canon-check.js +334 -0
  55. package/dist/docs/canon-check.test.js +207 -0
  56. package/dist/docs/doctrine.js +133 -0
  57. package/dist/docs/doctrine.test.js +139 -0
  58. package/dist/docs/index.js +168 -0
  59. package/dist/docs/index.test.js +83 -0
  60. package/dist/docs/ingest.js +295 -0
  61. package/dist/docs/ingest.test.js +71 -0
  62. package/dist/docs/migrate.js +165 -0
  63. package/dist/docs/migrate.test.js +136 -0
  64. package/dist/docs/seed-instructions.js +147 -0
  65. package/dist/docs/seed-instructions.test.js +137 -0
  66. package/dist/docs/validate-instructions.js +267 -0
  67. package/dist/docs/validate-instructions.test.js +238 -0
  68. package/dist/finalize/artifact-policy.js +214 -0
  69. package/dist/finalize/artifact-policy.test.js +84 -0
  70. package/dist/finalize/delivery-integrity.js +141 -0
  71. package/dist/finalize/finalize.test.js +210 -0
  72. package/dist/finalize/github.js +41 -0
  73. package/dist/finalize/index.js +474 -0
  74. package/dist/finalize/linear.js +213 -0
  75. package/dist/finalize/run-report.js +47 -0
  76. package/dist/finalize/steps/01-map-update.js +11 -0
  77. package/dist/finalize/steps/02-map-validate.js +11 -0
  78. package/dist/finalize/steps/03-schema-validate.js +11 -0
  79. package/dist/finalize/steps/04-run-checks.js +118 -0
  80. package/dist/finalize/steps/05-generate-report.js +14 -0
  81. package/dist/finalize/steps/06-commit.js +39 -0
  82. package/dist/finalize/steps/07-push.js +10 -0
  83. package/dist/finalize/steps/08-create-pr.js +9 -0
  84. package/dist/finalize/steps/09-update-state.js +9 -0
  85. package/dist/finalize/steps/10-append-jsonl.js +23 -0
  86. package/dist/finalize/steps/11-update-linear.js +22 -0
  87. package/dist/finalize/steps/12-archive.js +21 -0
  88. package/dist/graph/adapter/c/adapter.js +45 -0
  89. package/dist/graph/adapter/c/extract.js +135 -0
  90. package/dist/graph/adapter/c/index.js +10 -0
  91. package/dist/graph/adapter/c/runtime.js +67 -0
  92. package/dist/graph/adapter/cpp/adapter.js +45 -0
  93. package/dist/graph/adapter/cpp/extract.js +159 -0
  94. package/dist/graph/adapter/cpp/index.js +10 -0
  95. package/dist/graph/adapter/cpp/runtime.js +61 -0
  96. package/dist/graph/adapter/csharp/adapter.js +45 -0
  97. package/dist/graph/adapter/csharp/extract.js +127 -0
  98. package/dist/graph/adapter/csharp/index.js +10 -0
  99. package/dist/graph/adapter/csharp/runtime.js +61 -0
  100. package/dist/graph/adapter/dart/adapter.js +56 -0
  101. package/dist/graph/adapter/dart/extract.js +130 -0
  102. package/dist/graph/adapter/dart/index.js +10 -0
  103. package/dist/graph/adapter/dart/runtime.js +61 -0
  104. package/dist/graph/adapter/go/adapter.js +45 -0
  105. package/dist/graph/adapter/go/extract.js +112 -0
  106. package/dist/graph/adapter/go/index.js +10 -0
  107. package/dist/graph/adapter/go/runtime.js +58 -0
  108. package/dist/graph/adapter/kotlin-java/extract-common.js +83 -0
  109. package/dist/graph/adapter/kotlin-java/index.js +15 -0
  110. package/dist/graph/adapter/kotlin-java/java-adapter.js +45 -0
  111. package/dist/graph/adapter/kotlin-java/java-extract.js +42 -0
  112. package/dist/graph/adapter/kotlin-java/kotlin-adapter.js +48 -0
  113. package/dist/graph/adapter/kotlin-java/kotlin-extract.js +58 -0
  114. package/dist/graph/adapter/kotlin-java/runtime.js +75 -0
  115. package/dist/graph/adapter/python/adapter.js +45 -0
  116. package/dist/graph/adapter/python/extract.js +121 -0
  117. package/dist/graph/adapter/python/index.js +10 -0
  118. package/dist/graph/adapter/python/runtime.js +70 -0
  119. package/dist/graph/adapter/registry.js +82 -0
  120. package/dist/graph/adapter/rust/adapter.js +45 -0
  121. package/dist/graph/adapter/rust/extract.js +110 -0
  122. package/dist/graph/adapter/rust/index.js +10 -0
  123. package/dist/graph/adapter/rust/runtime.js +74 -0
  124. package/dist/graph/adapter/shell/adapter.js +34 -0
  125. package/dist/graph/adapter/shell/extract.js +98 -0
  126. package/dist/graph/adapter/shell/index.js +8 -0
  127. package/dist/graph/adapter/svelte/adapter.js +91 -0
  128. package/dist/graph/adapter/svelte/extract.js +41 -0
  129. package/dist/graph/adapter/svelte/index.js +8 -0
  130. package/dist/graph/adapter/swift/adapter.js +45 -0
  131. package/dist/graph/adapter/swift/extract.js +120 -0
  132. package/dist/graph/adapter/swift/index.js +10 -0
  133. package/dist/graph/adapter/swift/runtime.js +70 -0
  134. package/dist/graph/adapter/types.js +2 -0
  135. package/dist/graph/adapter/typescript-javascript/adapter.js +72 -0
  136. package/dist/graph/adapter/typescript-javascript/extract.js +115 -0
  137. package/dist/graph/adapter/typescript-javascript/index.js +10 -0
  138. package/dist/graph/adapter/typescript-javascript/runtime.js +97 -0
  139. package/dist/graph/capability/index.js +105 -0
  140. package/dist/graph/governance.js +104 -0
  141. package/dist/graph/parser/extract.js +5 -0
  142. package/dist/graph/parser/loader.js +2 -0
  143. package/dist/graph/parser/pipeline.js +194 -0
  144. package/dist/graph/query/index.js +277 -0
  145. package/dist/graph/query/types.js +5 -0
  146. package/dist/graph/resolver/build-edges.js +211 -0
  147. package/dist/graph/resolver/index.js +23 -0
  148. package/dist/graph/resolver/resolve-imports.js +168 -0
  149. package/dist/graph/store/adapter.js +132 -0
  150. package/dist/graph/store/queries.js +124 -0
  151. package/dist/graph/store/types.js +4 -0
  152. package/dist/ignore/defaults.js +41 -0
  153. package/dist/ignore/parser.js +39 -0
  154. package/dist/lint/map-reference-validator.js +72 -0
  155. package/dist/loop/abort.js +188 -0
  156. package/dist/loop/abort.test.js +151 -0
  157. package/dist/loop/adapters/__tests__/terminal-cli.test.js +92 -0
  158. package/dist/loop/adapters/agent-subtask.js +137 -0
  159. package/dist/loop/adapters/agent-subtask.test.js +151 -0
  160. package/dist/loop/adapters/cli-subtask-bridge.js +108 -0
  161. package/dist/loop/adapters/index.js +9 -0
  162. package/dist/loop/adapters/registry.js +17 -0
  163. package/dist/loop/adapters/terminal-cli.js +403 -0
  164. package/dist/loop/adapters/types.js +2 -0
  165. package/dist/loop/adapters/worker-instructions.js +85 -0
  166. package/dist/loop/body-parser.js +226 -0
  167. package/dist/loop/bootstrap-packet.js +97 -0
  168. package/dist/loop/bootstrap-packet.test.js +171 -0
  169. package/dist/loop/branch-governance.js +54 -0
  170. package/dist/loop/budget.js +63 -0
  171. package/dist/loop/budget.test.js +163 -0
  172. package/dist/loop/checkpoint.js +325 -0
  173. package/dist/loop/compact-return.js +59 -0
  174. package/dist/loop/continue.js +645 -0
  175. package/dist/loop/continue.test.js +337 -0
  176. package/dist/loop/dispatch-boundary.js +335 -0
  177. package/dist/loop/dispatch-state.js +223 -0
  178. package/dist/loop/dispatch-state.test.js +186 -0
  179. package/dist/loop/dispatch.js +1236 -0
  180. package/dist/loop/dispatch.test.js +499 -0
  181. package/dist/loop/ephemeral-smoke.test.js +160 -0
  182. package/dist/loop/evidence-backfill.js +216 -0
  183. package/dist/loop/execution-adapter.js +141 -0
  184. package/dist/loop/execution-adapter.test.js +66 -0
  185. package/dist/loop/finalize-evidence.js +169 -0
  186. package/dist/loop/git-custody.js +250 -0
  187. package/dist/loop/impl-packet-input.js +38 -0
  188. package/dist/loop/index.js +270 -0
  189. package/dist/loop/ledger.js +49 -0
  190. package/dist/loop/ledger.test.js +127 -0
  191. package/dist/loop/lifecycle-dispatch.js +348 -0
  192. package/dist/loop/lifecycle-dispatch.test.js +318 -0
  193. package/dist/loop/lifecycle.js +93 -0
  194. package/dist/loop/lifecycle.test.js +150 -0
  195. package/dist/loop/orphan-recovery.js +276 -0
  196. package/dist/loop/orphan-recovery.test.js +236 -0
  197. package/dist/loop/parent.js +1566 -0
  198. package/dist/loop/parent.test.js +738 -0
  199. package/dist/loop/provider-sealed-packet.test.js +164 -0
  200. package/dist/loop/resume.js +295 -0
  201. package/dist/loop/resume.test.js +172 -0
  202. package/dist/loop/run-bootstrap.js +234 -0
  203. package/dist/loop/run-preflight.js +203 -0
  204. package/dist/loop/status.js +665 -0
  205. package/dist/loop/status.test.js +235 -0
  206. package/dist/loop/wait.js +86 -0
  207. package/dist/loop/worker-packet.js +406 -0
  208. package/dist/loop/worker-packet.test.js +243 -0
  209. package/dist/loop/worker-prompt.js +260 -0
  210. package/dist/loop/worker-prompt.test.js +247 -0
  211. package/dist/loop/worker.js +498 -0
  212. package/dist/loop/worker.test.js +372 -0
  213. package/dist/map/atlas.js +77 -0
  214. package/dist/map/backfill.js +171 -0
  215. package/dist/map/backfill.test.js +120 -0
  216. package/dist/map/index.js +255 -0
  217. package/dist/map/inference.js +73 -0
  218. package/dist/map/inference.test.js +35 -0
  219. package/dist/map/query.js +157 -0
  220. package/dist/map/query.test.js +174 -0
  221. package/dist/map/update.js +355 -0
  222. package/dist/map/update.test.js +67 -0
  223. package/dist/map/validate.js +153 -0
  224. package/dist/map/validate.test.js +90 -0
  225. package/dist/map/welfare.js +86 -0
  226. package/dist/mcp/lib/invoke.js +35 -0
  227. package/dist/mcp/lib/redact.js +24 -0
  228. package/dist/mcp/lib/redact.test.js +112 -0
  229. package/dist/mcp/lib/root.js +41 -0
  230. package/dist/mcp/lib/root.test.js +114 -0
  231. package/dist/mcp/server.js +15 -0
  232. package/dist/mcp/tools/claim-child.js +119 -0
  233. package/dist/mcp/tools/claim-child.test.js +69 -0
  234. package/dist/mcp/tools/current-state.js +73 -0
  235. package/dist/mcp/tools/current-state.test.js +114 -0
  236. package/dist/mcp/tools/dispatch-result.js +186 -0
  237. package/dist/mcp/tools/dispatch-result.test.js +109 -0
  238. package/dist/mcp/tools/index.js +222 -0
  239. package/dist/mcp/tools/index.test.js +172 -0
  240. package/dist/mcp/tools/linear.js +74 -0
  241. package/dist/mcp/tools/loop-continue.js +159 -0
  242. package/dist/mcp/tools/loop-continue.test.js +186 -0
  243. package/dist/mcp/tools/loop-dry-run.js +15 -0
  244. package/dist/mcp/tools/status.js +65 -0
  245. package/dist/medic/chart-id.js +75 -0
  246. package/dist/medic/chart-schema.js +139 -0
  247. package/dist/runs/index.js +119 -0
  248. package/dist/runs/index.test.js +184 -0
  249. package/dist/runs/ledger-tail.js +15 -0
  250. package/dist/runs/list.js +29 -0
  251. package/dist/runs/reconcile.js +74 -0
  252. package/dist/runs/show.js +10 -0
  253. package/dist/runtime/audit/logger.js +15 -0
  254. package/dist/runtime/audit/reader.js +42 -0
  255. package/dist/runtime/checkpoint.js +89 -0
  256. package/dist/runtime/continuation/confirmed.js +225 -0
  257. package/dist/runtime/continuation/confirmed.test.js +421 -0
  258. package/dist/runtime/continuation/dry-run.js +74 -0
  259. package/dist/runtime/execution-window.js +106 -0
  260. package/dist/runtime/scheduling/child-selector.js +16 -0
  261. package/dist/runtime/state.js +43 -0
  262. package/dist/runtime/status.js +46 -0
  263. package/dist/runtime/verification/envelope.js +80 -0
  264. package/dist/runtime/verification/fingerprint.js +26 -0
  265. package/dist/skill-packet/cli.test.js +105 -0
  266. package/dist/skill-packet/generator.js +225 -0
  267. package/dist/skill-packet/generator.test.js +159 -0
  268. package/dist/skill-packet/index.js +49 -0
  269. package/dist/skill-packet/types.js +2 -0
  270. package/dist/smartdocs-engine/audit.js +194 -0
  271. package/dist/smartdocs-engine/audit.test.js +122 -0
  272. package/dist/smartdocs-engine/canon-check.js +340 -0
  273. package/dist/smartdocs-engine/canon-check.test.js +234 -0
  274. package/dist/smartdocs-engine/doctrine.js +633 -0
  275. package/dist/smartdocs-engine/doctrine.test.js +374 -0
  276. package/dist/smartdocs-engine/index.js +456 -0
  277. package/dist/smartdocs-engine/index.test.js +82 -0
  278. package/dist/smartdocs-engine/ingest.js +495 -0
  279. package/dist/smartdocs-engine/ingest.test.js +163 -0
  280. package/dist/smartdocs-engine/migrate.js +217 -0
  281. package/dist/smartdocs-engine/migrate.test.js +200 -0
  282. package/dist/smartdocs-engine/seed-instructions.js +335 -0
  283. package/dist/smartdocs-engine/seed-instructions.test.js +275 -0
  284. package/dist/smartdocs-engine/smartdoc-ignore.js +258 -0
  285. package/dist/smartdocs-engine/smartdoc-ignore.test.js +146 -0
  286. package/dist/smartdocs-engine/validate-instructions.js +366 -0
  287. package/dist/smartdocs-engine/validate-instructions.test.js +283 -0
  288. package/dist/tracker/adapters/github/index.js +305 -0
  289. package/dist/tracker/adapters/jira/index.js +293 -0
  290. package/dist/tracker/adapters/linear/http-errors.test.js +51 -0
  291. package/dist/tracker/adapters/linear/index.js +621 -0
  292. package/dist/tracker/adapters/linear/index.test.js +238 -0
  293. package/dist/tracker/adapters/linear.js +50 -0
  294. package/dist/tracker/adapters/local/index.js +10 -0
  295. package/dist/tracker/adapters/local-file/index.js +159 -0
  296. package/dist/tracker/adapters/mcp-bridge.js +158 -0
  297. package/dist/tracker/adapters/mcp-bridge.test.js +12 -0
  298. package/dist/tracker/adapters/spec/index.js +146 -0
  299. package/dist/tracker/capabilities.js +2 -0
  300. package/dist/tracker/index.js +49 -0
  301. package/dist/tracker/lifecycle-policy.js +139 -0
  302. package/dist/tracker/lifecycle-transition.js +164 -0
  303. package/dist/tracker/local-graph.js +98 -0
  304. package/dist/tracker/migration.js +56 -0
  305. package/dist/tracker/schema.js +86 -0
  306. package/dist/tracker/sync/index.js +404 -0
  307. package/dist/tracker/sync/queue-store.js +95 -0
  308. package/dist/tracker/sync/queue-store.test.js +104 -0
  309. package/dist/tracker/types.js +11 -0
  310. package/dist/types/linear.js +3 -0
  311. package/dist/types/result-packet.js +12 -0
  312. package/dist/types/runtime-state.js +2 -0
  313. package/dist/types/work-contract.js +2 -0
  314. package/dist/utils/monotonic-timestamp.js +13 -0
  315. package/dist/utils/monotonic-timestamp.test.js +19 -0
  316. package/package.json +64 -0
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ /**
3
+ * Unit tests for budget policy evaluation (src/loop/budget.ts).
4
+ *
5
+ * Covers:
6
+ * - fixed-cap mode: stop after max_children, continue before cap
7
+ * - run-until-done mode: always continue regardless of count
8
+ * - stop-on-fail mode: halt when lastChildStatus is "failed"
9
+ * - stop_on_fail flag: halts across all modes when a child fails
10
+ * - No-config default: 3-child fixed-cap (backwards compatibility)
11
+ * - policyFromConfig: precedence of config over state
12
+ * - policyFromState: backwards-compat shim
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const vitest_1 = require("vitest");
16
+ const budget_js_1 = require("./budget.js");
17
+ // ── Helpers ──────────────────────────────────────────────────────────────────
18
+ function makePolicy(overrides = {}) {
19
+ return {
20
+ mode: "fixed-cap",
21
+ maxChildrenPerSession: 3,
22
+ stopOnFail: false,
23
+ ...overrides,
24
+ };
25
+ }
26
+ // ── fixed-cap mode ────────────────────────────────────────────────────────────
27
+ (0, vitest_1.describe)("checkBudget / fixed-cap", () => {
28
+ (0, vitest_1.it)("returns ok when children completed is below cap", () => {
29
+ const result = (0, budget_js_1.checkBudget)({
30
+ childrenCompleted: 2,
31
+ policy: makePolicy({ mode: "fixed-cap", maxChildrenPerSession: 3 }),
32
+ });
33
+ (0, vitest_1.expect)(result.status).toBe("ok");
34
+ });
35
+ (0, vitest_1.it)("returns exhausted when children completed equals cap", () => {
36
+ const result = (0, budget_js_1.checkBudget)({
37
+ childrenCompleted: 3,
38
+ policy: makePolicy({ mode: "fixed-cap", maxChildrenPerSession: 3 }),
39
+ });
40
+ (0, vitest_1.expect)(result.status).toBe("exhausted");
41
+ if (result.status === "exhausted") {
42
+ (0, vitest_1.expect)(result.reason).toMatch(/3 of 3/);
43
+ }
44
+ });
45
+ (0, vitest_1.it)("returns exhausted when children completed exceeds cap", () => {
46
+ const result = (0, budget_js_1.checkBudget)({
47
+ childrenCompleted: 5,
48
+ policy: makePolicy({ mode: "fixed-cap", maxChildrenPerSession: 3 }),
49
+ });
50
+ (0, vitest_1.expect)(result.status).toBe("exhausted");
51
+ });
52
+ (0, vitest_1.it)("respects a custom cap of 5", () => {
53
+ const policy = makePolicy({ mode: "fixed-cap", maxChildrenPerSession: 5 });
54
+ // 4 done → ok
55
+ (0, vitest_1.expect)((0, budget_js_1.checkBudget)({ childrenCompleted: 4, policy }).status).toBe("ok");
56
+ // 5 done → exhausted
57
+ (0, vitest_1.expect)((0, budget_js_1.checkBudget)({ childrenCompleted: 5, policy }).status).toBe("exhausted");
58
+ });
59
+ (0, vitest_1.it)("returns ok at 0 children with cap 3 (no-config default behavior)", () => {
60
+ const result = (0, budget_js_1.checkBudget)({
61
+ childrenCompleted: 0,
62
+ policy: makePolicy({ mode: "fixed-cap", maxChildrenPerSession: 3 }),
63
+ });
64
+ (0, vitest_1.expect)(result.status).toBe("ok");
65
+ });
66
+ });
67
+ // ── run-until-done mode ───────────────────────────────────────────────────────
68
+ (0, vitest_1.describe)("checkBudget / run-until-done", () => {
69
+ (0, vitest_1.it)("always returns ok regardless of children completed", () => {
70
+ const policy = makePolicy({ mode: "run-until-done", maxChildrenPerSession: 3 });
71
+ for (const n of [0, 3, 10, 100]) {
72
+ (0, vitest_1.expect)((0, budget_js_1.checkBudget)({ childrenCompleted: n, policy }).status).toBe("ok");
73
+ }
74
+ });
75
+ (0, vitest_1.it)("runs all open children without stopping at 3", () => {
76
+ const policy = makePolicy({ mode: "run-until-done", maxChildrenPerSession: 3 });
77
+ // Even at 50 completed, budget is not exhausted
78
+ (0, vitest_1.expect)((0, budget_js_1.checkBudget)({ childrenCompleted: 50, policy }).status).toBe("ok");
79
+ });
80
+ });
81
+ // ── stop-on-fail mode ─────────────────────────────────────────────────────────
82
+ (0, vitest_1.describe)("checkBudget / stop-on-fail mode", () => {
83
+ (0, vitest_1.it)("returns ok when no child has failed", () => {
84
+ const policy = makePolicy({ mode: "stop-on-fail", stopOnFail: true, maxChildrenPerSession: 100 });
85
+ (0, vitest_1.expect)((0, budget_js_1.checkBudget)({ childrenCompleted: 5, policy }).status).toBe("ok");
86
+ });
87
+ (0, vitest_1.it)("does not halt on successful child status", () => {
88
+ const policy = makePolicy({ mode: "stop-on-fail", stopOnFail: true, maxChildrenPerSession: 100 });
89
+ (0, vitest_1.expect)((0, budget_js_1.checkBudget)({ childrenCompleted: 1, lastChildStatus: "done", policy }).status).toBe("ok");
90
+ });
91
+ });
92
+ // ── stopOnFail flag (cross-mode) ──────────────────────────────────────────────
93
+ (0, vitest_1.describe)("checkBudget / stopOnFail flag", () => {
94
+ (0, vitest_1.it)("halts when lastChildStatus is 'failed' and stopOnFail is true", () => {
95
+ const policy = makePolicy({ mode: "fixed-cap", maxChildrenPerSession: 10, stopOnFail: true });
96
+ const result = (0, budget_js_1.checkBudget)({ childrenCompleted: 1, lastChildStatus: "failed", policy });
97
+ (0, vitest_1.expect)(result.status).toBe("exhausted");
98
+ if (result.status === "exhausted") {
99
+ (0, vitest_1.expect)(result.reason).toMatch(/stop_on_fail/);
100
+ }
101
+ });
102
+ (0, vitest_1.it)("does not halt when lastChildStatus is 'failed' but stopOnFail is false", () => {
103
+ const policy = makePolicy({ mode: "fixed-cap", maxChildrenPerSession: 10, stopOnFail: false });
104
+ const result = (0, budget_js_1.checkBudget)({ childrenCompleted: 1, lastChildStatus: "failed", policy });
105
+ (0, vitest_1.expect)(result.status).toBe("ok");
106
+ });
107
+ (0, vitest_1.it)("halts on failed child in run-until-done mode when stopOnFail is true", () => {
108
+ const policy = makePolicy({ mode: "run-until-done", maxChildrenPerSession: 100, stopOnFail: true });
109
+ const result = (0, budget_js_1.checkBudget)({ childrenCompleted: 2, lastChildStatus: "failed", policy });
110
+ (0, vitest_1.expect)(result.status).toBe("exhausted");
111
+ });
112
+ });
113
+ // ── policyFromConfig ──────────────────────────────────────────────────────────
114
+ (0, vitest_1.describe)("policyFromConfig", () => {
115
+ (0, vitest_1.it)("uses default 3-child fixed-cap when no config provided", () => {
116
+ const policy = (0, budget_js_1.policyFromConfig)({});
117
+ (0, vitest_1.expect)(policy.mode).toBe("fixed-cap");
118
+ (0, vitest_1.expect)(policy.maxChildrenPerSession).toBe(3);
119
+ (0, vitest_1.expect)(policy.stopOnFail).toBe(false);
120
+ });
121
+ (0, vitest_1.it)("falls back to state max_children_per_session when no config budget", () => {
122
+ const policy = (0, budget_js_1.policyFromConfig)({ max_children_per_session: 7 });
123
+ (0, vitest_1.expect)(policy.maxChildrenPerSession).toBe(7);
124
+ });
125
+ (0, vitest_1.it)("config max_children overrides state max_children_per_session", () => {
126
+ const policy = (0, budget_js_1.policyFromConfig)({ max_children_per_session: 2 }, { mode: "fixed-cap", max_children: 5 });
127
+ (0, vitest_1.expect)(policy.maxChildrenPerSession).toBe(5);
128
+ });
129
+ (0, vitest_1.it)("reads run-until-done mode from config", () => {
130
+ const policy = (0, budget_js_1.policyFromConfig)({}, { mode: "run-until-done" });
131
+ (0, vitest_1.expect)(policy.mode).toBe("run-until-done");
132
+ });
133
+ (0, vitest_1.it)("reads stop_on_fail from config", () => {
134
+ const policy = (0, budget_js_1.policyFromConfig)({}, { stop_on_fail: true });
135
+ (0, vitest_1.expect)(policy.stopOnFail).toBe(true);
136
+ });
137
+ });
138
+ // ── policyFromState (backwards compat) ────────────────────────────────────────
139
+ (0, vitest_1.describe)("policyFromState (backwards compat)", () => {
140
+ (0, vitest_1.it)("returns 3-child fixed-cap when state has no max_children_per_session", () => {
141
+ const policy = (0, budget_js_1.policyFromState)({});
142
+ (0, vitest_1.expect)(policy.mode).toBe("fixed-cap");
143
+ (0, vitest_1.expect)(policy.maxChildrenPerSession).toBe(3);
144
+ });
145
+ (0, vitest_1.it)("respects max_children_per_session from state", () => {
146
+ const policy = (0, budget_js_1.policyFromState)({ max_children_per_session: 5 });
147
+ (0, vitest_1.expect)(policy.maxChildrenPerSession).toBe(5);
148
+ });
149
+ });
150
+ // ── no-config default backwards compatibility ─────────────────────────────────
151
+ (0, vitest_1.describe)("no-config default (backwards compatibility)", () => {
152
+ (0, vitest_1.it)("defaults to 3-child fixed-cap with policyFromConfig and empty state", () => {
153
+ const policy = (0, budget_js_1.policyFromConfig)({}, undefined);
154
+ (0, vitest_1.expect)(policy.mode).toBe("fixed-cap");
155
+ (0, vitest_1.expect)(policy.maxChildrenPerSession).toBe(3);
156
+ (0, vitest_1.expect)(policy.stopOnFail).toBe(false);
157
+ });
158
+ (0, vitest_1.it)("exhausts after 3 children with default policy", () => {
159
+ const policy = (0, budget_js_1.policyFromConfig)({}, undefined);
160
+ (0, vitest_1.expect)((0, budget_js_1.checkBudget)({ childrenCompleted: 2, policy }).status).toBe("ok");
161
+ (0, vitest_1.expect)((0, budget_js_1.checkBudget)({ childrenCompleted: 3, policy }).status).toBe("exhausted");
162
+ });
163
+ });
@@ -0,0 +1,325 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readState = readState;
4
+ exports.validateState = validateState;
5
+ exports.writeStateAtomic = writeStateAtomic;
6
+ exports.appendCheckpointEvent = appendCheckpointEvent;
7
+ exports.appendCompactReturnReceivedEvent = appendCompactReturnReceivedEvent;
8
+ exports.appendWorkerScopeFidelityEvent = appendWorkerScopeFidelityEvent;
9
+ exports.appendBoundaryEvent = appendBoundaryEvent;
10
+ exports.appendAbortEvent = appendAbortEvent;
11
+ exports.appendStaleDispatchAbortedEvent = appendStaleDispatchAbortedEvent;
12
+ exports.readBodyFromClusterSnapshot = readBodyFromClusterSnapshot;
13
+ const node_fs_1 = require("node:fs");
14
+ const node_crypto_1 = require("node:crypto");
15
+ const node_path_1 = require("node:path");
16
+ const monotonic_timestamp_js_1 = require("../utils/monotonic-timestamp.js");
17
+ function readState(stateFile) {
18
+ const raw = (0, node_fs_1.readFileSync)(stateFile, "utf-8");
19
+ return JSON.parse(raw);
20
+ }
21
+ function validateState(state) {
22
+ const errors = [];
23
+ if (typeof state !== "object" || state === null) {
24
+ return ["current-state.json must be a JSON object"];
25
+ }
26
+ const s = state;
27
+ if (typeof s["schema_version"] !== "string")
28
+ errors.push("missing schema_version");
29
+ if (typeof s["run_id"] !== "string" || !s["run_id"])
30
+ errors.push("missing run_id");
31
+ if (typeof s["cluster_id"] !== "string" || !s["cluster_id"])
32
+ errors.push("missing cluster_id");
33
+ if (typeof s["active_child"] !== "string")
34
+ errors.push("missing active_child");
35
+ if (!Array.isArray(s["completed_children"]))
36
+ errors.push("completed_children must be an array");
37
+ if (!Array.isArray(s["open_children"]))
38
+ errors.push("open_children must be an array");
39
+ if (s["step_cursor"] !== null && typeof s["step_cursor"] !== "string")
40
+ errors.push("missing step_cursor");
41
+ if (typeof s["context_budget"] !== "object" || s["context_budget"] === null) {
42
+ errors.push("missing context_budget");
43
+ }
44
+ else {
45
+ const budget = s["context_budget"];
46
+ if (typeof budget["children_completed"] !== "number" || !isFinite(budget["children_completed"]))
47
+ errors.push("missing or invalid context_budget.children_completed");
48
+ }
49
+ if (typeof s["status"] !== "string")
50
+ errors.push("missing status");
51
+ // Validate completed_children_results if present
52
+ if ("completed_children_results" in s && s["completed_children_results"] !== undefined) {
53
+ if (typeof s["completed_children_results"] !== "object" || s["completed_children_results"] === null || Array.isArray(s["completed_children_results"])) {
54
+ errors.push("completed_children_results must be an object");
55
+ }
56
+ else {
57
+ const results = s["completed_children_results"];
58
+ for (const [childId, value] of Object.entries(results)) {
59
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
60
+ errors.push(`completed_children_results["${childId}"] must be an object`);
61
+ continue;
62
+ }
63
+ const childResult = value;
64
+ if (typeof childResult["status"] !== "string" || !["done", "failed", "blocked", "error"].includes(childResult["status"]))
65
+ errors.push(`completed_children_results["${childId}"].status must be "done", "failed", "blocked", or "error"`);
66
+ if (typeof childResult["validation"] !== "string" || !["passed", "failed", "skipped"].includes(childResult["validation"]))
67
+ errors.push(`completed_children_results["${childId}"].validation must be "passed", "failed", or "skipped"`);
68
+ if (childResult["commit"] !== null && typeof childResult["commit"] !== "string")
69
+ errors.push(`completed_children_results["${childId}"].commit must be a string or null`);
70
+ if (typeof childResult["next_recommended_action"] !== "string" || !["continue", "stop", "investigate"].includes(childResult["next_recommended_action"]))
71
+ errors.push(`completed_children_results["${childId}"].next_recommended_action must be "continue", "stop", or "investigate"`);
72
+ if ("result_data" in childResult && childResult["result_data"] !== undefined) {
73
+ if (typeof childResult["result_data"] !== "object" || childResult["result_data"] === null || Array.isArray(childResult["result_data"])) {
74
+ errors.push(`completed_children_results["${childId}"].result_data must be an object`);
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ // Validate open_children_meta if present
81
+ if ("open_children_meta" in s && s["open_children_meta"] !== undefined) {
82
+ if (typeof s["open_children_meta"] !== "object" || s["open_children_meta"] === null || Array.isArray(s["open_children_meta"])) {
83
+ errors.push("open_children_meta must be an object");
84
+ }
85
+ else {
86
+ const meta = s["open_children_meta"];
87
+ for (const [childId, value] of Object.entries(meta)) {
88
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
89
+ errors.push(`open_children_meta["${childId}"] must be an object`);
90
+ continue;
91
+ }
92
+ const childMeta = value;
93
+ if ("type" in childMeta && childMeta["type"] !== undefined && typeof childMeta["type"] !== "string") {
94
+ errors.push(`open_children_meta["${childId}"].type must be a string`);
95
+ }
96
+ if ("title" in childMeta && childMeta["title"] !== undefined && typeof childMeta["title"] !== "string") {
97
+ errors.push(`open_children_meta["${childId}"].title must be a string`);
98
+ }
99
+ if ("body" in childMeta && childMeta["body"] !== undefined && typeof childMeta["body"] !== "string") {
100
+ errors.push(`open_children_meta["${childId}"].body must be a string`);
101
+ }
102
+ if ("labels" in childMeta && childMeta["labels"] !== undefined) {
103
+ if (!Array.isArray(childMeta["labels"]) || !childMeta["labels"].every((l) => typeof l === "string")) {
104
+ errors.push(`open_children_meta["${childId}"].labels must be an array of strings`);
105
+ }
106
+ }
107
+ // Validate dispatch_record if present
108
+ if ("dispatch_record" in childMeta && childMeta["dispatch_record"] !== undefined) {
109
+ const dr = childMeta["dispatch_record"];
110
+ if (typeof dr !== "object" || dr === null || Array.isArray(dr)) {
111
+ errors.push(`open_children_meta["${childId}"].dispatch_record must be an object`);
112
+ }
113
+ else {
114
+ if ("dispatch_mode" in dr && dr["dispatch_mode"] !== undefined) {
115
+ if (!["delegated", "direct-worker"].includes(dr["dispatch_mode"])) {
116
+ errors.push(`open_children_meta["${childId}"].dispatch_record.dispatch_mode must be "delegated" or "direct-worker"`);
117
+ }
118
+ }
119
+ if ("runtime_state" in dr && dr["runtime_state"] !== undefined) {
120
+ const validStates = ["packet-created", "delegated", "launching", "acknowledged", "running", "waiting-for-approval", "blocked", "completed", "failed", "orphaned"];
121
+ if (!validStates.includes(dr["runtime_state"])) {
122
+ errors.push(`open_children_meta["${childId}"].dispatch_record.runtime_state must be a valid WorkerRuntimeState`);
123
+ }
124
+ }
125
+ if ("last_heartbeat_at" in dr && dr["last_heartbeat_at"] !== undefined && typeof dr["last_heartbeat_at"] !== "string") {
126
+ errors.push(`open_children_meta["${childId}"].dispatch_record.last_heartbeat_at must be a string`);
127
+ }
128
+ if ("last_heartbeat_step" in dr && dr["last_heartbeat_step"] !== undefined && typeof dr["last_heartbeat_step"] !== "string") {
129
+ errors.push(`open_children_meta["${childId}"].dispatch_record.last_heartbeat_step must be a string`);
130
+ }
131
+ // Validate worker_assignment if present
132
+ if ("worker_assignment" in dr && dr["worker_assignment"] !== undefined) {
133
+ const wa = dr["worker_assignment"];
134
+ if (typeof wa !== "object" || wa === null || Array.isArray(wa)) {
135
+ errors.push(`open_children_meta["${childId}"].dispatch_record.worker_assignment must be an object`);
136
+ }
137
+ else {
138
+ if (typeof wa["assigned_at"] !== "string") {
139
+ errors.push(`open_children_meta["${childId}"].dispatch_record.worker_assignment.assigned_at must be a string`);
140
+ }
141
+ const validAssignmentTypes = ["subagent", "external-process", "human-handoff", "pending-escalation"];
142
+ if (!validAssignmentTypes.includes(wa["assignment_type"])) {
143
+ errors.push(`open_children_meta["${childId}"].dispatch_record.worker_assignment.assignment_type must be a valid assignment type`);
144
+ }
145
+ // Conditional field validation based on assignment_type
146
+ const assignmentType = wa["assignment_type"];
147
+ if (assignmentType === "subagent") {
148
+ if (typeof wa["subagent_session_id"] !== "string" || wa["subagent_session_id"].length === 0) {
149
+ errors.push(`open_children_meta["${childId}"].dispatch_record.worker_assignment.subagent_session_id must be a non-empty string for subagent assignments`);
150
+ }
151
+ }
152
+ else if (assignmentType === "external-process") {
153
+ if (typeof wa["process_pid"] !== "number" || wa["process_pid"] <= 0) {
154
+ errors.push(`open_children_meta["${childId}"].dispatch_record.worker_assignment.process_pid must be a number greater than 0 for external-process assignments`);
155
+ }
156
+ }
157
+ else if (assignmentType === "human-handoff") {
158
+ if (typeof wa["handoff_token"] !== "string" || wa["handoff_token"].length === 0) {
159
+ errors.push(`open_children_meta["${childId}"].dispatch_record.worker_assignment.handoff_token must be a non-empty string for human-handoff assignments`);
160
+ }
161
+ }
162
+ else if (assignmentType === "pending-escalation") {
163
+ if (typeof wa["escalation_reason"] !== "string" || wa["escalation_reason"].length === 0) {
164
+ errors.push(`open_children_meta["${childId}"].dispatch_record.worker_assignment.escalation_reason must be a non-empty string for pending-escalation assignments`);
165
+ }
166
+ }
167
+ }
168
+ }
169
+ // Validate new ChildDispatchRecord fields
170
+ if ("worker_id" in dr && dr["worker_id"] !== undefined) {
171
+ if (typeof dr["worker_id"] !== "string" || dr["worker_id"].length === 0) {
172
+ errors.push(`open_children_meta["${childId}"].dispatch_record.worker_id must be a non-empty string if present`);
173
+ }
174
+ }
175
+ if ("session_id" in dr && dr["session_id"] !== undefined) {
176
+ if (dr["session_id"] !== null && typeof dr["session_id"] !== "string") {
177
+ errors.push(`open_children_meta["${childId}"].dispatch_record.session_id must be a string or null`);
178
+ }
179
+ }
180
+ if ("attachment_capable" in dr && dr["attachment_capable"] !== undefined) {
181
+ if (typeof dr["attachment_capable"] !== "boolean") {
182
+ errors.push(`open_children_meta["${childId}"].dispatch_record.attachment_capable must be a boolean`);
183
+ }
184
+ }
185
+ if ("heartbeat_count" in dr && dr["heartbeat_count"] !== undefined) {
186
+ if (typeof dr["heartbeat_count"] !== "number" || !Number.isInteger(dr["heartbeat_count"]) || dr["heartbeat_count"] < 0) {
187
+ errors.push(`open_children_meta["${childId}"].dispatch_record.heartbeat_count must be a non-negative integer`);
188
+ }
189
+ }
190
+ if ("first_heartbeat_at" in dr && dr["first_heartbeat_at"] !== undefined) {
191
+ if (typeof dr["first_heartbeat_at"] !== "string") {
192
+ errors.push(`open_children_meta["${childId}"].dispatch_record.first_heartbeat_at must be a string (ISO timestamp) if present`);
193
+ }
194
+ }
195
+ // Validate role context fields
196
+ if ("role" in dr && dr["role"] !== undefined) {
197
+ if (typeof dr["role"] !== "string") {
198
+ errors.push(`open_children_meta["${childId}"].dispatch_record.role must be a string if present`);
199
+ }
200
+ }
201
+ if ("role_authority" in dr && dr["role_authority"] !== undefined) {
202
+ if (typeof dr["role_authority"] !== "string") {
203
+ errors.push(`open_children_meta["${childId}"].dispatch_record.role_authority must be a string if present`);
204
+ }
205
+ }
206
+ if ("may_implement" in dr && dr["may_implement"] !== undefined) {
207
+ if (typeof dr["may_implement"] !== "boolean") {
208
+ errors.push(`open_children_meta["${childId}"].dispatch_record.may_implement must be a boolean if present`);
209
+ }
210
+ }
211
+ if ("session_type" in dr && dr["session_type"] !== undefined) {
212
+ if (typeof dr["session_type"] !== "string") {
213
+ errors.push(`open_children_meta["${childId}"].dispatch_record.session_type must be a string if present`);
214
+ }
215
+ }
216
+ }
217
+ }
218
+ }
219
+ }
220
+ }
221
+ // Validate run_bootstrap_seal if present
222
+ if ("run_bootstrap_seal" in s && s["run_bootstrap_seal"] !== undefined) {
223
+ if (typeof s["run_bootstrap_seal"] !== "object" || s["run_bootstrap_seal"] === null || Array.isArray(s["run_bootstrap_seal"])) {
224
+ errors.push("run_bootstrap_seal must be an object");
225
+ }
226
+ else {
227
+ const seal = s["run_bootstrap_seal"];
228
+ if (seal["sealer"] !== "polaris-loop-bootstrap") {
229
+ errors.push(`run_bootstrap_seal.sealer must be "polaris-loop-bootstrap"`);
230
+ }
231
+ if (typeof seal["run_id"] !== "string" || !seal["run_id"]) {
232
+ errors.push("run_bootstrap_seal.run_id must be a non-empty string");
233
+ }
234
+ if (typeof seal["cluster_id"] !== "string" || !seal["cluster_id"]) {
235
+ errors.push("run_bootstrap_seal.cluster_id must be a non-empty string");
236
+ }
237
+ if (typeof seal["open_children_sha"] !== "string") {
238
+ errors.push("run_bootstrap_seal.open_children_sha must be a string");
239
+ }
240
+ if (typeof seal["sealed_at"] !== "string") {
241
+ errors.push("run_bootstrap_seal.sealed_at must be a string");
242
+ }
243
+ }
244
+ }
245
+ // Validate dispatch_boundary if present
246
+ if ("dispatch_boundary" in s && s["dispatch_boundary"] !== undefined) {
247
+ if (typeof s["dispatch_boundary"] !== "object" || s["dispatch_boundary"] === null || Array.isArray(s["dispatch_boundary"])) {
248
+ errors.push("dispatch_boundary must be an object");
249
+ }
250
+ else {
251
+ const db = s["dispatch_boundary"];
252
+ if (typeof db["dispatch_epoch"] !== "number" || !Number.isInteger(db["dispatch_epoch"]) || db["dispatch_epoch"] < 0) {
253
+ errors.push("dispatch_boundary.dispatch_epoch must be a non-negative integer");
254
+ }
255
+ if (typeof db["continue_epoch"] !== "number" || !Number.isInteger(db["continue_epoch"]) || db["continue_epoch"] < 0) {
256
+ errors.push("dispatch_boundary.continue_epoch must be a non-negative integer");
257
+ }
258
+ if (db["dispatch_epoch"] !== undefined && db["continue_epoch"] !== undefined) {
259
+ if (db["dispatch_epoch"] < db["continue_epoch"]) {
260
+ errors.push("dispatch_boundary.dispatch_epoch must be >= continue_epoch");
261
+ }
262
+ }
263
+ if (db["last_dispatched_child"] !== null && typeof db["last_dispatched_child"] !== "string") {
264
+ errors.push("dispatch_boundary.last_dispatched_child must be a string or null");
265
+ }
266
+ }
267
+ }
268
+ return errors;
269
+ }
270
+ function writeStateAtomic(stateFile, state) {
271
+ const content = JSON.stringify(state, null, 2);
272
+ const tmp = `${stateFile}.tmp`;
273
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(stateFile), { recursive: true });
274
+ (0, node_fs_1.writeFileSync)(tmp, content, "utf-8");
275
+ (0, node_fs_1.renameSync)(tmp, stateFile);
276
+ return (0, node_crypto_1.createHash)("sha256").update(content).digest("hex");
277
+ }
278
+ function appendCheckpointEvent(telemetryFile, event) {
279
+ const timestampedEvent = { ...event, timestamp: (0, monotonic_timestamp_js_1.getMonotonicTimestamp)() };
280
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(telemetryFile), { recursive: true });
281
+ (0, node_fs_1.appendFileSync)(telemetryFile, JSON.stringify(timestampedEvent) + "\n", "utf-8");
282
+ }
283
+ function appendCompactReturnReceivedEvent(telemetryFile, event) {
284
+ const timestampedEvent = { ...event, timestamp: (0, monotonic_timestamp_js_1.getMonotonicTimestamp)() };
285
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(telemetryFile), { recursive: true });
286
+ (0, node_fs_1.appendFileSync)(telemetryFile, JSON.stringify(timestampedEvent) + "\n", "utf-8");
287
+ }
288
+ function appendWorkerScopeFidelityEvent(telemetryFile, event) {
289
+ const timestampedEvent = { ...event, timestamp: (0, monotonic_timestamp_js_1.getMonotonicTimestamp)() };
290
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(telemetryFile), { recursive: true });
291
+ (0, node_fs_1.appendFileSync)(telemetryFile, JSON.stringify(timestampedEvent) + "\n", "utf-8");
292
+ }
293
+ function appendBoundaryEvent(telemetryFile, event) {
294
+ const timestampedEvent = { ...event, timestamp: (0, monotonic_timestamp_js_1.getMonotonicTimestamp)() };
295
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(telemetryFile), { recursive: true });
296
+ (0, node_fs_1.appendFileSync)(telemetryFile, JSON.stringify(timestampedEvent) + "\n", "utf-8");
297
+ }
298
+ function appendAbortEvent(telemetryFile, event) {
299
+ const timestampedEvent = { ...event, timestamp: (0, monotonic_timestamp_js_1.getMonotonicTimestamp)() };
300
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(telemetryFile), { recursive: true });
301
+ (0, node_fs_1.appendFileSync)(telemetryFile, JSON.stringify(timestampedEvent) + "\n", "utf-8");
302
+ }
303
+ function appendStaleDispatchAbortedEvent(telemetryFile, event) {
304
+ const timestampedEvent = { ...event, timestamp: (0, monotonic_timestamp_js_1.getMonotonicTimestamp)() };
305
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(telemetryFile), { recursive: true });
306
+ (0, node_fs_1.appendFileSync)(telemetryFile, JSON.stringify(timestampedEvent) + "\n", "utf-8");
307
+ }
308
+ /**
309
+ * Reads the issue body for a node from the durable cluster snapshot at
310
+ * `.polaris/clusters/<clusterId>/clusters.json`. Returns undefined when the
311
+ * snapshot is absent, unparseable, or the node has no body field.
312
+ * Never throws.
313
+ */
314
+ function readBodyFromClusterSnapshot(clusterId, nodeId, repoRoot) {
315
+ try {
316
+ const snapshotPath = (0, node_path_1.join)(repoRoot, ".polaris", "clusters", clusterId, "clusters.json");
317
+ const raw = (0, node_fs_1.readFileSync)(snapshotPath, "utf-8");
318
+ const graph = JSON.parse(raw);
319
+ const body = graph?.nodes?.[nodeId]?.body;
320
+ return typeof body === "string" && body.trim().length > 0 ? body : undefined;
321
+ }
322
+ catch {
323
+ return undefined;
324
+ }
325
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ /**
3
+ * CompactReturn — the structured summary a worker writes to stdout before exiting.
4
+ *
5
+ * The parent loop parses this to determine how to advance cluster state.
6
+ * Workers MUST write exactly one JSON line (the CompactReturn) as the last
7
+ * line of stdout and then exit immediately — no continuation to the next child.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.validateCompactReturn = validateCompactReturn;
11
+ /**
12
+ * Validate an unknown value against the CompactReturn schema.
13
+ * Returns an array of error strings; empty means valid.
14
+ */
15
+ function validateCompactReturn(value) {
16
+ const errors = [];
17
+ if (typeof value !== 'object' || value === null) {
18
+ return ['CompactReturn must be a JSON object'];
19
+ }
20
+ const r = value;
21
+ if (typeof r['child_id'] !== 'string' || !r['child_id']) {
22
+ errors.push('missing or empty child_id');
23
+ }
24
+ if (r['status'] !== 'done' && r['status'] !== 'failed' && r['status'] !== 'blocked') {
25
+ errors.push('status must be "done", "failed", or "blocked"');
26
+ }
27
+ if (r['commit'] !== null && typeof r['commit'] !== 'string') {
28
+ errors.push('commit must be a string or null');
29
+ }
30
+ if (r['validation'] !== 'passed' && r['validation'] !== 'failed' && r['validation'] !== 'skipped') {
31
+ errors.push('validation must be "passed", "failed", or "skipped"');
32
+ }
33
+ if (typeof r['tracker_updated'] !== 'boolean') {
34
+ errors.push('tracker_updated must be a boolean');
35
+ }
36
+ if (typeof r['state_updated'] !== 'boolean') {
37
+ errors.push('state_updated must be a boolean');
38
+ }
39
+ if (typeof r['telemetry_updated'] !== 'boolean') {
40
+ errors.push('telemetry_updated must be a boolean');
41
+ }
42
+ if (r['next_recommended_action'] !== 'continue' &&
43
+ r['next_recommended_action'] !== 'stop' &&
44
+ r['next_recommended_action'] !== 'investigate') {
45
+ errors.push('next_recommended_action must be "continue", "stop", or "investigate"');
46
+ }
47
+ if ('result_data' in r && r['result_data'] !== undefined) {
48
+ if (typeof r['result_data'] !== 'object' || r['result_data'] === null || Array.isArray(r['result_data'])) {
49
+ errors.push('result_data must be an object');
50
+ }
51
+ }
52
+ if ('work_note_paths' in r && r['work_note_paths'] !== undefined) {
53
+ if (!Array.isArray(r['work_note_paths']) ||
54
+ r['work_note_paths'].some((path) => typeof path !== 'string')) {
55
+ errors.push('work_note_paths must be an array of strings');
56
+ }
57
+ }
58
+ return errors;
59
+ }