@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
@@ -25,59 +25,77 @@
25
25
  * detection only (ws-002).
26
26
  *
27
27
  * A principal's class comes from the role grants in the trust policy at base,
28
- * through {@link principalClass} alone (the agent, runner and service roles),
28
+ * through {@link principalClass} alone (the agent, runner and service roles,
29
+ * then the role each domain class a pinned package supplies names, #3080),
29
30
  * or from naming an agent session. Claiming a session only ever narrows what
30
31
  * a writer may do, so an unverified `CHANT_AGENT` or trailer is safe to honour.
32
+ *
33
+ * A writeScope key no pinned package supplies fails closed: a writer judged
34
+ * `human` might be in that class, so it is refused with
35
+ * `write-scope-class-unknown` until the plugin is installed at the pinned
36
+ * version or the entry is removed.
31
37
  */
32
38
 
33
39
  import { execFileSync } from "node:child_process";
34
40
  import { posix, relative, sep } from "node:path";
41
+ import { isDeepStrictEqual } from "node:util";
42
+ // @ts-ignore — picomatch has no types declaration
43
+ import picomatch from "picomatch";
35
44
  import {
36
- PRINCIPAL_CLASSES,
37
45
  readDeclaration,
38
46
  WorkspaceReadError,
39
47
  type AgentDeclaration,
40
48
  type ClassScope,
41
49
  type Declaration,
42
50
  type PrincipalClass,
51
+ type ProtectedPath,
43
52
  type WriteVerb,
44
53
  } from "./declaration";
54
+ import {
55
+ classesOf,
56
+ coreClassRegistry,
57
+ loadClassRegistry,
58
+ principalClass as classOf,
59
+ roleGrants,
60
+ unknownScopeClasses,
61
+ type ClassRegistry,
62
+ } from "./principal-classes";
45
63
  import type { ReasonCode } from "./reason-codes";
46
64
  import { memberHolding } from "./record-assets";
47
65
  import { gitRoot } from "./record-source";
48
66
  import { normalisePrincipal, parseFrontMatter } from "./records";
67
+ import type { WorkspaceTree } from "./tree";
49
68
  import { emptyPolicy, type TrustPolicy } from "./trust/policy";
50
69
  import { commitProvenance, policyAtBase, resolveBase } from "./trust/provenance";
70
+ import { AGENT_TRAILER } from "./trailers";
51
71
  import { locateWorkspace } from "./which-chant";
52
72
 
53
73
  /** The environment variable naming the agent session a write is made in. */
54
74
  export const AGENT_ENV = "CHANT_AGENT";
55
75
 
56
- /** The commit trailer naming the agent session a commit was made in. */
57
- export const AGENT_TRAILER = "Chant-Agent";
76
+ /** The commit trailer naming the agent session a commit was made in, one of chant's trailers (`trailers.ts`). */
77
+ export { AGENT_TRAILER };
58
78
 
59
79
  /** Why a write is outside its writer's scope. Closed. */
60
- export const WRITE_SCOPE_CODES = ["write-scope-member", "write-scope-kind", "agent-unknown"] as const satisfies readonly ReasonCode[];
80
+ export const WRITE_SCOPE_CODES = ["write-scope-member", "write-scope-kind", "write-scope-class-unknown", "agent-unknown"] as const satisfies readonly ReasonCode[];
61
81
  export type WriteScopeCode = (typeof WRITE_SCOPE_CODES)[number];
62
82
 
63
- /** The classes a role grant puts a principal in, in the order they are tried. Human is the rest. */
64
- const ROLE_CLASSES = ["agent", "runner", "service"] as const satisfies readonly PrincipalClass[];
65
-
66
83
  /**
67
- * The role grants a class is read from. Every read of roles for write scope
68
- * goes through here, so where the grants live (`.chant/trust.json` today,
69
- * possibly the declaration, #2547) is decided in one place: the policy.
84
+ * What `check --changes` reports a path with: the record-write codes, and a
85
+ * write to a protected path (#3146), which a record write never is. Closed.
70
86
  */
71
- export function roleGrants(policy: TrustPolicy): Record<string, string[]> {
72
- return policy.roles;
73
- }
87
+ export const SCOPE_FINDING_CODES = [...WRITE_SCOPE_CODES, "write-scope-protected"] as const satisfies readonly ReasonCode[];
88
+ export type ScopeFindingCode = (typeof SCOPE_FINDING_CODES)[number];
89
+
90
+ export { classesOf, roleGrants };
74
91
 
75
- /** The class a principal is in: the first of agent, runner and service whose role it holds at base, or human. */
76
- export function principalClass(policy: TrustPolicy, principal: string | null): PrincipalClass {
77
- if (principal === null) return "human";
78
- const name = normalisePrincipal(principal);
79
- const grants = roleGrants(policy);
80
- return ROLE_CLASSES.find((cls) => (grants[cls] ?? []).some((p) => normalisePrincipal(p) === name)) ?? "human";
92
+ /**
93
+ * The class a principal is in: the first of agent, runner and service whose
94
+ * role it holds at base, then the first domain class in `classes` whose role
95
+ * it holds (#3080), or human.
96
+ */
97
+ export function principalClass(policy: TrustPolicy, principal: string | null, classes: ClassRegistry = coreClassRegistry()): PrincipalClass {
98
+ return classOf(policy, principal, classes);
81
99
  }
82
100
 
83
101
  /** Who is writing, as the scope judges it. */
@@ -104,7 +122,12 @@ export class WriteScopeError extends Error {
104
122
  * the declaration declares none by that name), in the session that lists
105
123
  * `principal`, or else in the class `principal`'s roles give it.
106
124
  */
107
- export function resolveWriter(declaration: Declaration | null, policy: TrustPolicy, given: { agent?: string | null; principal?: string | null }): Writer {
125
+ export function resolveWriter(
126
+ declaration: Declaration | null,
127
+ policy: TrustPolicy,
128
+ given: { agent?: string | null; principal?: string | null },
129
+ classes: ClassRegistry = coreClassRegistry(),
130
+ ): Writer {
108
131
  const principal = given.principal ?? null;
109
132
  const agents = declaration?.agents ?? [];
110
133
  if (given.agent !== undefined && given.agent !== null) {
@@ -125,19 +148,40 @@ export function resolveWriter(declaration: Declaration | null, policy: TrustPoli
125
148
  const agent = agents.find((a) => a.principals.some((p) => normalisePrincipal(p) === name));
126
149
  if (agent) return { principal, class: "agent", agent };
127
150
  }
128
- return { principal, class: principalClass(policy, principal), agent: null };
151
+ return { principal, class: principalClass(policy, principal, classes), agent: null };
152
+ }
153
+
154
+ /**
155
+ * Refuse `writer` when the declaration's writeScope names a class `classes`
156
+ * doesn't know and the writer is judged human (#3080): nothing says which
157
+ * role puts a principal in the unknown class, so this writer may be in it.
158
+ * A writer in another class is judged by that class, which comes first.
159
+ */
160
+ export function unknownClassVerdict(declaration: Declaration | null, writer: Writer, classes: ClassRegistry): { ok: true } | { ok: false; code: "write-scope-class-unknown"; message: string } {
161
+ if (writer.class !== "human") return OK;
162
+ const unknown = unknownScopeClasses(declaration?.writeScope ?? null, classes);
163
+ if (unknown.length === 0) return OK;
164
+ const list = unknown.map((c) => `writeScope.${c}`).join(", ");
165
+ return {
166
+ ok: false,
167
+ code: "write-scope-class-unknown",
168
+ message: `${who(writer)} may be in ${unknown.length === 1 ? "a class" : "classes"} no pinned package supplies (${list}), so its scope can't be told; install the package that supplies ${unknown.length === 1 ? "it" : "them"} at the pinned version, or remove the entry (known classes: ${classes.names().join(", ")})`,
169
+ };
129
170
  }
130
171
 
131
172
  /** The scope that applies to `writer`, or null when its class is not restricted. An agent is always restricted to its member. */
132
173
  export function scopeOf(declaration: Declaration | null, writer: Writer): ClassScope | null {
133
174
  const entry = declaration?.writeScope?.[writer.class];
134
- if (writer.class === "agent") return { members: null, records: entry?.records ?? null, pointer: entry?.pointer ?? "/agents" };
175
+ if (writer.class === "agent") return { members: null, records: entry?.records ?? null, protected: entry?.protected ?? [], pointer: entry?.pointer ?? "/agents" };
135
176
  return entry ?? null;
136
177
  }
137
178
 
138
- export type ScopeVerdict = { ok: true } | { ok: false; code: Exclude<WriteScopeCode, "agent-unknown">; message: string };
179
+ export type ScopeVerdict = { ok: true } | { ok: false; code: Exclude<ScopeFindingCode, "agent-unknown">; message: string };
180
+
181
+ /** A record write's verdict: never write-scope-protected, since protected paths judge files that aren't records. */
182
+ export type RecordVerdict = { ok: true } | { ok: false; code: Exclude<WriteScopeCode, "agent-unknown">; message: string };
139
183
 
140
- const OK: ScopeVerdict = { ok: true };
184
+ const OK = { ok: true } as const;
141
185
 
142
186
  function who(writer: Writer): string {
143
187
  if (writer.agent) return `agent session ${writer.agent.name}, bound to member ${writer.agent.member},`;
@@ -164,7 +208,7 @@ export interface ScopedKind {
164
208
  }
165
209
 
166
210
  /** Whether `writer` may write a record of `kind` with `verb`. A delete is never in a restricted scope. */
167
- export function judgeRecord(declaration: Declaration | null, writer: Writer, kind: ScopedKind, verb: WriteVerb | "delete"): ScopeVerdict {
211
+ export function judgeRecord(declaration: Declaration | null, writer: Writer, kind: ScopedKind, verb: WriteVerb | "delete"): RecordVerdict {
168
212
  const scope = scopeOf(declaration, writer);
169
213
  if (scope === null) return OK;
170
214
  // The workspace's own kinds are in every member's reach; a member's kinds only in its own.
@@ -194,17 +238,76 @@ export function judgeRecord(declaration: Declaration | null, writer: Writer, kin
194
238
  };
195
239
  }
196
240
 
197
- /** Whether `writer` may write the file at `path`, from the workspace root. */
198
- export function judgePath(declaration: Declaration | null, writer: Writer, path: string): ScopeVerdict {
241
+ /**
242
+ * The protected entry of `entries` that covers `path` (#3146, ws-077): one
243
+ * whose glob matches the path or a directory above it. Null when none does.
244
+ */
245
+ export function protectedEntry(entries: readonly ProtectedPath[], path: string): ProtectedPath | null {
246
+ if (entries.length === 0) return null;
247
+ const parts = path.split("/");
248
+ const prefixes = parts.map((_, i) => parts.slice(0, i + 1).join("/"));
249
+ for (const entry of entries) {
250
+ const match = picomatch(entry.path, { dot: true }) as (p: string) => boolean;
251
+ if (prefixes.some((p) => match(p))) return entry;
252
+ }
253
+ return null;
254
+ }
255
+
256
+ /**
257
+ * Whether JSON text `before` and `after` differ only in the top-level keys
258
+ * `except` names. False when either is missing or isn't a JSON object.
259
+ */
260
+ export function onlyKeysChanged(before: string | null, after: string | null, except: readonly string[]): boolean {
261
+ if (before === null || after === null || except.length === 0) return false;
262
+ const strip = (text: string): Record<string, unknown> | null => {
263
+ try {
264
+ const value = JSON.parse(text) as unknown;
265
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
266
+ const copy = { ...(value as Record<string, unknown>) };
267
+ for (const key of except) delete copy[key];
268
+ return copy;
269
+ } catch {
270
+ return null;
271
+ }
272
+ };
273
+ const a = strip(before);
274
+ const b = strip(after);
275
+ return a !== null && b !== null && isDeepStrictEqual(a, b);
276
+ }
277
+
278
+ /** A file's text before and after a write, read only when a protected entry with `except` needs it. */
279
+ export interface PathChange {
280
+ before(): string | null;
281
+ after(): string | null;
282
+ }
283
+
284
+ /**
285
+ * Whether `writer` may write the file at `path`, from the workspace root:
286
+ * the path is in a member the writer may write, and no protected entry of
287
+ * its scope covers it, unless the entry's `except` allows the change (#3146).
288
+ * Without `change`, a write to a path an `except` entry covers is judged as a
289
+ * change outside the allowed keys.
290
+ */
291
+ export function judgePath(declaration: Declaration | null, writer: Writer, path: string, change?: PathChange): ScopeVerdict {
199
292
  const scope = scopeOf(declaration, writer);
200
293
  if (scope === null) return OK;
201
294
  const member = memberHolding(path, declaration?.members ?? []);
202
- if (memberAllowed(writer, scope, member)) return OK;
203
- const where = member === null ? "is in no member" : `is in member ${member}`;
295
+ if (!memberAllowed(writer, scope, member)) {
296
+ const where = member === null ? "is in no member" : `is in member ${member}`;
297
+ return {
298
+ ok: false,
299
+ code: "write-scope-member",
300
+ message: `${who(writer)} may not write ${path}, which ${where}: ${writer.class === "agent" ? "an agent session writes only its own member" : `writeScope.${writer.class}.members leaves it out`}`,
301
+ };
302
+ }
303
+ const guarded = protectedEntry(scope.protected, path);
304
+ if (guarded === null) return OK;
305
+ if (guarded.except.length > 0 && change && onlyKeysChanged(change.before(), change.after(), guarded.except)) return OK;
306
+ const allowance = guarded.except.length > 0 ? `, outside the top-level keys it allows (${guarded.except.join(", ")})` : "";
204
307
  return {
205
308
  ok: false,
206
- code: "write-scope-member",
207
- message: `${who(writer)} may not write ${path}, which ${where}: ${writer.class === "agent" ? "an agent session writes only its own member" : `writeScope.${writer.class}.members leaves it out`}`,
309
+ code: "write-scope-protected",
310
+ message: `${who(writer)} may not write ${path}${allowance}: writeScope.${writer.class}.protected lists ${guarded.path}`,
208
311
  };
209
312
  }
210
313
 
@@ -220,12 +323,14 @@ export interface ScopeSource {
220
323
  /** The workspace root from the git root, "." for the git root, or null without a declaration. */
221
324
  root: string | null;
222
325
  policy: TrustPolicy;
326
+ /** The principal classes: the core four and those the declaration's pins supply, path pins read where the declaration was (#3080). */
327
+ classes: ClassRegistry;
223
328
  }
224
329
 
225
- function readAt(cwd: string, at?: string): { declaration: Declaration; rootOnDisk: string; root: string } | null {
330
+ function readAt(cwd: string, at?: string): { declaration: Declaration; rootOnDisk: string; root: string; tree: WorkspaceTree } | null {
226
331
  try {
227
332
  const located = locateWorkspace(cwd, at);
228
- return { declaration: readDeclaration(located.tree), rootOnDisk: located.rootOnDisk, root: located.root };
333
+ return { declaration: readDeclaration(located.tree), rootOnDisk: located.rootOnDisk, root: located.root, tree: located.tree };
229
334
  } catch (err) {
230
335
  if (err instanceof WorkspaceReadError) return null;
231
336
  throw err;
@@ -242,12 +347,16 @@ export function scopeSource(cwd: string): ScopeSource {
242
347
  const top = gitRoot(cwd);
243
348
  const base = top ? resolveBase(top) : null;
244
349
  const policy = top && base ? policyAtBase(top, base) : emptyPolicy(null);
350
+ const withClasses = (read: NonNullable<ReturnType<typeof readAt>>) => {
351
+ const { tree, ...rest } = read;
352
+ return { ...rest, classes: loadClassRegistry(read.declaration.pins, read.rootOnDisk, { tree }).registry };
353
+ };
245
354
  if (base?.commit) {
246
355
  const atBase = readAt(cwd, base.commit);
247
- if (atBase) return { ...atBase, from: "base", policy };
356
+ if (atBase) return { ...withClasses(atBase), from: "base", policy };
248
357
  }
249
358
  const here = readAt(cwd);
250
- return here ? { ...here, from: "working-tree", policy } : { declaration: null, from: null, rootOnDisk: null, root: null, policy };
359
+ return here ? { ...withClasses(here), from: "working-tree", policy } : { declaration: null, from: null, rootOnDisk: null, root: null, policy, classes: coreClassRegistry() };
251
360
  }
252
361
 
253
362
  const toPosix = (p: string) => (sep === "/" ? p : p.split(sep).join("/"));
@@ -271,16 +380,20 @@ export function scopedKind(source: ScopeSource, kindName: string, kindFile: stri
271
380
  * Refuse a record write outside the writer's scope (#2548): throws a
272
381
  * {@link WriteScopeError}. `agent` is the session the write names
273
382
  * (`CHANT_AGENT`), and `principal` who the write names as its author.
383
+ * Returns what it read at base, for the identity rule (#3163).
274
384
  */
275
385
  export function refuseRecordWrite(
276
386
  cwd: string,
277
387
  write: { kindName: string; kindFile: string; recordsDir: string; verb: WriteVerb; agent?: string | null; principal?: string | null },
278
- ): void {
388
+ ): ScopeSource {
279
389
  const source = scopeSource(cwd);
280
- if (source.declaration === null && (write.agent === undefined || write.agent === null)) return;
281
- const writer = resolveWriter(source.declaration, source.policy, { agent: write.agent, principal: write.principal });
390
+ if (source.declaration === null && (write.agent === undefined || write.agent === null)) return source;
391
+ const writer = resolveWriter(source.declaration, source.policy, { agent: write.agent, principal: write.principal }, source.classes);
392
+ const unknown = unknownClassVerdict(source.declaration, writer, source.classes);
393
+ if (!unknown.ok) throw new WriteScopeError(unknown.code, unknown.message);
282
394
  const verdict = judgeRecord(source.declaration, writer, scopedKind(source, write.kindName, write.kindFile, write.recordsDir), write.verb);
283
395
  if (!verdict.ok) throw new WriteScopeError(verdict.code, verdict.message);
396
+ return source;
284
397
  }
285
398
 
286
399
  // ── The check over a range ───────────────────────────────────────────────────
@@ -301,9 +414,9 @@ export interface ScopeCommit {
301
414
  }
302
415
 
303
416
  export interface ScopeFinding {
304
- /** `finding:<code>:<commit>:<path>`, or `finding:agent-unknown:<commit>`. */
417
+ /** `finding:<code>:<commit>:<path>`, or `finding:<code>:<commit>` for agent-unknown and write-scope-class-unknown, which judge the whole commit. */
305
418
  id: string;
306
- code: WriteScopeCode;
419
+ code: ScopeFindingCode;
307
420
  commit: string;
308
421
  /** From the workspace root, or null for a finding on the whole commit. */
309
422
  path: string | null;
@@ -318,7 +431,7 @@ export interface ScopeFinding {
318
431
  export interface ScopeReport {
319
432
  /** The commit the declaration and the trust policy were read at. */
320
433
  base: string;
321
- /** The classes with a writeScope entry, and agent when any session is declared. */
434
+ /** The classes with a writeScope entry, and agent when any session is declared: core first, then domain classes in pin order, then any no pinned package supplies. */
322
435
  restricted: PrincipalClass[];
323
436
  agents: string[];
324
437
  commits: ScopeCommit[];
@@ -384,13 +497,17 @@ export async function checkWriteScope(q: {
384
497
  prefix: string;
385
498
  declaration: Declaration;
386
499
  kinds: CheckKind[];
500
+ /** The principal classes the declaration's pins supply at base (#3080). Without it, the core four. */
501
+ classes?: ClassRegistry;
387
502
  }): Promise<ScopeReport | null> {
388
503
  const { top, base, head, prefix, declaration } = q;
504
+ const classes = q.classes ?? coreClassRegistry();
389
505
  if (declaration.writeScope === null && declaration.agents.length === 0) return null;
390
506
  const policy = policyAtBase(top, { commit: base, from: "flag" });
391
507
  const { activeAttestors } = await import("./trust/attestor");
392
508
  const attestors = await activeAttestors();
393
- const restricted = PRINCIPAL_CLASSES.filter((c) => declaration.writeScope?.[c] !== undefined || (c === "agent" && declaration.agents.length > 0));
509
+ const scoped = (c: string) => declaration.writeScope?.[c] !== undefined || (c === "agent" && declaration.agents.length > 0);
510
+ const restricted = [...classes.names().filter(scoped), ...unknownScopeClasses(declaration.writeScope, classes)];
394
511
  const report: ScopeReport = { base, restricted, agents: declaration.agents.map((a) => a.name), commits: [], findings: [] };
395
512
  const list = git(top, ["rev-list", "--reverse", "--no-merges", `${base}..${head}`]).split("\n").filter(Boolean);
396
513
  for (const commit of list) {
@@ -410,7 +527,7 @@ export async function checkWriteScope(q: {
410
527
  }
411
528
  let writer: Writer;
412
529
  try {
413
- writer = resolveWriter(declaration, policy, { agent: named, principal });
530
+ writer = resolveWriter(declaration, policy, { agent: named, principal }, classes);
414
531
  } catch (err) {
415
532
  if (!(err instanceof WriteScopeError)) throw err;
416
533
  report.commits.push({ commit, subject, principal, attested, class: "agent", agent: named, paths: paths.length });
@@ -418,6 +535,11 @@ export async function checkWriteScope(q: {
418
535
  continue;
419
536
  }
420
537
  report.commits.push({ commit, subject, principal, attested, class: writer.class, agent: writer.agent?.name ?? null, paths: paths.length });
538
+ const unknown = unknownClassVerdict(declaration, writer, classes);
539
+ if (!unknown.ok) {
540
+ report.findings.push({ id: `finding:${unknown.code}:${commit}`, code: unknown.code, commit, path: null, principal, class: writer.class, agent: null, verb: null, message: `${commit.slice(0, 8)}: ${unknown.message}` });
541
+ continue;
542
+ }
421
543
  if (scopeOf(declaration, writer) === null) continue;
422
544
  for (const p of paths) {
423
545
  const inWorkspace = prefix === "" ? p.path : p.path.slice(prefix.length + 1);
@@ -428,7 +550,10 @@ export async function checkWriteScope(q: {
428
550
  verb = p.status === "A" ? "new" : p.status === "D" ? "delete" : modifiedVerb(top, commit, p.path, kind);
429
551
  verdict = judgeRecord(declaration, writer, kind.scoped, verb);
430
552
  } else {
431
- verdict = judgePath(declaration, writer, inWorkspace);
553
+ verdict = judgePath(declaration, writer, inWorkspace, {
554
+ before: () => (p.status === "A" ? null : show(top, `${commit}^`, p.path)),
555
+ after: () => (p.status === "D" ? null : show(top, commit, p.path)),
556
+ });
432
557
  }
433
558
  if (verdict.ok) continue;
434
559
  report.findings.push({
package/src/yaml.test.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from "vitest";
2
2
  import { load as jsYamlLoad } from "js-yaml";
3
- import { emitYAML, parseYAML, parseYAMLDocument, parseScalar, splitYAMLDocuments, YAMLParseError } from "./yaml";
3
+ import { emitYAML, emitYAMLEntry, parseYAML, parseYAMLDocument, parseScalar, splitYAMLDocuments, YAMLParseError } from "./yaml";
4
4
 
5
5
  // ---------------------------------------------------------------------------
6
6
  // emitYAML
@@ -781,3 +781,113 @@ describe("parseYAML — anchors, aliases and merge keys (#2991)", () => {
781
781
  expect(() => parseYAML("a: &v 1\nb:\n <<: *v\n")).toThrow(YAMLParseError);
782
782
  });
783
783
  });
784
+
785
+ // #3006: parseYAML is js-yaml with chant's schema. Before it, an inline
786
+ // comment stayed in the value and a flow collection that was not JSON came
787
+ // back as a string, without a warning.
788
+ describe("parseYAML — comments and flow collections (#3006)", () => {
789
+ const same = (src: string): void => {
790
+ expect(parseYAMLDocument(src)).toEqual(jsYamlLoad(src));
791
+ };
792
+
793
+ test("an inline comment is not part of the value", () => {
794
+ expect(parseYAML("replicas: 1 # comment\n")).toEqual({ replicas: 1 });
795
+ expect(parseYAML("image: nginx:1.27 # pinned\nport: 80\t# tab\n")).toEqual({ image: "nginx:1.27", port: 80 });
796
+ same("items:\n - a # one\n - name: b # two\n url: http://x/#frag\n");
797
+ });
798
+
799
+ test("a # inside a quoted or plain scalar is kept", () => {
800
+ expect(parseYAML("a: 'x # y'\nb: \"x # y\"\nc: x#y\n")).toEqual({ a: "x # y", b: "x # y", c: "x#y" });
801
+ });
802
+
803
+ test("flow sequences and mappings that are not JSON", () => {
804
+ expect(parseYAML("args: [--foo, --bar]\n")).toEqual({ args: ["--foo", "--bar"] });
805
+ same("weekdays: [saturday, sunday]\ndays: ['1:7']\nmeta: { name: gw, labels: { app: x } }\n");
806
+ same("times:\n - { start_time: \"00:00\", end_time: \"24:00\" }\n");
807
+ });
808
+
809
+ test("a flow collection spanning lines", () => {
810
+ same("args: [\n --foo,\n --bar\n]\nmeta: {\n a: 1,\n b: [x, y]\n}\n");
811
+ });
812
+
813
+ test("a nested sequence on one line, as Grafana writes object matchers", () => {
814
+ expect(parseYAML("matchers:\n - - severity\n - =\n - page\n")).toEqual({ matchers: [["severity", "=", "page"]] });
815
+ });
816
+
817
+ test("a folded scalar keeps the line breaks of its more-indented lines (#2925)", () => {
818
+ same("msg: >\n a\n indented\n b\n");
819
+ });
820
+ });
821
+
822
+ describe("parseYAML — what chant keeps from its old parser (#3006)", () => {
823
+ test("yes and no are booleans; on and off stay strings", () => {
824
+ expect(parseYAML("a: yes\nb: no\nc: on\nd: off\n")).toEqual({ a: true, b: false, c: "on", d: "off" });
825
+ });
826
+
827
+ test("a date stays a string", () => {
828
+ expect(parseYAML("date: 2024-01-01\nat: 2024-01-01T00:00:00Z\n")).toEqual({ date: "2024-01-01", at: "2024-01-01T00:00:00Z" });
829
+ });
830
+
831
+ test("a local tag on a scalar or sequence reads as its text", () => {
832
+ expect(parseYAML("s: !reference [.base, script]\nr: !Ref MyApi\n")).toEqual({
833
+ s: "!reference [.base, script]",
834
+ r: "!Ref MyApi",
835
+ });
836
+ expect(parseYAML(emitYAMLEntry("s", { tag: "!reference", value: [".base", "script"] }))).toEqual({
837
+ s: "!reference [.base, script]",
838
+ });
839
+ });
840
+
841
+ test("a duplicate key takes the last value", () => {
842
+ expect(parseYAML("a: 1\na: 2\n")).toEqual({ a: 2 });
843
+ });
844
+
845
+ test("a `---`-separated stream reads as one mapping, as before", () => {
846
+ expect(parseYAML("a: 1\n---\nb: 2\n")).toEqual({ a: 1, b: 2 });
847
+ });
848
+
849
+ test("a scalar document is an error naming its line", () => {
850
+ expect(() => parseYAML("# header\njust text\n")).toThrow(/YAML line 2:/);
851
+ expect(parseYAML("")).toEqual({});
852
+ expect(parseYAML("# only a comment\n")).toEqual({});
853
+ });
854
+
855
+ test("a merged block is a copy, not shared with its anchor", () => {
856
+ const doc = parseYAML("a: &a\n x: { n: 1 }\nb:\n <<: *a\n") as { a: { x: { n: number } }; b: { x: { n: number } } };
857
+ doc.b.x.n = 2;
858
+ expect(doc.a.x.n).toBe(1);
859
+ });
860
+ });
861
+
862
+ describe("emitYAMLEntry (#3006)", () => {
863
+ test("an inline value gets a space after the colon", () => {
864
+ expect(emitYAMLEntry("jobs", {})).toBe("jobs: {}");
865
+ expect(emitYAMLEntry("stages", [])).toBe("stages: []");
866
+ expect(emitYAMLEntry("name", "x")).toBe("name: x");
867
+ });
868
+
869
+ test("a block value follows on the next line", () => {
870
+ expect(emitYAMLEntry("stages", ["a", "b"])).toBe("stages:\n - a\n - b");
871
+ expect(emitYAMLEntry("b", { c: 1 }, 1)).toBe(" b:\n c: 1");
872
+ });
873
+
874
+ test("what it writes parses back", () => {
875
+ const doc = { on: {}, env: { A: "1" }, stages: [], jobs: { build: { script: ["make"] } } };
876
+ const text = Object.entries(doc).map(([k, v]) => emitYAMLEntry(k, v)).join("\n");
877
+ expect(parseYAML(text)).toEqual(doc);
878
+ });
879
+ });
880
+
881
+ describe("emitYAML quotes strings a parser would read as something else (#3006)", () => {
882
+ const values = ["foo:", "- x", "? x", "|x", ">x", "@x", "`x", "%x", ",x", "]x", "}x", "True", "NULL", "~", ".inf", "-.inf", ".NaN", " lead", "trail ", "-1", "+1", ".5", "Yes"];
883
+
884
+ for (const v of values) {
885
+ test(`round-trips ${JSON.stringify(v)}`, () => {
886
+ expect(parseYAML(`k: ${emitYAML(v, 0)}\n`).k).toBe(v);
887
+ });
888
+ }
889
+
890
+ test("strings that were safe stay plain", () => {
891
+ for (const v of ["--flag", "-x", "a:b", "http://a/b", "on"]) expect(emitYAML(v, 0)).toBe(v);
892
+ });
893
+ });