@intentius/chant 0.101.0 → 0.102.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 (427) hide show
  1. package/dist/agents/checks.d.ts.map +1 -1
  2. package/dist/attrref.d.ts.map +1 -1
  3. package/dist/audit/discover.d.ts.map +1 -1
  4. package/dist/audit/nginx.d.ts.map +1 -1
  5. package/dist/audit/prior-art.d.ts +48 -0
  6. package/dist/audit/prior-art.d.ts.map +1 -1
  7. package/dist/behaviour-kinds.d.ts.map +1 -1
  8. package/dist/builder.d.ts.map +1 -1
  9. package/dist/cli/commands/carve-emit.d.ts.map +1 -1
  10. package/dist/cli/commands/check-lexicon.d.ts +16 -0
  11. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  12. package/dist/cli/commands/import.d.ts +8 -0
  13. package/dist/cli/commands/import.d.ts.map +1 -1
  14. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  15. package/dist/cli/commands/init.d.ts +19 -0
  16. package/dist/cli/commands/init.d.ts.map +1 -1
  17. package/dist/cli/commands/onboard.d.ts +9 -0
  18. package/dist/cli/commands/onboard.d.ts.map +1 -1
  19. package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
  20. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -1
  21. package/dist/cli/handlers/dev.d.ts.map +1 -1
  22. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  23. package/dist/cli/handlers/misc.d.ts.map +1 -1
  24. package/dist/cli/handlers/operator.d.ts +9 -0
  25. package/dist/cli/handlers/operator.d.ts.map +1 -1
  26. package/dist/cli/handlers/run.d.ts.map +1 -1
  27. package/dist/cli/lsp/server.d.ts.map +1 -1
  28. package/dist/cli/main.d.ts.map +1 -1
  29. package/dist/cli/mcp/server.d.ts.map +1 -1
  30. package/dist/cli/mcp/tools/build.d.ts.map +1 -1
  31. package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
  32. package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
  33. package/dist/cli/mcp/tools/import.d.ts.map +1 -1
  34. package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
  35. package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
  36. package/dist/cli/mcp/tools/search.d.ts.map +1 -1
  37. package/dist/cli/mcp/workspace-tools.d.ts +1 -1
  38. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  39. package/dist/cli/registry.d.ts +11 -2
  40. package/dist/cli/registry.d.ts.map +1 -1
  41. package/dist/codegen/fetch.d.ts.map +1 -1
  42. package/dist/codegen/naming.d.ts.map +1 -1
  43. package/dist/codegen/pinned-upgrade.d.ts +67 -3
  44. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  45. package/dist/codegen/registry.d.ts.map +1 -1
  46. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  47. package/dist/components/capability.d.ts.map +1 -1
  48. package/dist/components/driver.d.ts.map +1 -1
  49. package/dist/components/fan-out.d.ts.map +1 -1
  50. package/dist/components/starter-plugin.d.ts.map +1 -1
  51. package/dist/components/verbs/process-runner.d.ts.map +1 -1
  52. package/dist/components/verbs/r2-sync.d.ts.map +1 -1
  53. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  54. package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
  55. package/dist/components/verbs/sign.d.ts.map +1 -1
  56. package/dist/components/verbs/verify.d.ts.map +1 -1
  57. package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
  58. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  59. package/dist/components/verbs/wrangler.d.ts.map +1 -1
  60. package/dist/composite.d.ts.map +1 -1
  61. package/dist/config.d.ts +19 -7
  62. package/dist/config.d.ts.map +1 -1
  63. package/dist/declarable.d.ts +16 -0
  64. package/dist/declarable.d.ts.map +1 -1
  65. package/dist/discovery/cache.d.ts.map +1 -1
  66. package/dist/discovery/fold-import.d.ts +15 -5
  67. package/dist/discovery/fold-import.d.ts.map +1 -1
  68. package/dist/discovery/param-deps.d.ts +16 -0
  69. package/dist/discovery/param-deps.d.ts.map +1 -1
  70. package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
  71. package/dist/discovery/specifier-resolve.d.ts +37 -0
  72. package/dist/discovery/specifier-resolve.d.ts.map +1 -0
  73. package/dist/errors.d.ts.map +1 -1
  74. package/dist/fold/fold.d.ts +3 -1
  75. package/dist/fold/fold.d.ts.map +1 -1
  76. package/dist/graph-detail.d.ts.map +1 -1
  77. package/dist/graph-ir.d.ts.map +1 -1
  78. package/dist/graph-layout.d.ts.map +1 -1
  79. package/dist/import/base-parser.d.ts.map +1 -1
  80. package/dist/import/embedded.d.ts +7 -0
  81. package/dist/import/embedded.d.ts.map +1 -1
  82. package/dist/import/parser-options.d.ts +14 -0
  83. package/dist/import/parser-options.d.ts.map +1 -0
  84. package/dist/import/parser.d.ts +17 -0
  85. package/dist/import/parser.d.ts.map +1 -1
  86. package/dist/kubectl-context.d.ts.map +1 -1
  87. package/dist/lexicon-output.d.ts.map +1 -1
  88. package/dist/lexicon-schema.d.ts +3 -3
  89. package/dist/lexicon.d.ts +55 -3
  90. package/dist/lexicon.d.ts.map +1 -1
  91. package/dist/lifecycle/assert-live.d.ts.map +1 -1
  92. package/dist/lifecycle/gate-ledger.d.ts +15 -0
  93. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  94. package/dist/lifecycle/git.d.ts.map +1 -1
  95. package/dist/lifecycle/member-ledger.d.ts.map +1 -1
  96. package/dist/lifecycle/observe.d.ts.map +1 -1
  97. package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
  98. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  99. package/dist/lifecycle/scenario.d.ts.map +1 -1
  100. package/dist/lifecycle/status.d.ts.map +1 -1
  101. package/dist/lifecycle/work-lease.d.ts +61 -2
  102. package/dist/lifecycle/work-lease.d.ts.map +1 -1
  103. package/dist/lint/config.d.ts +8 -8
  104. package/dist/lint/engine.d.ts +6 -0
  105. package/dist/lint/engine.d.ts.map +1 -1
  106. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  107. package/dist/op/activities/activity-contracts.d.ts +11 -11
  108. package/dist/op/activities/apply.d.ts +79 -9
  109. package/dist/op/activities/apply.d.ts.map +1 -1
  110. package/dist/op/activities/decide.d.ts.map +1 -1
  111. package/dist/op/activities/index.d.ts +1 -1
  112. package/dist/op/activities/index.d.ts.map +1 -1
  113. package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
  114. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
  115. package/dist/op/activity-contract.d.ts +1 -1
  116. package/dist/op/activity-failure.d.ts.map +1 -1
  117. package/dist/op/activity-profiles.d.ts.map +1 -1
  118. package/dist/op/builders.d.ts.map +1 -1
  119. package/dist/op/composites/apply-op.d.ts +12 -5
  120. package/dist/op/composites/apply-op.d.ts.map +1 -1
  121. package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
  122. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
  123. package/dist/op/decide-backend.d.ts.map +1 -1
  124. package/dist/op/decide-read-inputs.d.ts.map +1 -1
  125. package/dist/op/emulator-lifecycle.d.ts +21 -2
  126. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  127. package/dist/op/gate-approval.d.ts +5 -1
  128. package/dist/op/gate-approval.d.ts.map +1 -1
  129. package/dist/op/gate-wait.d.ts.map +1 -1
  130. package/dist/op/gate.d.ts +19 -3
  131. package/dist/op/gate.d.ts.map +1 -1
  132. package/dist/op/index.d.ts +2 -0
  133. package/dist/op/index.d.ts.map +1 -1
  134. package/dist/op/local-executor.d.ts +6 -0
  135. package/dist/op/local-executor.d.ts.map +1 -1
  136. package/dist/op/operator.d.ts.map +1 -1
  137. package/dist/op/run-context.d.ts +39 -0
  138. package/dist/op/run-context.d.ts.map +1 -0
  139. package/dist/op/run-live.d.ts.map +1 -1
  140. package/dist/op/runtimes/local.d.ts.map +1 -1
  141. package/dist/op/source-archive.d.ts.map +1 -1
  142. package/dist/op/steward-beside.d.ts.map +1 -1
  143. package/dist/op/steward-points.d.ts.map +1 -1
  144. package/dist/op/steward.d.ts.map +1 -1
  145. package/dist/op/types.d.ts +8 -3
  146. package/dist/op/types.d.ts.map +1 -1
  147. package/dist/op/work-lease-run.d.ts +24 -4
  148. package/dist/op/work-lease-run.d.ts.map +1 -1
  149. package/dist/project-root.d.ts.map +1 -1
  150. package/dist/provenance.d.ts +21 -0
  151. package/dist/provenance.d.ts.map +1 -1
  152. package/dist/pseudo-parameter.d.ts.map +1 -1
  153. package/dist/reconcile.d.ts.map +1 -1
  154. package/dist/secret-materialization.d.ts.map +1 -1
  155. package/dist/terraform/emit-source.d.ts.map +1 -1
  156. package/dist/terraform/parse.d.ts.map +1 -1
  157. package/dist/testing.d.ts.map +1 -1
  158. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  159. package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
  160. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
  161. package/dist/workspace/agent-cli.d.ts +5 -0
  162. package/dist/workspace/agent-cli.d.ts.map +1 -1
  163. package/dist/workspace/box-factory.d.ts +73 -0
  164. package/dist/workspace/box-factory.d.ts.map +1 -0
  165. package/dist/workspace/changes.d.ts.map +1 -1
  166. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
  167. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  168. package/dist/workspace/checks.d.ts +5 -0
  169. package/dist/workspace/checks.d.ts.map +1 -1
  170. package/dist/workspace/compose-graph.d.ts +10 -0
  171. package/dist/workspace/compose-graph.d.ts.map +1 -1
  172. package/dist/workspace/conformance/index.d.ts +67 -6
  173. package/dist/workspace/conformance/index.d.ts.map +1 -1
  174. package/dist/workspace/conformance/vitest.d.ts +12 -3
  175. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  176. package/dist/workspace/conformance/writer.d.ts +368 -0
  177. package/dist/workspace/conformance/writer.d.ts.map +1 -0
  178. package/dist/workspace/decide.d.ts +1 -1
  179. package/dist/workspace/decide.d.ts.map +1 -1
  180. package/dist/workspace/declaration.d.ts +91 -4
  181. package/dist/workspace/declaration.d.ts.map +1 -1
  182. package/dist/workspace/declaration.schema.json +234 -5
  183. package/dist/workspace/generated-files.d.ts.map +1 -1
  184. package/dist/workspace/graph-cache.d.ts.map +1 -1
  185. package/dist/workspace/graph-cli.d.ts +2 -0
  186. package/dist/workspace/graph-cli.d.ts.map +1 -1
  187. package/dist/workspace/identity.d.ts +111 -0
  188. package/dist/workspace/identity.d.ts.map +1 -0
  189. package/dist/workspace/intent-cli.d.ts +7 -4
  190. package/dist/workspace/intent-cli.d.ts.map +1 -1
  191. package/dist/workspace/intent-record.d.ts +8 -1
  192. package/dist/workspace/intent-record.d.ts.map +1 -1
  193. package/dist/workspace/intent.d.ts +149 -5
  194. package/dist/workspace/intent.d.ts.map +1 -1
  195. package/dist/workspace/kinds.d.ts +18 -4
  196. package/dist/workspace/kinds.d.ts.map +1 -1
  197. package/dist/workspace/lineage-hash-index.d.ts.map +1 -1
  198. package/dist/workspace/lineage-lock.d.ts +2 -2
  199. package/dist/workspace/lineage-lock.d.ts.map +1 -1
  200. package/dist/workspace/links.d.ts.map +1 -1
  201. package/dist/workspace/points.d.ts.map +1 -1
  202. package/dist/workspace/principal-classes.d.ts +114 -0
  203. package/dist/workspace/principal-classes.d.ts.map +1 -0
  204. package/dist/workspace/reason-codes.d.ts +21 -1
  205. package/dist/workspace/reason-codes.d.ts.map +1 -1
  206. package/dist/workspace/record-assets.d.ts.map +1 -1
  207. package/dist/workspace/records-checkout.d.ts +54 -0
  208. package/dist/workspace/records-checkout.d.ts.map +1 -0
  209. package/dist/workspace/records-cli.d.ts +27 -1
  210. package/dist/workspace/records-cli.d.ts.map +1 -1
  211. package/dist/workspace/records-close.d.ts +1 -1
  212. package/dist/workspace/records-close.d.ts.map +1 -1
  213. package/dist/workspace/records-since.d.ts.map +1 -1
  214. package/dist/workspace/records-write.d.ts +5 -4
  215. package/dist/workspace/records-write.d.ts.map +1 -1
  216. package/dist/workspace/records.d.ts +15 -0
  217. package/dist/workspace/records.d.ts.map +1 -1
  218. package/dist/workspace/runs-cli.d.ts +164 -0
  219. package/dist/workspace/runs-cli.d.ts.map +1 -0
  220. package/dist/workspace/runs.d.ts +423 -0
  221. package/dist/workspace/runs.d.ts.map +1 -0
  222. package/dist/workspace/source-block.d.ts +1 -1
  223. package/dist/workspace/source-block.d.ts.map +1 -1
  224. package/dist/workspace/status-gates.d.ts +15 -3
  225. package/dist/workspace/status-gates.d.ts.map +1 -1
  226. package/dist/workspace/status.d.ts +10 -0
  227. package/dist/workspace/status.d.ts.map +1 -1
  228. package/dist/workspace/symbols.d.ts +52 -0
  229. package/dist/workspace/symbols.d.ts.map +1 -0
  230. package/dist/workspace/trailer-joins.d.ts +46 -0
  231. package/dist/workspace/trailer-joins.d.ts.map +1 -0
  232. package/dist/workspace/trailers.d.ts +89 -0
  233. package/dist/workspace/trailers.d.ts.map +1 -0
  234. package/dist/workspace/trust/attestor.d.ts.map +1 -1
  235. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  236. package/dist/workspace/trust/seal.d.ts +33 -0
  237. package/dist/workspace/trust/seal.d.ts.map +1 -1
  238. package/dist/workspace/trust/test-repo.d.ts.map +1 -1
  239. package/dist/workspace/work-cli.d.ts +39 -3
  240. package/dist/workspace/work-cli.d.ts.map +1 -1
  241. package/dist/workspace/work-evidence.d.ts +1 -1
  242. package/dist/workspace/work-evidence.d.ts.map +1 -1
  243. package/dist/workspace/work.d.ts +26 -1
  244. package/dist/workspace/work.d.ts.map +1 -1
  245. package/dist/workspace/workspace-principals.schema.json +60 -0
  246. package/dist/workspace/write-scope.d.ts +79 -19
  247. package/dist/workspace/write-scope.d.ts.map +1 -1
  248. package/dist/yaml.d.ts +21 -39
  249. package/dist/yaml.d.ts.map +1 -1
  250. package/package.json +2 -2
  251. package/src/audit/prior-art.ts +15 -0
  252. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
  253. package/src/cli/commands/carve-emit-state.test.ts +4 -0
  254. package/src/cli/commands/carve-emit.ts +5 -19
  255. package/src/cli/commands/check-lexicon.test.ts +62 -1
  256. package/src/cli/commands/check-lexicon.ts +36 -6
  257. package/src/cli/commands/import-live.test.ts +18 -0
  258. package/src/cli/commands/import-parser-options.test.ts +133 -0
  259. package/src/cli/commands/import.ts +21 -3
  260. package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
  261. package/src/cli/commands/init.test.ts +8 -1
  262. package/src/cli/commands/init.ts +23 -8
  263. package/src/cli/commands/lint.test.ts +37 -0
  264. package/src/cli/commands/onboard.test.ts +53 -3
  265. package/src/cli/commands/onboard.ts +52 -4
  266. package/src/cli/commands/pinned-upgrade.ts +73 -33
  267. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
  268. package/src/cli/handlers/components.ts +2 -2
  269. package/src/cli/handlers/dev.ts +8 -6
  270. package/src/cli/handlers/lifecycle.test.ts +67 -0
  271. package/src/cli/handlers/lifecycle.ts +6 -5
  272. package/src/cli/handlers/misc.ts +2 -0
  273. package/src/cli/handlers/operator.ts +87 -3
  274. package/src/cli/handlers/run.test.ts +111 -0
  275. package/src/cli/handlers/run.ts +85 -24
  276. package/src/cli/handlers/scenario.ts +2 -2
  277. package/src/cli/main.test.ts +1 -0
  278. package/src/cli/main.ts +65 -11
  279. package/src/cli/mcp/workspace-tools.test.ts +5 -2
  280. package/src/cli/mcp/workspace-tools.ts +29 -2
  281. package/src/cli/registry.ts +11 -2
  282. package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
  283. package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
  284. package/src/codegen/pinned-upgrade.test.ts +104 -0
  285. package/src/codegen/pinned-upgrade.ts +250 -56
  286. package/src/codegen/testdata/postgres-tags.json +242 -0
  287. package/src/config.test.ts +21 -0
  288. package/src/config.ts +15 -0
  289. package/src/declarable.test.ts +21 -0
  290. package/src/declarable.ts +19 -0
  291. package/src/discovery/fold-composite.test.ts +41 -5
  292. package/src/discovery/fold-import.test.ts +129 -0
  293. package/src/discovery/fold-import.ts +643 -131
  294. package/src/discovery/fold-package-composite.test.ts +264 -0
  295. package/src/discovery/fold-specifiers.test.ts +191 -0
  296. package/src/discovery/param-deps.test.ts +66 -1
  297. package/src/discovery/param-deps.ts +45 -0
  298. package/src/discovery/specifier-resolve.ts +344 -0
  299. package/src/fold/fold.test.ts +39 -1
  300. package/src/fold/fold.ts +83 -9
  301. package/src/fold-provenance.test.ts +140 -0
  302. package/src/graph-ir.test.ts +83 -0
  303. package/src/graph-ir.ts +29 -0
  304. package/src/import/embedded.test.ts +20 -0
  305. package/src/import/embedded.ts +13 -0
  306. package/src/import/parser-options.ts +46 -0
  307. package/src/import/parser.ts +20 -0
  308. package/src/lexicon.ts +55 -3
  309. package/src/lifecycle/gate-ledger.ts +16 -0
  310. package/src/lifecycle/observe.test.ts +18 -0
  311. package/src/lifecycle/observe.ts +6 -4
  312. package/src/lifecycle/snapshot.ts +2 -2
  313. package/src/lifecycle/work-lease.test.ts +1 -1
  314. package/src/lifecycle/work-lease.ts +93 -2
  315. package/src/lint/engine.test.ts +55 -0
  316. package/src/lint/engine.ts +30 -4
  317. package/src/lint/output-docs.test.ts +59 -56
  318. package/src/op/activities/apply.test.ts +189 -5
  319. package/src/op/activities/apply.ts +230 -8
  320. package/src/op/activities/index.ts +3 -0
  321. package/src/op/activities/lexicon-upgrade.test.ts +160 -6
  322. package/src/op/activities/lexicon-upgrade.ts +154 -51
  323. package/src/op/composites/apply-op.ts +12 -5
  324. package/src/op/composites/composites.test.ts +30 -3
  325. package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
  326. package/src/op/composites/lexicon-upgrade-op.ts +29 -8
  327. package/src/op/emulator-lifecycle.test.ts +23 -0
  328. package/src/op/emulator-lifecycle.ts +52 -14
  329. package/src/op/gate-approval.test.ts +74 -0
  330. package/src/op/gate-approval.ts +5 -1
  331. package/src/op/gate.ts +31 -5
  332. package/src/op/index.ts +2 -0
  333. package/src/op/local-executor.test.ts +15 -0
  334. package/src/op/local-executor.ts +40 -12
  335. package/src/op/local-output.ts +1 -1
  336. package/src/op/operator.ts +4 -0
  337. package/src/op/run-context.test.ts +141 -0
  338. package/src/op/run-context.ts +51 -0
  339. package/src/op/runtimes/local.test.ts +14 -0
  340. package/src/op/runtimes/local.ts +2 -0
  341. package/src/op/steward-beside.ts +2 -0
  342. package/src/op/types.ts +8 -3
  343. package/src/op/work-lease-run.test.ts +47 -0
  344. package/src/op/work-lease-run.ts +73 -11
  345. package/src/provenance.ts +35 -0
  346. package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
  347. package/src/workspace/agent-cli.ts +7 -3
  348. package/src/workspace/agent.schema.json +22 -0
  349. package/src/workspace/box-factory.test.ts +320 -0
  350. package/src/workspace/box-factory.ts +103 -0
  351. package/src/workspace/changes.schema.json +12 -20
  352. package/src/workspace/changes.ts +4 -1
  353. package/src/workspace/checks/boxes.test.ts +2 -0
  354. package/src/workspace/checks.ts +29 -5
  355. package/src/workspace/compose-graph.ts +7 -0
  356. package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +27 -0
  357. package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +95 -0
  358. package/src/workspace/conformance/__writer_fixture__/decisions/points.json +132 -0
  359. package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +48 -0
  360. package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +108 -0
  361. package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  362. package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  363. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +578 -0
  364. package/src/workspace/conformance/conformance.e2e.test.ts +35 -3
  365. package/src/workspace/conformance/index.mjs +30 -3
  366. package/src/workspace/conformance/index.ts +103 -15
  367. package/src/workspace/conformance/vitest.ts +44 -2
  368. package/src/workspace/conformance/writer-conformance.e2e.test.ts +131 -0
  369. package/src/workspace/conformance/writer.test.ts +156 -0
  370. package/src/workspace/conformance/writer.ts +880 -0
  371. package/src/workspace/decide.ts +7 -1
  372. package/src/workspace/declaration.schema.json +234 -5
  373. package/src/workspace/declaration.ts +174 -10
  374. package/src/workspace/graph-cli.ts +5 -2
  375. package/src/workspace/graph.schema.json +247 -0
  376. package/src/workspace/identity.test.ts +196 -0
  377. package/src/workspace/identity.ts +186 -0
  378. package/src/workspace/intent-cli.ts +64 -10
  379. package/src/workspace/intent-record.schema.json +208 -0
  380. package/src/workspace/intent-record.ts +35 -1
  381. package/src/workspace/intent-why.test.ts +274 -0
  382. package/src/workspace/intent.schema.json +718 -7
  383. package/src/workspace/intent.test.ts +11 -2
  384. package/src/workspace/intent.ts +495 -32
  385. package/src/workspace/kinds.ts +44 -37
  386. package/src/workspace/points-write.schema.json +1 -0
  387. package/src/workspace/principal-classes.test.ts +175 -0
  388. package/src/workspace/principal-classes.ts +307 -0
  389. package/src/workspace/reason-codes.test.ts +11 -3
  390. package/src/workspace/reason-codes.ts +24 -1
  391. package/src/workspace/records-amend.schema.json +2 -0
  392. package/src/workspace/records-checkout.ts +106 -0
  393. package/src/workspace/records-cli.ts +103 -11
  394. package/src/workspace/records-close.schema.json +1 -0
  395. package/src/workspace/records-new.schema.json +2 -0
  396. package/src/workspace/records-review.schema.json +2 -0
  397. package/src/workspace/records-uncommitted.test.ts +171 -0
  398. package/src/workspace/records-write.ts +28 -6
  399. package/src/workspace/records.schema.json +72 -1
  400. package/src/workspace/records.ts +27 -0
  401. package/src/workspace/runs-cli.ts +381 -0
  402. package/src/workspace/runs-write.schema.json +583 -0
  403. package/src/workspace/runs.schema.json +810 -0
  404. package/src/workspace/runs.test.ts +242 -0
  405. package/src/workspace/runs.ts +709 -0
  406. package/src/workspace/status-contract.test.ts +5 -0
  407. package/src/workspace/status-gates.ts +22 -8
  408. package/src/workspace/status.schema.json +127 -2
  409. package/src/workspace/status.ts +21 -1
  410. package/src/workspace/symbols.ts +180 -0
  411. package/src/workspace/trailer-joins.test.ts +171 -0
  412. package/src/workspace/trailer-joins.ts +82 -0
  413. package/src/workspace/trailers.ts +148 -0
  414. package/src/workspace/trust/seal.ts +52 -1
  415. package/src/workspace/work-cli.e2e.test.ts +28 -1
  416. package/src/workspace/work-cli.ts +133 -8
  417. package/src/workspace/work-evidence.schema.json +2 -0
  418. package/src/workspace/work-history.schema.json +73 -1
  419. package/src/workspace/work-history.test.ts +48 -5
  420. package/src/workspace/work-kind-fields.test.ts +154 -0
  421. package/src/workspace/work-lease.schema.json +14 -1
  422. package/src/workspace/work.ts +82 -0
  423. package/src/workspace/workspace-principals.schema.json +60 -0
  424. package/src/workspace/write-scope.test.ts +128 -2
  425. package/src/workspace/write-scope.ts +170 -45
  426. package/src/yaml.test.ts +111 -1
  427. package/src/yaml.ts +141 -549
