@intentius/chant 0.91.0 → 0.94.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 (361) hide show
  1. package/dist/cli/commands/build.d.ts.map +1 -1
  2. package/dist/cli/commands/lint.d.ts.map +1 -1
  3. package/dist/cli/handlers/graph.d.ts.map +1 -1
  4. package/dist/cli/handlers/operator.d.ts +6 -0
  5. package/dist/cli/handlers/operator.d.ts.map +1 -1
  6. package/dist/cli/handlers/run.d.ts.map +1 -1
  7. package/dist/cli/main.d.ts.map +1 -1
  8. package/dist/cli/mcp/workspace-plugins.d.ts +6 -6
  9. package/dist/cli/mcp/workspace-tools.d.ts +2 -1
  10. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  11. package/dist/cli/plugins.d.ts.map +1 -1
  12. package/dist/cli/registry.d.ts +12 -2
  13. package/dist/cli/registry.d.ts.map +1 -1
  14. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  15. package/dist/config.d.ts +22 -0
  16. package/dist/config.d.ts.map +1 -1
  17. package/dist/lexicon-module.d.ts +22 -0
  18. package/dist/lexicon-module.d.ts.map +1 -1
  19. package/dist/lifecycle/converge-ledger.d.ts +19 -0
  20. package/dist/lifecycle/converge-ledger.d.ts.map +1 -1
  21. package/dist/lifecycle/run-ledger.d.ts +2 -0
  22. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  23. package/dist/lifecycle/symptoms.d.ts +51 -0
  24. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  25. package/dist/lint/rules/index.d.ts +4 -2
  26. package/dist/lint/rules/index.d.ts.map +1 -1
  27. package/dist/lint/rules/op/index.d.ts +14 -3
  28. package/dist/lint/rules/op/index.d.ts.map +1 -1
  29. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -1
  30. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts +29 -0
  31. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts.map +1 -0
  32. package/dist/lint/rules/sys001-no-literal-key.d.ts +3 -0
  33. package/dist/lint/rules/sys001-no-literal-key.d.ts.map +1 -0
  34. package/dist/op/__fixtures__/decide-stub-backend.d.ts +53 -0
  35. package/dist/op/__fixtures__/decide-stub-backend.d.ts.map +1 -0
  36. package/dist/op/activities/activity-contracts.d.ts +201 -0
  37. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  38. package/dist/op/activities/apply.d.ts.map +1 -1
  39. package/dist/op/activities/change-coverage.d.ts +44 -0
  40. package/dist/op/activities/change-coverage.d.ts.map +1 -0
  41. package/dist/op/activities/converge.d.ts +24 -1
  42. package/dist/op/activities/converge.d.ts.map +1 -1
  43. package/dist/op/activities/decide.d.ts +107 -0
  44. package/dist/op/activities/decide.d.ts.map +1 -0
  45. package/dist/op/activities/index.d.ts +10 -0
  46. package/dist/op/activities/index.d.ts.map +1 -1
  47. package/dist/op/activities/shell.d.ts +32 -0
  48. package/dist/op/activities/shell.d.ts.map +1 -1
  49. package/dist/op/activities/source-release.d.ts +86 -0
  50. package/dist/op/activities/source-release.d.ts.map +1 -0
  51. package/dist/op/activities/source-rollback.d.ts +100 -0
  52. package/dist/op/activities/source-rollback.d.ts.map +1 -0
  53. package/dist/op/activities/work-evidence.d.ts +42 -0
  54. package/dist/op/activities/work-evidence.d.ts.map +1 -0
  55. package/dist/op/activity-contract.d.ts +1 -0
  56. package/dist/op/activity-contract.d.ts.map +1 -1
  57. package/dist/op/activity-profiles.d.ts +16 -0
  58. package/dist/op/activity-profiles.d.ts.map +1 -1
  59. package/dist/op/builders.d.ts +84 -2
  60. package/dist/op/builders.d.ts.map +1 -1
  61. package/dist/op/composites/converge-op.d.ts +31 -4
  62. package/dist/op/composites/converge-op.d.ts.map +1 -1
  63. package/dist/op/decide-backend.d.ts +116 -0
  64. package/dist/op/decide-backend.d.ts.map +1 -0
  65. package/dist/op/decide-config.d.ts +120 -0
  66. package/dist/op/decide-config.d.ts.map +1 -0
  67. package/dist/op/decide-read-inputs.d.ts +40 -0
  68. package/dist/op/decide-read-inputs.d.ts.map +1 -0
  69. package/dist/op/gate-wait.d.ts +43 -0
  70. package/dist/op/gate-wait.d.ts.map +1 -0
  71. package/dist/op/gate.d.ts +5 -2
  72. package/dist/op/gate.d.ts.map +1 -1
  73. package/dist/op/index.d.ts +11 -4
  74. package/dist/op/index.d.ts.map +1 -1
  75. package/dist/op/local-executor.d.ts +29 -4
  76. package/dist/op/local-executor.d.ts.map +1 -1
  77. package/dist/op/local-output.d.ts +2 -0
  78. package/dist/op/local-output.d.ts.map +1 -1
  79. package/dist/op/op-ir.d.ts +2 -0
  80. package/dist/op/op-ir.d.ts.map +1 -1
  81. package/dist/op/op-verb-class.d.ts.map +1 -1
  82. package/dist/op/operator.d.ts +97 -1
  83. package/dist/op/operator.d.ts.map +1 -1
  84. package/dist/op/runtime.d.ts +37 -5
  85. package/dist/op/runtime.d.ts.map +1 -1
  86. package/dist/op/runtimes/local.d.ts.map +1 -1
  87. package/dist/op/source-archive.d.ts +63 -0
  88. package/dist/op/source-archive.d.ts.map +1 -0
  89. package/dist/op/steward-points.d.ts +136 -0
  90. package/dist/op/steward-points.d.ts.map +1 -0
  91. package/dist/op/steward-turn.d.ts +53 -0
  92. package/dist/op/steward-turn.d.ts.map +1 -0
  93. package/dist/op/steward.d.ts +22 -1
  94. package/dist/op/steward.d.ts.map +1 -1
  95. package/dist/op/types.d.ts +7 -0
  96. package/dist/op/types.d.ts.map +1 -1
  97. package/dist/op/work-lease-run.d.ts +1 -1
  98. package/dist/op/work-lease-run.d.ts.map +1 -1
  99. package/dist/workspace/__fixtures__/decide-workspace.d.ts +100 -0
  100. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -0
  101. package/dist/workspace/answers-ledger.d.ts +76 -0
  102. package/dist/workspace/answers-ledger.d.ts.map +1 -0
  103. package/dist/workspace/box-intent.d.ts +85 -0
  104. package/dist/workspace/box-intent.d.ts.map +1 -0
  105. package/dist/workspace/changes-cli.d.ts +16 -0
  106. package/dist/workspace/changes-cli.d.ts.map +1 -0
  107. package/dist/workspace/changes.d.ts +200 -0
  108. package/dist/workspace/changes.d.ts.map +1 -0
  109. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts +59 -0
  110. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts.map +1 -0
  111. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts +51 -0
  112. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts.map +1 -0
  113. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts +26 -0
  114. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts.map +1 -0
  115. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts +59 -0
  116. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts.map +1 -0
  117. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts +22 -0
  118. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts.map +1 -0
  119. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts +71 -0
  120. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -0
  121. package/dist/workspace/chant-migrations.d.ts +104 -0
  122. package/dist/workspace/chant-migrations.d.ts.map +1 -0
  123. package/dist/workspace/checks/boxes.d.ts +47 -1
  124. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  125. package/dist/workspace/checks/diagrams.d.ts +31 -0
  126. package/dist/workspace/checks/diagrams.d.ts.map +1 -0
  127. package/dist/workspace/checks/records.d.ts +7 -0
  128. package/dist/workspace/checks/records.d.ts.map +1 -1
  129. package/dist/workspace/checks.d.ts +7 -0
  130. package/dist/workspace/checks.d.ts.map +1 -1
  131. package/dist/workspace/compose-graph.d.ts +2 -0
  132. package/dist/workspace/compose-graph.d.ts.map +1 -1
  133. package/dist/workspace/conformance/index.d.ts.map +1 -1
  134. package/dist/workspace/decide.d.ts +49 -4
  135. package/dist/workspace/decide.d.ts.map +1 -1
  136. package/dist/workspace/decision-points.schema.json +3 -3
  137. package/dist/workspace/declaration.d.ts +58 -0
  138. package/dist/workspace/declaration.d.ts.map +1 -1
  139. package/dist/workspace/declaration.schema.json +119 -1
  140. package/dist/workspace/declared-kinds.d.ts +20 -0
  141. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  142. package/dist/workspace/generated-files.d.ts +7 -0
  143. package/dist/workspace/generated-files.d.ts.map +1 -1
  144. package/dist/workspace/intent-joins.d.ts +5 -5
  145. package/dist/workspace/intent.d.ts.map +1 -1
  146. package/dist/workspace/lineage-check.d.ts +3 -0
  147. package/dist/workspace/lineage-check.d.ts.map +1 -1
  148. package/dist/workspace/lineage-upgrade.d.ts +6 -1
  149. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  150. package/dist/workspace/ls.d.ts +39 -1
  151. package/dist/workspace/ls.d.ts.map +1 -1
  152. package/dist/workspace/point-answer.schema.json +95 -0
  153. package/dist/workspace/points-cli.d.ts +2 -0
  154. package/dist/workspace/points-cli.d.ts.map +1 -1
  155. package/dist/workspace/points.d.ts +4 -6
  156. package/dist/workspace/points.d.ts.map +1 -1
  157. package/dist/workspace/reason-codes.d.ts +16 -2
  158. package/dist/workspace/reason-codes.d.ts.map +1 -1
  159. package/dist/workspace/records-cli.d.ts +11 -1
  160. package/dist/workspace/records-cli.d.ts.map +1 -1
  161. package/dist/workspace/records-close.d.ts +1 -1
  162. package/dist/workspace/records-close.d.ts.map +1 -1
  163. package/dist/workspace/records-write.d.ts +11 -5
  164. package/dist/workspace/records-write.d.ts.map +1 -1
  165. package/dist/workspace/records.d.ts +34 -4
  166. package/dist/workspace/records.d.ts.map +1 -1
  167. package/dist/workspace/status-gates.d.ts +7 -2
  168. package/dist/workspace/status-gates.d.ts.map +1 -1
  169. package/dist/workspace/status-stewards.d.ts +65 -1
  170. package/dist/workspace/status-stewards.d.ts.map +1 -1
  171. package/dist/workspace/status.d.ts +24 -0
  172. package/dist/workspace/status.d.ts.map +1 -1
  173. package/dist/workspace/work-cli.d.ts +113 -2
  174. package/dist/workspace/work-cli.d.ts.map +1 -1
  175. package/dist/workspace/work-evidence.d.ts +74 -0
  176. package/dist/workspace/work-evidence.d.ts.map +1 -0
  177. package/dist/workspace/work.d.ts +43 -1
  178. package/dist/workspace/work.d.ts.map +1 -1
  179. package/package.json +1 -1
  180. package/src/cli/commands/audit.test.ts +9 -1
  181. package/src/cli/commands/build.ts +3 -2
  182. package/src/cli/commands/lint.ts +7 -1
  183. package/src/cli/handlers/graph.test.ts +20 -0
  184. package/src/cli/handlers/graph.ts +16 -2
  185. package/src/cli/handlers/operator.ts +34 -2
  186. package/src/cli/handlers/run.test.ts +154 -2
  187. package/src/cli/handlers/run.ts +121 -2
  188. package/src/cli/main.test.ts +10 -0
  189. package/src/cli/main.ts +23 -3
  190. package/src/cli/mcp/workspace-plugins.ts +6 -6
  191. package/src/cli/mcp/workspace-tools.test.ts +35 -19
  192. package/src/cli/mcp/workspace-tools.ts +62 -4
  193. package/src/cli/plugins.ts +11 -3
  194. package/src/cli/registry.ts +12 -2
  195. package/src/cli/serve-mcp-workspace.test.ts +6 -6
  196. package/src/codegen/pinned-upgrade.ts +2 -1
  197. package/src/codegen/release-wiring.test.ts +49 -2
  198. package/src/components/capability-plugin-loader.ts +3 -3
  199. package/src/components/cli-support.ts +2 -2
  200. package/src/config.ts +11 -0
  201. package/src/lexicon-module.ts +56 -1
  202. package/src/lexicon-package.test.ts +96 -0
  203. package/src/lifecycle/converge-ledger.ts +20 -0
  204. package/src/lifecycle/run-ledger.ts +6 -0
  205. package/src/lifecycle/symptoms.ts +95 -0
  206. package/src/lint/rules/index.ts +6 -2
  207. package/src/lint/rules/op/index.ts +17 -4
  208. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +23 -1
  209. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +10 -6
  210. package/src/lint/rules/op/sys010-key-over-plain-http.test.ts +52 -0
  211. package/src/lint/rules/op/sys010-key-over-plain-http.ts +100 -0
  212. package/src/lint/rules/sys001-no-literal-key.test.ts +31 -0
  213. package/src/lint/rules/sys001-no-literal-key.ts +67 -0
  214. package/src/op/__fixtures__/decide-stub-backend.ts +143 -0
  215. package/src/op/activities/activity-contracts.ts +190 -1
  216. package/src/op/activities/apply.ts +7 -6
  217. package/src/op/activities/change-coverage.ts +60 -0
  218. package/src/op/activities/converge.test.ts +55 -1
  219. package/src/op/activities/converge.ts +208 -29
  220. package/src/op/activities/decide.live.test.ts +78 -0
  221. package/src/op/activities/decide.test.ts +308 -0
  222. package/src/op/activities/decide.ts +267 -0
  223. package/src/op/activities/index.ts +21 -0
  224. package/src/op/activities/shell.test.ts +91 -1
  225. package/src/op/activities/shell.ts +91 -2
  226. package/src/op/activities/source-release.test.ts +118 -0
  227. package/src/op/activities/source-release.ts +168 -0
  228. package/src/op/activities/source-rollback.test.ts +131 -0
  229. package/src/op/activities/source-rollback.ts +239 -0
  230. package/src/op/activities/work-evidence.ts +62 -0
  231. package/src/op/activity-contract-registry.test.ts +2 -0
  232. package/src/op/activity-contract-registry.ts +2 -2
  233. package/src/op/activity-contract.test.ts +12 -0
  234. package/src/op/activity-contract.ts +6 -1
  235. package/src/op/activity-profiles.ts +29 -0
  236. package/src/op/activity-registry.test.ts +1 -1
  237. package/src/op/activity-registry.ts +2 -2
  238. package/src/op/builders.ts +119 -5
  239. package/src/op/command-gate.test.ts +192 -0
  240. package/src/op/composites/converge-op.test.ts +27 -1
  241. package/src/op/composites/converge-op.ts +69 -15
  242. package/src/op/decide-backend.test.ts +44 -0
  243. package/src/op/decide-backend.ts +212 -0
  244. package/src/op/decide-config.test.ts +52 -0
  245. package/src/op/decide-config.ts +80 -0
  246. package/src/op/decide-read-inputs.ts +117 -0
  247. package/src/op/gate-approval.ts +2 -2
  248. package/src/op/gate-wait.ts +57 -0
  249. package/src/op/gate.ts +8 -3
  250. package/src/op/generate-pipeline.ts +2 -2
  251. package/src/op/index.ts +18 -3
  252. package/src/op/local-executor.ts +151 -15
  253. package/src/op/local-output.test.ts +33 -0
  254. package/src/op/local-output.ts +20 -2
  255. package/src/op/op-ir.test.ts +10 -0
  256. package/src/op/op-ir.ts +4 -0
  257. package/src/op/op-verb-class.ts +1 -0
  258. package/src/op/operator.ts +290 -11
  259. package/src/op/runtime.ts +31 -8
  260. package/src/op/runtimes/local.ts +3 -1
  261. package/src/op/source-archive.ts +161 -0
  262. package/src/op/steward-points.test.ts +364 -0
  263. package/src/op/steward-points.ts +194 -0
  264. package/src/op/steward-turn.ts +79 -0
  265. package/src/op/steward.test.ts +87 -3
  266. package/src/op/steward.ts +25 -1
  267. package/src/op/types.ts +7 -0
  268. package/src/op/work-lease-run.test.ts +20 -1
  269. package/src/op/work-lease-run.ts +2 -2
  270. package/src/workspace/__fixtures__/decide-workspace.ts +129 -0
  271. package/src/workspace/__fixtures__/studio-template-84c8b21.json +67 -0
  272. package/src/workspace/answers-ledger.ts +118 -0
  273. package/src/workspace/box-intent.test.ts +205 -0
  274. package/src/workspace/box-intent.ts +159 -0
  275. package/src/workspace/changes-cli.ts +56 -0
  276. package/src/workspace/changes.schema.json +472 -0
  277. package/src/workspace/changes.test.ts +315 -0
  278. package/src/workspace/changes.ts +512 -0
  279. package/src/workspace/chant-migrations/chud-lexicon-exit-fly-site.ts +234 -0
  280. package/src/workspace/chant-migrations/chud-lexicon-exit-live-names.ts +164 -0
  281. package/src/workspace/chant-migrations/chud-lexicon-exit-rollback.ts +223 -0
  282. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.ts +195 -0
  283. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-policy.ts +108 -0
  284. package/src/workspace/chant-migrations/chud-lexicon-exit.ts +882 -0
  285. package/src/workspace/chant-migrations.e2e.test.ts +1089 -0
  286. package/src/workspace/chant-migrations.ts +169 -0
  287. package/src/workspace/check-contract.test.ts +4 -2
  288. package/src/workspace/check.schema.json +19 -5
  289. package/src/workspace/checks/boxes.test.ts +82 -2
  290. package/src/workspace/checks/boxes.ts +202 -1
  291. package/src/workspace/checks/diagrams.test.ts +134 -0
  292. package/src/workspace/checks/diagrams.ts +108 -0
  293. package/src/workspace/checks/records.ts +47 -0
  294. package/src/workspace/checks.ts +20 -2
  295. package/src/workspace/compose-graph.ts +2 -0
  296. package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +10 -0
  297. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +26 -0
  298. package/src/workspace/conformance/index.ts +5 -0
  299. package/src/workspace/decide.test.ts +1 -1
  300. package/src/workspace/decide.ts +156 -23
  301. package/src/workspace/decision-points.schema.json +3 -3
  302. package/src/workspace/declaration.schema.json +119 -1
  303. package/src/workspace/declaration.test.ts +58 -0
  304. package/src/workspace/declaration.ts +112 -0
  305. package/src/workspace/declared-kinds.test.ts +31 -5
  306. package/src/workspace/declared-kinds.ts +46 -1
  307. package/src/workspace/generated-files.ts +14 -0
  308. package/src/workspace/graph-cli.ts +1 -1
  309. package/src/workspace/graph.schema.json +9 -1
  310. package/src/workspace/intent-joins.test.ts +5 -5
  311. package/src/workspace/intent-joins.ts +5 -5
  312. package/src/workspace/intent.schema.json +8 -2
  313. package/src/workspace/intent.ts +3 -10
  314. package/src/workspace/lineage-check.ts +16 -1
  315. package/src/workspace/lineage-upgrade-cli.ts +1 -1
  316. package/src/workspace/lineage-upgrade.test.ts +70 -0
  317. package/src/workspace/lineage-upgrade.ts +81 -10
  318. package/src/workspace/ls-contract.test.ts +57 -0
  319. package/src/workspace/ls.schema.json +46 -1
  320. package/src/workspace/ls.ts +62 -3
  321. package/src/workspace/points-cli.ts +17 -3
  322. package/src/workspace/points-write.schema.json +11 -1
  323. package/src/workspace/points.schema.json +51 -0
  324. package/src/workspace/points.test.ts +16 -15
  325. package/src/workspace/points.ts +5 -17
  326. package/src/workspace/read-contract.test.ts +9 -1
  327. package/src/workspace/reason-codes.test.ts +18 -8
  328. package/src/workspace/reason-codes.ts +21 -2
  329. package/src/workspace/records-amend.schema.json +2 -0
  330. package/src/workspace/records-cli.ts +27 -4
  331. package/src/workspace/records-close.schema.json +2 -0
  332. package/src/workspace/records-formats.test.ts +15 -15
  333. package/src/workspace/records-new.schema.json +2 -0
  334. package/src/workspace/records-review.schema.json +2 -0
  335. package/src/workspace/records-sessions-write.e2e.test.ts +77 -0
  336. package/src/workspace/records-sessions-write.test.ts +2 -34
  337. package/src/workspace/records-since.test.ts +8 -7
  338. package/src/workspace/records-write.e2e.test.ts +108 -0
  339. package/src/workspace/records-write.test.ts +32 -41
  340. package/src/workspace/records-write.ts +33 -15
  341. package/src/workspace/records.schema.json +36 -2
  342. package/src/workspace/records.test.ts +49 -4
  343. package/src/workspace/records.ts +109 -3
  344. package/src/workspace/status-contract.test.ts +14 -8
  345. package/src/workspace/status-gates.ts +9 -4
  346. package/src/workspace/status-stewards.ts +155 -5
  347. package/src/workspace/status.schema.json +129 -8
  348. package/src/workspace/status.ts +46 -1
  349. package/src/workspace/work-acceptance.test.ts +274 -0
  350. package/src/workspace/{work-cli.test.ts → work-cli.e2e.test.ts} +1 -1
  351. package/src/workspace/work-cli.ts +193 -10
  352. package/src/workspace/work-evidence.schema.json +212 -0
  353. package/src/workspace/work-evidence.ts +169 -0
  354. package/src/workspace/work-history.schema.json +349 -0
  355. package/src/workspace/work-history.test.ts +156 -0
  356. package/src/workspace/{work-readiness-chud.test.ts → work-readiness.test.ts} +30 -32
  357. package/src/workspace/work.test.ts +20 -2
  358. package/src/workspace/work.ts +100 -1
  359. /package/src/cli/{discovery-skip.test.ts → discovery-skip.e2e.test.ts} +0 -0
  360. /package/src/workspace/conformance/{conformance.test.ts → conformance.e2e.test.ts} +0 -0
  361. /package/src/workspace/{which-chant.test.ts → which-chant.e2e.test.ts} +0 -0
