@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,223 @@
1
+ "use strict";
2
+ /**
3
+ * Worker dispatch state machine types and transition logic.
4
+ *
5
+ * This module defines the runtime state machine for tracking worker execution
6
+ * from packet creation through completion, failure, or orphaning.
7
+ *
8
+ * @module dispatch-state
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.DEFAULT_TIMEOUTS = exports.PRE_LAUNCH_STATES = exports.ACTIVE_STATES = exports.TERMINAL_STATES = void 0;
12
+ exports.isTerminalState = isTerminalState;
13
+ exports.isActiveState = isActiveState;
14
+ exports.isPreLaunchState = isPreLaunchState;
15
+ exports.resolveTimeoutConfig = resolveTimeoutConfig;
16
+ exports.deriveDispatchState = deriveDispatchState;
17
+ exports.isValidTransition = isValidTransition;
18
+ exports.createStateHistoryEntry = createStateHistoryEntry;
19
+ /**
20
+ * Terminal states - once reached, no further transitions.
21
+ */
22
+ exports.TERMINAL_STATES = ["completed", "failed", "orphaned"];
23
+ /**
24
+ * Active states - worker is still expected to produce telemetry.
25
+ */
26
+ exports.ACTIVE_STATES = [
27
+ "launching",
28
+ "acknowledged",
29
+ "running",
30
+ "waiting-for-approval",
31
+ "blocked",
32
+ ];
33
+ /**
34
+ * Pre-launch states - worker has not yet started.
35
+ */
36
+ exports.PRE_LAUNCH_STATES = ["packet-created", "delegated"];
37
+ /**
38
+ * Default timeout values.
39
+ */
40
+ exports.DEFAULT_TIMEOUTS = {
41
+ launch_to_first_heartbeat_ms: 30000, // 30 seconds
42
+ heartbeat_interval_ms: 300000, // 5 minutes
43
+ orphan_timeout_ms: 600000, // 10 minutes
44
+ approval_timeout_ms: 3600000, // 1 hour
45
+ ack_to_first_heartbeat_ms: 120000, // 2 minutes
46
+ };
47
+ // ─────────────────────────────────────────────────────────────────────────────
48
+ // State Machine Logic
49
+ // ─────────────────────────────────────────────────────────────────────────────
50
+ /**
51
+ * Check if a state is terminal (no further transitions expected).
52
+ */
53
+ function isTerminalState(state) {
54
+ return exports.TERMINAL_STATES.includes(state);
55
+ }
56
+ /**
57
+ * Check if a state represents an active worker (telemetry expected).
58
+ */
59
+ function isActiveState(state) {
60
+ return exports.ACTIVE_STATES.includes(state);
61
+ }
62
+ /**
63
+ * Check if state is pre-launch (worker not yet started).
64
+ */
65
+ function isPreLaunchState(state) {
66
+ return exports.PRE_LAUNCH_STATES.includes(state);
67
+ }
68
+ /**
69
+ * Get the timeout configuration with defaults applied.
70
+ */
71
+ function resolveTimeoutConfig(config) {
72
+ return {
73
+ launch_to_first_heartbeat_ms: config?.launch_to_first_heartbeat_ms ?? exports.DEFAULT_TIMEOUTS.launch_to_first_heartbeat_ms,
74
+ heartbeat_interval_ms: config?.heartbeat_interval_ms ?? exports.DEFAULT_TIMEOUTS.heartbeat_interval_ms,
75
+ orphan_timeout_ms: config?.orphan_timeout_ms ?? exports.DEFAULT_TIMEOUTS.orphan_timeout_ms,
76
+ approval_timeout_ms: config?.approval_timeout_ms ?? exports.DEFAULT_TIMEOUTS.approval_timeout_ms,
77
+ ack_to_first_heartbeat_ms: config?.ack_to_first_heartbeat_ms ?? exports.DEFAULT_TIMEOUTS.ack_to_first_heartbeat_ms,
78
+ };
79
+ }
80
+ /**
81
+ * Derive the current dispatch state from events and configuration.
82
+ *
83
+ * This is the core state machine function that determines the current
84
+ * state based on telemetry events and timeout thresholds.
85
+ *
86
+ * For delegated mode, also considers Foreman coordination events:
87
+ * - worker-assignment-attempted
88
+ * - worker-assigned
89
+ * - worker-assignment-failed
90
+ * - escalation-initiated
91
+ */
92
+ function deriveDispatchState(events, config, now = new Date()) {
93
+ // Sort events by timestamp
94
+ const sorted = [...events].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
95
+ // Find result event (terminal)
96
+ const resultEvent = sorted.find((e) => e.event === "worker-result");
97
+ if (resultEvent) {
98
+ return resultEvent.status === "success" ? "completed" : "failed";
99
+ }
100
+ // Find blocked event
101
+ const blockedEvents = sorted.filter((e) => e.event === "worker-blocked");
102
+ const lastBlocked = blockedEvents.at(-1);
103
+ if (lastBlocked) {
104
+ // Check for approval after this block
105
+ const approvalEvents = sorted.filter((e) => (e.event === "worker-approved" || e.event === "worker-auto-approved") &&
106
+ new Date(e.timestamp) > new Date(lastBlocked.timestamp));
107
+ if (approvalEvents.length === 0) {
108
+ // Still blocked - check for timeout
109
+ const blockedMs = now.getTime() - new Date(lastBlocked.timestamp).getTime();
110
+ if (blockedMs > config.approval_timeout_ms) {
111
+ return "failed";
112
+ }
113
+ return "waiting-for-approval";
114
+ }
115
+ // Approved - continue checking heartbeats below
116
+ }
117
+ // Find rejection event - context aware
118
+ // Only treat rejection as terminal if it's relevant to current unresolved cycle
119
+ // A rejection is relevant if:
120
+ // 1. It occurred after the most recent approval (if any approvals exist), OR
121
+ // 2. It matches the blocker_id of the current unresolved blocked event
122
+ const rejectionEvents = sorted.filter((e) => e.event === "worker-rejected");
123
+ if (rejectionEvents.length > 0) {
124
+ // Find the most recent approval timestamp (if any approvals exist)
125
+ const lastApproval = sorted
126
+ .filter((e) => e.event === "worker-approved" || e.event === "worker-auto-approved")
127
+ .at(-1);
128
+ const lastApprovalTimestamp = lastApproval ? new Date(lastApproval.timestamp).getTime() : 0;
129
+ // Find if there's a current unresolved blocked event with a blocker_id
130
+ const currentBlocked = lastBlocked;
131
+ const currentBlockerId = currentBlocked?.blocker_id;
132
+ // Check for relevant rejections
133
+ const relevantRejection = rejectionEvents.find((rejection) => {
134
+ const rejectionTimestamp = new Date(rejection.timestamp).getTime();
135
+ // Rejection is relevant if it happened after the last approval
136
+ if (rejectionTimestamp > lastApprovalTimestamp) {
137
+ return true;
138
+ }
139
+ // Or if it matches the current unresolved blocked event's blocker_id
140
+ if (currentBlockerId && rejection.blocker_id === currentBlockerId) {
141
+ return true;
142
+ }
143
+ return false;
144
+ });
145
+ if (relevantRejection) {
146
+ return "failed";
147
+ }
148
+ }
149
+ // Check heartbeats
150
+ const heartbeats = sorted.filter((e) => e.event === "worker-heartbeat");
151
+ const lastHeartbeat = heartbeats.at(-1);
152
+ if (lastHeartbeat) {
153
+ const msSinceHeartbeat = now.getTime() - new Date(lastHeartbeat.timestamp).getTime();
154
+ // Check for orphan timeout
155
+ if (msSinceHeartbeat > config.orphan_timeout_ms) {
156
+ return "orphaned";
157
+ }
158
+ // Check for blocked (stale heartbeat)
159
+ if (msSinceHeartbeat > config.heartbeat_interval_ms) {
160
+ return "blocked";
161
+ }
162
+ // Active worker
163
+ return "running";
164
+ }
165
+ // No heartbeats yet — check for worker-acknowledged event
166
+ const acknowledgedEvent = sorted.find((e) => e.event === "worker-acknowledged");
167
+ if (acknowledgedEvent) {
168
+ // Worker acknowledged packet but has not yet sent a heartbeat
169
+ // Check if the acknowledgement has aged out
170
+ const msSinceAck = now.getTime() - new Date(acknowledgedEvent.timestamp).getTime();
171
+ if (msSinceAck > config.ack_to_first_heartbeat_ms) {
172
+ // Ack timeout — worker never started actual work
173
+ return "failed";
174
+ }
175
+ return "acknowledged";
176
+ }
177
+ // No heartbeats yet - check for launch
178
+ const launchEvent = sorted.find((e) => e.event === "worker-launch");
179
+ if (launchEvent) {
180
+ const msSinceLaunch = now.getTime() - new Date(launchEvent.timestamp).getTime();
181
+ // Check for launch timeout
182
+ if (msSinceLaunch > config.launch_to_first_heartbeat_ms) {
183
+ return "blocked";
184
+ }
185
+ return "launching";
186
+ }
187
+ // No launch event - check for provider assignment
188
+ // This would require external knowledge, default to packet-created
189
+ return "packet-created";
190
+ }
191
+ /**
192
+ * Determine if a state transition is valid.
193
+ */
194
+ function isValidTransition(from, to) {
195
+ // Cannot transition from terminal states
196
+ if (isTerminalState(from)) {
197
+ return false;
198
+ }
199
+ // Define valid transitions
200
+ const validTransitions = {
201
+ "packet-created": ["delegated", "launching", "acknowledged", "running", "failed"],
202
+ "delegated": ["launching", "acknowledged", "running", "completed", "failed", "blocked"],
203
+ "launching": ["acknowledged", "running", "waiting-for-approval", "blocked", "completed", "failed"],
204
+ "acknowledged": ["running", "failed"],
205
+ "running": ["waiting-for-approval", "blocked", "completed", "failed", "orphaned"],
206
+ "waiting-for-approval": ["running", "completed", "failed"],
207
+ "blocked": ["running", "completed", "failed", "orphaned"],
208
+ "completed": [],
209
+ "failed": [],
210
+ "orphaned": [],
211
+ };
212
+ return validTransitions[from]?.includes(to) ?? false;
213
+ }
214
+ /**
215
+ * Create a state history entry.
216
+ */
217
+ function createStateHistoryEntry(state, evidence) {
218
+ return {
219
+ state,
220
+ entered_at: new Date().toISOString(),
221
+ evidence,
222
+ };
223
+ }
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for dispatch-state.ts — state machine transitions and backward compat helpers.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const vitest_1 = require("vitest");
7
+ const dispatch_state_js_1 = require("./dispatch-state.js");
8
+ // ─────────────────────────────────────────────────────────────────────────────
9
+ // Transition tests
10
+ // ─────────────────────────────────────────────────────────────────────────────
11
+ (0, vitest_1.describe)("isValidTransition — added transitions", () => {
12
+ (0, vitest_1.it)("packet-created → failed is valid", () => {
13
+ (0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("packet-created", "failed")).toBe(true);
14
+ });
15
+ (0, vitest_1.it)("running → orphaned is valid", () => {
16
+ (0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("running", "orphaned")).toBe(true);
17
+ });
18
+ (0, vitest_1.it)("delegated → blocked is valid", () => {
19
+ (0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("delegated", "blocked")).toBe(true);
20
+ });
21
+ });
22
+ (0, vitest_1.describe)("isValidTransition — pre-existing transitions still valid", () => {
23
+ (0, vitest_1.it)("packet-created → delegated", () => {
24
+ (0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("packet-created", "delegated")).toBe(true);
25
+ });
26
+ (0, vitest_1.it)("delegated → launching", () => {
27
+ (0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("delegated", "launching")).toBe(true);
28
+ });
29
+ (0, vitest_1.it)("running → completed", () => {
30
+ (0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("running", "completed")).toBe(true);
31
+ });
32
+ (0, vitest_1.it)("blocked → orphaned", () => {
33
+ (0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("blocked", "orphaned")).toBe(true);
34
+ });
35
+ });
36
+ // ─────────────────────────────────────────────────────────────────────────────
37
+ // POL-221: acknowledged state transitions
38
+ // ─────────────────────────────────────────────────────────────────────────────
39
+ (0, vitest_1.describe)("isValidTransition — acknowledged state (POL-221)", () => {
40
+ (0, vitest_1.it)("launching → acknowledged is valid", () => {
41
+ (0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("launching", "acknowledged")).toBe(true);
42
+ });
43
+ (0, vitest_1.it)("acknowledged → running is valid", () => {
44
+ (0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("acknowledged", "running")).toBe(true);
45
+ });
46
+ (0, vitest_1.it)("acknowledged → failed is valid", () => {
47
+ (0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("acknowledged", "failed")).toBe(true);
48
+ });
49
+ (0, vitest_1.it)("launching → running is still valid for backward compat", () => {
50
+ // The state machine allows this, but deriveDispatchState now routes through acknowledged
51
+ (0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("launching", "running")).toBe(true);
52
+ });
53
+ });
54
+ // ─────────────────────────────────────────────────────────────────────────────
55
+ // POL-221: deriveDispatchState with worker-acknowledged event
56
+ // ─────────────────────────────────────────────────────────────────────────────
57
+ function makeBaseEvent(event, overrides = {}) {
58
+ return {
59
+ event,
60
+ event_id: "evt-001",
61
+ dispatch_id: "dispatch-001",
62
+ run_id: "run-001",
63
+ child_id: "POL-221",
64
+ timestamp: new Date().toISOString(),
65
+ ...overrides,
66
+ };
67
+ }
68
+ (0, vitest_1.describe)("deriveDispatchState — acknowledged (POL-221)", () => {
69
+ const now = new Date();
70
+ (0, vitest_1.it)("returns 'acknowledged' when worker-acknowledged event present and no heartbeat", () => {
71
+ const events = [
72
+ makeBaseEvent("worker-launch"),
73
+ makeBaseEvent("worker-acknowledged", { worker_id: "worker-uuid", packet_sha: "abc123" }),
74
+ ];
75
+ (0, vitest_1.expect)((0, dispatch_state_js_1.deriveDispatchState)(events, dispatch_state_js_1.DEFAULT_TIMEOUTS, now)).toBe("acknowledged");
76
+ });
77
+ (0, vitest_1.it)("returns 'acknowledged' without a prior launch event if only acknowledged present", () => {
78
+ const events = [
79
+ makeBaseEvent("worker-acknowledged", { worker_id: "worker-uuid", packet_sha: "abc123" }),
80
+ ];
81
+ (0, vitest_1.expect)((0, dispatch_state_js_1.deriveDispatchState)(events, dispatch_state_js_1.DEFAULT_TIMEOUTS, now)).toBe("acknowledged");
82
+ });
83
+ (0, vitest_1.it)("returns 'running' when heartbeat present after acknowledgment", () => {
84
+ const events = [
85
+ makeBaseEvent("worker-launch"),
86
+ makeBaseEvent("worker-acknowledged", { worker_id: "worker-uuid", packet_sha: "abc123" }),
87
+ makeBaseEvent("worker-heartbeat", { step_cursor: "step-1" }),
88
+ ];
89
+ (0, vitest_1.expect)((0, dispatch_state_js_1.deriveDispatchState)(events, dispatch_state_js_1.DEFAULT_TIMEOUTS, now)).toBe("running");
90
+ });
91
+ (0, vitest_1.it)("does NOT return 'running' from launching without acknowledgment (proxy retirement)", () => {
92
+ // Without worker-acknowledged or heartbeat, should be 'launching', not 'running'
93
+ const events = [
94
+ makeBaseEvent("worker-launch"),
95
+ ];
96
+ const state = (0, dispatch_state_js_1.deriveDispatchState)(events, dispatch_state_js_1.DEFAULT_TIMEOUTS, now);
97
+ (0, vitest_1.expect)(state).toBe("launching");
98
+ (0, vitest_1.expect)(state).not.toBe("running");
99
+ });
100
+ });
101
+ (0, vitest_1.describe)("isValidTransition — terminal states cannot transition", () => {
102
+ (0, vitest_1.it)("completed → running is invalid", () => {
103
+ (0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("completed", "running")).toBe(false);
104
+ });
105
+ (0, vitest_1.it)("failed → running is invalid", () => {
106
+ (0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("failed", "running")).toBe(false);
107
+ });
108
+ (0, vitest_1.it)("orphaned → running is invalid", () => {
109
+ (0, vitest_1.expect)((0, dispatch_state_js_1.isValidTransition)("orphaned", "running")).toBe(false);
110
+ });
111
+ });
112
+ // ─────────────────────────────────────────────────────────────────────────────
113
+ // Backward-compat helper tests for ChildDispatchRecord optional fields
114
+ // ─────────────────────────────────────────────────────────────────────────────
115
+ /**
116
+ * Resolve worker_id with backward-compat fallback.
117
+ * Records created before worker_id was added fall back to dispatch_id.
118
+ */
119
+ function resolveWorkerId(record) {
120
+ return record.worker_id ?? record.dispatch_id;
121
+ }
122
+ function resolveSessionId(record) {
123
+ return record.session_id !== undefined ? record.session_id : null;
124
+ }
125
+ function resolveAttachmentCapable(record) {
126
+ return record.attachment_capable ?? false;
127
+ }
128
+ function resolveHeartbeatCount(record) {
129
+ return record.heartbeat_count ?? 0;
130
+ }
131
+ (0, vitest_1.describe)("ChildDispatchRecord backward compat", () => {
132
+ const legacyRecord = {
133
+ dispatch_id: "legacy-dispatch-id",
134
+ child_id: "POL-001",
135
+ run_id: "run-1",
136
+ cluster_id: "cluster-1",
137
+ packet_path: "/path/to/packet",
138
+ expected_result_path: "/path/to/result",
139
+ dispatched_at: new Date().toISOString(),
140
+ status: "dispatched",
141
+ };
142
+ (0, vitest_1.it)("worker_id falls back to dispatch_id when absent", () => {
143
+ (0, vitest_1.expect)(resolveWorkerId(legacyRecord)).toBe("legacy-dispatch-id");
144
+ });
145
+ (0, vitest_1.it)("session_id defaults to null when absent", () => {
146
+ (0, vitest_1.expect)(resolveSessionId(legacyRecord)).toBeNull();
147
+ });
148
+ (0, vitest_1.it)("attachment_capable defaults to false when absent", () => {
149
+ (0, vitest_1.expect)(resolveAttachmentCapable(legacyRecord)).toBe(false);
150
+ });
151
+ (0, vitest_1.it)("heartbeat_count defaults to 0 when absent", () => {
152
+ (0, vitest_1.expect)(resolveHeartbeatCount(legacyRecord)).toBe(0);
153
+ });
154
+ });
155
+ (0, vitest_1.describe)("ChildDispatchRecord new fields when present", () => {
156
+ const newRecord = {
157
+ dispatch_id: "dispatch-abc",
158
+ child_id: "POL-219",
159
+ run_id: "run-2",
160
+ cluster_id: "cluster-2",
161
+ packet_path: "/path/to/packet",
162
+ expected_result_path: "/path/to/result",
163
+ dispatched_at: new Date().toISOString(),
164
+ status: "dispatched",
165
+ worker_id: "worker-uuid-xyz",
166
+ session_id: "session-123",
167
+ attachment_capable: true,
168
+ heartbeat_count: 5,
169
+ first_heartbeat_at: "2026-05-29T00:00:00.000Z",
170
+ };
171
+ (0, vitest_1.it)("worker_id uses explicit value", () => {
172
+ (0, vitest_1.expect)(resolveWorkerId(newRecord)).toBe("worker-uuid-xyz");
173
+ });
174
+ (0, vitest_1.it)("session_id returns explicit value", () => {
175
+ (0, vitest_1.expect)(resolveSessionId(newRecord)).toBe("session-123");
176
+ });
177
+ (0, vitest_1.it)("attachment_capable returns true when set", () => {
178
+ (0, vitest_1.expect)(resolveAttachmentCapable(newRecord)).toBe(true);
179
+ });
180
+ (0, vitest_1.it)("heartbeat_count returns explicit count", () => {
181
+ (0, vitest_1.expect)(resolveHeartbeatCount(newRecord)).toBe(5);
182
+ });
183
+ (0, vitest_1.it)("first_heartbeat_at is present", () => {
184
+ (0, vitest_1.expect)(newRecord.first_heartbeat_at).toBe("2026-05-29T00:00:00.000Z");
185
+ });
186
+ });