@@ -81,6 +81,20 @@ describe("the local op runtime", () => {
81
81
  expect(status.records?.map((r) => r.fn)).toEqual(["ok"]);
82
82
  });
83
83
 
84
+ test("an activity reads --env and the run id the ledger records (#2522)", async () => {
85
+ const { currentOpRun } = await import("../run-context");
86
+ let seen: ReturnType<typeof currentOpRun>;
87
+ loadActivitiesMock.mockResolvedValue(new Map([["ok", vi.fn(async () => { seen = currentOpRun(); })]]));
88
+ const runtime = createLocalOpRuntime();
89
+
90
+ const handle = await runtime.start(op("hello", [{ kind: "activity", fn: "ok", args: {} }]), { env: "staging" });
91
+ const status = await handle.result();
92
+
93
+ expect(seen?.env).toBe("staging");
94
+ expect(seen?.runId).toBe(handle.runId);
95
+ expect(status.result?.record?.id).toBe(handle.runId);
96
+ });
97
+
84
98
  test("the project's configured lexicons decide which appliers load", async () => {
85
99
  loadActivitiesMock.mockResolvedValue(new Map([["ok", vi.fn(async () => ({}))]]));
86
100
  const runtime = createLocalOpRuntime();
@@ -152,6 +152,8 @@ export function createLocalOpRuntime(opts: { projectPath?: string } = {}): OpRun
152
152
  startOpts.signal,
153
153
  {
154
154
  runId,
155
+ // #2522: `--env` reaches the activities through the run context.
156
+ ...(startOpts.env !== undefined ? { env: startOpts.env } : {}),
155
157
  // The run's outcome is a durable fact (#2118): the executor
156
158
  // appends it here, at the one seam every local run passes
157
159
  // through, rather than in the CLI handler above it.
@@ -118,6 +118,8 @@ export function inProcessBesideLauncher(
118
118
  try {
119
119
  const result = await runOpLocally(start.op, activities, profiles, controller.signal, {
120
120
  steward: start.steward,
121
+ // #2522: the same `--env` the spawned launcher passes on the command line.
122
+ ...(start.env ? { env: start.env } : {}),
121
123
  ledger: { cwd: start.cwd },
122
124
  work: { holder: start.holder },
123
125
  });
package/src/op/types.ts CHANGED
@@ -91,8 +91,12 @@ export interface OpWorkLease {
91
91
  ttl?: string;
92
92
  /**
93
93
  * How the release records a run that ended `ok`: a reference to a step
94
- * output holding a short string (`done`, `not_done`, ...). Default `done`;
95
- * a failed run releases `not_done` and a gated one `gated`.
94
+ * output holding one of the closed list of lease outcomes
95
+ * (`WORK_LEASE_OUTCOMES` in `../lifecycle/work-lease.ts`, #3147: `done`,
96
+ * `not_done`, `abandoned`, `gated`, `waiting`, `dropped`, `redraft`,
97
+ * `ask`). Default `done`; a failed run releases `not_done`, a gated one
98
+ * `gated` and one waiting on a decision point `waiting`. A word outside
99
+ * the list is released `not_done`, with a note naming it.
96
100
  */
97
101
  outcome?: StepOutputRef;
98
102
  }
@@ -178,7 +182,8 @@ export interface ActivityStep {
178
182
  *
179
183
  * `from` is a dot-path into the return value (e.g. `"drifted"` for
180
184
  * `{ drifted: boolean }`); when omitted, the whole return value is
181
- * recorded.
185
+ * recorded. An attribute whose path the result does not hold (an optional
186
+ * field left out) is not recorded, rather than recorded as `undefined`.
182
187
  */
183
188
  outcomeAttribute?: OutcomeAttribute | OutcomeAttribute[];
184
189
  }
@@ -342,4 +342,51 @@ describe("an Op that changes the checkout", () => {
342
342
  expect(git(["status", "--porcelain"], dir).stdout).toBe(" M app/index.ts\n");
343
343
  });
344
344
  });
345
+
346
+ test("a run that ends not_done keeps its attempt at refs/chant/kept/<item>/<token>, committed and uncommitted work, and the branch goes back (#3147)", async () => {
347
+ await withTestDir(async (dir) => {
348
+ await initRepo(dir);
349
+ const head = git(["rev-parse", "HEAD"], dir).stdout.trim();
350
+ const activities = new Map<string, ActivityFn>([
351
+ [
352
+ "build",
353
+ async (args) => {
354
+ const worktree = String(args.worktree);
355
+ writeFileSync(join(worktree, "app", "feature.ts"), "export const feature = true;\n");
356
+ expect(git(["add", "-A"], worktree).status).toBe(0);
357
+ expect(git(["commit", "-q", "-m", "half a build"], worktree).status).toBe(0);
358
+ writeFileSync(join(worktree, "app", "notes.md"), "left uncommitted\n");
359
+ throw new Error("the check fails");
360
+ },
361
+ ],
362
+ ]);
363
+ const result = await run(dispatchOp({ workLease: { item: "W-8" }, changesCheckout: true }, [step("build", { worktree: workLeaseOutput("worktree") })]), activities, dir);
364
+ expect(result.status).toBe("fail");
365
+ const token = result.workLease!.token!;
366
+ const kept = `refs/chant/kept/W-8/${token}`;
367
+ expect(result.workLease).toMatchObject({ item: "W-8", released: true, outcome: "not_done", kept });
368
+ // Both the commit and the uncommitted file are in the kept attempt; the branch is where the run started.
369
+ expect(git(["show", `${kept}:app/feature.ts`], dir).stdout).toBe("export const feature = true;\n");
370
+ expect(git(["show", `${kept}:app/notes.md`], dir).stdout).toBe("left uncommitted\n");
371
+ expect(git(["rev-parse", "chant/work/W-8"], dir).stdout.trim()).toBe(head);
372
+ expect(git(["rev-parse", "HEAD"], dir).stdout.trim()).toBe(head);
373
+ });
374
+ });
375
+
376
+ test("a run whose step names a lease outcome releases it, and keeps nothing when it changed nothing; a word outside the list is not_done (#3147)", async () => {
377
+ await withTestDir(async (dir) => {
378
+ await initRepo(dir);
379
+ let word = "dropped";
380
+ const activities = new Map<string, ActivityFn>([["record", async () => ({ outcome: word })]]);
381
+ const op = () => dispatchOp({ workLease: { item: "W-9", outcome: stepOutput("record", "outcome") }, changesCheckout: true }, [step("record", {}, { id: "record" })]);
382
+ const dropped = await run(op(), activities, dir);
383
+ expect(dropped.workLease).toMatchObject({ released: true, outcome: "dropped", kept: null });
384
+ word = "skipped";
385
+ const other = await run(op(), activities, dir);
386
+ expect(other.workLease).toMatchObject({ released: true, outcome: "not_done", kept: null });
387
+ const last = (await readLeaseHistory("W-9", { cwd: dir })).records.at(-1);
388
+ expect(last).toMatchObject({ event: "release", outcome: "not_done" });
389
+ expect(last?.note).toMatch(/"skipped" is not a work lease outcome/);
390
+ });
391
+ });
345
392
  });
@@ -24,9 +24,11 @@
24
24
  * the lease is still this run's, with the same token. Refused, the run is
25
25
  * recorded as failed with `lease-lost`, never as done.
26
26
  * - Release. When the run ends, the lease is given back with the run's
27
- * outcome (`done`, `not_done`, `gated`, or the Op's own `outcome`), which
28
- * lands on `_leases/<id>.jsonl`. A lost lease is not released: it isn't
29
- * this run's to give back.
27
+ * outcome (`done`, `not_done`, `gated`, `waiting`, or the Op's own
28
+ * `outcome`), which lands on `_leases/<id>.jsonl`. Outcomes are a closed
29
+ * list (`WORK_LEASE_OUTCOMES`, #3147): an Op's own outcome outside it is
30
+ * released `not_done`, with a note naming it. A lost lease is not
31
+ * released: it isn't this run's to give back.
30
32
  *
31
33
  * Steps read the lease through {@link workLeaseOutput}: its item, holder,
32
34
  * token and expiry, and for an Op that changes the checkout, the worktree and
@@ -42,6 +44,13 @@
42
44
  * worktree is removed when the run ends and the branch keeps whatever the
43
45
  * steps committed, for the runner to deliver. The checkout a coding agent is
44
46
  * editing, its index and its uncommitted files, are never touched.
47
+ *
48
+ * A run that ends `not_done` keeps its attempt aside (#3147): whatever the
49
+ * steps committed on the branch since the run started, and anything they
50
+ * left uncommitted in the worktree, is committed to
51
+ * `refs/chant/kept/[<member>/]<item>/<token>`, and the branch goes back to
52
+ * where the run started. The next attempt starts clean, and the earlier one
53
+ * stays readable by its lease token (`chant workspace work history`).
45
54
  */
46
55
 
47
56
  import { existsSync, rmSync } from "node:fs";
@@ -50,6 +59,8 @@ import { getRuntime } from "../runtime-adapter";
50
59
  import {
51
60
  claimWorkLease,
52
61
  DEFAULT_WORK_LEASE_TTL_MS,
62
+ isWorkLeaseOutcome,
63
+ keptAttemptRef,
53
64
  releaseWorkLease,
54
65
  renewWorkLease,
55
66
  workLeaseRef,
@@ -97,8 +108,10 @@ export interface WorkLeaseRunResult {
97
108
  branch: string | null;
98
109
  /** Whether the lease was given back. False when it was lost, or never claimed. */
99
110
  released: boolean;
100
- /** The outcome the release recorded, or null when there was no release. */
111
+ /** The outcome the release recorded, one of `WORK_LEASE_OUTCOMES`, or null when there was no release. */
101
112
  outcome: string | null;
113
+ /** For an Op that changes the checkout and ended `not_done`: the ref that keeps the attempt's work, or null when there was nothing to keep. */
114
+ kept: string | null;
102
115
  /** Why the lease was lost, or null when it wasn't. */
103
116
  lost: string | null;
104
117
  /** Why nothing was claimed, or null when something was. */
@@ -159,6 +172,8 @@ export class RunWorkLease {
159
172
  attempted = false;
160
173
  lease: WorkLeaseOutput | undefined;
161
174
  refusal: string | undefined;
175
+ /** For an Op that changes the checkout: the branch's commit when the run started, and the members its branch is scoped by. */
176
+ private start: { base: string; members: readonly string[] } | undefined;
162
177
  lost: string | undefined;
163
178
 
164
179
  private readonly spec: NonNullable<OpConfig["workLease"]>;
@@ -277,6 +292,8 @@ export class RunWorkLease {
277
292
  ? await git(["worktree", "add", "--quiet", worktree, branch], cwd)
278
293
  : await git(["worktree", "add", "--quiet", "-b", branch, worktree, "HEAD"], cwd);
279
294
  if (!add.ok) throw new Error(`Op "${this.config.name}": could not make the worktree for ${id} on ${branch}: ${add.stderr}`);
295
+ const base = await git(["rev-parse", "HEAD"], worktree);
296
+ if (base.ok) this.start = { base: base.stdout, members };
280
297
  return { worktree, branch };
281
298
  }
282
299
 
@@ -365,28 +382,73 @@ export class RunWorkLease {
365
382
  branch: lease?.branch ?? null,
366
383
  released: false,
367
384
  outcome: null,
385
+ kept: null,
368
386
  lost: this.lost ?? null,
369
387
  refusal: this.refusal ?? null,
370
388
  };
371
389
  if (!lease) return base;
390
+ let outcome = status === "ok" ? "done" : status === "gated" || status === "waiting" ? status : "not_done";
391
+ let note = `op ${this.config.name}`;
392
+ if (status === "ok" && this.spec.outcome && isStepOutputRef(this.spec.outcome)) {
393
+ const declared = resolvePath(resultsById.get(this.spec.outcome.step), this.spec.outcome.path);
394
+ if (typeof declared === "string" && declared.trim() !== "") {
395
+ const word = declared.trim();
396
+ if (isWorkLeaseOutcome(word)) outcome = word;
397
+ else {
398
+ outcome = "not_done";
399
+ note = `op ${this.config.name}: its outcome ${JSON.stringify(word.slice(0, 80))} is not a work lease outcome`;
400
+ }
401
+ }
402
+ }
403
+ let kept: string | null = null;
404
+ if (lease.worktree && this.lost === undefined && outcome === "not_done") {
405
+ kept = await this.keepAttempt(lease).catch(() => null);
406
+ }
372
407
  if (lease.worktree) {
373
408
  await git(["worktree", "remove", "--force", lease.worktree], this.opts.cwd).catch(() => undefined);
374
409
  rmSync(lease.worktree, { recursive: true, force: true });
375
410
  await git(["worktree", "prune"], this.opts.cwd).catch(() => undefined);
376
411
  }
377
412
  if (this.lost !== undefined) return base;
378
- let outcome = status === "ok" ? "done" : status === "gated" || status === "waiting" ? status : "not_done";
379
- if (status === "ok" && this.spec.outcome && isStepOutputRef(this.spec.outcome)) {
380
- const declared = resolvePath(resultsById.get(this.spec.outcome.step), this.spec.outcome.path);
381
- if (typeof declared === "string" && declared.trim() !== "") outcome = declared.trim();
382
- }
383
413
  const released = await releaseWorkLease(lease.item, this.holder, {
384
414
  cwd: this.ledgerCwd,
385
415
  token: lease.token,
386
416
  outcome,
387
- note: `op ${this.config.name}`,
417
+ note,
388
418
  }).catch(() => undefined);
389
- return { ...base, released: released?.ok === true, outcome: released?.ok ? outcome : null };
419
+ return { ...base, kept, released: released?.ok === true, outcome: released?.ok ? outcome : null };
420
+ }
421
+
422
+ /**
423
+ * Keep a `not_done` attempt aside (#3147): commit what the steps left in
424
+ * the worktree, committed or not, at `refs/chant/kept/.../<item>/<token>`,
425
+ * and move the branch back to where the run started. Returns the ref, or
426
+ * null when the attempt changed nothing.
427
+ */
428
+ private async keepAttempt(lease: WorkLeaseOutput): Promise<string | null> {
429
+ const dir = lease.worktree;
430
+ const start = this.start;
431
+ if (!dir || !start) return null;
432
+ const head = await git(["rev-parse", "HEAD"], dir);
433
+ if (!head.ok) return null;
434
+ if (!(await git(["add", "-A"], dir)).ok) return null;
435
+ const tree = await git(["write-tree"], dir);
436
+ const headTree = await git(["rev-parse", `${head.stdout}^{tree}`], dir);
437
+ if (!tree.ok || !headTree.ok) return null;
438
+ let commit = head.stdout;
439
+ if (tree.stdout !== headTree.stdout) {
440
+ // An identity only when git has none, so a configured one is kept.
441
+ const ident = (await git(["var", "GIT_COMMITTER_IDENT"], dir)).ok && (await git(["var", "GIT_AUTHOR_IDENT"], dir)).ok;
442
+ const who = ident ? [] : ["-c", "user.name=chant", "-c", "user.email=chant@localhost"];
443
+ const made = await git([...who, "commit-tree", tree.stdout, "-p", head.stdout, "-m", `${lease.item}: not done, kept (lease ${lease.token})`], dir);
444
+ if (!made.ok) return null;
445
+ commit = made.stdout;
446
+ }
447
+ if (commit === start.base) return null;
448
+ const ref = keptAttemptRef(lease.item, lease.token, start.members);
449
+ if (!(await git(["update-ref", ref, commit], dir)).ok) return null;
450
+ await git(["reset", "-q", "--hard", start.base], dir);
451
+ return ref;
390
452
  }
391
453
  }
392
454
 
package/src/provenance.ts CHANGED
@@ -177,6 +177,41 @@ export function originOfPath(
177
177
  return best === undefined ? undefined : paths[best];
178
178
  }
179
179
 
180
+ const INTERPOLATION_FIELDS = Symbol.for("chant.interpolationFields");
181
+
182
+ /**
183
+ * For an entity a tagged template built: which of its `props` paths each
184
+ * interpolation fed, by interpolation index (chant #3212). Element `i` lists
185
+ * the paths the `i`-th `${...}` landed in, in {@link EntityProvenance.paths}'
186
+ * grammar (dotted names, no array segments). An interpolation that fed no
187
+ * field the author typed, such as a reference to another entity, lists none.
188
+ *
189
+ * Only the tag can say this. It is the tag that parsed the text, so a
190
+ * `${props.ttlDays}` inside `TTL ts + INTERVAL ${props.ttlDays} DAY` is known
191
+ * to have fed `ttl` only there. When a composite factory interpreted at fold
192
+ * time writes the template, `discovery/fold-import.ts` joins these paths with
193
+ * the factory parameters each interpolation reads, which is what lets drift on
194
+ * such a field be reported against the parameter. A tag that never calls this
195
+ * leaves its fields `unknown`, the honest answer, rather than claiming either.
196
+ *
197
+ * Stored like provenance itself: non-enumerable and symbol-keyed, so no
198
+ * serializer, spread or comparison of the output ever sees it.
199
+ */
200
+ export function setInterpolationFields(entity: object, fields: ReadonlyArray<readonly string[]>): void {
201
+ if (!Object.isExtensible(entity)) return;
202
+ Object.defineProperty(entity, INTERPOLATION_FIELDS, {
203
+ value: Object.freeze(fields.map((paths) => Object.freeze([...paths]))),
204
+ enumerable: false,
205
+ writable: true,
206
+ configurable: true,
207
+ });
208
+ }
209
+
210
+ /** What {@link setInterpolationFields} recorded on an entity, if anything. */
211
+ export function getInterpolationFields(entity: object): ReadonlyArray<readonly string[]> | undefined {
212
+ return (entity as Record<symbol, unknown>)[INTERPOLATION_FIELDS] as ReadonlyArray<readonly string[]> | undefined;
213
+ }
214
+
180
215
  /** One-line rendering of an origin, for diff output. */
181
216
  export function describePathOrigin(origin: PathOrigin): string {
182
217
  switch (origin.kind) {
@@ -2,7 +2,7 @@
2
2
  "$comment": "The studio kit's app template, arugula-salad/studio 84c8b21 template/, which is chud's template at jhgaylor/chud 43afcf1 with the chud CLI steps taken out of its prose and delivery/package.json. The fixture for chant workspace upgrade's chud-lexicon-exit migration (INTENTIUS/chant#2737). Data only: nothing here is imported or run by chant's tests, and delivery/ names @intentius/chud-runtime, which chant never installs.",
3
3
  "source": "https://github.com/arugula-salad/studio/tree/84c8b21/template",
4
4
  "files": {
5
- ".github/workflows/ci.yml": "name: ci\n\non:\n push:\n branches:\n - main\n pull_request: {}\n\npermissions:\n contents: read\n\nconcurrency:\n group: ci-${{ github.ref }}\n cancel-in-progress: '${{ github.event_name == ''pull_request'' }}'\n\njobs:\n ci:\n runs-on: ubuntu-latest\n timeout-minutes: 20\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n - name: Setup Node.js\n uses: actions/setup-node@v4\n with:\n node-version: '22.x'\n cache: npm\n cache-dependency-path: delivery/package-lock.json\n - name: Read access to @intentius/chud-runtime\n env:\n CHUD_RUNTIME_TOKEN: '${{ secrets.CHUD_RUNTIME_TOKEN }}'\n CHUD_RUNTIME_DEPLOY_KEY: '${{ secrets.CHUD_RUNTIME_DEPLOY_KEY }}'\n run: |\n if [ -n \"$CHUD_RUNTIME_TOKEN\" ]; then\n git config --global url.\"https://x-access-token:${CHUD_RUNTIME_TOKEN}@github.com/\".insteadOf ssh://git@github.com/\n git config --global --add url.\"https://x-access-token:${CHUD_RUNTIME_TOKEN}@github.com/\".insteadOf git@github.com:\n elif [ -n \"$CHUD_RUNTIME_DEPLOY_KEY\" ]; then\n mkdir -p ~/.ssh && chmod 700 ~/.ssh\n printf '%s\\n' \"$CHUD_RUNTIME_DEPLOY_KEY\" > ~/.ssh/id_ed25519 && chmod 600 ~/.ssh/id_ed25519\n echo \"github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\" >> ~/.ssh/known_hosts\n else\n echo \"neither CHUD_RUNTIME_TOKEN nor CHUD_RUNTIME_DEPLOY_KEY is set: a private @intentius/chud-runtime will not install\"\n fi\n - name: Install\n run: npm ci\n working-directory: delivery\n - name: Workflow YAML is built from ci/ci.ts\n run: npm run ci:check\n working-directory: delivery\n - name: chant workspace check\n run: npx chant workspace check\n working-directory: delivery\n - name: chant build\n run: npx chant build -o \"$RUNNER_TEMP/chant-build.out\"\n working-directory: delivery\n - name: chant lint\n run: npx chant lint\n working-directory: delivery\n - name: App tests and approved contract checks\n run: npm run check\n working-directory: delivery\n",
5
+ ".github/workflows/ci.yml": "name: ci\n\non:\n push:\n branches:\n - main\n pull_request: {}\n\npermissions:\n contents: read\n\nconcurrency:\n group: ci-${{ github.ref }}\n cancel-in-progress: '${{ github.event_name == ''pull_request'' }}'\n\njobs:\n ci:\n runs-on: ubuntu-latest\n timeout-minutes: 20\n steps:\n - name: Checkout\n uses: actions/checkout@v7\n - name: Setup Node.js\n uses: actions/setup-node@v7\n with:\n node-version: '22.x'\n cache: npm\n cache-dependency-path: delivery/package-lock.json\n - name: Read access to @intentius/chud-runtime\n env:\n CHUD_RUNTIME_TOKEN: '${{ secrets.CHUD_RUNTIME_TOKEN }}'\n CHUD_RUNTIME_DEPLOY_KEY: '${{ secrets.CHUD_RUNTIME_DEPLOY_KEY }}'\n run: |\n if [ -n \"$CHUD_RUNTIME_TOKEN\" ]; then\n git config --global url.\"https://x-access-token:${CHUD_RUNTIME_TOKEN}@github.com/\".insteadOf ssh://git@github.com/\n git config --global --add url.\"https://x-access-token:${CHUD_RUNTIME_TOKEN}@github.com/\".insteadOf git@github.com:\n elif [ -n \"$CHUD_RUNTIME_DEPLOY_KEY\" ]; then\n mkdir -p ~/.ssh && chmod 700 ~/.ssh\n printf '%s\\n' \"$CHUD_RUNTIME_DEPLOY_KEY\" > ~/.ssh/id_ed25519 && chmod 600 ~/.ssh/id_ed25519\n echo \"github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\" >> ~/.ssh/known_hosts\n else\n echo \"neither CHUD_RUNTIME_TOKEN nor CHUD_RUNTIME_DEPLOY_KEY is set: a private @intentius/chud-runtime will not install\"\n fi\n - name: Install\n run: npm ci\n working-directory: delivery\n - name: Workflow YAML is built from ci/ci.ts\n run: npm run ci:check\n working-directory: delivery\n - name: chant workspace check\n run: npx chant workspace check\n working-directory: delivery\n - name: chant build\n run: npx chant build -o \"$RUNNER_TEMP/chant-build.out\"\n working-directory: delivery\n - name: chant lint\n run: npx chant lint\n working-directory: delivery\n - name: App tests and approved contract checks\n run: npm run check\n working-directory: delivery\n",
6
6
  ".gitignore": "node_modules/\n.chud/\n.data/\n.hud/\n# chant build output (the Ops as op.json)\ndist/\n",
7
7
  "CLAUDE.md": "# {{chant:name}}: notes for agents\n\nThis repo is a chant workspace made from the studio kit's template\n(`chant init --from <studio>/template`, then the kit's `box/setup-app.mjs`).\nIt holds everything about {{chant:name}}: the app, the contracts it must\nmeet, the decisions made about it, the agents that build it, and the Ops that\nship it. `chant.workspace.json` declares its members, and chant finds\neverything through them.\n\nYou are probably running as hud's agent in the app's page: the app's own\nserver (its `dev` script, `node --watch`) behind hud's proxy. Your working\ndirectory is this one (the workspace root), and your job is to build the app\nin `app/`. See `app/CLAUDE.md`. Decisions and work items are chant records:\nwrite them with `chant workspace records`, and hud shows them.\n\n## Where things are\n\n| Member | Directory | What it holds |\n|---|---|---|\n| app | `app/` | The product. Edited live while its `dev` script runs. |\n| delivery | `delivery/` | The chant project: `chant.config.ts` (this repo's settings as `buildParams`: ports and how to run the app), `ops/` (the release, rollback, upgrade and dispatch Ops), `deploy/` (the app as a chant component and the sites it ships to), `agents/team.ts` (the agents that build the app, as fountain specs), `decisions/` (the decision points and the ship gate's Cedar policy), `ci/ci.ts` (this repo's CI), `.chant/` (the write-scope policy and the contract-sizing lint rules), and `node_modules/`. |\n| design | `design/` | chud's records, written by the design app: `contracts/` (and `contracts/checks/`), `drivers/`, `evidence/`, `sessions/`, `context/`, `proposals/`. Also the design app's overrides and the screen specs in `screens/`. |\n| design-client | `design-client/` | A placeholder for a vendored design client. Nothing here yet. |\n\n`work/` at the root holds the units of work: chant work items\n(`W-NNN-<slug>.md`, `work.kind.mjs` and `work.schema.json` beside them), one\nper agent run or piece of manual work, with chud's fields under `x-chud`.\nchud writes them through `chant workspace records new|amend`; list them with\n`chant workspace records --kind work/work.kind.mjs`.\n\n`decisions/` at the root holds the decision records: chant decision records\n(`decision.kind.mjs` and `decision.schema.json` beside them define the\nformat), the template's `ref-001`, `ref-002` and `chud-001`, and the\n`chud-NNN` records the design app writes. Each cites the issue in\n`chud.decisions.issue` (`delivery/chant.config.ts`) as its source, or\n`chud-001`'s when none is set. List the\ncurrent ones with\n`chant workspace records --kind decisions/decision.kind.mjs --current`.\n\nA decision pins the files it rests on by path and hash: an `evidence` entry\n`{ title, path, sha256, as_of }`, `path` from the workspace root and inside a\nmember (such as `design/screens/home.json`), and `sha256` the hash of the\nfile's bytes, which `chant workspace records pin <path>` prints. What it\ngoverns goes in `constrains` as `member:<name>` or `path:<path>`. `chant\nworkspace records` reports each pin as `pinned`, `drifted` (the file changed\nsince), `missing` or `stale` (a superseding decision pins the file at the\nsame hash, and the file has not changed since). A drifted pin means the file\nmoved away from what was decided: say so, and do not re-pin it yourself. The\ndesign app pins a file by name (`POST /api/decisions` with `{ title, path }`\nin `evidence` hashes it) and re-pins with `PUT /api/decisions/<id>`.\n\nEvidence (`design/evidence/<sha256>.json`) and design sessions\n(`design/sessions/S-NNNN.json`) are chant record kinds too, declared on the\ndesign member in `chant.workspace.json` (`design/evidence/evidence.kind.mjs`,\n`design/sessions/session.kind.mjs`). Read them with\n`chant workspace records --json`, which reads every declared kind. An\nevidence record's id is the sha256 of its bytes; a file whose name is not\nthat hash carries `asset-drift` and is not evidence. A closed session is\nsealed, and an edit to it is `session-seal-mismatch`. Only the design app and\nthe Ops' Check phases write these files, and each write is read back through\nchant before it is committed. Never write or edit one by hand, and never\nrename one.\n\n- Records keep their core fields in YAML front matter (or JSON), validated\n against the schemas in\n `delivery/node_modules/@intentius/chud-runtime/schemas/`. Extra fields must\n start with `x-`.\n- `delivery/node_modules/@intentius/chud-runtime` is the machinery (the design\n app, the Op steps). It is a versioned dependency: never edit it. Upgrade it\n with the upgrade Op (`npm run upgrade` in `delivery/`), which stops at a\n gate for a person.\n- `.chud/`: local state (hud data, staged releases, the local site). Never\n edit it, and never commit it.\n\nEvery path chud records (a unit's scope, a protected path, a record's `file`)\nis relative to this directory, the git top-level.\n\n## Rules\n\n- Contracts and decisions are written by the design app. Read the ones that\n touch your work and do not undo them. Never edit `design/contracts/`,\n `decisions/`, `work/`, `design/evidence/`, `design/drivers/`,\n `design/context/`, `design/sessions/`, `delivery/decisions/`,\n `delivery/.chant/`, `delivery/chant.config.ts` or `chant.workspace.json`\n (or the paths `chud.protectedPaths` in `delivery/chant.config.ts` adds)\n from the app's page. This is enforced: an agent run's changes there are thrown\n away, and a release refuses to plan while a commit that changes them lacks\n the design app's `Chud-Author: design-app` trailer.\n- Never commit, tag or push. Committing, planning and shipping happen in the\n design app, through the release Op and its gate.\n\n## Why a line is the way it is\n\nThe design app's commits for a unit of work carry trailers that tie the\ncommit to that unit: `Chud-Unit: W-NNN`, `Chud-Contract: C-NNN` (or\n`Chud-Driver: D-NNN` for a split) and one `Chud-Evidence: <sha256>` per\nevidence record the unit cites, beside `Chud-Author: design-app`.\n`decisions/decision.kind.mjs` exports `commitJoins`, which joins a commit\nto its unit from the work item whose `x-chud.result.commit` is the commit\n(chant 0.86's `list` on the hook's context), and from those trailers for the\nunit's other commits, so\n\n```sh\nchant workspace graph --intent app/server.js --json\n```\n\nlists the commits that touched the file (a `path:start-end` region narrows\nit to lines), each joined to the unit that made it (`work/W-NNN-<slug>.md`),\nthe contract it served and the evidence it cited, with the decisions whose\n`constrains` cover the file and what they pin, and the work items whose\nwindow a commit fell in (`worked`). Read it before changing code\na decision governs. The template pins chant 0.86, which has all of this.\nchant marks a commit inside a decision's window `decided-by-window`, and the\nkind adds `plugin:chud:contract-moved` for a unit commit whose contract's\ncriteria differ from those the first unit under that decision served: the\ncontract moved with no decision. Record a decision before you change what a\ncontract asks for.\n",
8
8
  "README.md": "# {{chant:name}}\n\nA chant workspace made from the studio kit's template\n([arugula-salad/studio](https://github.com/arugula-salad/studio) `template/`),\nwhich is chant's reference workspace with an app, a delivery project and\nrecords laid onto it. It started as chud's template (jhgaylor/chud 43afcf1)\nand moved into the kit when chud began to retire (arugula-salad/studio#37).\nThe app is built with hud in the page, and releases ship through chant Ops.\n\n```sh\nchant init --from <studio checkout>/template --param name=\"{{chant:name}}\" [--param issue=owner/repo#1] <dir>\nnode <studio checkout>/box/setup-app.mjs <dir> # free ports, npm install in delivery/, git init, the first commit\ncd <dir>/app && npm run dev # the app, restarted on every change (node --watch)\n```\n\nA studio box does both steps in `box/provision-template.sh` and runs the\napp's `dev` script behind hud's proxy. No chud command runs.\n\n[`chant.workspace.json`](chant.workspace.json) declares the members, and\n`chant workspace ls` lists them. The release Op finds everything through them.\n\n| Member | Directory | Kind | What it is |\n|---|---|---|---|\n| app | [`app/`](app) | `other` | the product: a Node HTTP server with SQLite migrations and its own tests |\n| delivery | [`delivery/`](delivery) | `chant` | the chant project, which still runs on chud's runtime package (`@intentius/chud-runtime`) and the chud lexicon until they are split out of chud (INTENTIUS/chant#2713): the release, rollback, upgrade and dispatch Ops, the sites releases ship to, the agents that build the app, the decision points, the write-scope policy and this repo's CI |\n| design-client | [`design-client/`](design-client) | `other`, role `design-app` | a placeholder for a vendored design client (chant's reference workspace, #2524 D18) |\n| design | [`design/`](design) | `other` | the design data: chud's records (contracts, drivers, evidence, sessions, context, proposals), the screen specs, and the design app's overrides |\n\nThe root also holds [`decisions/`](decisions), the decision records. They\nare chant decision records, the format of chant's `docs/design/decisions/`\n(the kind file and schema are beside them): `ref-001` and `ref-002` from\nchant's reference workspace, `chud-001`, and the `chud-NNN` records the design\napp writes as the team decides. Each cites the issue this app's design is\ntracked in (`chud.decisions.issue` in `delivery/chant.config.ts`, the\ntemplate's optional `issue` parameter) as its source, or without one the\nsource of `chud-001`:\n\n```sh\nchant workspace records --kind decisions/decision.kind.mjs --current\n```\n\n[`work/`](work) holds the units of work: every agent run and piece of\nmanual work is a chant work item (`W-NNN-<slug>.md`, chant's work kind with\nchud's fields under `x-chud`), written by chud through `chant workspace\nrecords new` and `records amend`. Both kinds are declared in\n`chant.workspace.json` `records`:\n\n```sh\nchant workspace records --kind work/work.kind.mjs\n```\n\n`.chant/workspace.lock.json`, written by `chant init --from`, records the\ntemplate's lineage: where it came from, at which commit, and the parameters\nit was made with.\n\n## Everyday commands\n\nIn `delivery/`:\n\n```sh\nnpm run release # chant run release --on chud: check, plan, stop at the ship gate\nchant approve release ship --plan <digest>\nnpm run release # ship it\nnpm run rollback\nnpm run upgrade # move to a newer @intentius/chud-runtime, through its gate\nnpm run check # the app's tests and every approved contract's check (CI runs this)\nnpm run lint # chant lint agents, with the contract-sizing rules\n```\n\nAt the root:\n\n```sh\nchant workspace check\nchant workspace build\nchant workspace lint\n```\n\n`CLAUDE.md` is the map for agents, and `design/CLAUDE.md` has the rules for\nthe records.\n",
@@ -60,6 +60,8 @@ export type AgentDocument =
60
60
  members: string[];
61
61
  /** Every declared kind in reach: the workspace's own and the member's. */
62
62
  records: AgentKindScope[];
63
+ /** The paths writeScope.agent.protected keeps the session from writing, with the JSON keys each still allows (#3146). */
64
+ protected: { path: string; except: string[] }[];
63
65
  };
64
66
  /** The spec, as records --current --json prints it. */
65
67
  spec: SpecView;
@@ -77,10 +79,11 @@ export async function agentSession(q: { cwd: string; name: string }): Promise<Ag
77
79
  throw new WorkspaceReadError("declaration-missing", "no chant.workspace.json or .jsonc between this directory and the git root, so no agent session is declared");
78
80
  }
79
81
  const decl = source.declaration;
80
- const writer = resolveWriter(decl, source.policy, { agent: q.name });
82
+ const writer = resolveWriter(decl, source.policy, { agent: q.name }, source.classes);
81
83
  const agent = writer.agent!;
82
84
  const member = decl.members.find((m) => m.name === agent.member)!;
83
- const rules = scopeOf(decl, writer)!.records;
85
+ const scope = scopeOf(decl, writer)!;
86
+ const rules = scope.records;
84
87
  // The spec query, over every declared kind in the working tree.
85
88
  const set = await queryDeclaredRecords(declaredKindFiles(q.cwd), { cwd: q.cwd, current: true });
86
89
  const loadedName = new Map(set.kinds.map((k) => [k.declared.path, "error" in k ? null : k.kind.name]));
@@ -94,7 +97,7 @@ export async function agentSession(q: { cwd: string; name: string }): Promise<Ag
94
97
  ...head,
95
98
  workspace: { name: decl.name, root: source.root ?? ".", scopeFrom: source.from },
96
99
  agent: { name: agent.name, member: { name: member.name, dir: member.dir, kind: member.kind }, principals: agent.principals },
97
- scope: { members: [member.name], records },
100
+ scope: { members: [member.name], records, protected: scope.protected.map((p) => ({ path: p.path, except: [...p.except] })) },
98
101
  spec: set.spec ?? { kinds: [], records: [] },
99
102
  reload: [`chant workspace agent ${agent.name} --json`, "chant workspace records --current --json"],
100
103
  };
@@ -126,6 +129,7 @@ export async function runWorkspaceAgent(ctx: CommandContext): Promise<number> {
126
129
  const out: string[] = [];
127
130
  out.push(`agent ${doc.agent.name}, bound to member ${doc.agent.member.name} (${doc.agent.member.dir}), scope read from ${doc.workspace.scopeFrom}`);
128
131
  for (const r of doc.scope.records) out.push(`records ${r.name ?? r.kind ?? r.path} (${r.path}): ${r.verbs.length > 0 ? r.verbs.join(", ") : "not writable"}`);
132
+ for (const p of doc.scope.protected) out.push(`protected ${p.path}${p.except.length > 0 ? ` (except ${p.except.join(", ")})` : ""}`);
129
133
  out.push(`spec ${doc.spec.records.length} current records of ${doc.spec.kinds.length} spec kinds`);
130
134
  for (const r of doc.spec.records) out.push(` ${r.kind}/${r.id ?? "?"} ${r.state ?? ""} ${r.path}`);
131
135
  out.push(`reload ${doc.reload.join("; ")}`);
@@ -165,6 +165,28 @@
165
165
  }
166
166
  }
167
167
  }
168
+ },
169
+ "protected": {
170
+ "type": "array",
171
+ "description": "Added in contract 1 by #3146. The paths writeScope.agent.protected keeps the session from writing even inside its member, each a glob from the workspace root that also covers everything under a directory it matches, with the top-level keys of a JSON file a write may still change in except. Empty when none is listed. check --changes reports a write to one with write-scope-protected.",
172
+ "items": {
173
+ "type": "object",
174
+ "required": [
175
+ "path",
176
+ "except"
177
+ ],
178
+ "properties": {
179
+ "path": {
180
+ "type": "string"
181
+ },
182
+ "except": {
183
+ "type": "array",
184
+ "items": {
185
+ "type": "string"
186
+ }
187
+ }
188
+ }
189
+ }
168
190
  }
169
191
  }
170
192
  },