@@ -40,7 +40,14 @@ import {
40
40
  } from "../converge-rule";
41
41
  import { classifyOpVerbClass, type OpVerbClass } from "../op-verb-class";
42
42
  import { discoverOps } from "../discover";
43
- import { deriveSymptoms, type ConvergeSymptom } from "../../lifecycle/symptoms";
43
+ import {
44
+ deriveSymptoms,
45
+ parseResourceObservation,
46
+ CONVERGE_RESOURCE_ENV,
47
+ type ConvergeSymptom,
48
+ type ObservedResource,
49
+ type ResourceSymptom,
50
+ } from "../../lifecycle/symptoms";
44
51
  import {
45
52
  appendConvergeRecord,
46
53
  readConvergeLedger,
@@ -88,6 +95,14 @@ export interface ConvergeTickArgs {
88
95
  * (this activity re-derives its own symptom independently).
89
96
  */
90
97
  preflightDrift?: boolean;
98
+ /**
99
+ * The observer step's result, for a ConvergeOp declared with `observe`
100
+ * (#2778): a {@link ResourceObservation}, or a `shell()` result whose
101
+ * stdout is one. Present, the tick observes nothing itself: it evaluates
102
+ * `rules` once per reported resource, against a `ResourceSymptom`, and
103
+ * dispatches a fired `run()` for that resource.
104
+ */
105
+ observed?: unknown;
91
106
  }
92
107
 
93
108
  export interface ConvergeTickResult {
@@ -209,9 +224,10 @@ export function classifyDispatchFailure(raw: string): { gateName: string } | und
209
224
  async function dispatchOp(
210
225
  opName: string,
211
226
  signal?: AbortSignal,
227
+ env?: Record<string, string>,
212
228
  ): Promise<{ ok: true } | { ok: false; error: string; gateName?: string }> {
213
229
  try {
214
- await execAsync(`chant run ${shellQuote(opName)} --json`, { signal });
230
+ await execAsync(`chant run ${shellQuote(opName)} --json`, { signal, ...(env ? { env: { ...process.env, ...env } } : {}) });
215
231
  return { ok: true };
216
232
  } catch (err) {
217
233
  const e = err as { code?: number; stdout?: string; stderr?: string; message?: string };
@@ -383,25 +399,106 @@ function renderLog(env: string, s: ConvergeSymptom, counts: ReturnType<typeof su
383
399
  );
384
400
  }
385
401
 
386
- export async function convergeTick(args: ConvergeTickArgs, signal?: AbortSignal): Promise<ConvergeTickResult> {
387
- await fetchLifecycle().catch(() => undefined);
402
+ // ── Resources an observer step reports (#2778) ──────────────────────────────
388
403
 
389
- const [cs, statusRows, ledger] = await Promise.all([
390
- observeChangeSet(args.env, signal),
391
- observeStatusRows(args.env, signal),
392
- readConvergeLedger(args.env),
393
- ]);
404
+ /**
405
+ * How many consecutive most-recent ticks of `op` fired `ruleId` for
406
+ * `resource`. A rule of a ConvergeOp with an observer step fires once per
407
+ * resource, so its flap damping is counted per resource: a rule that
408
+ * restarts `app` on one tick and `hud` on the next is not flapping.
409
+ */
410
+ export function consecutiveResourceFires(records: ConvergeTickRecord[], op: string, ruleId: string, resource: string): number {
411
+ let count = 0;
412
+ for (let i = records.length - 1; i >= 0; i--) {
413
+ if (records[i].op !== op) continue;
414
+ if (!records[i].outcomes.some((o) => o.ruleId === ruleId && o.resource === resource)) break;
415
+ count++;
416
+ }
417
+ return count;
418
+ }
394
419
 
395
- const symptom = deriveSymptoms(args.env, cs, statusRows);
396
- const plan = planConvergeTick(symptom, args.rules, ledger.records, args.dial, args.budget);
420
+ /**
421
+ * The pure planning core of a resource tick (#2778): every rule against every
422
+ * observed resource, in authored order within each resource. The rules of
423
+ * {@link planConvergeTick} hold per resource: `unknown` never remediates, a
424
+ * `report()` never dispatches, the dial decides whether a `run()` may, and
425
+ * `budget` bounds the dispatches across the whole tick.
426
+ */
427
+ export function planResourceTick(
428
+ env: string,
429
+ resources: ObservedResource[],
430
+ rules: ConvergeRule<ResourceSymptom>[],
431
+ priorRecords: ConvergeTickRecord[],
432
+ op: string,
433
+ dial: ConvergeTickArgs["dial"],
434
+ budget: number,
435
+ ): ConvergeTickPlan {
436
+ const fired = new Set<string>();
437
+ const outcomes: ConvergeRuleOutcome[] = [];
438
+ let dispatched = 0;
397
439
 
398
- // Execute: only "ran" outcomes cause a subprocess dispatch. Before that
399
- // dispatch, the runtime backstop (Finding A, #1954 pre-merge review)
400
- // re-classifies the target and downgrades to "reported" if this dial/verb
401
- // class pairing was never supposed to reach dispatch — OPS014 (build time)
402
- // is the primary defense; this is what catches a rule table that reached
403
- // `convergeTick` without going through it.
404
- for (const outcome of plan.outcomes) {
440
+ for (const resource of resources) {
441
+ const symptom: ResourceSymptom = { env, resource: resource.name, status: resource.status, detail: resource.detail ?? "" };
442
+ for (const rule of rules) {
443
+ if (!evaluatePredicate(rule.when, symptom)) continue;
444
+ fired.add(rule.id);
445
+ const base = { ruleId: rule.id, resource: resource.name };
446
+
447
+ const threshold = rule.flapThreshold ?? DEFAULT_FLAP_THRESHOLD;
448
+ const consecutive = consecutiveResourceFires(priorRecords, op, rule.id, resource.name);
449
+ if (consecutive >= threshold) {
450
+ outcomes.push({
451
+ ...base,
452
+ action: "skipped-flap",
453
+ reason: `fired for ${resource.name} ${consecutive + 1} consecutive ticks without clearing (threshold ${threshold}) — escalated to report`,
454
+ });
455
+ continue;
456
+ }
457
+
458
+ const action: RuleAction = rule.then;
459
+ if (resource.status === "unknown" && action.kind === "run") {
460
+ outcomes.push({
461
+ ...base,
462
+ action: "reported",
463
+ reason: `${resource.name} is unknown (${resource.detail || "unobserved"}) — unknown never remediates`,
464
+ });
465
+ continue;
466
+ }
467
+ if (action.kind === "report") {
468
+ outcomes.push({ ...base, action: "reported", reason: action.reason });
469
+ continue;
470
+ }
471
+ if (!dialAllowsDispatch(dial)) {
472
+ outcomes.push({ ...base, action: "reported", reason: `dial "${dial}" does not permit dispatch — report only`, op: action.op });
473
+ continue;
474
+ }
475
+ if (dispatched >= budget) {
476
+ outcomes.push({ ...base, action: "skipped-budget", op: action.op });
477
+ continue;
478
+ }
479
+ dispatched++;
480
+ outcomes.push({ ...base, action: "ran", op: action.op });
481
+ }
482
+ }
483
+ return { firedRuleIds: [...fired], outcomes };
484
+ }
485
+
486
+ function renderResourceLog(env: string, resources: ObservedResource[], counts: ReturnType<typeof summarizeOutcomes>): string {
487
+ const n = (status: ObservedResource["status"]) => resources.filter((r) => r.status === status).length;
488
+ return (
489
+ `converge(${env}): resources=${resources.length} in-sync=${n("in-sync")} drifted=${n("drifted")} unknown=${n("unknown")} ` +
490
+ `remediated=${counts.remediated} reported=${counts.reported} skipped-budget=${counts.skippedBudget} ` +
491
+ `skipped-flap=${counts.skippedFlap} gated=${counts.gated}`
492
+ );
493
+ }
494
+
495
+ /** Run the planned dispatches of a tick, in place: the verb-class backstop, then `chant run`, then what came of it. */
496
+ async function dispatchPlanned(
497
+ outcomes: ConvergeRuleOutcome[],
498
+ args: ConvergeTickArgs,
499
+ signal?: AbortSignal,
500
+ ): Promise<void> {
501
+ for (const outcome of outcomes) {
405
502
  if (outcome.action !== "ran" || !outcome.op) continue;
406
503
 
407
504
  const verbClass = await classifyDispatchTarget(outcome.op);
@@ -410,7 +507,9 @@ export async function convergeTick(args: ConvergeTickArgs, signal?: AbortSignal)
410
507
  outcome.reason = backstopped.reason;
411
508
  if (outcome.action !== "ran") continue;
412
509
 
413
- const result = await dispatchOp(outcome.op, signal);
510
+ // A rule fired for one resource (#2778) hands the dispatched Op its name.
511
+ const env = outcome.resource !== undefined ? { [CONVERGE_RESOURCE_ENV]: outcome.resource } : undefined;
512
+ const result = await dispatchOp(outcome.op, signal, env);
414
513
  if (!result.ok && result.gateName) {
415
514
  // Gate-as-fact (#1485, closed by #2119): a terminal, durable,
416
515
  // non-blocking fact — not an ordinary dispatch failure the operator
@@ -432,6 +531,95 @@ export async function convergeTick(args: ConvergeTickArgs, signal?: AbortSignal)
432
531
  outcome.reason = `dispatch of "${outcome.op}" failed: ${result.error}`;
433
532
  }
434
533
  }
534
+ }
535
+
536
+ /** Push the tick's ledger record, and say whether it reached the remote (chant#2337). */
537
+ async function pushTick(): Promise<{ pushed: boolean; pushWarning?: string }> {
538
+ try {
539
+ const pushed = await pushLifecycle();
540
+ return pushed ? { pushed } : { pushed, pushWarning: "no remote is configured for chant/lifecycle — the tick record was recorded locally only" };
541
+ } catch (err) {
542
+ return { pushed: false, pushWarning: err instanceof Error ? err.message : String(err) };
543
+ }
544
+ }
545
+
546
+ /** A tick of a ConvergeOp with an observer step (#2778): the observation is already in `args.observed`. */
547
+ async function resourceTick(args: ConvergeTickArgs, signal?: AbortSignal): Promise<ConvergeTickResult> {
548
+ const { resources } = parseResourceObservation(args.observed);
549
+ const ledger = await readConvergeLedger(args.env);
550
+ const plan = planResourceTick(
551
+ args.env,
552
+ resources,
553
+ args.rules as unknown as ConvergeRule<ResourceSymptom>[],
554
+ ledger.records,
555
+ args.opName,
556
+ args.dial,
557
+ args.budget,
558
+ );
559
+ await dispatchPlanned(plan.outcomes, args, signal);
560
+
561
+ const counts = summarizeOutcomes(plan.outcomes);
562
+ const log = renderResourceLog(args.env, resources, counts);
563
+ const drifted = resources.filter((r) => r.status === "drifted").length;
564
+ const { record } = await appendConvergeRecord({
565
+ op: args.opName,
566
+ env: args.env,
567
+ timestamp: new Date().toISOString(),
568
+ firedRuleIds: plan.firedRuleIds,
569
+ outcomes: plan.outcomes,
570
+ resources: resources.map((r) => ({
571
+ name: r.name,
572
+ status: r.status,
573
+ ...(r.detail ? { detail: sanitizeLedgerText(r.detail) } : {}),
574
+ })),
575
+ summary: {
576
+ drifted,
577
+ remediated: counts.remediated,
578
+ reported: counts.reported,
579
+ skippedBudget: counts.skippedBudget,
580
+ skippedFlap: counts.skippedFlap,
581
+ gated: counts.gated,
582
+ unobserved: resources.filter((r) => r.status === "unknown").length,
583
+ adopted: 0,
584
+ },
585
+ log,
586
+ });
587
+ const push = await pushTick();
588
+ return {
589
+ id: record.id,
590
+ drifted: drifted > 0,
591
+ remediated: record.summary.remediated,
592
+ reported: record.summary.reported,
593
+ skippedBudget: record.summary.skippedBudget,
594
+ skippedFlap: record.summary.skippedFlap,
595
+ gated: record.summary.gated ?? 0,
596
+ unobserved: record.summary.unobserved,
597
+ adopted: 0,
598
+ log: record.log,
599
+ ...push,
600
+ };
601
+ }
602
+
603
+ export async function convergeTick(args: ConvergeTickArgs, signal?: AbortSignal): Promise<ConvergeTickResult> {
604
+ await fetchLifecycle().catch(() => undefined);
605
+ if (args.observed !== undefined) return resourceTick(args, signal);
606
+
607
+ const [cs, statusRows, ledger] = await Promise.all([
608
+ observeChangeSet(args.env, signal),
609
+ observeStatusRows(args.env, signal),
610
+ readConvergeLedger(args.env),
611
+ ]);
612
+
613
+ const symptom = deriveSymptoms(args.env, cs, statusRows);
614
+ const plan = planConvergeTick(symptom, args.rules, ledger.records, args.dial, args.budget);
615
+
616
+ // Execute: only "ran" outcomes cause a subprocess dispatch. Before that
617
+ // dispatch, the runtime backstop (Finding A, #1954 pre-merge review)
618
+ // re-classifies the target and downgrades to "reported" if this dial/verb
619
+ // class pairing was never supposed to reach dispatch — OPS014 (build time)
620
+ // is the primary defense; this is what catches a rule table that reached
621
+ // `convergeTick` without going through it.
622
+ await dispatchPlanned(plan.outcomes, args, signal);
435
623
 
436
624
  const counts = summarizeOutcomes(plan.outcomes);
437
625
  const log = renderLog(args.env, symptom, counts);
@@ -461,16 +649,7 @@ export async function convergeTick(args: ConvergeTickArgs, signal?: AbortSignal)
461
649
  // chant#2337 — reported rather than swallowed, the way #2336 does it for the
462
650
  // gate. A push that did not land leaves a correct local record nobody else
463
651
  // can read, and the caller is the only one positioned to say so.
464
- let pushed = false;
465
- let pushWarning: string | undefined;
466
- try {
467
- pushed = await pushLifecycle();
468
- if (!pushed) {
469
- pushWarning = "no remote is configured for chant/lifecycle — the tick record was recorded locally only";
470
- }
471
- } catch (err) {
472
- pushWarning = err instanceof Error ? err.message : String(err);
473
- }
652
+ const { pushed, pushWarning } = await pushTick();
474
653
 
475
654
  return {
476
655
  id: record.id,
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The decide activity against a real Jev-compatible server (#2740). Gated:
3
+ * it runs only when TYPESAFE_API_KEY is set, and is skipped otherwise, so the
4
+ * suite never needs an account or the network.
5
+ *
6
+ * TYPESAFE_API_KEY=... npx vitest run packages/core/src/op/activities/decide.live.test.ts
7
+ *
8
+ * TYPESAFE_BASE_URL points it at another compatible server (the default is
9
+ * https://api.typesafe.ai), and TYPESAFE_MODEL at another pinned model id (the
10
+ * default is jev-1.13.0). A model id is always a versioned one, never an alias.
11
+ */
12
+
13
+ import { afterAll, describe, expect, test } from "vitest";
14
+ import { isPointWait } from "../steward-points";
15
+ import { workspacePoints } from "../../workspace/points-cli";
16
+ import { postQuestion } from "../decide-backend";
17
+ import { cleanScratch, workspace } from "../../workspace/__fixtures__/decide-workspace";
18
+ import { runDecide } from "./decide";
19
+
20
+ const key = process.env.TYPESAFE_API_KEY;
21
+ const url = process.env.TYPESAFE_BASE_URL ?? "https://api.typesafe.ai";
22
+ const model = process.env.TYPESAFE_MODEL ?? "jev-1.13.0";
23
+ const backend = { url, key: { env: "TYPESAFE_API_KEY" }, timeoutMs: 60_000 };
24
+
25
+ afterAll(cleanScratch);
26
+
27
+ describe.skipIf(!key)("decide against a real Jev-compatible server (TYPESAFE_API_KEY is set)", () => {
28
+ test("the server answers each question type in the wire format", async () => {
29
+ const cwd = process.cwd();
30
+ const noul = await postQuestion(backend, "systemone", {
31
+ point: "live-noul",
32
+ backend: "systemone",
33
+ model,
34
+ question: { type: "noul", instructions: "Is the sky in the state blue?", criteria: { true: "It is blue.", false: "It is not blue." } },
35
+ state: { sky: "a clear blue sky at noon" },
36
+ }, { cwd });
37
+ expect(noul.model).toBe(model);
38
+ expect(noul.answer.type).toBe("noul");
39
+ expect((noul.answer as { noul: number }).noul).toBeGreaterThanOrEqual(0);
40
+
41
+ const choice = await postQuestion(backend, "systemone", {
42
+ point: "live-choice",
43
+ backend: "systemone",
44
+ model,
45
+ question: { type: "choice", instructions: "Which team takes the ticket in the state?", criteria: { billing: "Payments and invoices.", platform: "Servers and deploys." } },
46
+ state: { ticket: "The production deploy failed with a disk full error." },
47
+ }, { cwd });
48
+ expect(choice.answer).toMatchObject({ type: "choice" });
49
+ expect(["billing", "platform"]).toContain((choice.answer as { choice: string }).choice);
50
+
51
+ const score = await postQuestion(backend, "systemone", {
52
+ point: "live-score",
53
+ backend: "systemone",
54
+ model,
55
+ question: { type: "score", instructions: "How urgent is the ticket in the state?", criteria: ["not urgent", "somewhat urgent", "very urgent"] },
56
+ state: { ticket: "Every customer is locked out of their account." },
57
+ }, { cwd });
58
+ expect(score.answer).toMatchObject({ type: "score" });
59
+ }, 120_000);
60
+
61
+ test("the activity asks a point and records the model's answer or its escalation", async () => {
62
+ const root = workspace();
63
+ const outcome = await runDecide({ cwd: root, point: "triage", inputs: { "record.size": 5, "record.risky": true }, backends: { systemone: backend } }, {}).then(
64
+ (r) => r.id,
65
+ (e: unknown) => {
66
+ if (isPointWait(e)) return e.question.id;
67
+ throw e;
68
+ },
69
+ );
70
+ const doc = await workspacePoints({ cwd: root });
71
+ if ("error" in doc) throw new Error(doc.error.message);
72
+ const q = doc.questions.find((x) => x.id === outcome)!;
73
+ // The server answered: a proposal at the pin, or an escalation below the threshold with the model's lean. Never unreachable.
74
+ expect(["proposed", "escalated"]).toContain(q.state);
75
+ if (q.state === "proposed") expect(q.decider).toMatchObject({ kind: "model", model });
76
+ else expect(q.escalations.find((e) => e.kind === "model")?.reason).not.toContain("could not answer");
77
+ }, 120_000);
78
+ });