open-multi-agent-kit 0.99.0 → 1.2.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 (464) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +3 -3
  3. package/dist/bun/cli.d.ts.map +1 -1
  4. package/dist/bun/cli.js +1 -0
  5. package/dist/bun/cli.js.map +1 -1
  6. package/dist/bun/register-bundled-coding-agent.d.ts +10 -0
  7. package/dist/bun/register-bundled-coding-agent.d.ts.map +1 -0
  8. package/dist/bun/register-bundled-coding-agent.js +12 -0
  9. package/dist/bun/register-bundled-coding-agent.js.map +1 -0
  10. package/dist/cli/args.d.ts +1 -1
  11. package/dist/cli/args.d.ts.map +1 -1
  12. package/dist/cli/args.js +1 -1
  13. package/dist/cli/args.js.map +1 -1
  14. package/dist/cli/help.d.ts.map +1 -1
  15. package/dist/cli/help.js +2 -1
  16. package/dist/cli/help.js.map +1 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +14 -2
  19. package/dist/cli.js.map +1 -1
  20. package/dist/commands/neo-cli.d.ts +9 -0
  21. package/dist/commands/neo-cli.d.ts.map +1 -0
  22. package/dist/commands/neo-cli.js +61 -0
  23. package/dist/commands/neo-cli.js.map +1 -0
  24. package/dist/coordination/awareness.d.ts +32 -0
  25. package/dist/coordination/awareness.d.ts.map +1 -0
  26. package/dist/coordination/awareness.js +55 -0
  27. package/dist/coordination/awareness.js.map +1 -0
  28. package/dist/coordination/broker.d.ts +79 -0
  29. package/dist/coordination/broker.d.ts.map +1 -0
  30. package/dist/coordination/broker.js +195 -0
  31. package/dist/coordination/broker.js.map +1 -0
  32. package/dist/coordination/index.d.ts +16 -0
  33. package/dist/coordination/index.d.ts.map +1 -0
  34. package/dist/coordination/index.js +16 -0
  35. package/dist/coordination/index.js.map +1 -0
  36. package/dist/coordination/integration.d.ts +59 -0
  37. package/dist/coordination/integration.d.ts.map +1 -0
  38. package/dist/coordination/integration.js +126 -0
  39. package/dist/coordination/integration.js.map +1 -0
  40. package/dist/coordination/operation.d.ts +106 -0
  41. package/dist/coordination/operation.d.ts.map +1 -0
  42. package/dist/coordination/operation.js +179 -0
  43. package/dist/coordination/operation.js.map +1 -0
  44. package/dist/coordination/resource.d.ts +33 -0
  45. package/dist/coordination/resource.d.ts.map +1 -0
  46. package/dist/coordination/resource.js +89 -0
  47. package/dist/coordination/resource.js.map +1 -0
  48. package/dist/coordination/session.d.ts +74 -0
  49. package/dist/coordination/session.d.ts.map +1 -0
  50. package/dist/coordination/session.js +143 -0
  51. package/dist/coordination/session.js.map +1 -0
  52. package/dist/coordination/types.d.ts +68 -0
  53. package/dist/coordination/types.d.ts.map +1 -0
  54. package/dist/coordination/types.js +26 -0
  55. package/dist/coordination/types.js.map +1 -0
  56. package/dist/core/agent-session.d.ts +29 -2
  57. package/dist/core/agent-session.d.ts.map +1 -1
  58. package/dist/core/agent-session.js +135 -37
  59. package/dist/core/agent-session.js.map +1 -1
  60. package/dist/core/bundled-skills.d.ts +4 -0
  61. package/dist/core/bundled-skills.d.ts.map +1 -0
  62. package/dist/core/bundled-skills.js +32 -0
  63. package/dist/core/bundled-skills.js.map +1 -0
  64. package/dist/core/cli-diagnostics.d.ts +6 -0
  65. package/dist/core/cli-diagnostics.d.ts.map +1 -0
  66. package/dist/core/cli-diagnostics.js +20 -0
  67. package/dist/core/cli-diagnostics.js.map +1 -0
  68. package/dist/core/compaction/control-state.d.ts +41 -0
  69. package/dist/core/compaction/control-state.d.ts.map +1 -0
  70. package/dist/core/compaction/control-state.js +85 -0
  71. package/dist/core/compaction/control-state.js.map +1 -0
  72. package/dist/core/compaction/fallback.d.ts +60 -0
  73. package/dist/core/compaction/fallback.d.ts.map +1 -0
  74. package/dist/core/compaction/fallback.js +117 -0
  75. package/dist/core/compaction/fallback.js.map +1 -0
  76. package/dist/core/compaction/index.d.ts +2 -0
  77. package/dist/core/compaction/index.d.ts.map +1 -1
  78. package/dist/core/compaction/index.js +2 -0
  79. package/dist/core/compaction/index.js.map +1 -1
  80. package/dist/core/context-budget-headroom-candidates.d.ts +16 -1
  81. package/dist/core/context-budget-headroom-candidates.d.ts.map +1 -1
  82. package/dist/core/context-budget-headroom-candidates.js +31 -13
  83. package/dist/core/context-budget-headroom-candidates.js.map +1 -1
  84. package/dist/core/context-budget-headroom-types.d.ts +7 -1
  85. package/dist/core/context-budget-headroom-types.d.ts.map +1 -1
  86. package/dist/core/context-budget-headroom-types.js +0 -1
  87. package/dist/core/context-budget-headroom-types.js.map +1 -1
  88. package/dist/core/context-budget-headroom.d.ts +22 -0
  89. package/dist/core/context-budget-headroom.d.ts.map +1 -1
  90. package/dist/core/context-budget-headroom.js +46 -2
  91. package/dist/core/context-budget-headroom.js.map +1 -1
  92. package/dist/core/context-budget-v2-global-pass.d.ts +21 -0
  93. package/dist/core/context-budget-v2-global-pass.d.ts.map +1 -0
  94. package/dist/core/context-budget-v2-global-pass.js +203 -0
  95. package/dist/core/context-budget-v2-global-pass.js.map +1 -0
  96. package/dist/core/context-budget-v2-input-validation.d.ts +4 -0
  97. package/dist/core/context-budget-v2-input-validation.d.ts.map +1 -0
  98. package/dist/core/context-budget-v2-input-validation.js +79 -0
  99. package/dist/core/context-budget-v2-input-validation.js.map +1 -0
  100. package/dist/core/context-budget-v2-observability.d.ts +15 -0
  101. package/dist/core/context-budget-v2-observability.d.ts.map +1 -0
  102. package/dist/core/context-budget-v2-observability.js +35 -0
  103. package/dist/core/context-budget-v2-observability.js.map +1 -0
  104. package/dist/core/context-budget-v2-planned-items.d.ts +17 -0
  105. package/dist/core/context-budget-v2-planned-items.d.ts.map +1 -0
  106. package/dist/core/context-budget-v2-planned-items.js +59 -0
  107. package/dist/core/context-budget-v2-planned-items.js.map +1 -0
  108. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  109. package/dist/core/context-budget-v2-planner.js +69 -51
  110. package/dist/core/context-budget-v2-planner.js.map +1 -1
  111. package/dist/core/context-budget-v2-scoring.d.ts +7 -1
  112. package/dist/core/context-budget-v2-scoring.d.ts.map +1 -1
  113. package/dist/core/context-budget-v2-scoring.js.map +1 -1
  114. package/dist/core/context-budget-v2-selection.d.ts +22 -3
  115. package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
  116. package/dist/core/context-budget-v2-selection.js +41 -59
  117. package/dist/core/context-budget-v2-selection.js.map +1 -1
  118. package/dist/core/context-budget-v2-tiers.d.ts.map +1 -1
  119. package/dist/core/context-budget-v2-tiers.js +9 -42
  120. package/dist/core/context-budget-v2-tiers.js.map +1 -1
  121. package/dist/core/context-budget-v2-types.d.ts +14 -2
  122. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  123. package/dist/core/context-budget-v2-types.js +6 -1
  124. package/dist/core/context-budget-v2-types.js.map +1 -1
  125. package/dist/core/extensions/bundled-virtual-modules.d.ts +15 -0
  126. package/dist/core/extensions/bundled-virtual-modules.d.ts.map +1 -0
  127. package/dist/core/extensions/bundled-virtual-modules.js +63 -0
  128. package/dist/core/extensions/bundled-virtual-modules.js.map +1 -0
  129. package/dist/core/extensions/loader.d.ts.map +1 -1
  130. package/dist/core/extensions/loader.js +2 -42
  131. package/dist/core/extensions/loader.js.map +1 -1
  132. package/dist/core/extensions/runner.d.ts +1 -0
  133. package/dist/core/extensions/runner.d.ts.map +1 -1
  134. package/dist/core/extensions/runner.js +6 -0
  135. package/dist/core/extensions/runner.js.map +1 -1
  136. package/dist/core/extensions/types.d.ts +10 -0
  137. package/dist/core/extensions/types.d.ts.map +1 -1
  138. package/dist/core/extensions/types.js.map +1 -1
  139. package/dist/core/index.d.ts +2 -1
  140. package/dist/core/index.d.ts.map +1 -1
  141. package/dist/core/index.js +2 -1
  142. package/dist/core/index.js.map +1 -1
  143. package/dist/core/loadout-runtime.d.ts +2 -8
  144. package/dist/core/loadout-runtime.d.ts.map +1 -1
  145. package/dist/core/loadout-runtime.js.map +1 -1
  146. package/dist/core/mcp/client.d.ts +27 -6
  147. package/dist/core/mcp/client.d.ts.map +1 -1
  148. package/dist/core/mcp/client.js +80 -20
  149. package/dist/core/mcp/client.js.map +1 -1
  150. package/dist/core/mcp/manager.d.ts +10 -1
  151. package/dist/core/mcp/manager.d.ts.map +1 -1
  152. package/dist/core/mcp/manager.js +68 -9
  153. package/dist/core/mcp/manager.js.map +1 -1
  154. package/dist/core/mcp/protocol.d.ts +9 -3
  155. package/dist/core/mcp/protocol.d.ts.map +1 -1
  156. package/dist/core/mcp/protocol.js +61 -15
  157. package/dist/core/mcp/protocol.js.map +1 -1
  158. package/dist/core/mcp/stdio-transport.d.ts +12 -1
  159. package/dist/core/mcp/stdio-transport.d.ts.map +1 -1
  160. package/dist/core/mcp/stdio-transport.js +30 -2
  161. package/dist/core/mcp/stdio-transport.js.map +1 -1
  162. package/dist/core/mcp-descriptor-injection.d.ts +26 -0
  163. package/dist/core/mcp-descriptor-injection.d.ts.map +1 -0
  164. package/dist/core/mcp-descriptor-injection.js +25 -0
  165. package/dist/core/mcp-descriptor-injection.js.map +1 -0
  166. package/dist/core/mcp-public-presets.d.ts +1 -3
  167. package/dist/core/mcp-public-presets.d.ts.map +1 -1
  168. package/dist/core/mcp-public-presets.js +3 -15
  169. package/dist/core/mcp-public-presets.js.map +1 -1
  170. package/dist/core/model-registry.d.ts.map +1 -1
  171. package/dist/core/model-registry.js +24 -3
  172. package/dist/core/model-registry.js.map +1 -1
  173. package/dist/core/neo/catalog.d.ts +20 -0
  174. package/dist/core/neo/catalog.d.ts.map +1 -0
  175. package/dist/core/neo/catalog.js +50 -0
  176. package/dist/core/neo/catalog.js.map +1 -0
  177. package/dist/core/neo/setup.d.ts +3 -0
  178. package/dist/core/neo/setup.d.ts.map +1 -0
  179. package/dist/core/neo/setup.js +47 -0
  180. package/dist/core/neo/setup.js.map +1 -0
  181. package/dist/core/provider-default-models.d.ts +2 -0
  182. package/dist/core/provider-default-models.d.ts.map +1 -1
  183. package/dist/core/provider-default-models.js +2 -0
  184. package/dist/core/provider-default-models.js.map +1 -1
  185. package/dist/core/provider-display-names.d.ts.map +1 -1
  186. package/dist/core/provider-display-names.js +2 -0
  187. package/dist/core/provider-display-names.js.map +1 -1
  188. package/dist/core/provider-error-classification.d.ts +57 -0
  189. package/dist/core/provider-error-classification.d.ts.map +1 -0
  190. package/dist/core/provider-error-classification.js +103 -0
  191. package/dist/core/provider-error-classification.js.map +1 -0
  192. package/dist/core/provider-resilience.d.ts +10 -0
  193. package/dist/core/provider-resilience.d.ts.map +1 -1
  194. package/dist/core/provider-resilience.js +36 -3
  195. package/dist/core/provider-resilience.js.map +1 -1
  196. package/dist/core/provider-usage-commandcode.d.ts +9 -0
  197. package/dist/core/provider-usage-commandcode.d.ts.map +1 -0
  198. package/dist/core/provider-usage-commandcode.js +198 -0
  199. package/dist/core/provider-usage-commandcode.js.map +1 -0
  200. package/dist/core/provider-usage-devin.d.ts +5 -3
  201. package/dist/core/provider-usage-devin.d.ts.map +1 -1
  202. package/dist/core/provider-usage-devin.js +15 -5
  203. package/dist/core/provider-usage-devin.js.map +1 -1
  204. package/dist/core/provider-usage-types.d.ts +1 -1
  205. package/dist/core/provider-usage-types.d.ts.map +1 -1
  206. package/dist/core/provider-usage-types.js.map +1 -1
  207. package/dist/core/provider-usage.d.ts.map +1 -1
  208. package/dist/core/provider-usage.js +6 -0
  209. package/dist/core/provider-usage.js.map +1 -1
  210. package/dist/core/reasoning-router-resolver.d.ts +7 -0
  211. package/dist/core/reasoning-router-resolver.d.ts.map +1 -1
  212. package/dist/core/reasoning-router-resolver.js +21 -3
  213. package/dist/core/reasoning-router-resolver.js.map +1 -1
  214. package/dist/core/reasoning-router-v4.d.ts +8 -5
  215. package/dist/core/reasoning-router-v4.d.ts.map +1 -1
  216. package/dist/core/reasoning-router-v4.js +8 -5
  217. package/dist/core/reasoning-router-v4.js.map +1 -1
  218. package/dist/core/resource-admission.d.ts +36 -0
  219. package/dist/core/resource-admission.d.ts.map +1 -1
  220. package/dist/core/resource-admission.js +59 -0
  221. package/dist/core/resource-admission.js.map +1 -1
  222. package/dist/core/resource-loader.d.ts.map +1 -1
  223. package/dist/core/resource-loader.js +2 -2
  224. package/dist/core/resource-loader.js.map +1 -1
  225. package/dist/core/run-usage-ledger.d.ts +47 -0
  226. package/dist/core/run-usage-ledger.d.ts.map +1 -0
  227. package/dist/core/run-usage-ledger.js +162 -0
  228. package/dist/core/run-usage-ledger.js.map +1 -0
  229. package/dist/core/run-usage-operation.d.ts +8 -0
  230. package/dist/core/run-usage-operation.d.ts.map +1 -0
  231. package/dist/core/run-usage-operation.js +12 -0
  232. package/dist/core/run-usage-operation.js.map +1 -0
  233. package/dist/core/sdk.d.ts.map +1 -1
  234. package/dist/core/sdk.js +5 -6
  235. package/dist/core/sdk.js.map +1 -1
  236. package/dist/core/session-compaction-service.d.ts +15 -0
  237. package/dist/core/session-compaction-service.d.ts.map +1 -1
  238. package/dist/core/session-compaction-service.js +17 -4
  239. package/dist/core/session-compaction-service.js.map +1 -1
  240. package/dist/core/session-failure-cause.d.ts.map +1 -1
  241. package/dist/core/session-failure-cause.js +14 -5
  242. package/dist/core/session-failure-cause.js.map +1 -1
  243. package/dist/core/session-prompt-lifecycle.d.ts +6 -0
  244. package/dist/core/session-prompt-lifecycle.d.ts.map +1 -1
  245. package/dist/core/session-prompt-lifecycle.js +47 -2
  246. package/dist/core/session-prompt-lifecycle.js.map +1 -1
  247. package/dist/core/session-termination.d.ts.map +1 -1
  248. package/dist/core/session-termination.js +4 -2
  249. package/dist/core/session-termination.js.map +1 -1
  250. package/dist/core/subagent-lane-authority.d.ts +28 -0
  251. package/dist/core/subagent-lane-authority.d.ts.map +1 -0
  252. package/dist/core/subagent-lane-authority.js +119 -0
  253. package/dist/core/subagent-lane-authority.js.map +1 -0
  254. package/dist/core/subagent-lane-contract.d.ts +114 -0
  255. package/dist/core/subagent-lane-contract.d.ts.map +1 -0
  256. package/dist/core/subagent-lane-contract.js +18 -0
  257. package/dist/core/subagent-lane-contract.js.map +1 -0
  258. package/dist/core/subagent-lane-launcher.d.ts +3 -6
  259. package/dist/core/subagent-lane-launcher.d.ts.map +1 -1
  260. package/dist/core/subagent-lane-launcher.js +53 -12
  261. package/dist/core/subagent-lane-launcher.js.map +1 -1
  262. package/dist/core/subagent-orchestration.d.ts +4 -17
  263. package/dist/core/subagent-orchestration.d.ts.map +1 -1
  264. package/dist/core/subagent-orchestration.js.map +1 -1
  265. package/dist/core/todo-runtime-state.d.ts +12 -0
  266. package/dist/core/todo-runtime-state.d.ts.map +1 -1
  267. package/dist/core/todo-runtime-state.js +23 -0
  268. package/dist/core/todo-runtime-state.js.map +1 -1
  269. package/dist/core/verified-run/broker.d.ts.map +1 -1
  270. package/dist/core/verified-run/broker.js +4 -1
  271. package/dist/core/verified-run/broker.js.map +1 -1
  272. package/dist/core/workload-permit-pool.d.ts +1 -1
  273. package/dist/core/workload-permit-pool.d.ts.map +1 -1
  274. package/dist/core/workload-permit-pool.js +3 -0
  275. package/dist/core/workload-permit-pool.js.map +1 -1
  276. package/dist/guardrails/strict-evidence-approval-adapter.d.ts +34 -0
  277. package/dist/guardrails/strict-evidence-approval-adapter.d.ts.map +1 -0
  278. package/dist/guardrails/strict-evidence-approval-adapter.js +70 -0
  279. package/dist/guardrails/strict-evidence-approval-adapter.js.map +1 -0
  280. package/dist/index.d.ts +5 -0
  281. package/dist/index.d.ts.map +1 -1
  282. package/dist/index.js +3 -0
  283. package/dist/index.js.map +1 -1
  284. package/dist/main.d.ts.map +1 -1
  285. package/dist/main.js +11 -18
  286. package/dist/main.js.map +1 -1
  287. package/dist/metacognition/calibration-selective.d.ts +78 -0
  288. package/dist/metacognition/calibration-selective.d.ts.map +1 -0
  289. package/dist/metacognition/calibration-selective.js +161 -0
  290. package/dist/metacognition/calibration-selective.js.map +1 -0
  291. package/dist/metacognition/calibration.d.ts +62 -0
  292. package/dist/metacognition/calibration.d.ts.map +1 -0
  293. package/dist/metacognition/calibration.js +122 -0
  294. package/dist/metacognition/calibration.js.map +1 -0
  295. package/dist/metacognition/checkpoint.d.ts +60 -0
  296. package/dist/metacognition/checkpoint.d.ts.map +1 -0
  297. package/dist/metacognition/checkpoint.js +57 -0
  298. package/dist/metacognition/checkpoint.js.map +1 -0
  299. package/dist/metacognition/context7.d.ts +48 -0
  300. package/dist/metacognition/context7.d.ts.map +1 -0
  301. package/dist/metacognition/context7.js +154 -0
  302. package/dist/metacognition/context7.js.map +1 -0
  303. package/dist/metacognition/decision.d.ts +44 -0
  304. package/dist/metacognition/decision.d.ts.map +1 -0
  305. package/dist/metacognition/decision.js +111 -0
  306. package/dist/metacognition/decision.js.map +1 -0
  307. package/dist/metacognition/evaluation.d.ts +73 -0
  308. package/dist/metacognition/evaluation.d.ts.map +1 -0
  309. package/dist/metacognition/evaluation.js +97 -0
  310. package/dist/metacognition/evaluation.js.map +1 -0
  311. package/dist/metacognition/index.d.ts +31 -0
  312. package/dist/metacognition/index.d.ts.map +1 -0
  313. package/dist/metacognition/index.js +31 -0
  314. package/dist/metacognition/index.js.map +1 -0
  315. package/dist/metacognition/knowledge-action.d.ts +43 -0
  316. package/dist/metacognition/knowledge-action.d.ts.map +1 -0
  317. package/dist/metacognition/knowledge-action.js +60 -0
  318. package/dist/metacognition/knowledge-action.js.map +1 -0
  319. package/dist/metacognition/knowledge.d.ts +84 -0
  320. package/dist/metacognition/knowledge.d.ts.map +1 -0
  321. package/dist/metacognition/knowledge.js +165 -0
  322. package/dist/metacognition/knowledge.js.map +1 -0
  323. package/dist/metacognition/obligations.d.ts +82 -0
  324. package/dist/metacognition/obligations.d.ts.map +1 -0
  325. package/dist/metacognition/obligations.js +139 -0
  326. package/dist/metacognition/obligations.js.map +1 -0
  327. package/dist/metacognition/observation-validity.d.ts +83 -0
  328. package/dist/metacognition/observation-validity.d.ts.map +1 -0
  329. package/dist/metacognition/observation-validity.js +118 -0
  330. package/dist/metacognition/observation-validity.js.map +1 -0
  331. package/dist/metacognition/observe.d.ts +45 -0
  332. package/dist/metacognition/observe.d.ts.map +1 -0
  333. package/dist/metacognition/observe.js +59 -0
  334. package/dist/metacognition/observe.js.map +1 -0
  335. package/dist/metacognition/policy.d.ts +53 -0
  336. package/dist/metacognition/policy.d.ts.map +1 -0
  337. package/dist/metacognition/policy.js +155 -0
  338. package/dist/metacognition/policy.js.map +1 -0
  339. package/dist/metacognition/predictions.d.ts +78 -0
  340. package/dist/metacognition/predictions.d.ts.map +1 -0
  341. package/dist/metacognition/predictions.js +181 -0
  342. package/dist/metacognition/predictions.js.map +1 -0
  343. package/dist/metacognition/retrieval.d.ts +53 -0
  344. package/dist/metacognition/retrieval.d.ts.map +1 -0
  345. package/dist/metacognition/retrieval.js +170 -0
  346. package/dist/metacognition/retrieval.js.map +1 -0
  347. package/dist/metacognition/risk.d.ts +48 -0
  348. package/dist/metacognition/risk.d.ts.map +1 -0
  349. package/dist/metacognition/risk.js +165 -0
  350. package/dist/metacognition/risk.js.map +1 -0
  351. package/dist/metacognition/route-economics.d.ts +95 -0
  352. package/dist/metacognition/route-economics.d.ts.map +1 -0
  353. package/dist/metacognition/route-economics.js +110 -0
  354. package/dist/metacognition/route-economics.js.map +1 -0
  355. package/dist/metacognition/runtime-bridge.d.ts +54 -0
  356. package/dist/metacognition/runtime-bridge.d.ts.map +1 -0
  357. package/dist/metacognition/runtime-bridge.js +140 -0
  358. package/dist/metacognition/runtime-bridge.js.map +1 -0
  359. package/dist/metacognition/skills.d.ts +42 -0
  360. package/dist/metacognition/skills.d.ts.map +1 -0
  361. package/dist/metacognition/skills.js +220 -0
  362. package/dist/metacognition/skills.js.map +1 -0
  363. package/dist/metacognition/state.d.ts +110 -0
  364. package/dist/metacognition/state.d.ts.map +1 -0
  365. package/dist/metacognition/state.js +52 -0
  366. package/dist/metacognition/state.js.map +1 -0
  367. package/dist/metacognition/validation.d.ts +19 -0
  368. package/dist/metacognition/validation.d.ts.map +1 -0
  369. package/dist/metacognition/validation.js +58 -0
  370. package/dist/metacognition/validation.js.map +1 -0
  371. package/dist/metacognition/verification.d.ts +93 -0
  372. package/dist/metacognition/verification.d.ts.map +1 -0
  373. package/dist/metacognition/verification.js +132 -0
  374. package/dist/metacognition/verification.js.map +1 -0
  375. package/dist/metacognition/verifier.d.ts +49 -0
  376. package/dist/metacognition/verifier.d.ts.map +1 -0
  377. package/dist/metacognition/verifier.js +88 -0
  378. package/dist/metacognition/verifier.js.map +1 -0
  379. package/dist/modes/acp/acp-agent.d.ts +24 -0
  380. package/dist/modes/acp/acp-agent.d.ts.map +1 -0
  381. package/dist/modes/acp/acp-agent.js +133 -0
  382. package/dist/modes/acp/acp-agent.js.map +1 -0
  383. package/dist/modes/acp/acp-mode.d.ts +4 -0
  384. package/dist/modes/acp/acp-mode.d.ts.map +1 -0
  385. package/dist/modes/acp/acp-mode.js +34 -0
  386. package/dist/modes/acp/acp-mode.js.map +1 -0
  387. package/dist/modes/acp/acp-session.d.ts +4 -0
  388. package/dist/modes/acp/acp-session.d.ts.map +1 -0
  389. package/dist/modes/acp/acp-session.js +76 -0
  390. package/dist/modes/acp/acp-session.js.map +1 -0
  391. package/dist/modes/acp/acp-transport.d.ts +5 -0
  392. package/dist/modes/acp/acp-transport.d.ts.map +1 -0
  393. package/dist/modes/acp/acp-transport.js +90 -0
  394. package/dist/modes/acp/acp-transport.js.map +1 -0
  395. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  396. package/dist/modes/interactive/interactive-mode.js +1 -0
  397. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  398. package/dist/observation/identity.d.ts +14 -0
  399. package/dist/observation/identity.d.ts.map +1 -0
  400. package/dist/observation/identity.js +29 -0
  401. package/dist/observation/identity.js.map +1 -0
  402. package/dist/observation/index.d.ts +13 -0
  403. package/dist/observation/index.d.ts.map +1 -0
  404. package/dist/observation/index.js +13 -0
  405. package/dist/observation/index.js.map +1 -0
  406. package/dist/observation/observe-mode.d.ts +46 -0
  407. package/dist/observation/observe-mode.d.ts.map +1 -0
  408. package/dist/observation/observe-mode.js +83 -0
  409. package/dist/observation/observe-mode.js.map +1 -0
  410. package/dist/observation/store.d.ts +53 -0
  411. package/dist/observation/store.d.ts.map +1 -0
  412. package/dist/observation/store.js +126 -0
  413. package/dist/observation/store.js.map +1 -0
  414. package/dist/observation/types.d.ts +72 -0
  415. package/dist/observation/types.d.ts.map +1 -0
  416. package/dist/observation/types.js +9 -0
  417. package/dist/observation/types.js.map +1 -0
  418. package/dist/observation/view.d.ts +39 -0
  419. package/dist/observation/view.d.ts.map +1 -0
  420. package/dist/observation/view.js +173 -0
  421. package/dist/observation/view.js.map +1 -0
  422. package/docs/audit-revalidation-2026-09-17.md +77 -0
  423. package/docs/devin-harness.md +33 -4
  424. package/docs/ecraf-normalization.md +83 -0
  425. package/docs/metacognition.md +51 -0
  426. package/docs/model-catalog-refresh.md +110 -1
  427. package/docs/models.md +1 -1
  428. package/docs/neo.md +134 -0
  429. package/docs/providers.md +125 -6
  430. package/docs/run-usage-ledger.md +56 -0
  431. package/docs/runtime-algorithms.md +151 -3
  432. package/docs/skills.md +1 -1
  433. package/docs/usage.md +2 -1
  434. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  435. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  436. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  437. package/examples/extensions/gondolin/package-lock.json +2 -2
  438. package/examples/extensions/gondolin/package.json +1 -1
  439. package/examples/extensions/sandbox/package-lock.json +2 -2
  440. package/examples/extensions/sandbox/package.json +1 -1
  441. package/examples/extensions/subagent/adaptive-agent-runtime.ts +14 -1
  442. package/examples/extensions/subagent/graph-result.ts +48 -0
  443. package/examples/extensions/subagent/index.ts +246 -111
  444. package/examples/extensions/subagent/managed-process-tree.ts +42 -0
  445. package/examples/extensions/subagent/managed-process.test.ts +24 -0
  446. package/examples/extensions/subagent/managed-process.ts +93 -112
  447. package/examples/extensions/subagent/subagent-runtime-types.ts +17 -1
  448. package/examples/extensions/subagent/subagent-stream.ts +161 -0
  449. package/examples/extensions/terminal-browser/README.md +54 -0
  450. package/examples/extensions/terminal-browser/bridge-protocol.ts +29 -0
  451. package/examples/extensions/terminal-browser/bridge.ts +219 -0
  452. package/examples/extensions/terminal-browser/browser-surface.ts +288 -0
  453. package/examples/extensions/terminal-browser/index.ts +215 -0
  454. package/examples/extensions/terminal-browser/placeholders.ts +49 -0
  455. package/examples/extensions/with-deps/package-lock.json +2 -2
  456. package/examples/extensions/with-deps/package.json +1 -1
  457. package/npm-shrinkwrap.json +18 -18
  458. package/package.json +8 -7
  459. package/resources/neo/skills/omk-browser/SKILL.md +32 -0
  460. package/resources/neo/skills/omk-code-review/SKILL.md +24 -0
  461. package/resources/neo/skills/omk-computeruse/SKILL.md +34 -0
  462. package/resources/neo/skills/omk-mcp-setup/SKILL.md +48 -0
  463. package/resources/neo/skills/omk-research/SKILL.md +24 -0
  464. package/resources/neo/skills/omk-site/SKILL.md +28 -0
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Candidate publication with read-version preconditions and bound receipts.
3
+ *
4
+ * A publication is admitted only when the snapshot that was verified is still
5
+ * the snapshot being published. Three separate facts are checked, because any
6
+ * one of them alone lets a stale candidate through:
7
+ * - every key the proposal read is still at the version it read,
8
+ * - the parent revision has not advanced,
9
+ * - the receipt is bound to this candidate, contract, check set and
10
+ * environment rather than to an earlier composition of them.
11
+ *
12
+ * Absence is a dependency: reading a key that did not exist records version 0,
13
+ * so a later creation invalidates the proposal instead of silently racing it.
14
+ *
15
+ * Bounded model: one accepted snapshot pointer, file effects abstracted as a
16
+ * single update. Real multi-file atomicity needs immutable candidates plus a
17
+ * Git CAS/outbox protocol; this class does not provide that.
18
+ */
19
+ import { createHash } from "node:crypto";
20
+ import { canonical } from "../metacognition/validation.js";
21
+ function digest(value) {
22
+ return createHash("sha256").update(canonical(value), "utf8").digest("hex");
23
+ }
24
+ function byKey(a, b) {
25
+ if (a[0] < b[0])
26
+ return -1;
27
+ return a[0] > b[0] ? 1 : 0;
28
+ }
29
+ export class IntegrationPublisher {
30
+ values = new Map();
31
+ versions = new Map();
32
+ acceptedFingerprints = new Map();
33
+ currentRevision = 0;
34
+ constructor(initial) {
35
+ for (const [key, value] of Object.entries(initial)) {
36
+ this.values.set(key, value);
37
+ this.versions.set(key, 1);
38
+ }
39
+ }
40
+ get revision() {
41
+ return this.currentRevision;
42
+ }
43
+ snapshot() {
44
+ return Object.fromEntries(this.values);
45
+ }
46
+ /** Record the versions of every key this change reads or writes. */
47
+ propose(proposalId, reads, writes) {
48
+ if (typeof proposalId !== "string" || proposalId.length === 0) {
49
+ throw new TypeError("proposalId must be a non-empty string");
50
+ }
51
+ const touched = new Set([...reads, ...Object.keys(writes)]);
52
+ return Object.freeze({
53
+ proposalId,
54
+ readVersions: Object.freeze([...touched].map((key) => Object.freeze([key, this.versions.get(key) ?? 0])).sort(byKey)),
55
+ writes: Object.freeze(Object.entries(writes)
56
+ .map(([key, value]) => Object.freeze([key, value]))
57
+ .sort(byKey)),
58
+ });
59
+ }
60
+ isCurrent(proposal) {
61
+ const declared = new Map(proposal.readVersions);
62
+ if (proposal.writes.some(([key]) => !declared.has(key)))
63
+ return false;
64
+ return proposal.readVersions.every(([key, version]) => (this.versions.get(key) ?? 0) === version);
65
+ }
66
+ applied(proposal) {
67
+ const next = new Map(this.values);
68
+ for (const [key, value] of proposal.writes) {
69
+ if (value === null)
70
+ next.delete(key);
71
+ else
72
+ next.set(key, value);
73
+ }
74
+ return next;
75
+ }
76
+ stage(proposal, contractDigest = "contract-v1", checkDigest = "checks-v1", environmentDigest = "env-v1") {
77
+ if (!this.isCurrent(proposal))
78
+ return null;
79
+ return Object.freeze({
80
+ proposal,
81
+ parentRevision: this.currentRevision,
82
+ candidateDigest: digest(Object.fromEntries(this.applied(proposal))),
83
+ contractDigest,
84
+ checkDigest,
85
+ environmentDigest,
86
+ });
87
+ }
88
+ publish(staged, receipt) {
89
+ const proposal = staged.proposal;
90
+ const fingerprint = digest({
91
+ proposalId: proposal.proposalId,
92
+ readVersions: proposal.readVersions,
93
+ writes: proposal.writes,
94
+ });
95
+ const seen = this.acceptedFingerprints.get(proposal.proposalId);
96
+ if (seen !== undefined)
97
+ return seen === fingerprint ? "already-accepted" : "id-collision";
98
+ if (staged.parentRevision !== this.currentRevision || !this.isCurrent(proposal))
99
+ return "stale";
100
+ if (!sameBinding(staged, receipt))
101
+ return "invalid-binding";
102
+ if (receipt.verdict !== "pass" || receipt.pendingEffects !== 0)
103
+ return "unverified";
104
+ // Recompute the candidate: guards a staged payload swapped after verification.
105
+ const rebuilt = this.stage(proposal, staged.contractDigest, staged.checkDigest, staged.environmentDigest);
106
+ if (rebuilt === null || rebuilt.candidateDigest !== staged.candidateDigest)
107
+ return "invalid-binding";
108
+ for (const [key, value] of proposal.writes) {
109
+ if (value === null)
110
+ this.values.delete(key);
111
+ else
112
+ this.values.set(key, value);
113
+ this.versions.set(key, (this.versions.get(key) ?? 0) + 1);
114
+ }
115
+ this.currentRevision += 1;
116
+ this.acceptedFingerprints.set(proposal.proposalId, fingerprint);
117
+ return "accepted";
118
+ }
119
+ }
120
+ function sameBinding(staged, receipt) {
121
+ return (staged.candidateDigest === receipt.candidateDigest &&
122
+ staged.contractDigest === receipt.contractDigest &&
123
+ staged.checkDigest === receipt.checkDigest &&
124
+ staged.environmentDigest === receipt.environmentDigest);
125
+ }
126
+ //# sourceMappingURL=integration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.js","sourceRoot":"","sources":["../../src/coordination/integration.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Candidate publication with read-version preconditions and bound receipts.\n *\n * A publication is admitted only when the snapshot that was verified is still\n * the snapshot being published. Three separate facts are checked, because any\n * one of them alone lets a stale candidate through:\n * - every key the proposal read is still at the version it read,\n * - the parent revision has not advanced,\n * - the receipt is bound to this candidate, contract, check set and\n * environment rather than to an earlier composition of them.\n *\n * Absence is a dependency: reading a key that did not exist records version 0,\n * so a later creation invalidates the proposal instead of silently racing it.\n *\n * Bounded model: one accepted snapshot pointer, file effects abstracted as a\n * single update. Real multi-file atomicity needs immutable candidates plus a\n * Git CAS/outbox protocol; this class does not provide that.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { canonical } from \"../metacognition/validation.ts\";\n\nexport type PublicationOutcome =\n\t| \"accepted\"\n\t| \"already-accepted\"\n\t| \"id-collision\"\n\t| \"stale\"\n\t| \"invalid-binding\"\n\t| \"unverified\";\n\nexport type WriteValue = string | null;\n\nexport interface ChangeProposal {\n\treadonly proposalId: string;\n\t/** Every key touched, with the version observed at propose time. */\n\treadonly readVersions: readonly (readonly [string, number])[];\n\t/** `null` deletes the key. */\n\treadonly writes: readonly (readonly [string, WriteValue])[];\n}\n\nexport interface StagedChange {\n\treadonly proposal: ChangeProposal;\n\treadonly parentRevision: number;\n\treadonly candidateDigest: string;\n\treadonly contractDigest: string;\n\treadonly checkDigest: string;\n\treadonly environmentDigest: string;\n}\n\nexport interface VerificationReceipt {\n\treadonly candidateDigest: string;\n\treadonly contractDigest: string;\n\treadonly checkDigest: string;\n\treadonly environmentDigest: string;\n\treadonly verdict: \"pass\" | \"fail\" | \"inconclusive\";\n\treadonly pendingEffects: number;\n}\n\nfunction digest(value: unknown): string {\n\treturn createHash(\"sha256\").update(canonical(value), \"utf8\").digest(\"hex\");\n}\n\nfunction byKey(a: readonly [string, unknown], b: readonly [string, unknown]): number {\n\tif (a[0] < b[0]) return -1;\n\treturn a[0] > b[0] ? 1 : 0;\n}\n\nexport class IntegrationPublisher {\n\tprivate readonly values = new Map<string, string>();\n\tprivate readonly versions = new Map<string, number>();\n\tprivate readonly acceptedFingerprints = new Map<string, string>();\n\tprivate currentRevision = 0;\n\n\tconstructor(initial: Readonly<Record<string, string>>) {\n\t\tfor (const [key, value] of Object.entries(initial)) {\n\t\t\tthis.values.set(key, value);\n\t\t\tthis.versions.set(key, 1);\n\t\t}\n\t}\n\n\tget revision(): number {\n\t\treturn this.currentRevision;\n\t}\n\n\tsnapshot(): Record<string, string> {\n\t\treturn Object.fromEntries(this.values);\n\t}\n\n\t/** Record the versions of every key this change reads or writes. */\n\tpropose(proposalId: string, reads: readonly string[], writes: Readonly<Record<string, WriteValue>>): ChangeProposal {\n\t\tif (typeof proposalId !== \"string\" || proposalId.length === 0) {\n\t\t\tthrow new TypeError(\"proposalId must be a non-empty string\");\n\t\t}\n\t\tconst touched = new Set<string>([...reads, ...Object.keys(writes)]);\n\t\treturn Object.freeze({\n\t\t\tproposalId,\n\t\t\treadVersions: Object.freeze(\n\t\t\t\t[...touched].map((key) => Object.freeze([key, this.versions.get(key) ?? 0] as const)).sort(byKey),\n\t\t\t),\n\t\t\twrites: Object.freeze(\n\t\t\t\tObject.entries(writes)\n\t\t\t\t\t.map(([key, value]) => Object.freeze([key, value] as const))\n\t\t\t\t\t.sort(byKey),\n\t\t\t),\n\t\t});\n\t}\n\n\tprivate isCurrent(proposal: ChangeProposal): boolean {\n\t\tconst declared = new Map(proposal.readVersions);\n\t\tif (proposal.writes.some(([key]) => !declared.has(key))) return false;\n\t\treturn proposal.readVersions.every(([key, version]) => (this.versions.get(key) ?? 0) === version);\n\t}\n\n\tprivate applied(proposal: ChangeProposal): Map<string, string> {\n\t\tconst next = new Map(this.values);\n\t\tfor (const [key, value] of proposal.writes) {\n\t\t\tif (value === null) next.delete(key);\n\t\t\telse next.set(key, value);\n\t\t}\n\t\treturn next;\n\t}\n\n\tstage(\n\t\tproposal: ChangeProposal,\n\t\tcontractDigest = \"contract-v1\",\n\t\tcheckDigest = \"checks-v1\",\n\t\tenvironmentDigest = \"env-v1\",\n\t): StagedChange | null {\n\t\tif (!this.isCurrent(proposal)) return null;\n\t\treturn Object.freeze({\n\t\t\tproposal,\n\t\t\tparentRevision: this.currentRevision,\n\t\t\tcandidateDigest: digest(Object.fromEntries(this.applied(proposal))),\n\t\t\tcontractDigest,\n\t\t\tcheckDigest,\n\t\t\tenvironmentDigest,\n\t\t});\n\t}\n\n\tpublish(staged: StagedChange, receipt: VerificationReceipt): PublicationOutcome {\n\t\tconst proposal = staged.proposal;\n\t\tconst fingerprint = digest({\n\t\t\tproposalId: proposal.proposalId,\n\t\t\treadVersions: proposal.readVersions,\n\t\t\twrites: proposal.writes,\n\t\t});\n\t\tconst seen = this.acceptedFingerprints.get(proposal.proposalId);\n\t\tif (seen !== undefined) return seen === fingerprint ? \"already-accepted\" : \"id-collision\";\n\t\tif (staged.parentRevision !== this.currentRevision || !this.isCurrent(proposal)) return \"stale\";\n\t\tif (!sameBinding(staged, receipt)) return \"invalid-binding\";\n\t\tif (receipt.verdict !== \"pass\" || receipt.pendingEffects !== 0) return \"unverified\";\n\n\t\t// Recompute the candidate: guards a staged payload swapped after verification.\n\t\tconst rebuilt = this.stage(proposal, staged.contractDigest, staged.checkDigest, staged.environmentDigest);\n\t\tif (rebuilt === null || rebuilt.candidateDigest !== staged.candidateDigest) return \"invalid-binding\";\n\n\t\tfor (const [key, value] of proposal.writes) {\n\t\t\tif (value === null) this.values.delete(key);\n\t\t\telse this.values.set(key, value);\n\t\t\tthis.versions.set(key, (this.versions.get(key) ?? 0) + 1);\n\t\t}\n\t\tthis.currentRevision += 1;\n\t\tthis.acceptedFingerprints.set(proposal.proposalId, fingerprint);\n\t\treturn \"accepted\";\n\t}\n}\n\nfunction sameBinding(staged: StagedChange, receipt: VerificationReceipt): boolean {\n\treturn (\n\t\tstaged.candidateDigest === receipt.candidateDigest &&\n\t\tstaged.contractDigest === receipt.contractDigest &&\n\t\tstaged.checkDigest === receipt.checkDigest &&\n\t\tstaged.environmentDigest === receipt.environmentDigest\n\t);\n}\n"]}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Authorized operation lifecycle for GUI-driving adapters (Jev audit A6).
3
+ *
4
+ * Two reproduced defects shape this module:
5
+ * F01 — an adapter re-interpreted a free-text instruction at execution time,
6
+ * so an approved candidate and the executed action could differ. Here
7
+ * dispatch consumes a recorded observation binding, never a sentence.
8
+ * F02 — a cancellation signal was accepted and then ignored across the
9
+ * approval await, so a cancelled operation still dispatched. Here the
10
+ * permit is re-evaluated at the dispatch boundary, and cancellation
11
+ * after dispatch can never be reported as cancelled-before-dispatch.
12
+ *
13
+ * Pure state machine: no browser, no driver, no timers. "Dispatched" means the
14
+ * command left this process, which is not evidence about the remote effect —
15
+ * that is why `outcome-unknown` is a first-class state rather than a failure.
16
+ */
17
+ import type { Sequence } from "./types.ts";
18
+ export type OperationState = "created" | "observed" | "proposed" | "authorized" | "dispatched" | "applied" | "verified" | "inspection-required" | "failed-confirmed" | "outcome-unknown" | "cancelled-before-dispatch";
19
+ /** Outcomes a driver may report once a command has left the process. */
20
+ export type SettledOutcome = "applied" | "failed-confirmed" | "outcome-unknown";
21
+ /**
22
+ * Host-held reference to one observed candidate.
23
+ *
24
+ * The adapter receives these identifiers, not an instruction string, so the
25
+ * action that was approved is the action that runs.
26
+ */
27
+ export interface ObservationBinding {
28
+ readonly observationId: string;
29
+ readonly actionId: string;
30
+ readonly targetId: string;
31
+ /** Document/DOM generation the candidate was observed against. */
32
+ readonly documentGeneration: Sequence;
33
+ }
34
+ export type PermitDenial = "cancelled" | "policy-denied" | "approval-mismatch" | "stale-lease" | "observation-invalid" | "budget-exhausted";
35
+ export type PermitDecision = {
36
+ readonly allowed: true;
37
+ } | {
38
+ readonly allowed: false;
39
+ readonly reason: PermitDenial;
40
+ };
41
+ export interface PermitInput {
42
+ readonly policyAllowed: boolean;
43
+ /** Digest of the intent actually about to run. */
44
+ readonly intentDigest: string;
45
+ /** Digest the operator approved; a post-approval edit breaks this equality. */
46
+ readonly approvalDigest: string;
47
+ readonly policyVersion: string;
48
+ readonly approvalPolicyVersion: string;
49
+ readonly leaseGeneration: Sequence;
50
+ readonly currentLeaseGeneration: Sequence;
51
+ readonly observationValid: boolean;
52
+ readonly cancelled: boolean;
53
+ readonly budgetAvailable: boolean;
54
+ }
55
+ /**
56
+ * Permit(I) = PolicyAllowed ∧ ApprovalMatches ∧ LeaseCurrent ∧ ObservationValid
57
+ * ∧ ¬Cancelled ∧ BudgetAvailable.
58
+ *
59
+ * Cancellation is reported first: when a run is cancelled, saying "budget
60
+ * exhausted" would send the operator looking for the wrong problem.
61
+ */
62
+ export declare function evaluatePermit(input: PermitInput): PermitDecision;
63
+ export declare class OperationLifecycle {
64
+ readonly operationId: string;
65
+ private current;
66
+ private readonly states;
67
+ private observations;
68
+ private selected;
69
+ private hasDispatched;
70
+ private cancelRequested;
71
+ constructor(operationId: string);
72
+ get state(): OperationState;
73
+ get history(): readonly OperationState[];
74
+ /** True once a command left this process; it says nothing about the effect. */
75
+ get dispatched(): boolean;
76
+ get cancellationRequested(): boolean;
77
+ private enter;
78
+ private assertOpen;
79
+ /** Record the host-held candidate set. Without this, nothing can dispatch. */
80
+ observe(candidates: readonly ObservationBinding[]): void;
81
+ /** Select one observed candidate. Free-text re-interpretation has no path here. */
82
+ propose(candidate: ObservationBinding): void;
83
+ authorize(permit: PermitInput): PermitDecision;
84
+ /**
85
+ * Re-evaluate the permit and hand the command to the driver.
86
+ *
87
+ * The second evaluation is the point of the audit finding: approval and
88
+ * dispatch are separated by awaits (queueing, the operator prompt, browser
89
+ * startup), and state observed before those awaits is not state now.
90
+ */
91
+ dispatch(permit: PermitInput): PermitDecision;
92
+ private applyPreDispatchDenial;
93
+ /** Driver-reported outcome. Only valid once the command actually left. */
94
+ settle(outcome: SettledOutcome): void;
95
+ /** Explicit postcondition check. A driver's return value is not this. */
96
+ verify(postconditionMet: boolean): void;
97
+ /**
98
+ * Request cancellation.
99
+ *
100
+ * Before dispatch this is terminal. After dispatch it only records the
101
+ * request: the command is already outside this process, so the real outcome
102
+ * has to be settled separately and stays `outcome-unknown` until it is.
103
+ */
104
+ cancel(): void;
105
+ }
106
+ //# sourceMappingURL=operation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operation.d.ts","sourceRoot":"","sources":["../../src/coordination/operation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Authorized operation lifecycle for GUI-driving adapters (Jev audit A6).\n *\n * Two reproduced defects shape this module:\n * F01 — an adapter re-interpreted a free-text instruction at execution time,\n * so an approved candidate and the executed action could differ. Here\n * dispatch consumes a recorded observation binding, never a sentence.\n * F02 — a cancellation signal was accepted and then ignored across the\n * approval await, so a cancelled operation still dispatched. Here the\n * permit is re-evaluated at the dispatch boundary, and cancellation\n * after dispatch can never be reported as cancelled-before-dispatch.\n *\n * Pure state machine: no browser, no driver, no timers. \"Dispatched\" means the\n * command left this process, which is not evidence about the remote effect —\n * that is why `outcome-unknown` is a first-class state rather than a failure.\n */\n\nimport type { Sequence } from \"./types.ts\";\n\nexport type OperationState =\n\t| \"created\"\n\t| \"observed\"\n\t| \"proposed\"\n\t| \"authorized\"\n\t| \"dispatched\"\n\t| \"applied\"\n\t| \"verified\"\n\t| \"inspection-required\"\n\t| \"failed-confirmed\"\n\t| \"outcome-unknown\"\n\t| \"cancelled-before-dispatch\";\n\n/** Outcomes a driver may report once a command has left the process. */\nexport type SettledOutcome = \"applied\" | \"failed-confirmed\" | \"outcome-unknown\";\n\nconst TERMINAL_STATES: ReadonlySet<OperationState> = new Set<OperationState>([\n\t\"verified\",\n\t\"inspection-required\",\n\t\"failed-confirmed\",\n\t\"cancelled-before-dispatch\",\n]);\n\n/**\n * Host-held reference to one observed candidate.\n *\n * The adapter receives these identifiers, not an instruction string, so the\n * action that was approved is the action that runs.\n */\nexport interface ObservationBinding {\n\treadonly observationId: string;\n\treadonly actionId: string;\n\treadonly targetId: string;\n\t/** Document/DOM generation the candidate was observed against. */\n\treadonly documentGeneration: Sequence;\n}\n\nexport type PermitDenial =\n\t| \"cancelled\"\n\t| \"policy-denied\"\n\t| \"approval-mismatch\"\n\t| \"stale-lease\"\n\t| \"observation-invalid\"\n\t| \"budget-exhausted\";\n\nexport type PermitDecision = { readonly allowed: true } | { readonly allowed: false; readonly reason: PermitDenial };\n\nexport interface PermitInput {\n\treadonly policyAllowed: boolean;\n\t/** Digest of the intent actually about to run. */\n\treadonly intentDigest: string;\n\t/** Digest the operator approved; a post-approval edit breaks this equality. */\n\treadonly approvalDigest: string;\n\treadonly policyVersion: string;\n\treadonly approvalPolicyVersion: string;\n\treadonly leaseGeneration: Sequence;\n\treadonly currentLeaseGeneration: Sequence;\n\treadonly observationValid: boolean;\n\treadonly cancelled: boolean;\n\treadonly budgetAvailable: boolean;\n}\n\n/**\n * Permit(I) = PolicyAllowed ∧ ApprovalMatches ∧ LeaseCurrent ∧ ObservationValid\n * ∧ ¬Cancelled ∧ BudgetAvailable.\n *\n * Cancellation is reported first: when a run is cancelled, saying \"budget\n * exhausted\" would send the operator looking for the wrong problem.\n */\nexport function evaluatePermit(input: PermitInput): PermitDecision {\n\tif (input.cancelled) return { allowed: false, reason: \"cancelled\" };\n\tif (!input.policyAllowed) return { allowed: false, reason: \"policy-denied\" };\n\tif (input.approvalDigest !== input.intentDigest || input.approvalPolicyVersion !== input.policyVersion) {\n\t\treturn { allowed: false, reason: \"approval-mismatch\" };\n\t}\n\tif (input.leaseGeneration !== input.currentLeaseGeneration) return { allowed: false, reason: \"stale-lease\" };\n\tif (!input.observationValid) return { allowed: false, reason: \"observation-invalid\" };\n\tif (!input.budgetAvailable) return { allowed: false, reason: \"budget-exhausted\" };\n\treturn { allowed: true };\n}\n\nexport class OperationLifecycle {\n\treadonly operationId: string;\n\tprivate current: OperationState = \"created\";\n\tprivate readonly states: OperationState[] = [\"created\"];\n\tprivate observations: readonly ObservationBinding[] = [];\n\tprivate selected: ObservationBinding | undefined;\n\tprivate hasDispatched = false;\n\tprivate cancelRequested = false;\n\n\tconstructor(operationId: string) {\n\t\tif (typeof operationId !== \"string\" || operationId.length === 0) {\n\t\t\tthrow new TypeError(\"operationId must be a non-empty string\");\n\t\t}\n\t\tthis.operationId = operationId;\n\t}\n\n\tget state(): OperationState {\n\t\treturn this.current;\n\t}\n\n\tget history(): readonly OperationState[] {\n\t\treturn [...this.states];\n\t}\n\n\t/** True once a command left this process; it says nothing about the effect. */\n\tget dispatched(): boolean {\n\t\treturn this.hasDispatched;\n\t}\n\n\tget cancellationRequested(): boolean {\n\t\treturn this.cancelRequested;\n\t}\n\n\tprivate enter(next: OperationState): void {\n\t\tthis.current = next;\n\t\tthis.states.push(next);\n\t}\n\n\tprivate assertOpen(): void {\n\t\tif (TERMINAL_STATES.has(this.current)) {\n\t\t\tthrow new Error(`operation ${this.operationId} is terminal in state ${this.current}`);\n\t\t}\n\t}\n\n\t/** Record the host-held candidate set. Without this, nothing can dispatch. */\n\tobserve(candidates: readonly ObservationBinding[]): void {\n\t\tthis.assertOpen();\n\t\tif (candidates.length === 0) throw new TypeError(\"observation must yield at least one candidate\");\n\t\tthis.observations = [...candidates];\n\t\tthis.enter(\"observed\");\n\t}\n\n\t/** Select one observed candidate. Free-text re-interpretation has no path here. */\n\tpropose(candidate: ObservationBinding): void {\n\t\tthis.assertOpen();\n\t\tif (this.observations.length === 0) {\n\t\t\tthrow new Error(\"cannot propose before observe: the candidate set is unbound\");\n\t\t}\n\t\tconst match = this.observations.find(\n\t\t\t(o) =>\n\t\t\t\to.observationId === candidate.observationId &&\n\t\t\t\to.actionId === candidate.actionId &&\n\t\t\t\to.targetId === candidate.targetId &&\n\t\t\t\to.documentGeneration === candidate.documentGeneration,\n\t\t);\n\t\tif (match === undefined) throw new Error(\"proposed action is not among the recorded observations\");\n\t\tthis.selected = match;\n\t\tthis.enter(\"proposed\");\n\t}\n\n\tauthorize(permit: PermitInput): PermitDecision {\n\t\tthis.assertOpen();\n\t\tif (this.current !== \"proposed\") throw new Error(\"authorize requires a proposed operation\");\n\t\tconst decision = evaluatePermit(permit);\n\t\tif (!decision.allowed) {\n\t\t\tthis.applyPreDispatchDenial(decision.reason);\n\t\t\treturn decision;\n\t\t}\n\t\tthis.enter(\"authorized\");\n\t\treturn decision;\n\t}\n\n\t/**\n\t * Re-evaluate the permit and hand the command to the driver.\n\t *\n\t * The second evaluation is the point of the audit finding: approval and\n\t * dispatch are separated by awaits (queueing, the operator prompt, browser\n\t * startup), and state observed before those awaits is not state now.\n\t */\n\tdispatch(permit: PermitInput): PermitDecision {\n\t\tthis.assertOpen();\n\t\tif (this.current !== \"authorized\") throw new Error(\"dispatch requires an authorized operation\");\n\t\tif (this.selected === undefined) throw new Error(\"dispatch requires a bound observation\");\n\t\tconst decision = evaluatePermit(permit);\n\t\tif (!decision.allowed) {\n\t\t\tthis.applyPreDispatchDenial(decision.reason);\n\t\t\treturn decision;\n\t\t}\n\t\tthis.hasDispatched = true;\n\t\tthis.enter(\"dispatched\");\n\t\treturn decision;\n\t}\n\n\tprivate applyPreDispatchDenial(reason: PermitDenial): void {\n\t\tif (reason === \"cancelled\") {\n\t\t\tthis.cancelRequested = true;\n\t\t\tthis.enter(\"cancelled-before-dispatch\");\n\t\t}\n\t}\n\n\t/** Driver-reported outcome. Only valid once the command actually left. */\n\tsettle(outcome: SettledOutcome): void {\n\t\tif (!this.hasDispatched) throw new Error(\"settle requires a dispatched operation\");\n\t\tif (this.current === \"verified\" || this.current === \"inspection-required\") {\n\t\t\tthrow new Error(\"outcome already verified\");\n\t\t}\n\t\tthis.enter(outcome);\n\t}\n\n\t/** Explicit postcondition check. A driver's return value is not this. */\n\tverify(postconditionMet: boolean): void {\n\t\tif (this.current !== \"applied\") throw new Error(\"verify requires an applied outcome\");\n\t\tthis.enter(postconditionMet ? \"verified\" : \"inspection-required\");\n\t}\n\n\t/**\n\t * Request cancellation.\n\t *\n\t * Before dispatch this is terminal. After dispatch it only records the\n\t * request: the command is already outside this process, so the real outcome\n\t * has to be settled separately and stays `outcome-unknown` until it is.\n\t */\n\tcancel(): void {\n\t\tthis.cancelRequested = true;\n\t\tif (TERMINAL_STATES.has(this.current)) return;\n\t\tif (!this.hasDispatched) {\n\t\t\tthis.enter(\"cancelled-before-dispatch\");\n\t\t\treturn;\n\t\t}\n\t\tif (this.current === \"dispatched\") this.enter(\"outcome-unknown\");\n\t}\n}\n"]}
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Authorized operation lifecycle for GUI-driving adapters (Jev audit A6).
3
+ *
4
+ * Two reproduced defects shape this module:
5
+ * F01 — an adapter re-interpreted a free-text instruction at execution time,
6
+ * so an approved candidate and the executed action could differ. Here
7
+ * dispatch consumes a recorded observation binding, never a sentence.
8
+ * F02 — a cancellation signal was accepted and then ignored across the
9
+ * approval await, so a cancelled operation still dispatched. Here the
10
+ * permit is re-evaluated at the dispatch boundary, and cancellation
11
+ * after dispatch can never be reported as cancelled-before-dispatch.
12
+ *
13
+ * Pure state machine: no browser, no driver, no timers. "Dispatched" means the
14
+ * command left this process, which is not evidence about the remote effect —
15
+ * that is why `outcome-unknown` is a first-class state rather than a failure.
16
+ */
17
+ const TERMINAL_STATES = new Set([
18
+ "verified",
19
+ "inspection-required",
20
+ "failed-confirmed",
21
+ "cancelled-before-dispatch",
22
+ ]);
23
+ /**
24
+ * Permit(I) = PolicyAllowed ∧ ApprovalMatches ∧ LeaseCurrent ∧ ObservationValid
25
+ * ∧ ¬Cancelled ∧ BudgetAvailable.
26
+ *
27
+ * Cancellation is reported first: when a run is cancelled, saying "budget
28
+ * exhausted" would send the operator looking for the wrong problem.
29
+ */
30
+ export function evaluatePermit(input) {
31
+ if (input.cancelled)
32
+ return { allowed: false, reason: "cancelled" };
33
+ if (!input.policyAllowed)
34
+ return { allowed: false, reason: "policy-denied" };
35
+ if (input.approvalDigest !== input.intentDigest || input.approvalPolicyVersion !== input.policyVersion) {
36
+ return { allowed: false, reason: "approval-mismatch" };
37
+ }
38
+ if (input.leaseGeneration !== input.currentLeaseGeneration)
39
+ return { allowed: false, reason: "stale-lease" };
40
+ if (!input.observationValid)
41
+ return { allowed: false, reason: "observation-invalid" };
42
+ if (!input.budgetAvailable)
43
+ return { allowed: false, reason: "budget-exhausted" };
44
+ return { allowed: true };
45
+ }
46
+ export class OperationLifecycle {
47
+ operationId;
48
+ current = "created";
49
+ states = ["created"];
50
+ observations = [];
51
+ selected;
52
+ hasDispatched = false;
53
+ cancelRequested = false;
54
+ constructor(operationId) {
55
+ if (typeof operationId !== "string" || operationId.length === 0) {
56
+ throw new TypeError("operationId must be a non-empty string");
57
+ }
58
+ this.operationId = operationId;
59
+ }
60
+ get state() {
61
+ return this.current;
62
+ }
63
+ get history() {
64
+ return [...this.states];
65
+ }
66
+ /** True once a command left this process; it says nothing about the effect. */
67
+ get dispatched() {
68
+ return this.hasDispatched;
69
+ }
70
+ get cancellationRequested() {
71
+ return this.cancelRequested;
72
+ }
73
+ enter(next) {
74
+ this.current = next;
75
+ this.states.push(next);
76
+ }
77
+ assertOpen() {
78
+ if (TERMINAL_STATES.has(this.current)) {
79
+ throw new Error(`operation ${this.operationId} is terminal in state ${this.current}`);
80
+ }
81
+ }
82
+ /** Record the host-held candidate set. Without this, nothing can dispatch. */
83
+ observe(candidates) {
84
+ this.assertOpen();
85
+ if (candidates.length === 0)
86
+ throw new TypeError("observation must yield at least one candidate");
87
+ this.observations = [...candidates];
88
+ this.enter("observed");
89
+ }
90
+ /** Select one observed candidate. Free-text re-interpretation has no path here. */
91
+ propose(candidate) {
92
+ this.assertOpen();
93
+ if (this.observations.length === 0) {
94
+ throw new Error("cannot propose before observe: the candidate set is unbound");
95
+ }
96
+ const match = this.observations.find((o) => o.observationId === candidate.observationId &&
97
+ o.actionId === candidate.actionId &&
98
+ o.targetId === candidate.targetId &&
99
+ o.documentGeneration === candidate.documentGeneration);
100
+ if (match === undefined)
101
+ throw new Error("proposed action is not among the recorded observations");
102
+ this.selected = match;
103
+ this.enter("proposed");
104
+ }
105
+ authorize(permit) {
106
+ this.assertOpen();
107
+ if (this.current !== "proposed")
108
+ throw new Error("authorize requires a proposed operation");
109
+ const decision = evaluatePermit(permit);
110
+ if (!decision.allowed) {
111
+ this.applyPreDispatchDenial(decision.reason);
112
+ return decision;
113
+ }
114
+ this.enter("authorized");
115
+ return decision;
116
+ }
117
+ /**
118
+ * Re-evaluate the permit and hand the command to the driver.
119
+ *
120
+ * The second evaluation is the point of the audit finding: approval and
121
+ * dispatch are separated by awaits (queueing, the operator prompt, browser
122
+ * startup), and state observed before those awaits is not state now.
123
+ */
124
+ dispatch(permit) {
125
+ this.assertOpen();
126
+ if (this.current !== "authorized")
127
+ throw new Error("dispatch requires an authorized operation");
128
+ if (this.selected === undefined)
129
+ throw new Error("dispatch requires a bound observation");
130
+ const decision = evaluatePermit(permit);
131
+ if (!decision.allowed) {
132
+ this.applyPreDispatchDenial(decision.reason);
133
+ return decision;
134
+ }
135
+ this.hasDispatched = true;
136
+ this.enter("dispatched");
137
+ return decision;
138
+ }
139
+ applyPreDispatchDenial(reason) {
140
+ if (reason === "cancelled") {
141
+ this.cancelRequested = true;
142
+ this.enter("cancelled-before-dispatch");
143
+ }
144
+ }
145
+ /** Driver-reported outcome. Only valid once the command actually left. */
146
+ settle(outcome) {
147
+ if (!this.hasDispatched)
148
+ throw new Error("settle requires a dispatched operation");
149
+ if (this.current === "verified" || this.current === "inspection-required") {
150
+ throw new Error("outcome already verified");
151
+ }
152
+ this.enter(outcome);
153
+ }
154
+ /** Explicit postcondition check. A driver's return value is not this. */
155
+ verify(postconditionMet) {
156
+ if (this.current !== "applied")
157
+ throw new Error("verify requires an applied outcome");
158
+ this.enter(postconditionMet ? "verified" : "inspection-required");
159
+ }
160
+ /**
161
+ * Request cancellation.
162
+ *
163
+ * Before dispatch this is terminal. After dispatch it only records the
164
+ * request: the command is already outside this process, so the real outcome
165
+ * has to be settled separately and stays `outcome-unknown` until it is.
166
+ */
167
+ cancel() {
168
+ this.cancelRequested = true;
169
+ if (TERMINAL_STATES.has(this.current))
170
+ return;
171
+ if (!this.hasDispatched) {
172
+ this.enter("cancelled-before-dispatch");
173
+ return;
174
+ }
175
+ if (this.current === "dispatched")
176
+ this.enter("outcome-unknown");
177
+ }
178
+ }
179
+ //# sourceMappingURL=operation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operation.js","sourceRoot":"","sources":["../../src/coordination/operation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Authorized operation lifecycle for GUI-driving adapters (Jev audit A6).\n *\n * Two reproduced defects shape this module:\n * F01 — an adapter re-interpreted a free-text instruction at execution time,\n * so an approved candidate and the executed action could differ. Here\n * dispatch consumes a recorded observation binding, never a sentence.\n * F02 — a cancellation signal was accepted and then ignored across the\n * approval await, so a cancelled operation still dispatched. Here the\n * permit is re-evaluated at the dispatch boundary, and cancellation\n * after dispatch can never be reported as cancelled-before-dispatch.\n *\n * Pure state machine: no browser, no driver, no timers. \"Dispatched\" means the\n * command left this process, which is not evidence about the remote effect —\n * that is why `outcome-unknown` is a first-class state rather than a failure.\n */\n\nimport type { Sequence } from \"./types.ts\";\n\nexport type OperationState =\n\t| \"created\"\n\t| \"observed\"\n\t| \"proposed\"\n\t| \"authorized\"\n\t| \"dispatched\"\n\t| \"applied\"\n\t| \"verified\"\n\t| \"inspection-required\"\n\t| \"failed-confirmed\"\n\t| \"outcome-unknown\"\n\t| \"cancelled-before-dispatch\";\n\n/** Outcomes a driver may report once a command has left the process. */\nexport type SettledOutcome = \"applied\" | \"failed-confirmed\" | \"outcome-unknown\";\n\nconst TERMINAL_STATES: ReadonlySet<OperationState> = new Set<OperationState>([\n\t\"verified\",\n\t\"inspection-required\",\n\t\"failed-confirmed\",\n\t\"cancelled-before-dispatch\",\n]);\n\n/**\n * Host-held reference to one observed candidate.\n *\n * The adapter receives these identifiers, not an instruction string, so the\n * action that was approved is the action that runs.\n */\nexport interface ObservationBinding {\n\treadonly observationId: string;\n\treadonly actionId: string;\n\treadonly targetId: string;\n\t/** Document/DOM generation the candidate was observed against. */\n\treadonly documentGeneration: Sequence;\n}\n\nexport type PermitDenial =\n\t| \"cancelled\"\n\t| \"policy-denied\"\n\t| \"approval-mismatch\"\n\t| \"stale-lease\"\n\t| \"observation-invalid\"\n\t| \"budget-exhausted\";\n\nexport type PermitDecision = { readonly allowed: true } | { readonly allowed: false; readonly reason: PermitDenial };\n\nexport interface PermitInput {\n\treadonly policyAllowed: boolean;\n\t/** Digest of the intent actually about to run. */\n\treadonly intentDigest: string;\n\t/** Digest the operator approved; a post-approval edit breaks this equality. */\n\treadonly approvalDigest: string;\n\treadonly policyVersion: string;\n\treadonly approvalPolicyVersion: string;\n\treadonly leaseGeneration: Sequence;\n\treadonly currentLeaseGeneration: Sequence;\n\treadonly observationValid: boolean;\n\treadonly cancelled: boolean;\n\treadonly budgetAvailable: boolean;\n}\n\n/**\n * Permit(I) = PolicyAllowed ∧ ApprovalMatches ∧ LeaseCurrent ∧ ObservationValid\n * ∧ ¬Cancelled ∧ BudgetAvailable.\n *\n * Cancellation is reported first: when a run is cancelled, saying \"budget\n * exhausted\" would send the operator looking for the wrong problem.\n */\nexport function evaluatePermit(input: PermitInput): PermitDecision {\n\tif (input.cancelled) return { allowed: false, reason: \"cancelled\" };\n\tif (!input.policyAllowed) return { allowed: false, reason: \"policy-denied\" };\n\tif (input.approvalDigest !== input.intentDigest || input.approvalPolicyVersion !== input.policyVersion) {\n\t\treturn { allowed: false, reason: \"approval-mismatch\" };\n\t}\n\tif (input.leaseGeneration !== input.currentLeaseGeneration) return { allowed: false, reason: \"stale-lease\" };\n\tif (!input.observationValid) return { allowed: false, reason: \"observation-invalid\" };\n\tif (!input.budgetAvailable) return { allowed: false, reason: \"budget-exhausted\" };\n\treturn { allowed: true };\n}\n\nexport class OperationLifecycle {\n\treadonly operationId: string;\n\tprivate current: OperationState = \"created\";\n\tprivate readonly states: OperationState[] = [\"created\"];\n\tprivate observations: readonly ObservationBinding[] = [];\n\tprivate selected: ObservationBinding | undefined;\n\tprivate hasDispatched = false;\n\tprivate cancelRequested = false;\n\n\tconstructor(operationId: string) {\n\t\tif (typeof operationId !== \"string\" || operationId.length === 0) {\n\t\t\tthrow new TypeError(\"operationId must be a non-empty string\");\n\t\t}\n\t\tthis.operationId = operationId;\n\t}\n\n\tget state(): OperationState {\n\t\treturn this.current;\n\t}\n\n\tget history(): readonly OperationState[] {\n\t\treturn [...this.states];\n\t}\n\n\t/** True once a command left this process; it says nothing about the effect. */\n\tget dispatched(): boolean {\n\t\treturn this.hasDispatched;\n\t}\n\n\tget cancellationRequested(): boolean {\n\t\treturn this.cancelRequested;\n\t}\n\n\tprivate enter(next: OperationState): void {\n\t\tthis.current = next;\n\t\tthis.states.push(next);\n\t}\n\n\tprivate assertOpen(): void {\n\t\tif (TERMINAL_STATES.has(this.current)) {\n\t\t\tthrow new Error(`operation ${this.operationId} is terminal in state ${this.current}`);\n\t\t}\n\t}\n\n\t/** Record the host-held candidate set. Without this, nothing can dispatch. */\n\tobserve(candidates: readonly ObservationBinding[]): void {\n\t\tthis.assertOpen();\n\t\tif (candidates.length === 0) throw new TypeError(\"observation must yield at least one candidate\");\n\t\tthis.observations = [...candidates];\n\t\tthis.enter(\"observed\");\n\t}\n\n\t/** Select one observed candidate. Free-text re-interpretation has no path here. */\n\tpropose(candidate: ObservationBinding): void {\n\t\tthis.assertOpen();\n\t\tif (this.observations.length === 0) {\n\t\t\tthrow new Error(\"cannot propose before observe: the candidate set is unbound\");\n\t\t}\n\t\tconst match = this.observations.find(\n\t\t\t(o) =>\n\t\t\t\to.observationId === candidate.observationId &&\n\t\t\t\to.actionId === candidate.actionId &&\n\t\t\t\to.targetId === candidate.targetId &&\n\t\t\t\to.documentGeneration === candidate.documentGeneration,\n\t\t);\n\t\tif (match === undefined) throw new Error(\"proposed action is not among the recorded observations\");\n\t\tthis.selected = match;\n\t\tthis.enter(\"proposed\");\n\t}\n\n\tauthorize(permit: PermitInput): PermitDecision {\n\t\tthis.assertOpen();\n\t\tif (this.current !== \"proposed\") throw new Error(\"authorize requires a proposed operation\");\n\t\tconst decision = evaluatePermit(permit);\n\t\tif (!decision.allowed) {\n\t\t\tthis.applyPreDispatchDenial(decision.reason);\n\t\t\treturn decision;\n\t\t}\n\t\tthis.enter(\"authorized\");\n\t\treturn decision;\n\t}\n\n\t/**\n\t * Re-evaluate the permit and hand the command to the driver.\n\t *\n\t * The second evaluation is the point of the audit finding: approval and\n\t * dispatch are separated by awaits (queueing, the operator prompt, browser\n\t * startup), and state observed before those awaits is not state now.\n\t */\n\tdispatch(permit: PermitInput): PermitDecision {\n\t\tthis.assertOpen();\n\t\tif (this.current !== \"authorized\") throw new Error(\"dispatch requires an authorized operation\");\n\t\tif (this.selected === undefined) throw new Error(\"dispatch requires a bound observation\");\n\t\tconst decision = evaluatePermit(permit);\n\t\tif (!decision.allowed) {\n\t\t\tthis.applyPreDispatchDenial(decision.reason);\n\t\t\treturn decision;\n\t\t}\n\t\tthis.hasDispatched = true;\n\t\tthis.enter(\"dispatched\");\n\t\treturn decision;\n\t}\n\n\tprivate applyPreDispatchDenial(reason: PermitDenial): void {\n\t\tif (reason === \"cancelled\") {\n\t\t\tthis.cancelRequested = true;\n\t\t\tthis.enter(\"cancelled-before-dispatch\");\n\t\t}\n\t}\n\n\t/** Driver-reported outcome. Only valid once the command actually left. */\n\tsettle(outcome: SettledOutcome): void {\n\t\tif (!this.hasDispatched) throw new Error(\"settle requires a dispatched operation\");\n\t\tif (this.current === \"verified\" || this.current === \"inspection-required\") {\n\t\t\tthrow new Error(\"outcome already verified\");\n\t\t}\n\t\tthis.enter(outcome);\n\t}\n\n\t/** Explicit postcondition check. A driver's return value is not this. */\n\tverify(postconditionMet: boolean): void {\n\t\tif (this.current !== \"applied\") throw new Error(\"verify requires an applied outcome\");\n\t\tthis.enter(postconditionMet ? \"verified\" : \"inspection-required\");\n\t}\n\n\t/**\n\t * Request cancellation.\n\t *\n\t * Before dispatch this is terminal. After dispatch it only records the\n\t * request: the command is already outside this process, so the real outcome\n\t * has to be settled separately and stays `outcome-unknown` until it is.\n\t */\n\tcancel(): void {\n\t\tthis.cancelRequested = true;\n\t\tif (TERMINAL_STATES.has(this.current)) return;\n\t\tif (!this.hasDispatched) {\n\t\t\tthis.enter(\"cancelled-before-dispatch\");\n\t\t\treturn;\n\t\t}\n\t\tif (this.current === \"dispatched\") this.enter(\"outcome-unknown\");\n\t}\n}\n"]}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Resource claim identity and conflict algebra.
3
+ *
4
+ * Two claims conflict when they name overlapping resources and at least one
5
+ * side writes. Overlap is prefix containment on path components, so `src`
6
+ * covers `src/a/x` but never `src/ab` — a shared character prefix that straddles
7
+ * a component boundary is a different resource, not a nested one.
8
+ */
9
+ import type { ResourceClaim } from "./types.ts";
10
+ export interface ResourceClaimInput {
11
+ readonly namespace: string;
12
+ readonly instanceId: string;
13
+ readonly canonicalKey: string;
14
+ readonly access: string;
15
+ readonly generation: string;
16
+ }
17
+ /**
18
+ * Validate and freeze one claim.
19
+ *
20
+ * A non-canonical key is rejected rather than normalized: normalizing
21
+ * `a/../b` here would let one session hold `a/../b` and another hold `b`
22
+ * while both believe their scopes are disjoint.
23
+ */
24
+ export declare function canonicalClaim(input: ResourceClaimInput): ResourceClaim;
25
+ /** Same resource identity, or one key nested under the other. Ignores access. */
26
+ export declare function resourcesOverlap(a: ResourceClaim, b: ResourceClaim): boolean;
27
+ export declare function claimsConflict(a: ResourceClaim, b: ResourceClaim): boolean;
28
+ export declare function claimSetsConflict(a: readonly ResourceClaim[], b: readonly ResourceClaim[]): boolean;
29
+ /** Stable key for exact-set comparison when binding a start to its reservation. */
30
+ export declare function claimKey(claim: ResourceClaim): string;
31
+ /** Exact set equality by identity+access; a post-hook that widened scope fails this. */
32
+ export declare function sameClaimSet(a: readonly ResourceClaim[], b: readonly ResourceClaim[]): boolean;
33
+ //# sourceMappingURL=resource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/coordination/resource.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAA+B,aAAa,EAAE,MAAM,YAAY,CAAC;AAc7E,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,kBAAkB,GAAG,aAAa,CAkBvE;AAcD,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,GAAG,OAAO,CAI5E;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,GAAG,OAAO,CAE1E;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,SAAS,aAAa,EAAE,EAAE,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,OAAO,CAEnG;AAED,mFAAmF;AACnF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAErD;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,CAAC,EAAE,SAAS,aAAa,EAAE,EAAE,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,OAAO,CAS9F","sourcesContent":["/**\n * Resource claim identity and conflict algebra.\n *\n * Two claims conflict when they name overlapping resources and at least one\n * side writes. Overlap is prefix containment on path components, so `src`\n * covers `src/a/x` but never `src/ab` — a shared character prefix that straddles\n * a component boundary is a different resource, not a nested one.\n */\n\nimport type { ClaimAccess, ClaimNamespace, ResourceClaim } from \"./types.ts\";\nimport { sequence } from \"./types.ts\";\n\nconst CLAIM_NAMESPACES: ReadonlySet<string> = new Set<ClaimNamespace>([\n\t\"filesystem\",\n\t\"git-ref\",\n\t\"contract\",\n\t\"socket\",\n\t\"database\",\n\t\"host-budget\",\n]);\nconst CLAIM_ACCESS: ReadonlySet<string> = new Set<ClaimAccess>([\"read\", \"write\"]);\nconst MAX_KEY_LENGTH = 4096;\n\nexport interface ResourceClaimInput {\n\treadonly namespace: string;\n\treadonly instanceId: string;\n\treadonly canonicalKey: string;\n\treadonly access: string;\n\treadonly generation: string;\n}\n\n/**\n * Validate and freeze one claim.\n *\n * A non-canonical key is rejected rather than normalized: normalizing\n * `a/../b` here would let one session hold `a/../b` and another hold `b`\n * while both believe their scopes are disjoint.\n */\nexport function canonicalClaim(input: ResourceClaimInput): ResourceClaim {\n\tif (!CLAIM_NAMESPACES.has(input.namespace)) {\n\t\tthrow new TypeError(`unsupported claim namespace: ${String(input.namespace)}`);\n\t}\n\tif (!CLAIM_ACCESS.has(input.access)) {\n\t\tthrow new TypeError(`unsupported claim access: ${String(input.access)}`);\n\t}\n\tif (typeof input.instanceId !== \"string\" || input.instanceId.length === 0) {\n\t\tthrow new TypeError(\"claim instanceId must be a non-empty string\");\n\t}\n\tassertCanonicalKey(input.canonicalKey);\n\treturn Object.freeze({\n\t\tnamespace: input.namespace as ClaimNamespace,\n\t\tinstanceId: input.instanceId,\n\t\tcanonicalKey: input.canonicalKey,\n\t\taccess: input.access as ClaimAccess,\n\t\tgeneration: sequence(input.generation),\n\t});\n}\n\nfunction assertCanonicalKey(key: unknown): asserts key is string {\n\tif (typeof key !== \"string\" || key.length === 0 || key.length > MAX_KEY_LENGTH) {\n\t\tthrow new TypeError(\"claim canonicalKey must be a bounded non-empty string\");\n\t}\n\tif (key.startsWith(\"/\")) throw new TypeError(\"claim canonicalKey must be relative\");\n\tfor (const part of key.split(\"/\")) {\n\t\tif (part === \"\" || part === \".\" || part === \"..\") {\n\t\t\tthrow new TypeError(`claim canonicalKey is not canonical: ${key}`);\n\t\t}\n\t}\n}\n\n/** Same resource identity, or one key nested under the other. Ignores access. */\nexport function resourcesOverlap(a: ResourceClaim, b: ResourceClaim): boolean {\n\tif (a.namespace !== b.namespace || a.instanceId !== b.instanceId) return false;\n\tif (a.canonicalKey === b.canonicalKey) return true;\n\treturn a.canonicalKey.startsWith(`${b.canonicalKey}/`) || b.canonicalKey.startsWith(`${a.canonicalKey}/`);\n}\n\nexport function claimsConflict(a: ResourceClaim, b: ResourceClaim): boolean {\n\treturn resourcesOverlap(a, b) && (a.access === \"write\" || b.access === \"write\");\n}\n\nexport function claimSetsConflict(a: readonly ResourceClaim[], b: readonly ResourceClaim[]): boolean {\n\treturn a.some((x) => b.some((y) => claimsConflict(x, y)));\n}\n\n/** Stable key for exact-set comparison when binding a start to its reservation. */\nexport function claimKey(claim: ResourceClaim): string {\n\treturn `${claim.namespace}\\u0000${claim.instanceId}\\u0000${claim.canonicalKey}\\u0000${claim.access}`;\n}\n\n/** Exact set equality by identity+access; a post-hook that widened scope fails this. */\nexport function sameClaimSet(a: readonly ResourceClaim[], b: readonly ResourceClaim[]): boolean {\n\tif (a.length !== b.length) return false;\n\tconst byCodeUnit = (x: string, y: string): number => {\n\t\tif (x < y) return -1;\n\t\treturn x > y ? 1 : 0;\n\t};\n\tconst left = a.map(claimKey).sort(byCodeUnit);\n\tconst right = b.map(claimKey).sort(byCodeUnit);\n\treturn left.every((value, index) => value === right[index]);\n}\n"]}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Resource claim identity and conflict algebra.
3
+ *
4
+ * Two claims conflict when they name overlapping resources and at least one
5
+ * side writes. Overlap is prefix containment on path components, so `src`
6
+ * covers `src/a/x` but never `src/ab` — a shared character prefix that straddles
7
+ * a component boundary is a different resource, not a nested one.
8
+ */
9
+ import { sequence } from "./types.js";
10
+ const CLAIM_NAMESPACES = new Set([
11
+ "filesystem",
12
+ "git-ref",
13
+ "contract",
14
+ "socket",
15
+ "database",
16
+ "host-budget",
17
+ ]);
18
+ const CLAIM_ACCESS = new Set(["read", "write"]);
19
+ const MAX_KEY_LENGTH = 4096;
20
+ /**
21
+ * Validate and freeze one claim.
22
+ *
23
+ * A non-canonical key is rejected rather than normalized: normalizing
24
+ * `a/../b` here would let one session hold `a/../b` and another hold `b`
25
+ * while both believe their scopes are disjoint.
26
+ */
27
+ export function canonicalClaim(input) {
28
+ if (!CLAIM_NAMESPACES.has(input.namespace)) {
29
+ throw new TypeError(`unsupported claim namespace: ${String(input.namespace)}`);
30
+ }
31
+ if (!CLAIM_ACCESS.has(input.access)) {
32
+ throw new TypeError(`unsupported claim access: ${String(input.access)}`);
33
+ }
34
+ if (typeof input.instanceId !== "string" || input.instanceId.length === 0) {
35
+ throw new TypeError("claim instanceId must be a non-empty string");
36
+ }
37
+ assertCanonicalKey(input.canonicalKey);
38
+ return Object.freeze({
39
+ namespace: input.namespace,
40
+ instanceId: input.instanceId,
41
+ canonicalKey: input.canonicalKey,
42
+ access: input.access,
43
+ generation: sequence(input.generation),
44
+ });
45
+ }
46
+ function assertCanonicalKey(key) {
47
+ if (typeof key !== "string" || key.length === 0 || key.length > MAX_KEY_LENGTH) {
48
+ throw new TypeError("claim canonicalKey must be a bounded non-empty string");
49
+ }
50
+ if (key.startsWith("/"))
51
+ throw new TypeError("claim canonicalKey must be relative");
52
+ for (const part of key.split("/")) {
53
+ if (part === "" || part === "." || part === "..") {
54
+ throw new TypeError(`claim canonicalKey is not canonical: ${key}`);
55
+ }
56
+ }
57
+ }
58
+ /** Same resource identity, or one key nested under the other. Ignores access. */
59
+ export function resourcesOverlap(a, b) {
60
+ if (a.namespace !== b.namespace || a.instanceId !== b.instanceId)
61
+ return false;
62
+ if (a.canonicalKey === b.canonicalKey)
63
+ return true;
64
+ return a.canonicalKey.startsWith(`${b.canonicalKey}/`) || b.canonicalKey.startsWith(`${a.canonicalKey}/`);
65
+ }
66
+ export function claimsConflict(a, b) {
67
+ return resourcesOverlap(a, b) && (a.access === "write" || b.access === "write");
68
+ }
69
+ export function claimSetsConflict(a, b) {
70
+ return a.some((x) => b.some((y) => claimsConflict(x, y)));
71
+ }
72
+ /** Stable key for exact-set comparison when binding a start to its reservation. */
73
+ export function claimKey(claim) {
74
+ return `${claim.namespace}\u0000${claim.instanceId}\u0000${claim.canonicalKey}\u0000${claim.access}`;
75
+ }
76
+ /** Exact set equality by identity+access; a post-hook that widened scope fails this. */
77
+ export function sameClaimSet(a, b) {
78
+ if (a.length !== b.length)
79
+ return false;
80
+ const byCodeUnit = (x, y) => {
81
+ if (x < y)
82
+ return -1;
83
+ return x > y ? 1 : 0;
84
+ };
85
+ const left = a.map(claimKey).sort(byCodeUnit);
86
+ const right = b.map(claimKey).sort(byCodeUnit);
87
+ return left.every((value, index) => value === right[index]);
88
+ }
89
+ //# sourceMappingURL=resource.js.map