@intentius/chant 0.90.0 → 0.93.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 (402) 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/components.d.ts +8 -0
  4. package/dist/cli/handlers/components.d.ts.map +1 -1
  5. package/dist/cli/handlers/graph.d.ts.map +1 -1
  6. package/dist/cli/handlers/operator.d.ts +20 -0
  7. package/dist/cli/handlers/operator.d.ts.map +1 -1
  8. package/dist/cli/handlers/run.d.ts.map +1 -1
  9. package/dist/cli/main.d.ts.map +1 -1
  10. package/dist/cli/mcp/workspace-tools.d.ts +7 -4
  11. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  12. package/dist/cli/plugins.d.ts.map +1 -1
  13. package/dist/cli/registry.d.ts +42 -1
  14. package/dist/cli/registry.d.ts.map +1 -1
  15. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  16. package/dist/components/verbs/vuln-scan.d.ts +72 -0
  17. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  18. package/dist/config.d.ts +22 -0
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/lexicon-module.d.ts +22 -0
  21. package/dist/lexicon-module.d.ts.map +1 -1
  22. package/dist/lifecycle/converge-ledger.d.ts +19 -0
  23. package/dist/lifecycle/converge-ledger.d.ts.map +1 -1
  24. package/dist/lifecycle/git.d.ts +40 -5
  25. package/dist/lifecycle/git.d.ts.map +1 -1
  26. package/dist/lifecycle/lease.d.ts +72 -16
  27. package/dist/lifecycle/lease.d.ts.map +1 -1
  28. package/dist/lifecycle/member-ledger.d.ts +3 -2
  29. package/dist/lifecycle/member-ledger.d.ts.map +1 -1
  30. package/dist/lifecycle/plan-ledger.d.ts +114 -0
  31. package/dist/lifecycle/plan-ledger.d.ts.map +1 -0
  32. package/dist/lifecycle/run-ledger.d.ts +2 -0
  33. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  34. package/dist/lifecycle/symptoms.d.ts +51 -0
  35. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  36. package/dist/lifecycle/work-lease.d.ts +140 -0
  37. package/dist/lifecycle/work-lease.d.ts.map +1 -0
  38. package/dist/lint/rules/index.d.ts +4 -2
  39. package/dist/lint/rules/index.d.ts.map +1 -1
  40. package/dist/lint/rules/op/index.d.ts +14 -3
  41. package/dist/lint/rules/op/index.d.ts.map +1 -1
  42. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -1
  43. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts +29 -0
  44. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts.map +1 -0
  45. package/dist/lint/rules/sys001-no-literal-key.d.ts +3 -0
  46. package/dist/lint/rules/sys001-no-literal-key.d.ts.map +1 -0
  47. package/dist/op/__fixtures__/decide-stub-backend.d.ts +53 -0
  48. package/dist/op/__fixtures__/decide-stub-backend.d.ts.map +1 -0
  49. package/dist/op/activities/activity-contracts.d.ts +203 -2
  50. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  51. package/dist/op/activities/apply.d.ts.map +1 -1
  52. package/dist/op/activities/change-coverage.d.ts +44 -0
  53. package/dist/op/activities/change-coverage.d.ts.map +1 -0
  54. package/dist/op/activities/converge.d.ts +24 -1
  55. package/dist/op/activities/converge.d.ts.map +1 -1
  56. package/dist/op/activities/decide.d.ts +107 -0
  57. package/dist/op/activities/decide.d.ts.map +1 -0
  58. package/dist/op/activities/index.d.ts +10 -0
  59. package/dist/op/activities/index.d.ts.map +1 -1
  60. package/dist/op/activities/shell.d.ts +32 -0
  61. package/dist/op/activities/shell.d.ts.map +1 -1
  62. package/dist/op/activities/source-release.d.ts +86 -0
  63. package/dist/op/activities/source-release.d.ts.map +1 -0
  64. package/dist/op/activities/source-rollback.d.ts +100 -0
  65. package/dist/op/activities/source-rollback.d.ts.map +1 -0
  66. package/dist/op/activities/work-evidence.d.ts +42 -0
  67. package/dist/op/activities/work-evidence.d.ts.map +1 -0
  68. package/dist/op/activity-contract.d.ts +1 -0
  69. package/dist/op/activity-contract.d.ts.map +1 -1
  70. package/dist/op/activity-profiles.d.ts +16 -0
  71. package/dist/op/activity-profiles.d.ts.map +1 -1
  72. package/dist/op/builders.d.ts +84 -2
  73. package/dist/op/builders.d.ts.map +1 -1
  74. package/dist/op/composites/converge-op.d.ts +31 -4
  75. package/dist/op/composites/converge-op.d.ts.map +1 -1
  76. package/dist/op/decide-backend.d.ts +116 -0
  77. package/dist/op/decide-backend.d.ts.map +1 -0
  78. package/dist/op/decide-config.d.ts +120 -0
  79. package/dist/op/decide-config.d.ts.map +1 -0
  80. package/dist/op/decide-read-inputs.d.ts +40 -0
  81. package/dist/op/decide-read-inputs.d.ts.map +1 -0
  82. package/dist/op/discover.d.ts +25 -0
  83. package/dist/op/discover.d.ts.map +1 -1
  84. package/dist/op/gate-wait.d.ts +43 -0
  85. package/dist/op/gate-wait.d.ts.map +1 -0
  86. package/dist/op/gate.d.ts +5 -2
  87. package/dist/op/gate.d.ts.map +1 -1
  88. package/dist/op/index.d.ts +18 -5
  89. package/dist/op/index.d.ts.map +1 -1
  90. package/dist/op/lifecycle-receipt-store.d.ts +34 -0
  91. package/dist/op/lifecycle-receipt-store.d.ts.map +1 -0
  92. package/dist/op/local-executor.d.ts +48 -4
  93. package/dist/op/local-executor.d.ts.map +1 -1
  94. package/dist/op/local-output.d.ts +2 -0
  95. package/dist/op/local-output.d.ts.map +1 -1
  96. package/dist/op/op-ir.d.ts +12 -1
  97. package/dist/op/op-ir.d.ts.map +1 -1
  98. package/dist/op/op-verb-class.d.ts.map +1 -1
  99. package/dist/op/operator.d.ts +126 -1
  100. package/dist/op/operator.d.ts.map +1 -1
  101. package/dist/op/runtime.d.ts +46 -5
  102. package/dist/op/runtime.d.ts.map +1 -1
  103. package/dist/op/runtimes/local.d.ts.map +1 -1
  104. package/dist/op/source-archive.d.ts +63 -0
  105. package/dist/op/source-archive.d.ts.map +1 -0
  106. package/dist/op/step-output-ref.d.ts +2 -2
  107. package/dist/op/step-output-ref.d.ts.map +1 -1
  108. package/dist/op/steward-points.d.ts +136 -0
  109. package/dist/op/steward-points.d.ts.map +1 -0
  110. package/dist/op/steward-turn.d.ts +53 -0
  111. package/dist/op/steward-turn.d.ts.map +1 -0
  112. package/dist/op/steward.d.ts +161 -0
  113. package/dist/op/steward.d.ts.map +1 -0
  114. package/dist/op/types.d.ts +58 -0
  115. package/dist/op/types.d.ts.map +1 -1
  116. package/dist/op/work-lease-decl.d.ts +18 -0
  117. package/dist/op/work-lease-decl.d.ts.map +1 -0
  118. package/dist/op/work-lease-run.d.ts +173 -0
  119. package/dist/op/work-lease-run.d.ts.map +1 -0
  120. package/dist/workspace/__fixtures__/decide-workspace.d.ts +100 -0
  121. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -0
  122. package/dist/workspace/answers-ledger.d.ts +76 -0
  123. package/dist/workspace/answers-ledger.d.ts.map +1 -0
  124. package/dist/workspace/box-isolation.d.ts +99 -0
  125. package/dist/workspace/box-isolation.d.ts.map +1 -0
  126. package/dist/workspace/changes-cli.d.ts +16 -0
  127. package/dist/workspace/changes-cli.d.ts.map +1 -0
  128. package/dist/workspace/changes.d.ts +200 -0
  129. package/dist/workspace/changes.d.ts.map +1 -0
  130. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts +59 -0
  131. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts.map +1 -0
  132. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts +51 -0
  133. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts.map +1 -0
  134. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts +26 -0
  135. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts.map +1 -0
  136. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts +59 -0
  137. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts.map +1 -0
  138. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts +22 -0
  139. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts.map +1 -0
  140. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts +71 -0
  141. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -0
  142. package/dist/workspace/chant-migrations.d.ts +99 -0
  143. package/dist/workspace/chant-migrations.d.ts.map +1 -0
  144. package/dist/workspace/checks/box-isolation.d.ts +18 -0
  145. package/dist/workspace/checks/box-isolation.d.ts.map +1 -0
  146. package/dist/workspace/checks/boxes.d.ts +104 -0
  147. package/dist/workspace/checks/boxes.d.ts.map +1 -0
  148. package/dist/workspace/checks/diagrams.d.ts +31 -0
  149. package/dist/workspace/checks/diagrams.d.ts.map +1 -0
  150. package/dist/workspace/checks/records.d.ts +8 -0
  151. package/dist/workspace/checks/records.d.ts.map +1 -1
  152. package/dist/workspace/checks.d.ts +13 -1
  153. package/dist/workspace/checks.d.ts.map +1 -1
  154. package/dist/workspace/compose-graph.d.ts +2 -0
  155. package/dist/workspace/compose-graph.d.ts.map +1 -1
  156. package/dist/workspace/conformance/index.d.ts.map +1 -1
  157. package/dist/workspace/decide.d.ts +229 -0
  158. package/dist/workspace/decide.d.ts.map +1 -0
  159. package/dist/workspace/decision-points.schema.json +137 -0
  160. package/dist/workspace/declaration.d.ts +103 -0
  161. package/dist/workspace/declaration.d.ts.map +1 -1
  162. package/dist/workspace/declaration.schema.json +285 -0
  163. package/dist/workspace/declared-kinds.d.ts +32 -0
  164. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  165. package/dist/workspace/generated-files.d.ts +7 -0
  166. package/dist/workspace/generated-files.d.ts.map +1 -1
  167. package/dist/workspace/intent.d.ts.map +1 -1
  168. package/dist/workspace/lineage-check.d.ts +3 -0
  169. package/dist/workspace/lineage-check.d.ts.map +1 -1
  170. package/dist/workspace/lineage-upgrade.d.ts +6 -1
  171. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  172. package/dist/workspace/ls.d.ts +39 -1
  173. package/dist/workspace/ls.d.ts.map +1 -1
  174. package/dist/workspace/point-answer.schema.json +95 -0
  175. package/dist/workspace/points-cli.d.ts +113 -0
  176. package/dist/workspace/points-cli.d.ts.map +1 -0
  177. package/dist/workspace/points.d.ts +320 -0
  178. package/dist/workspace/points.d.ts.map +1 -0
  179. package/dist/workspace/reason-codes.d.ts +38 -0
  180. package/dist/workspace/reason-codes.d.ts.map +1 -1
  181. package/dist/workspace/record-assets.d.ts.map +1 -1
  182. package/dist/workspace/records-cli.d.ts +23 -1
  183. package/dist/workspace/records-cli.d.ts.map +1 -1
  184. package/dist/workspace/records-close.d.ts +1 -1
  185. package/dist/workspace/records-close.d.ts.map +1 -1
  186. package/dist/workspace/records-write.d.ts +11 -5
  187. package/dist/workspace/records-write.d.ts.map +1 -1
  188. package/dist/workspace/records.d.ts +42 -6
  189. package/dist/workspace/records.d.ts.map +1 -1
  190. package/dist/workspace/status-gates.d.ts +7 -2
  191. package/dist/workspace/status-gates.d.ts.map +1 -1
  192. package/dist/workspace/status-stewards.d.ts +179 -0
  193. package/dist/workspace/status-stewards.d.ts.map +1 -0
  194. package/dist/workspace/status.d.ts +68 -1
  195. package/dist/workspace/status.d.ts.map +1 -1
  196. package/dist/workspace/work-cli.d.ts +189 -0
  197. package/dist/workspace/work-cli.d.ts.map +1 -0
  198. package/dist/workspace/work-evidence.d.ts +74 -0
  199. package/dist/workspace/work-evidence.d.ts.map +1 -0
  200. package/dist/workspace/work.d.ts +43 -1
  201. package/dist/workspace/work.d.ts.map +1 -1
  202. package/package.json +1 -1
  203. package/src/cli/commands/audit.test.ts +9 -1
  204. package/src/cli/commands/build.ts +3 -2
  205. package/src/cli/commands/lint.ts +7 -1
  206. package/src/cli/handlers/components.test.ts +93 -0
  207. package/src/cli/handlers/components.ts +44 -3
  208. package/src/cli/handlers/graph.test.ts +20 -0
  209. package/src/cli/handlers/graph.ts +16 -2
  210. package/src/cli/handlers/operator.ts +141 -4
  211. package/src/cli/handlers/run.test.ts +173 -2
  212. package/src/cli/handlers/run.ts +174 -3
  213. package/src/cli/main.test.ts +50 -0
  214. package/src/cli/main.ts +97 -2
  215. package/src/cli/mcp/workspace-tools.test.ts +48 -19
  216. package/src/cli/mcp/workspace-tools.ts +110 -8
  217. package/src/cli/plugins.ts +11 -3
  218. package/src/cli/registry.ts +42 -1
  219. package/src/codegen/pinned-upgrade.ts +2 -1
  220. package/src/codegen/release-wiring.test.ts +49 -2
  221. package/src/components/capability-plugin-loader.ts +3 -3
  222. package/src/components/cli-support.ts +2 -2
  223. package/src/components/verbs/vuln-scan.test.ts +124 -1
  224. package/src/components/verbs/vuln-scan.ts +142 -1
  225. package/src/config.ts +11 -0
  226. package/src/lexicon-module.ts +56 -1
  227. package/src/lexicon-package.test.ts +96 -0
  228. package/src/lifecycle/converge-ledger.ts +20 -0
  229. package/src/lifecycle/git.ts +65 -15
  230. package/src/lifecycle/lease.test.ts +22 -0
  231. package/src/lifecycle/lease.ts +133 -29
  232. package/src/lifecycle/member-ledger.ts +3 -2
  233. package/src/lifecycle/plan-ledger.test.ts +148 -0
  234. package/src/lifecycle/plan-ledger.ts +158 -0
  235. package/src/lifecycle/run-ledger.ts +6 -0
  236. package/src/lifecycle/symptoms.ts +95 -0
  237. package/src/lifecycle/work-lease.test.ts +236 -0
  238. package/src/lifecycle/work-lease.ts +426 -0
  239. package/src/lint/rules/index.ts +6 -2
  240. package/src/lint/rules/op/index.ts +17 -4
  241. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +23 -1
  242. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +10 -6
  243. package/src/lint/rules/op/sys010-key-over-plain-http.test.ts +52 -0
  244. package/src/lint/rules/op/sys010-key-over-plain-http.ts +100 -0
  245. package/src/lint/rules/sys001-no-literal-key.test.ts +31 -0
  246. package/src/lint/rules/sys001-no-literal-key.ts +67 -0
  247. package/src/op/__fixtures__/decide-stub-backend.ts +143 -0
  248. package/src/op/activities/activity-contracts.ts +190 -1
  249. package/src/op/activities/apply.ts +7 -6
  250. package/src/op/activities/change-coverage.ts +60 -0
  251. package/src/op/activities/converge.test.ts +55 -1
  252. package/src/op/activities/converge.ts +208 -29
  253. package/src/op/activities/decide.live.test.ts +78 -0
  254. package/src/op/activities/decide.test.ts +300 -0
  255. package/src/op/activities/decide.ts +267 -0
  256. package/src/op/activities/index.ts +21 -0
  257. package/src/op/activities/shell.test.ts +91 -1
  258. package/src/op/activities/shell.ts +91 -2
  259. package/src/op/activities/source-release.test.ts +118 -0
  260. package/src/op/activities/source-release.ts +168 -0
  261. package/src/op/activities/source-rollback.test.ts +131 -0
  262. package/src/op/activities/source-rollback.ts +239 -0
  263. package/src/op/activities/work-evidence.ts +62 -0
  264. package/src/op/activity-contract-registry.test.ts +2 -0
  265. package/src/op/activity-contract-registry.ts +2 -2
  266. package/src/op/activity-contract.test.ts +12 -0
  267. package/src/op/activity-contract.ts +6 -1
  268. package/src/op/activity-profiles.ts +29 -0
  269. package/src/op/activity-registry.test.ts +1 -1
  270. package/src/op/activity-registry.ts +2 -2
  271. package/src/op/builders.ts +124 -5
  272. package/src/op/command-gate.test.ts +192 -0
  273. package/src/op/composites/converge-op.test.ts +27 -1
  274. package/src/op/composites/converge-op.ts +69 -15
  275. package/src/op/decide-backend.test.ts +44 -0
  276. package/src/op/decide-backend.ts +212 -0
  277. package/src/op/decide-config.test.ts +52 -0
  278. package/src/op/decide-config.ts +80 -0
  279. package/src/op/decide-read-inputs.ts +117 -0
  280. package/src/op/discover.ts +71 -0
  281. package/src/op/gate-approval.ts +2 -2
  282. package/src/op/gate-wait.ts +57 -0
  283. package/src/op/gate.ts +8 -3
  284. package/src/op/generate-pipeline.ts +2 -2
  285. package/src/op/index.ts +34 -6
  286. package/src/op/lifecycle-receipt-store.test.ts +60 -0
  287. package/src/op/lifecycle-receipt-store.ts +61 -0
  288. package/src/op/local-executor.ts +367 -33
  289. package/src/op/local-output.test.ts +33 -0
  290. package/src/op/local-output.ts +33 -2
  291. package/src/op/op-ir.test.ts +10 -0
  292. package/src/op/op-ir.ts +18 -0
  293. package/src/op/op-verb-class.ts +1 -0
  294. package/src/op/operator.ts +360 -10
  295. package/src/op/runtime.ts +37 -8
  296. package/src/op/runtimes/local.ts +6 -1
  297. package/src/op/source-archive.ts +161 -0
  298. package/src/op/step-output-ref.ts +6 -2
  299. package/src/op/steward-points.test.ts +313 -0
  300. package/src/op/steward-points.ts +194 -0
  301. package/src/op/steward-turn.ts +79 -0
  302. package/src/op/steward.test.ts +296 -0
  303. package/src/op/steward.ts +277 -0
  304. package/src/op/types.ts +60 -0
  305. package/src/op/work-lease-decl.ts +80 -0
  306. package/src/op/work-lease-run.test.ts +345 -0
  307. package/src/op/work-lease-run.ts +395 -0
  308. package/src/workspace/__fixtures__/decide-workspace.ts +129 -0
  309. package/src/workspace/__fixtures__/studio-template-84c8b21.json +67 -0
  310. package/src/workspace/answers-ledger.ts +118 -0
  311. package/src/workspace/box-isolation.test.ts +261 -0
  312. package/src/workspace/box-isolation.ts +205 -0
  313. package/src/workspace/changes-cli.ts +56 -0
  314. package/src/workspace/changes.schema.json +472 -0
  315. package/src/workspace/changes.test.ts +315 -0
  316. package/src/workspace/changes.ts +512 -0
  317. package/src/workspace/chant-migrations/chud-lexicon-exit-fly-site.ts +234 -0
  318. package/src/workspace/chant-migrations/chud-lexicon-exit-live-names.ts +164 -0
  319. package/src/workspace/chant-migrations/chud-lexicon-exit-rollback.ts +223 -0
  320. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.ts +195 -0
  321. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-policy.ts +108 -0
  322. package/src/workspace/chant-migrations/chud-lexicon-exit.ts +882 -0
  323. package/src/workspace/chant-migrations.e2e.test.ts +1089 -0
  324. package/src/workspace/chant-migrations.ts +164 -0
  325. package/src/workspace/check-contract.test.ts +5 -1
  326. package/src/workspace/check.schema.json +25 -7
  327. package/src/workspace/checks/box-isolation.ts +68 -0
  328. package/src/workspace/checks/boxes.test.ts +276 -0
  329. package/src/workspace/checks/boxes.ts +442 -0
  330. package/src/workspace/checks/diagrams.test.ts +134 -0
  331. package/src/workspace/checks/diagrams.ts +108 -0
  332. package/src/workspace/checks/records.ts +72 -0
  333. package/src/workspace/checks.test.ts +7 -0
  334. package/src/workspace/checks.ts +27 -3
  335. package/src/workspace/compose-graph.ts +2 -0
  336. package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +10 -0
  337. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +26 -0
  338. package/src/workspace/conformance/index.ts +5 -0
  339. package/src/workspace/decide.test.ts +224 -0
  340. package/src/workspace/decide.ts +709 -0
  341. package/src/workspace/decision-points.schema.json +137 -0
  342. package/src/workspace/declaration.schema.json +285 -0
  343. package/src/workspace/declaration.test.ts +58 -0
  344. package/src/workspace/declaration.ts +241 -0
  345. package/src/workspace/declared-kinds.test.ts +5 -5
  346. package/src/workspace/declared-kinds.ts +71 -3
  347. package/src/workspace/generated-files.ts +14 -0
  348. package/src/workspace/graph-cli.ts +1 -1
  349. package/src/workspace/graph.schema.json +9 -1
  350. package/src/workspace/intent.schema.json +12 -3
  351. package/src/workspace/intent.ts +3 -10
  352. package/src/workspace/lineage-check.ts +16 -1
  353. package/src/workspace/lineage-upgrade-cli.ts +1 -1
  354. package/src/workspace/lineage-upgrade.test.ts +70 -0
  355. package/src/workspace/lineage-upgrade.ts +81 -10
  356. package/src/workspace/ls-contract.test.ts +57 -0
  357. package/src/workspace/ls.schema.json +46 -1
  358. package/src/workspace/ls.ts +62 -3
  359. package/src/workspace/point-answer.schema.json +95 -0
  360. package/src/workspace/points-cli.ts +284 -0
  361. package/src/workspace/points-write.schema.json +499 -0
  362. package/src/workspace/points.schema.json +743 -0
  363. package/src/workspace/points.test.ts +264 -0
  364. package/src/workspace/points.ts +564 -0
  365. package/src/workspace/read-contract.test.ts +18 -1
  366. package/src/workspace/reason-codes.test.ts +24 -1
  367. package/src/workspace/reason-codes.ts +49 -0
  368. package/src/workspace/record-assets.test.ts +3 -2
  369. package/src/workspace/record-assets.ts +4 -1
  370. package/src/workspace/records-amend.schema.json +2 -0
  371. package/src/workspace/records-cli.ts +28 -5
  372. package/src/workspace/records-close.schema.json +2 -0
  373. package/src/workspace/records-contract.test.ts +3 -2
  374. package/src/workspace/records-new.schema.json +2 -0
  375. package/src/workspace/records-review.schema.json +2 -0
  376. package/src/workspace/records-sessions-write.e2e.test.ts +77 -0
  377. package/src/workspace/records-sessions-write.test.ts +2 -34
  378. package/src/workspace/records-write.e2e.test.ts +108 -0
  379. package/src/workspace/records-write.test.ts +32 -41
  380. package/src/workspace/records-write.ts +33 -15
  381. package/src/workspace/records.schema.json +53 -2
  382. package/src/workspace/records.test.ts +48 -3
  383. package/src/workspace/records.ts +142 -6
  384. package/src/workspace/status-contract.test.ts +189 -6
  385. package/src/workspace/status-gates.ts +9 -4
  386. package/src/workspace/status-stewards.ts +329 -0
  387. package/src/workspace/status.schema.json +335 -5
  388. package/src/workspace/status.ts +142 -6
  389. package/src/workspace/work-acceptance.test.ts +274 -0
  390. package/src/workspace/work-cli.e2e.test.ts +180 -0
  391. package/src/workspace/work-cli.ts +429 -0
  392. package/src/workspace/work-evidence.schema.json +212 -0
  393. package/src/workspace/work-evidence.ts +169 -0
  394. package/src/workspace/work-history.schema.json +349 -0
  395. package/src/workspace/work-history.test.ts +156 -0
  396. package/src/workspace/work-lease.schema.json +233 -0
  397. package/src/workspace/work-readiness-chud.test.ts +145 -0
  398. package/src/workspace/work.test.ts +3 -2
  399. package/src/workspace/work.ts +100 -1
  400. /package/src/cli/{discovery-skip.test.ts → discovery-skip.e2e.test.ts} +0 -0
  401. /package/src/workspace/conformance/{conformance.test.ts → conformance.e2e.test.ts} +0 -0
  402. /package/src/workspace/{which-chant.test.ts → which-chant.e2e.test.ts} +0 -0
@@ -24,7 +24,11 @@
24
24
  * The JSON also lists each member's gates, read from its gate ledger on the
25
25
  * same branch (#2674, `status-gates.ts`): the state of each, the approvals
26
26
  * that count, and the `chant approve` line that answers it. The text view
27
- * doesn't show them.
27
+ * doesn't show them. So does each member's box block (#2726): the
28
+ * capabilities it reaches through a broker, with the broker and the scope a
29
+ * broker enforces, and, when the block names a host, the box's ports, state
30
+ * paths and cookie names resolved from its identity (#2727,
31
+ * `box-isolation.ts`), so a runtime reads them instead of choosing its own.
28
32
  */
29
33
 
30
34
  import { execFileSync } from "node:child_process";
@@ -35,10 +39,14 @@ import type { CommandContext } from "../cli/registry";
35
39
  import { GATES_DIR } from "../lifecycle/gate-ledger";
36
40
  import { readPathSha } from "../lifecycle/git";
37
41
  import { latestPerComponent, readReleaseLedger, type ReleaseRecord } from "../lifecycle/release-ledger";
42
+ import { readReleasePlan, type ReleasePlan } from "../lifecycle/plan-ledger";
43
+ import { listWorkLeases, type WorkLeaseState } from "../lifecycle/work-lease";
38
44
  import { findWorkspaceRoot } from "../project-root";
39
- import { readDeclaration, readerVersion, WorkspaceReadError, type ErrorLocation, type Member } from "./declaration";
45
+ import { resolveBoxes, type ResolvedIsolation } from "./box-isolation";
46
+ import { declaredRecordKinds, readDeclaration, readerVersion, WorkspaceReadError, type Declaration, type ErrorLocation, type Member } from "./declaration";
40
47
  import type { ReasonCode } from "./reason-codes";
41
48
  import { GATE_REASON_CODES, readMemberGates, type GateLedgerReader, type StatusGate, type StatusGateLedger } from "./status-gates";
49
+ import { STEWARD_REASON_CODES, readMemberStewards, type StatusSteward, type StewardReasonCode } from "./status-stewards";
42
50
  import { gitTop, workingTree } from "./tree";
43
51
  import { handToRootChant } from "./which-chant";
44
52
 
@@ -71,6 +79,9 @@ export type StatusReasonCode = (typeof STATUS_REASON_CODES)[number];
71
79
  /** Why a member's gates can't be listed (#2674). Closed, like {@link STATUS_REASON_CODES}. */
72
80
  export const STATUS_GATE_REASON_CODES = GATE_REASON_CODES;
73
81
 
82
+ /** Why a member's stewards can't be fully listed (#2731). Closed, like {@link STATUS_REASON_CODES}. */
83
+ export const STATUS_STEWARD_REASON_CODES = STEWARD_REASON_CODES;
84
+
74
85
  /**
75
86
  * Why the status couldn't be read at all. The declaration's own codes, except
76
87
  * the two that only `--at` returns, and one for the environment name.
@@ -104,6 +115,15 @@ export interface StatusRelease {
104
115
  actor: string;
105
116
  /** Read flags, such as `legacy-digest` (#2514). */
106
117
  flags: string[];
118
+ /**
119
+ * The release plan `digest` names, read from `_plans/<digest>.json` on
120
+ * chant/lifecycle (ws-055, #2733) — the work items and evidence this
121
+ * release shipped, without the runner's own files. Null when this
122
+ * checkout has no plan stored under that digest: an ordinary component
123
+ * release (most releases carry no plan at all), or a plan this checkout
124
+ * hasn't fetched yet.
125
+ */
126
+ plan: ReleasePlan | null;
107
127
  }
108
128
 
109
129
  export interface StatusLedger {
@@ -145,6 +165,44 @@ export interface StatusMember {
145
165
  gateLedger: StatusGateLedger;
146
166
  /** Each gate in the member's gate ledger, one per environment asked for, sorted by component then gate. */
147
167
  gates: StatusGate[];
168
+ /** The member's box block as declared, or null when it declares none (#2726). A broker reads the scopes it enforces here. */
169
+ box: StatusBox | null;
170
+ /** The stewards the member declares (#2731), with their form in `env`, their Ops and each Op's last run. Sorted by name. */
171
+ stewards: StatusSteward[];
172
+ /** Why a steward or a last run may be missing from `stewards`. */
173
+ stewardReasons: { code: StewardReasonCode; message: string }[];
174
+ }
175
+
176
+ /** A box's brokered capabilities, from the declaration (#2726). */
177
+ export interface StatusBox {
178
+ capabilities: { name: string; broker: string | null; scope: string[] }[];
179
+ /** The box's ports, state paths and cookie names, resolved from its identity, or null when the block declares no host (#2727). */
180
+ isolation: ResolvedIsolation | null;
181
+ }
182
+
183
+ /**
184
+ * One work lease (#2732): a work item's lease ref in the ledger of the member
185
+ * that owns its work kind, read from the local refs without fetching.
186
+ */
187
+ export interface StatusLease extends Omit<WorkLeaseState, "ref"> {
188
+ /** The member whose ledger holds the lease, or null for the flat ledger (the root member, or a kind no member owns). */
189
+ member: string | null;
190
+ ref: string;
191
+ }
192
+
193
+ /**
194
+ * One work item's acceptance criteria, counted (#2772): each current record
195
+ * of a declared work kind with acceptance criteria that lists some.
196
+ */
197
+ export interface StatusAcceptance {
198
+ /** The member that declares the work kind, or null for the workspace's own. */
199
+ member: string | null;
200
+ /** The work kind file, from the workspace root. */
201
+ kind: string;
202
+ item: string;
203
+ state: string | null;
204
+ met: number;
205
+ total: number;
148
206
  }
149
207
 
150
208
  export type StatusDocument =
@@ -157,6 +215,10 @@ export type StatusDocument =
157
215
  lifecycle: { ref: string; commit: string | null };
158
216
  workspace: { name: string; root: string; file: string };
159
217
  members: StatusMember[];
218
+ /** Every work lease in the workspace's ledgers, active and expired, by member then item (#2732). A released lease has no ref and is not listed. */
219
+ leases: StatusLease[];
220
+ /** Each work item's acceptance criteria, met of total, by declared kind then item (#2772). Empty when no declared work kind has criteria. */
221
+ acceptance: StatusAcceptance[];
160
222
  summary: { members: number; released: number; unreadable: number; differing: number | null };
161
223
  }
162
224
  | {
@@ -179,6 +241,8 @@ export interface StatusQuery {
179
241
  readGates?: GateLedgerReader;
180
242
  /** The instant a gate's expiry is measured against; now by default. */
181
243
  now?: string;
244
+ /** Reads one member's stewards; the declaration and ledger reader unless a test swaps it. */
245
+ readStewards?: typeof readMemberStewards;
182
246
  }
183
247
 
184
248
  class StatusError extends Error {
@@ -240,8 +304,16 @@ async function readEnvironment(member: Member, env: string, cwd: string, read: L
240
304
  reason: { code: "ledger-unreadable", message: `${ledger.path}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` },
241
305
  };
242
306
  }
243
- const releases = [...latestPerComponent(records).values()]
244
- .map((r) => ({
307
+ // A member's plans live beside its other lifecycle stores (#2524 D7,
308
+ // #2538): _members/<member>/_plans/ for the "members" layout, the flat
309
+ // top-level _plans/ for "flat" — the same split ledger.path already
310
+ // resolved above for releases.jsonl, mirrored here since readReleasePlan
311
+ // reads through one process for every member without changing cwd per
312
+ // member (see plan-ledger.ts's readReleasePlan doc).
313
+ const planPrefix = ledger.layout === "members" ? `${MEMBERS_DIR}/${member.name}/` : "";
314
+ const latest = [...latestPerComponent(records).values()].sort((a, b) => (a.component < b.component ? -1 : a.component > b.component ? 1 : 0));
315
+ const releases: StatusRelease[] = await Promise.all(
316
+ latest.map(async (r) => ({
245
317
  component: r.component,
246
318
  digest: r.digest,
247
319
  gitSha: r.gitSha,
@@ -250,8 +322,9 @@ async function readEnvironment(member: Member, env: string, cwd: string, read: L
250
322
  timestamp: r.timestamp,
251
323
  actor: r.actor,
252
324
  flags: [...(r.flags ?? [])],
253
- }))
254
- .sort((a, b) => (a.component < b.component ? -1 : a.component > b.component ? 1 : 0));
325
+ plan: await readReleasePlan(r.digest, { cwd, prefix: planPrefix }),
326
+ })),
327
+ );
255
328
  return {
256
329
  env,
257
330
  ledger: { ...ledger, shared: false },
@@ -303,12 +376,14 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
303
376
  const commit = lifecycleTip(found.dir);
304
377
  const now = query.now ?? new Date().toISOString();
305
378
 
379
+ const isolation = new Map(resolveBoxes(declaration).map((b) => [b.member.name, b.isolation]));
306
380
  const members: StatusMember[] = [];
307
381
  for (const m of declaration.members) {
308
382
  const environments: StatusEnvironment[] = [];
309
383
  for (const env of envs) environments.push(await readEnvironment(m, env, found.dir, read));
310
384
  const own = await hasMemberLedger(m, found.dir);
311
385
  const gates = await readMemberGates(own ? `${MEMBERS_DIR}/${m.name}/${GATES_DIR}` : GATES_DIR, own ? "members" : "flat", commit, envs, found.dir, now, query.readGates);
386
+ const stewards = await (query.readStewards ?? readMemberStewards)(resolve(found.dir, m.dir), query.env, now, m.kind, m.box);
312
387
  members.push({
313
388
  name: m.name,
314
389
  dir: m.dir,
@@ -318,6 +393,15 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
318
393
  readable: environments.every((e) => e.reason === null),
319
394
  gateLedger: gates.ledger,
320
395
  gates: gates.gates,
396
+ box:
397
+ m.box === null
398
+ ? null
399
+ : {
400
+ capabilities: m.box.capabilities.map((c) => ({ name: c.name, broker: c.broker, scope: [...c.scope] })),
401
+ isolation: isolation.get(m.name) ?? null,
402
+ },
403
+ stewards: stewards.stewards,
404
+ stewardReasons: stewards.reasons,
321
405
  });
322
406
  }
323
407
  // Several members read from one flat ledger see the same records; say so.
@@ -327,6 +411,9 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
327
411
  const flatGates = members.filter((m) => m.gateLedger.layout === "flat").length;
328
412
  for (const m of members) m.gateLedger.shared = m.gateLedger.layout === "flat" && flatGates > 1;
329
413
 
414
+ const leases = await readWorkspaceLeases(declaration.members, found.dir, new Date(now));
415
+ const acceptance = await readWorkspaceAcceptance(declaration, found.dir);
416
+
330
417
  return {
331
418
  ...head,
332
419
  env: query.env,
@@ -334,6 +421,8 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
334
421
  lifecycle: { ref: LIFECYCLE_REF, commit },
335
422
  workspace: { name: declaration.name, root: rootDir === "" ? "." : rootDir, file: declaration.file },
336
423
  members,
424
+ leases,
425
+ acceptance,
337
426
  summary: {
338
427
  members: members.length,
339
428
  released: members.filter((m) => m.environments[0].releases.length > 0).length,
@@ -351,6 +440,31 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
351
440
  }
352
441
  }
353
442
 
443
+ /**
444
+ * The work leases of the flat ledger and of each member's (#2732). Leases are
445
+ * env-independent, so the environment asked for doesn't narrow them. Never
446
+ * fetches: the remote's leases are the ones last fetched.
447
+ */
448
+ async function readWorkspaceLeases(members: Member[], cwd: string, now: Date): Promise<StatusLease[]> {
449
+ const ledgers: { member: string | null; prefix: string }[] = [{ member: null, prefix: "" }];
450
+ for (const m of members) if (m.dir !== ".") ledgers.push({ member: m.name, prefix: `${MEMBERS_DIR}/${m.name}/` });
451
+ const out: StatusLease[] = [];
452
+ for (const l of ledgers) {
453
+ for (const lease of await listWorkLeases({ cwd, memberPrefix: l.prefix, now })) out.push({ ...lease, member: l.member });
454
+ }
455
+ return out;
456
+ }
457
+
458
+ /** The acceptance counts of every declared work kind with criteria (#2772), read in the working tree. */
459
+ async function readWorkspaceAcceptance(declaration: Declaration, root: string): Promise<StatusAcceptance[]> {
460
+ if (declaredRecordKinds(declaration).length === 0) return [];
461
+ const { loadDeclaredKinds } = await import("./declared-kinds");
462
+ const kinds = await loadDeclaredKinds(declaration, workingTree(root), root, { acceptance: true });
463
+ return kinds.flatMap((k) =>
464
+ (k.acceptance ?? []).map((a) => ({ member: k.declared.member, kind: k.declared.path, item: a.item, state: a.state, met: a.met, total: a.total })),
465
+ );
466
+ }
467
+
354
468
  export async function runWorkspaceStatus(ctx: CommandContext): Promise<number> {
355
469
  const { args } = ctx;
356
470
  const env = args.extraPositional;
@@ -434,6 +548,28 @@ export function formatStatus(doc: Extract<StatusDocument, { members: unknown }>)
434
548
  for (const n of notes.get(i) ?? []) lines.push(n);
435
549
  });
436
550
  }
551
+ if (doc.leases.length > 0) {
552
+ lines.push("");
553
+ const rows: string[][] = [["WORK ITEM", "HOLDER", "EXPIRES", "STATE"]];
554
+ for (const l of doc.leases) rows.push([l.member ? `${l.member}/${l.item}` : l.item, l.holder, l.expiresAt, l.state]);
555
+ lines.push(...table(rows));
556
+ }
557
+ if (doc.acceptance.length > 0) {
558
+ lines.push("");
559
+ const rows: string[][] = [["WORK ITEM", "STATE", "CRITERIA MET"]];
560
+ for (const a of doc.acceptance) rows.push([a.member ? `${a.member}/${a.item}` : a.item, a.state ?? "-", `${a.met}/${a.total}`]);
561
+ lines.push(...table(rows));
562
+ }
563
+ const boxes = doc.members.filter((m) => m.box?.isolation);
564
+ if (boxes.length > 0) {
565
+ lines.push("");
566
+ const rows: string[][] = [["BOX", "HOST", "SLOT", "PORTS", "STATE"]];
567
+ for (const m of boxes) {
568
+ const i = m.box!.isolation!;
569
+ rows.push([m.name, i.host, String(i.slot), `${i.portRange.from}-${i.portRange.to}`, i.stateDir]);
570
+ }
571
+ lines.push(...table(rows));
572
+ }
437
573
  const s = doc.summary;
438
574
  lines.push("");
439
575
  lines.push(
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Acceptance criteria on work records (#2772), on a workspace built from the
3
+ * reference workspace's decision and work kinds in a throwaway git repository.
4
+ *
5
+ * - W-001 is done, and passing evidence of each criterion's verification
6
+ * meets both of them, the manual one by bob while alice owns the item: met.
7
+ * - W-002 is done, and its only evidence is a unit test for an e2e
8
+ * criterion: unmet.
9
+ * - W-003 is done, and its manual criterion's verdict is by alice, its owner:
10
+ * the verdict does not count, so it is unmet as well.
11
+ * - W-004 is done and lists no criteria: a legacy item, read as before.
12
+ * - W-005 is in progress. A steward's Op attaches evidence to it under its
13
+ * work lease, and is refused on the manual criterion.
14
+ */
15
+
16
+ import { readFileSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
19
+ import { cleanScratch, contract, git, REPO, repo } from "./__fixtures__/contract-repo";
20
+ import { runDeclarationChecks } from "./checks";
21
+ import { listWorkspaceWithKinds } from "./ls";
22
+ import lsSchema from "./ls.schema.json";
23
+ import { queryRecords } from "./records-cli";
24
+ import recordsSchema from "./records.schema.json";
25
+ import { workspaceStatus } from "./status";
26
+ import statusSchema from "./status.schema.json";
27
+ import { attachWorkEvidence } from "./work-evidence";
28
+ import workEvidenceSchema from "./work-evidence.schema.json";
29
+ import { workEvidence } from "../op/builders";
30
+ import { workEvidence as workEvidenceActivity } from "../op/activities";
31
+ import { ACTIVITY_PROFILES } from "../op/activity-profiles";
32
+ import { OpRunFailure, runOpLocally, type OpRunResult } from "../op/local-executor";
33
+ import type { ActivityFn } from "../op/activity-registry";
34
+ import type { OpConfig } from "../op/types";
35
+ import { declareSteward } from "../op/steward";
36
+ import { claimWorkLease, releaseWorkLease } from "../lifecycle/work-lease";
37
+
38
+ const REF = join(REPO, "reference-workspace");
39
+ const ref = (path: string) => readFileSync(join(REF, path), "utf-8");
40
+
41
+ function work(id: string, fields: Record<string, unknown>): string {
42
+ const data = {
43
+ schema: 1,
44
+ id,
45
+ title: `Work ${id}`,
46
+ state: "done",
47
+ implements: [],
48
+ needs: [],
49
+ constrains: ["member:app"],
50
+ evidence: [],
51
+ owner: "alice",
52
+ opened_on: "2026-09-25",
53
+ closed_on: "2026-09-25",
54
+ source: { kind: "workspace", member: "app" },
55
+ supersedes: [],
56
+ ...fields,
57
+ };
58
+ return `---\n${JSON.stringify(data, null, 2)}\n---\n\n# ${id}\n`;
59
+ }
60
+
61
+ const link = (criterion: string, verification: string, result: string, extra: Record<string, unknown> = {}) => ({
62
+ title: `${criterion} ${verification}`,
63
+ url: `https://example.com/${criterion}`,
64
+ criterion,
65
+ verification,
66
+ result,
67
+ ...extra,
68
+ });
69
+
70
+ const KIND = "work/work.kind.mjs";
71
+ let root: string;
72
+
73
+ beforeAll(() => {
74
+ root = repo({
75
+ "chant.workspace.json": JSON.stringify(
76
+ {
77
+ name: "studio",
78
+ schema: 1,
79
+ members: [{ name: "app", dir: "app", kind: "other", because: "a plain Node server" }],
80
+ records: [{ kind: "decisions/decision.kind.mjs" }, { kind: KIND }],
81
+ },
82
+ null,
83
+ 2,
84
+ ),
85
+ "app/server.mjs": "export const port = 8080;\n",
86
+ "decisions/decision.kind.mjs": ref("decisions/decision.kind.mjs"),
87
+ "decisions/decision.schema.json": ref("decisions/decision.schema.json"),
88
+ "decisions/ref-001-how-the-app-is-deployed.md": ref("decisions/ref-001-how-the-app-is-deployed.md"),
89
+ [KIND]: ref(KIND),
90
+ "work/work.schema.json": ref("work/work.schema.json"),
91
+ "work/W-001-met.md": work("W-001", {
92
+ acceptance: [
93
+ { id: "AC-1", text: "The server answers on its port", verification: "unit" },
94
+ { id: "AC-2", text: "A designer finds the page matches the spec", verification: "manual" },
95
+ ],
96
+ evidence: [link("AC-1", "unit", "fail"), link("AC-1", "unit", "pass"), link("AC-2", "manual", "pass", { by: "bob" })],
97
+ }),
98
+ "work/W-002-unmet.md": work("W-002", {
99
+ acceptance: [{ id: "AC-1", text: "The page loads in a browser", verification: "e2e" }],
100
+ evidence: [link("AC-1", "unit", "pass")],
101
+ }),
102
+ "work/W-003-self.md": work("W-003", {
103
+ acceptance: [{ id: "AC-1", text: "Someone else signs off", verification: "manual" }],
104
+ evidence: [link("AC-1", "manual", "pass", { by: "Alice" })],
105
+ }),
106
+ "work/W-004-legacy.md": work("W-004", { evidence: [{ title: "The review", url: "https://example.com/review" }] }),
107
+ "work/W-005-leased.md": work("W-005", {
108
+ state: "in-progress",
109
+ closed_on: null,
110
+ acceptance: [
111
+ { id: "AC-1", text: "The server's test passes", verification: "unit" },
112
+ { id: "AC-2", text: "A person tries it", verification: "manual" },
113
+ ],
114
+ }),
115
+ });
116
+ git(root, "config", "user.name", "t");
117
+ git(root, "config", "user.email", "t@example.com");
118
+ git(root, "config", "commit.gpgsign", "false");
119
+ git(root, "add", "-A");
120
+ git(root, "commit", "-q", "-m", "the work");
121
+ });
122
+ afterAll(cleanScratch);
123
+
124
+ const records = contract(recordsSchema);
125
+
126
+ async function read() {
127
+ const doc = await queryRecords({ kind: join(root, KIND), cwd: root, workGaps: false });
128
+ records.expectValid(doc);
129
+ if ("error" in doc || !("records" in doc)) throw new Error(JSON.stringify(doc));
130
+ return doc.records;
131
+ }
132
+
133
+ const codes = (r: { warnings: { code: string }[] }) => r.warnings.map((w) => w.code);
134
+
135
+ describe("acceptance criteria on read (#2772)", () => {
136
+ test("met: each criterion has passing evidence of its verification, and the manual verdict is someone else's", async () => {
137
+ const w1 = (await read()).find((r) => r.id === "W-001")!;
138
+ expect(w1.valid).toBe(true);
139
+ expect(w1.acceptance).toEqual({
140
+ met: 2,
141
+ total: 2,
142
+ criteria: [
143
+ { id: "AC-1", verification: "unit", met: true },
144
+ { id: "AC-2", verification: "manual", met: true },
145
+ ],
146
+ });
147
+ expect(codes(w1)).toEqual([]);
148
+ });
149
+
150
+ test("unmet: evidence of another verification does not count, and the done item is warned", async () => {
151
+ const w2 = (await read()).find((r) => r.id === "W-002")!;
152
+ expect(w2.acceptance).toMatchObject({ met: 0, total: 1 });
153
+ expect(codes(w2)).toEqual(["work-acceptance-unmet"]);
154
+ expect(w2.warnings[0].message).toMatch(/AC-1 \(e2e\)/);
155
+ });
156
+
157
+ test("manual by the implementer: the verdict is refused, warned, and the criterion stays unmet", async () => {
158
+ const w3 = (await read()).find((r) => r.id === "W-003")!;
159
+ expect(w3.acceptance).toMatchObject({ met: 0, total: 1 });
160
+ expect(codes(w3)).toEqual(["work-acceptance-self-verified", "work-acceptance-unmet"]);
161
+ });
162
+
163
+ test("a legacy item without criteria is valid, reads acceptance null and gets no acceptance warning", async () => {
164
+ const w4 = (await read()).find((r) => r.id === "W-004")!;
165
+ expect(w4.valid).toBe(true);
166
+ expect(w4.acceptance).toBeNull();
167
+ expect(codes(w4)).toEqual([]);
168
+ });
169
+
170
+ test("an open item with criteria unmet is not warned: only done needs them met", async () => {
171
+ const w5 = (await read()).find((r) => r.id === "W-005")!;
172
+ expect(w5.acceptance).toMatchObject({ met: 0, total: 2 });
173
+ expect(codes(w5)).toEqual([]);
174
+ });
175
+
176
+ test("check fails with WSP117 and the reason code on each done item with a criterion unmet", async () => {
177
+ const report = await runDeclarationChecks(root);
178
+ const wsp117 = report.diagnostics.filter((d) => d.ruleId === "WSP117");
179
+ expect(wsp117.map((d) => [d.file, d.code, d.severity])).toEqual([
180
+ ["work/W-002-unmet.md", "work-acceptance-unmet", "error"],
181
+ ["work/W-003-self.md", "work-acceptance-unmet", "error"],
182
+ ]);
183
+ expect(report.ok).toBe(false);
184
+ });
185
+
186
+ test("ls --json and status --json count each item's criteria, met of total, in the read contract", async () => {
187
+ const ls = await listWorkspaceWithKinds({ cwd: root });
188
+ contract(lsSchema).expectValid(ls);
189
+ if ("error" in ls) throw new Error(ls.error.message);
190
+ const kinds = Object.fromEntries(ls.workspace.records.map((r) => [r.path, r.acceptance]));
191
+ expect(kinds["decisions/decision.kind.mjs"]).toBeNull();
192
+ expect(kinds[KIND]).toEqual([
193
+ { item: "W-001", state: "done", met: 2, total: 2 },
194
+ { item: "W-002", state: "done", met: 0, total: 1 },
195
+ { item: "W-003", state: "done", met: 0, total: 1 },
196
+ { item: "W-005", state: "in-progress", met: 0, total: 2 },
197
+ ]);
198
+
199
+ const status = await workspaceStatus({ cwd: root, env: "dev" });
200
+ contract(statusSchema).expectValid(status);
201
+ if ("error" in status) throw new Error(status.error.message);
202
+ expect(status.acceptance.map((a) => [a.member, a.kind, a.item, `${a.met}/${a.total}`])).toEqual([
203
+ [null, KIND, "W-001", "2/2"],
204
+ [null, KIND, "W-002", "0/1"],
205
+ [null, KIND, "W-003", "0/1"],
206
+ [null, KIND, "W-005", "0/2"],
207
+ ]);
208
+ });
209
+ });
210
+
211
+ describe("a steward Op under workLease attaches evidence to a criterion (#2772)", () => {
212
+ const evidenceDoc = contract(workEvidenceSchema);
213
+
214
+ async function run(config: OpConfig, holder: string): Promise<OpRunResult> {
215
+ const activities = new Map<string, ActivityFn>([["workEvidence", workEvidenceActivity as unknown as ActivityFn]]);
216
+ try {
217
+ return await runOpLocally(config, activities, ACTIVITY_PROFILES, undefined, { cwd: root, ledger: { cwd: root }, work: { holder } });
218
+ } catch (err) {
219
+ if (err instanceof OpRunFailure) return err.result;
220
+ throw err;
221
+ }
222
+ }
223
+
224
+ const op = (step: ReturnType<typeof workEvidence>): OpConfig => ({
225
+ name: "verify",
226
+ overview: "attach the test run to its criterion",
227
+ workLease: { item: "W-005", kind: KIND },
228
+ phases: [{ name: "Verify", steps: [step] }],
229
+ });
230
+
231
+ test("the run holding the lease appends passing evidence, and the criterion is met", async () => {
232
+ const config = op(workEvidence("AC-1", { result: "pass", title: "the server test", url: "https://example.com/ci/1", cwd: root }));
233
+ expect(declareSteward({ name: "box", ops: [config] }).ops).toHaveLength(1);
234
+ const result = await run(config, "box/verify@h1");
235
+ expect(result.status).toBe("ok");
236
+ expect(result.workLease).toMatchObject({ item: "W-005", released: true });
237
+ const w5 = (await read()).find((r) => r.id === "W-005")!;
238
+ expect(w5.acceptance).toMatchObject({ met: 1, total: 2 });
239
+ expect(w5.data!.evidence).toEqual([
240
+ expect.objectContaining({ url: "https://example.com/ci/1", criterion: "AC-1", verification: "unit", result: "pass", by: "box/verify@h1" }),
241
+ ]);
242
+ });
243
+
244
+ test("a manual criterion is refused: the run holding the lease is the implementer", async () => {
245
+ const result = await run(op(workEvidence("AC-2", { result: "pass", title: "tried it", url: "https://example.com/tried", cwd: root })), "box/verify@h2");
246
+ expect(result.status).toBe("fail");
247
+ expect(JSON.stringify(result.records)).toMatch(/work-acceptance-self-verified/);
248
+ expect((await read()).find((r) => r.id === "W-005")!.acceptance).toMatchObject({ met: 1, total: 2 });
249
+ });
250
+
251
+ test("without the live lease, or with another token or criterion, nothing is written", async () => {
252
+ const ask = (over: Partial<Parameters<typeof attachWorkEvidence>[0]>) =>
253
+ attachWorkEvidence({ cwd: root, item: "W-005", holder: "me", token: "t", criterion: "AC-1", result: "pass", title: "x", url: "https://example.com/x", ...over });
254
+ const unleased = await ask({});
255
+ evidenceDoc.expectValid(unleased);
256
+ expect("error" in unleased && unleased.error.code).toBe("lease-not-held");
257
+
258
+ const claimed = await claimWorkLease("W-005", "me", { cwd: join(root, "work") });
259
+ if (!claimed.ok) throw new Error(claimed.message);
260
+ try {
261
+ expect(await ask({ token: "other" })).toMatchObject({ error: { code: "lease-token-mismatch" } });
262
+ expect(await ask({ holder: "you", token: claimed.lease.token })).toMatchObject({ error: { code: "lease-held" } });
263
+ expect(await ask({ token: claimed.lease.token, criterion: "AC-9" })).toMatchObject({ error: { code: "work-criterion-unknown" } });
264
+ const ok = await ask({ token: claimed.lease.token, result: "fail" });
265
+ evidenceDoc.expectValid(ok);
266
+ expect(ok).toMatchObject({ item: "W-005", kind: KIND, path: "work/W-005-leased.md", acceptance: { met: 1, total: 2 } });
267
+ } finally {
268
+ await releaseWorkLease("W-005", "me", { cwd: join(root, "work"), token: claimed.lease.token });
269
+ }
270
+ expect(await attachWorkEvidence({ cwd: root, item: "W-004", holder: "me", token: "t", criterion: "AC-1", result: "pass", title: "x", url: "https://example.com/x" })).toMatchObject({
271
+ error: { code: "work-item-closed" },
272
+ });
273
+ });
274
+ });