@intentius/chant 0.100.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 (608) 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/build.d.ts +6 -0
  9. package/dist/build.d.ts.map +1 -1
  10. package/dist/builder.d.ts.map +1 -1
  11. package/dist/cli/build-options.d.ts +2 -0
  12. package/dist/cli/build-options.d.ts.map +1 -1
  13. package/dist/cli/commands/build.d.ts.map +1 -1
  14. package/dist/cli/commands/carve-emit.d.ts.map +1 -1
  15. package/dist/cli/commands/check-lexicon.d.ts +16 -0
  16. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  17. package/dist/cli/commands/import.d.ts +8 -0
  18. package/dist/cli/commands/import.d.ts.map +1 -1
  19. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  20. package/dist/cli/commands/init.d.ts +19 -0
  21. package/dist/cli/commands/init.d.ts.map +1 -1
  22. package/dist/cli/commands/onboard.d.ts +9 -0
  23. package/dist/cli/commands/onboard.d.ts.map +1 -1
  24. package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
  25. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -1
  26. package/dist/cli/handlers/dev.d.ts.map +1 -1
  27. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  28. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  29. package/dist/cli/handlers/misc.d.ts.map +1 -1
  30. package/dist/cli/handlers/operator.d.ts +9 -0
  31. package/dist/cli/handlers/operator.d.ts.map +1 -1
  32. package/dist/cli/handlers/run.d.ts.map +1 -1
  33. package/dist/cli/lsp/server.d.ts.map +1 -1
  34. package/dist/cli/main.d.ts.map +1 -1
  35. package/dist/cli/mcp/server.d.ts.map +1 -1
  36. package/dist/cli/mcp/tools/build.d.ts.map +1 -1
  37. package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
  38. package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
  39. package/dist/cli/mcp/tools/import.d.ts.map +1 -1
  40. package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
  41. package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
  42. package/dist/cli/mcp/tools/search.d.ts.map +1 -1
  43. package/dist/cli/mcp/workspace-tools.d.ts +9 -1
  44. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  45. package/dist/cli/registry.d.ts +33 -2
  46. package/dist/cli/registry.d.ts.map +1 -1
  47. package/dist/codegen/fetch.d.ts.map +1 -1
  48. package/dist/codegen/naming.d.ts.map +1 -1
  49. package/dist/codegen/pinned-upgrade.d.ts +67 -3
  50. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  51. package/dist/codegen/registry.d.ts.map +1 -1
  52. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  53. package/dist/components/capability.d.ts.map +1 -1
  54. package/dist/components/driver.d.ts.map +1 -1
  55. package/dist/components/fan-out.d.ts.map +1 -1
  56. package/dist/components/starter-plugin.d.ts.map +1 -1
  57. package/dist/components/verbs/process-runner.d.ts.map +1 -1
  58. package/dist/components/verbs/r2-sync.d.ts.map +1 -1
  59. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  60. package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
  61. package/dist/components/verbs/sign.d.ts.map +1 -1
  62. package/dist/components/verbs/verify.d.ts.map +1 -1
  63. package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
  64. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  65. package/dist/components/verbs/wrangler.d.ts.map +1 -1
  66. package/dist/composite.d.ts.map +1 -1
  67. package/dist/config.d.ts +30 -7
  68. package/dist/config.d.ts.map +1 -1
  69. package/dist/declarable.d.ts +16 -0
  70. package/dist/declarable.d.ts.map +1 -1
  71. package/dist/discovery/cache.d.ts.map +1 -1
  72. package/dist/discovery/fold-import.d.ts +15 -5
  73. package/dist/discovery/fold-import.d.ts.map +1 -1
  74. package/dist/discovery/param-deps.d.ts +16 -0
  75. package/dist/discovery/param-deps.d.ts.map +1 -1
  76. package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
  77. package/dist/discovery/specifier-resolve.d.ts +37 -0
  78. package/dist/discovery/specifier-resolve.d.ts.map +1 -0
  79. package/dist/errors.d.ts.map +1 -1
  80. package/dist/fold/fold.d.ts +3 -1
  81. package/dist/fold/fold.d.ts.map +1 -1
  82. package/dist/graph-detail.d.ts.map +1 -1
  83. package/dist/graph-ir.d.ts.map +1 -1
  84. package/dist/graph-layout.d.ts.map +1 -1
  85. package/dist/import/base-parser.d.ts.map +1 -1
  86. package/dist/import/embedded.d.ts +7 -0
  87. package/dist/import/embedded.d.ts.map +1 -1
  88. package/dist/import/parser-options.d.ts +14 -0
  89. package/dist/import/parser-options.d.ts.map +1 -0
  90. package/dist/import/parser.d.ts +17 -0
  91. package/dist/import/parser.d.ts.map +1 -1
  92. package/dist/kubectl-context.d.ts.map +1 -1
  93. package/dist/lexicon-output.d.ts.map +1 -1
  94. package/dist/lexicon-schema.d.ts +3 -3
  95. package/dist/lexicon.d.ts +78 -3
  96. package/dist/lexicon.d.ts.map +1 -1
  97. package/dist/lifecycle/assert-live.d.ts.map +1 -1
  98. package/dist/lifecycle/gate-ledger.d.ts +15 -0
  99. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  100. package/dist/lifecycle/git.d.ts.map +1 -1
  101. package/dist/lifecycle/member-ledger.d.ts.map +1 -1
  102. package/dist/lifecycle/observe.d.ts.map +1 -1
  103. package/dist/lifecycle/plan-digest.d.ts +26 -5
  104. package/dist/lifecycle/plan-digest.d.ts.map +1 -1
  105. package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
  106. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  107. package/dist/lifecycle/scenario.d.ts.map +1 -1
  108. package/dist/lifecycle/status.d.ts.map +1 -1
  109. package/dist/lifecycle/work-lease.d.ts +61 -2
  110. package/dist/lifecycle/work-lease.d.ts.map +1 -1
  111. package/dist/lint/config.d.ts +4 -4
  112. package/dist/lint/engine.d.ts +6 -0
  113. package/dist/lint/engine.d.ts.map +1 -1
  114. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  115. package/dist/op/activities/activity-contracts.d.ts +12 -11
  116. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  117. package/dist/op/activities/apply.d.ts +79 -9
  118. package/dist/op/activities/apply.d.ts.map +1 -1
  119. package/dist/op/activities/decide.d.ts.map +1 -1
  120. package/dist/op/activities/index.d.ts +1 -1
  121. package/dist/op/activities/index.d.ts.map +1 -1
  122. package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
  123. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
  124. package/dist/op/activities/propose-upgrade.d.ts +2 -0
  125. package/dist/op/activities/propose-upgrade.d.ts.map +1 -1
  126. package/dist/op/activity-contract.d.ts +1 -1
  127. package/dist/op/activity-failure.d.ts.map +1 -1
  128. package/dist/op/activity-profiles.d.ts.map +1 -1
  129. package/dist/op/builders.d.ts.map +1 -1
  130. package/dist/op/composites/apply-op.d.ts +12 -5
  131. package/dist/op/composites/apply-op.d.ts.map +1 -1
  132. package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
  133. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
  134. package/dist/op/decide-backend.d.ts.map +1 -1
  135. package/dist/op/decide-read-inputs.d.ts.map +1 -1
  136. package/dist/op/emulator-lifecycle.d.ts +21 -2
  137. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  138. package/dist/op/gate-approval.d.ts +5 -1
  139. package/dist/op/gate-approval.d.ts.map +1 -1
  140. package/dist/op/gate-wait.d.ts.map +1 -1
  141. package/dist/op/gate.d.ts +19 -3
  142. package/dist/op/gate.d.ts.map +1 -1
  143. package/dist/op/index.d.ts +3 -1
  144. package/dist/op/index.d.ts.map +1 -1
  145. package/dist/op/local-executor.d.ts +6 -0
  146. package/dist/op/local-executor.d.ts.map +1 -1
  147. package/dist/op/operator.d.ts.map +1 -1
  148. package/dist/op/run-context.d.ts +39 -0
  149. package/dist/op/run-context.d.ts.map +1 -0
  150. package/dist/op/run-live.d.ts.map +1 -1
  151. package/dist/op/runtimes/local.d.ts.map +1 -1
  152. package/dist/op/source-archive.d.ts.map +1 -1
  153. package/dist/op/steward-beside.d.ts.map +1 -1
  154. package/dist/op/steward-points.d.ts.map +1 -1
  155. package/dist/op/steward.d.ts.map +1 -1
  156. package/dist/op/types.d.ts +8 -3
  157. package/dist/op/types.d.ts.map +1 -1
  158. package/dist/op/work-lease-run.d.ts +24 -4
  159. package/dist/op/work-lease-run.d.ts.map +1 -1
  160. package/dist/project-root.d.ts.map +1 -1
  161. package/dist/provenance.d.ts +21 -0
  162. package/dist/provenance.d.ts.map +1 -1
  163. package/dist/pseudo-parameter.d.ts.map +1 -1
  164. package/dist/reconcile.d.ts.map +1 -1
  165. package/dist/secret-materialization.d.ts.map +1 -1
  166. package/dist/serializer.d.ts +8 -0
  167. package/dist/serializer.d.ts.map +1 -1
  168. package/dist/telemetry-attribution.d.ts +77 -0
  169. package/dist/telemetry-attribution.d.ts.map +1 -0
  170. package/dist/terraform/emit-source.d.ts.map +1 -1
  171. package/dist/terraform/parse.d.ts.map +1 -1
  172. package/dist/testing.d.ts.map +1 -1
  173. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  174. package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
  175. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
  176. package/dist/workspace/agent-cli.d.ts +88 -0
  177. package/dist/workspace/agent-cli.d.ts.map +1 -0
  178. package/dist/workspace/box-factory.d.ts +73 -0
  179. package/dist/workspace/box-factory.d.ts.map +1 -0
  180. package/dist/workspace/changes-cli.d.ts.map +1 -1
  181. package/dist/workspace/changes.d.ts +8 -1
  182. package/dist/workspace/changes.d.ts.map +1 -1
  183. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
  184. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  185. package/dist/workspace/checks/links.d.ts +1 -0
  186. package/dist/workspace/checks/links.d.ts.map +1 -1
  187. package/dist/workspace/checks/live.d.ts +40 -0
  188. package/dist/workspace/checks/live.d.ts.map +1 -0
  189. package/dist/workspace/checks.d.ts +26 -2
  190. package/dist/workspace/checks.d.ts.map +1 -1
  191. package/dist/workspace/compose-graph.d.ts +73 -0
  192. package/dist/workspace/compose-graph.d.ts.map +1 -1
  193. package/dist/workspace/conformance/index.d.ts +67 -6
  194. package/dist/workspace/conformance/index.d.ts.map +1 -1
  195. package/dist/workspace/conformance/vitest.d.ts +12 -3
  196. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  197. package/dist/workspace/conformance/writer.d.ts +368 -0
  198. package/dist/workspace/conformance/writer.d.ts.map +1 -0
  199. package/dist/workspace/decide.d.ts +1 -1
  200. package/dist/workspace/decide.d.ts.map +1 -1
  201. package/dist/workspace/declaration.d.ts +119 -0
  202. package/dist/workspace/declaration.d.ts.map +1 -1
  203. package/dist/workspace/declaration.schema.json +368 -4
  204. package/dist/workspace/export-cli.d.ts +12 -0
  205. package/dist/workspace/export-cli.d.ts.map +1 -0
  206. package/dist/workspace/export.d.ts +145 -0
  207. package/dist/workspace/export.d.ts.map +1 -0
  208. package/dist/workspace/generated-files.d.ts.map +1 -1
  209. package/dist/workspace/graph-cache.d.ts.map +1 -1
  210. package/dist/workspace/graph-cli.d.ts +2 -0
  211. package/dist/workspace/graph-cli.d.ts.map +1 -1
  212. package/dist/workspace/identity.d.ts +111 -0
  213. package/dist/workspace/identity.d.ts.map +1 -0
  214. package/dist/workspace/import.d.ts +73 -0
  215. package/dist/workspace/import.d.ts.map +1 -0
  216. package/dist/workspace/intent-cli.d.ts +7 -4
  217. package/dist/workspace/intent-cli.d.ts.map +1 -1
  218. package/dist/workspace/intent-record.d.ts +8 -1
  219. package/dist/workspace/intent-record.d.ts.map +1 -1
  220. package/dist/workspace/intent.d.ts +149 -5
  221. package/dist/workspace/intent.d.ts.map +1 -1
  222. package/dist/workspace/kinds.d.ts +24 -6
  223. package/dist/workspace/kinds.d.ts.map +1 -1
  224. package/dist/workspace/lineage-adopt-cli.d.ts +15 -0
  225. package/dist/workspace/lineage-adopt-cli.d.ts.map +1 -0
  226. package/dist/workspace/lineage-adopt.d.ts +106 -0
  227. package/dist/workspace/lineage-adopt.d.ts.map +1 -0
  228. package/dist/workspace/lineage-check.d.ts +9 -2
  229. package/dist/workspace/lineage-check.d.ts.map +1 -1
  230. package/dist/workspace/lineage-cli.d.ts +6 -1
  231. package/dist/workspace/lineage-cli.d.ts.map +1 -1
  232. package/dist/workspace/lineage-hash-index.d.ts +110 -0
  233. package/dist/workspace/lineage-hash-index.d.ts.map +1 -0
  234. package/dist/workspace/lineage-init.d.ts +10 -0
  235. package/dist/workspace/lineage-init.d.ts.map +1 -1
  236. package/dist/workspace/lineage-lock.d.ts +149 -2
  237. package/dist/workspace/lineage-lock.d.ts.map +1 -1
  238. package/dist/workspace/lineage-migrations.d.ts +15 -3
  239. package/dist/workspace/lineage-migrations.d.ts.map +1 -1
  240. package/dist/workspace/lineage-provenance.d.ts +18 -0
  241. package/dist/workspace/lineage-provenance.d.ts.map +1 -0
  242. package/dist/workspace/lineage-upgrade-cli.d.ts +2 -0
  243. package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -1
  244. package/dist/workspace/lineage-upgrade.d.ts +30 -1
  245. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  246. package/dist/workspace/lineage-versions.d.ts +116 -0
  247. package/dist/workspace/lineage-versions.d.ts.map +1 -0
  248. package/dist/workspace/links.d.ts +31 -5
  249. package/dist/workspace/links.d.ts.map +1 -1
  250. package/dist/workspace/ls-generated.d.ts +37 -0
  251. package/dist/workspace/ls-generated.d.ts.map +1 -0
  252. package/dist/workspace/ls.d.ts +4 -0
  253. package/dist/workspace/ls.d.ts.map +1 -1
  254. package/dist/workspace/member-commands.d.ts.map +1 -1
  255. package/dist/workspace/nested-graph.d.ts +56 -0
  256. package/dist/workspace/nested-graph.d.ts.map +1 -0
  257. package/dist/workspace/nesting.d.ts +21 -0
  258. package/dist/workspace/nesting.d.ts.map +1 -0
  259. package/dist/workspace/pin-cli.d.ts +10 -0
  260. package/dist/workspace/pin-cli.d.ts.map +1 -0
  261. package/dist/workspace/pin-integrity.d.ts +51 -0
  262. package/dist/workspace/pin-integrity.d.ts.map +1 -0
  263. package/dist/workspace/points.d.ts.map +1 -1
  264. package/dist/workspace/principal-classes.d.ts +114 -0
  265. package/dist/workspace/principal-classes.d.ts.map +1 -0
  266. package/dist/workspace/reason-codes.d.ts +47 -3
  267. package/dist/workspace/reason-codes.d.ts.map +1 -1
  268. package/dist/workspace/record-assets.d.ts.map +1 -1
  269. package/dist/workspace/record-sessions.d.ts +7 -11
  270. package/dist/workspace/record-sessions.d.ts.map +1 -1
  271. package/dist/workspace/records-checkout.d.ts +54 -0
  272. package/dist/workspace/records-checkout.d.ts.map +1 -0
  273. package/dist/workspace/records-cli.d.ts +57 -2
  274. package/dist/workspace/records-cli.d.ts.map +1 -1
  275. package/dist/workspace/records-close.d.ts +5 -2
  276. package/dist/workspace/records-close.d.ts.map +1 -1
  277. package/dist/workspace/records-since.d.ts.map +1 -1
  278. package/dist/workspace/records-write.d.ts +23 -4
  279. package/dist/workspace/records-write.d.ts.map +1 -1
  280. package/dist/workspace/records.d.ts +58 -5
  281. package/dist/workspace/records.d.ts.map +1 -1
  282. package/dist/workspace/returns.d.ts +129 -0
  283. package/dist/workspace/returns.d.ts.map +1 -0
  284. package/dist/workspace/runs-cli.d.ts +164 -0
  285. package/dist/workspace/runs-cli.d.ts.map +1 -0
  286. package/dist/workspace/runs.d.ts +423 -0
  287. package/dist/workspace/runs.d.ts.map +1 -0
  288. package/dist/workspace/source-block.d.ts +1 -1
  289. package/dist/workspace/source-block.d.ts.map +1 -1
  290. package/dist/workspace/status-gates.d.ts +15 -3
  291. package/dist/workspace/status-gates.d.ts.map +1 -1
  292. package/dist/workspace/status.d.ts +10 -0
  293. package/dist/workspace/status.d.ts.map +1 -1
  294. package/dist/workspace/symbols.d.ts +52 -0
  295. package/dist/workspace/symbols.d.ts.map +1 -0
  296. package/dist/workspace/template-manifest.d.ts +11 -3
  297. package/dist/workspace/template-manifest.d.ts.map +1 -1
  298. package/dist/workspace/trailer-joins.d.ts +46 -0
  299. package/dist/workspace/trailer-joins.d.ts.map +1 -0
  300. package/dist/workspace/trailers.d.ts +89 -0
  301. package/dist/workspace/trailers.d.ts.map +1 -0
  302. package/dist/workspace/trust/attestor.d.ts +8 -0
  303. package/dist/workspace/trust/attestor.d.ts.map +1 -1
  304. package/dist/workspace/trust/dsse.d.ts +58 -0
  305. package/dist/workspace/trust/dsse.d.ts.map +1 -0
  306. package/dist/workspace/trust/evidence-cli.d.ts +66 -0
  307. package/dist/workspace/trust/evidence-cli.d.ts.map +1 -0
  308. package/dist/workspace/trust/evidence.d.ts +93 -0
  309. package/dist/workspace/trust/evidence.d.ts.map +1 -0
  310. package/dist/workspace/trust/policy.d.ts +54 -1
  311. package/dist/workspace/trust/policy.d.ts.map +1 -1
  312. package/dist/workspace/trust/provenance.d.ts +21 -1
  313. package/dist/workspace/trust/provenance.d.ts.map +1 -1
  314. package/dist/workspace/trust/rotation.d.ts +132 -0
  315. package/dist/workspace/trust/rotation.d.ts.map +1 -0
  316. package/dist/workspace/trust/seal.d.ts +33 -0
  317. package/dist/workspace/trust/seal.d.ts.map +1 -1
  318. package/dist/workspace/trust/signers-cli.d.ts +49 -0
  319. package/dist/workspace/trust/signers-cli.d.ts.map +1 -0
  320. package/dist/workspace/trust/ssh-commit.d.ts +12 -0
  321. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
  322. package/dist/workspace/trust/test-repo.d.ts +13 -0
  323. package/dist/workspace/trust/test-repo.d.ts.map +1 -1
  324. package/dist/workspace/trust/verify.d.ts +15 -0
  325. package/dist/workspace/trust/verify.d.ts.map +1 -1
  326. package/dist/workspace/work-cli.d.ts +39 -3
  327. package/dist/workspace/work-cli.d.ts.map +1 -1
  328. package/dist/workspace/work-evidence.d.ts +1 -1
  329. package/dist/workspace/work-evidence.d.ts.map +1 -1
  330. package/dist/workspace/work.d.ts +26 -1
  331. package/dist/workspace/work.d.ts.map +1 -1
  332. package/dist/workspace/workspace-principals.schema.json +60 -0
  333. package/dist/workspace/write-scope.d.ts +259 -0
  334. package/dist/workspace/write-scope.d.ts.map +1 -0
  335. package/dist/yaml.d.ts +21 -39
  336. package/dist/yaml.d.ts.map +1 -1
  337. package/package.json +2 -2
  338. package/src/audit/prior-art.ts +15 -0
  339. package/src/build.ts +8 -0
  340. package/src/cli/build-options.ts +4 -1
  341. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
  342. package/src/cli/commands/build.ts +2 -0
  343. package/src/cli/commands/carve-emit-state.test.ts +4 -0
  344. package/src/cli/commands/carve-emit.ts +5 -19
  345. package/src/cli/commands/check-lexicon.test.ts +62 -1
  346. package/src/cli/commands/check-lexicon.ts +36 -6
  347. package/src/cli/commands/import-live.test.ts +87 -1
  348. package/src/cli/commands/import-parser-options.test.ts +133 -0
  349. package/src/cli/commands/import.ts +69 -25
  350. package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
  351. package/src/cli/commands/init.test.ts +8 -1
  352. package/src/cli/commands/init.ts +23 -8
  353. package/src/cli/commands/lint.test.ts +37 -0
  354. package/src/cli/commands/onboard.test.ts +53 -3
  355. package/src/cli/commands/onboard.ts +52 -4
  356. package/src/cli/commands/pinned-upgrade.ts +73 -33
  357. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
  358. package/src/cli/handlers/components.ts +2 -2
  359. package/src/cli/handlers/dev.ts +8 -6
  360. package/src/cli/handlers/fan-out.test.ts +6 -6
  361. package/src/cli/handlers/fan-out.ts +2 -1
  362. package/src/cli/handlers/graph.test.ts +42 -0
  363. package/src/cli/handlers/graph.ts +22 -0
  364. package/src/cli/handlers/lifecycle.test.ts +67 -0
  365. package/src/cli/handlers/lifecycle.ts +6 -5
  366. package/src/cli/handlers/misc.ts +2 -0
  367. package/src/cli/handlers/operator.ts +88 -4
  368. package/src/cli/handlers/run.test.ts +111 -0
  369. package/src/cli/handlers/run.ts +85 -24
  370. package/src/cli/handlers/scenario.ts +2 -2
  371. package/src/cli/main.test.ts +33 -0
  372. package/src/cli/main.ts +168 -17
  373. package/src/cli/mcp/workspace-tools.test.ts +5 -2
  374. package/src/cli/mcp/workspace-tools.ts +62 -4
  375. package/src/cli/registry.ts +33 -2
  376. package/src/cli/serve-mcp-workspace.test.ts +1 -1
  377. package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
  378. package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
  379. package/src/codegen/pinned-upgrade.test.ts +104 -0
  380. package/src/codegen/pinned-upgrade.ts +250 -56
  381. package/src/codegen/release-wiring.test.ts +5 -1
  382. package/src/codegen/testdata/postgres-tags.json +242 -0
  383. package/src/components/fan-out-output.test.ts +1 -1
  384. package/src/components/fan-out.test.ts +1 -1
  385. package/src/components/promote.test.ts +1 -1
  386. package/src/config.test.ts +21 -0
  387. package/src/config.ts +27 -0
  388. package/src/content-digest.test.ts +2 -2
  389. package/src/declarable.test.ts +21 -0
  390. package/src/declarable.ts +19 -0
  391. package/src/discovery/fold-composite.test.ts +41 -5
  392. package/src/discovery/fold-import.test.ts +129 -0
  393. package/src/discovery/fold-import.ts +643 -131
  394. package/src/discovery/fold-package-composite.test.ts +264 -0
  395. package/src/discovery/fold-specifiers.test.ts +191 -0
  396. package/src/discovery/param-deps.test.ts +66 -1
  397. package/src/discovery/param-deps.ts +45 -0
  398. package/src/discovery/specifier-resolve.ts +344 -0
  399. package/src/fold/fold.test.ts +39 -1
  400. package/src/fold/fold.ts +83 -9
  401. package/src/fold-provenance.test.ts +140 -0
  402. package/src/graph-ir.test.ts +83 -0
  403. package/src/graph-ir.ts +29 -0
  404. package/src/import/embedded.test.ts +20 -0
  405. package/src/import/embedded.ts +13 -0
  406. package/src/import/parser-options.ts +46 -0
  407. package/src/import/parser.ts +20 -0
  408. package/src/lexicon.ts +79 -3
  409. package/src/lifecycle/gate-ledger.test.ts +14 -0
  410. package/src/lifecycle/gate-ledger.ts +18 -1
  411. package/src/lifecycle/observe.test.ts +18 -0
  412. package/src/lifecycle/observe.ts +6 -4
  413. package/src/lifecycle/plan-digest.test.ts +54 -3
  414. package/src/lifecycle/plan-digest.ts +38 -8
  415. package/src/lifecycle/snapshot.ts +2 -2
  416. package/src/lifecycle/work-lease.test.ts +1 -1
  417. package/src/lifecycle/work-lease.ts +93 -2
  418. package/src/lint/engine.test.ts +55 -0
  419. package/src/lint/engine.ts +30 -4
  420. package/src/lint/output-docs.test.ts +59 -56
  421. package/src/op/activities/activity-contracts.ts +1 -0
  422. package/src/op/activities/apply.test.ts +189 -5
  423. package/src/op/activities/apply.ts +230 -8
  424. package/src/op/activities/index.ts +3 -0
  425. package/src/op/activities/lexicon-upgrade.test.ts +160 -6
  426. package/src/op/activities/lexicon-upgrade.ts +154 -51
  427. package/src/op/activities/propose-upgrade.ts +8 -5
  428. package/src/op/composites/apply-op.ts +12 -5
  429. package/src/op/composites/composites.test.ts +30 -3
  430. package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
  431. package/src/op/composites/lexicon-upgrade-op.ts +29 -8
  432. package/src/op/emulator-lifecycle.test.ts +23 -0
  433. package/src/op/emulator-lifecycle.ts +52 -14
  434. package/src/op/gate-approval.test.ts +91 -0
  435. package/src/op/gate-approval.ts +5 -1
  436. package/src/op/gate.ts +34 -8
  437. package/src/op/index.ts +3 -1
  438. package/src/op/local-executor.test.ts +15 -0
  439. package/src/op/local-executor.ts +40 -12
  440. package/src/op/local-output.ts +1 -1
  441. package/src/op/operator.ts +4 -0
  442. package/src/op/run-context.test.ts +141 -0
  443. package/src/op/run-context.ts +51 -0
  444. package/src/op/runtimes/local.test.ts +14 -0
  445. package/src/op/runtimes/local.ts +2 -0
  446. package/src/op/steward-beside.ts +2 -0
  447. package/src/op/types.ts +8 -3
  448. package/src/op/work-lease-run.test.ts +47 -0
  449. package/src/op/work-lease-run.ts +73 -11
  450. package/src/provenance.ts +35 -0
  451. package/src/serializer.ts +9 -0
  452. package/src/telemetry-attribution.test.ts +91 -0
  453. package/src/telemetry-attribution.ts +145 -0
  454. package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
  455. package/src/workspace/agent-cli.ts +138 -0
  456. package/src/workspace/agent.schema.json +378 -0
  457. package/src/workspace/behold-kinds.test.ts +1 -1
  458. package/src/workspace/box-factory.test.ts +320 -0
  459. package/src/workspace/box-factory.ts +103 -0
  460. package/src/workspace/changes-cli.ts +5 -0
  461. package/src/workspace/changes.schema.json +171 -1
  462. package/src/workspace/changes.ts +61 -4
  463. package/src/workspace/check-live.test.ts +192 -0
  464. package/src/workspace/check.schema.json +64 -0
  465. package/src/workspace/checks/boxes.test.ts +2 -0
  466. package/src/workspace/checks/links.ts +23 -2
  467. package/src/workspace/checks/live.ts +113 -0
  468. package/src/workspace/checks.test.ts +2 -2
  469. package/src/workspace/checks.ts +49 -8
  470. package/src/workspace/compose-graph.test.ts +47 -0
  471. package/src/workspace/compose-graph.ts +127 -3
  472. package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +7 -1
  473. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +5 -0
  474. package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +27 -0
  475. package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +95 -0
  476. package/src/workspace/conformance/__writer_fixture__/decisions/points.json +132 -0
  477. package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +48 -0
  478. package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +108 -0
  479. package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  480. package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  481. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +578 -0
  482. package/src/workspace/conformance/conformance.e2e.test.ts +35 -3
  483. package/src/workspace/conformance/index.mjs +30 -3
  484. package/src/workspace/conformance/index.ts +103 -15
  485. package/src/workspace/conformance/vitest.ts +44 -2
  486. package/src/workspace/conformance/writer-conformance.e2e.test.ts +131 -0
  487. package/src/workspace/conformance/writer.test.ts +156 -0
  488. package/src/workspace/conformance/writer.ts +880 -0
  489. package/src/workspace/decide.ts +7 -1
  490. package/src/workspace/declaration.schema.json +368 -4
  491. package/src/workspace/declaration.ts +270 -1
  492. package/src/workspace/declared-kinds.test.ts +33 -0
  493. package/src/workspace/evidence.schema.json +279 -0
  494. package/src/workspace/export-cli.ts +180 -0
  495. package/src/workspace/export-import.test.ts +282 -0
  496. package/src/workspace/export.ts +486 -0
  497. package/src/workspace/graph-cli.ts +51 -2
  498. package/src/workspace/graph-contract.test.ts +89 -4
  499. package/src/workspace/graph.schema.json +453 -1
  500. package/src/workspace/identity.test.ts +196 -0
  501. package/src/workspace/identity.ts +186 -0
  502. package/src/workspace/import.ts +325 -0
  503. package/src/workspace/intent-cli.ts +64 -10
  504. package/src/workspace/intent-record.schema.json +208 -0
  505. package/src/workspace/intent-record.ts +35 -1
  506. package/src/workspace/intent-why.test.ts +274 -0
  507. package/src/workspace/intent.schema.json +718 -7
  508. package/src/workspace/intent.test.ts +11 -2
  509. package/src/workspace/intent.ts +495 -32
  510. package/src/workspace/kinds.test.ts +5 -5
  511. package/src/workspace/kinds.ts +62 -33
  512. package/src/workspace/lineage-adopt-cli.ts +103 -0
  513. package/src/workspace/lineage-adopt.test.ts +552 -0
  514. package/src/workspace/lineage-adopt.ts +452 -0
  515. package/src/workspace/lineage-check.ts +26 -5
  516. package/src/workspace/lineage-cli.ts +12 -2
  517. package/src/workspace/lineage-hash-index.ts +305 -0
  518. package/src/workspace/lineage-init.test.ts +9 -0
  519. package/src/workspace/lineage-init.ts +29 -9
  520. package/src/workspace/lineage-lock.ts +54 -0
  521. package/src/workspace/lineage-migrations.test.ts +27 -0
  522. package/src/workspace/lineage-migrations.ts +44 -14
  523. package/src/workspace/lineage-provenance.ts +40 -0
  524. package/src/workspace/lineage-upgrade-cli.ts +9 -5
  525. package/src/workspace/lineage-upgrade.test.ts +92 -1
  526. package/src/workspace/lineage-upgrade.ts +111 -16
  527. package/src/workspace/lineage-versions.ts +348 -0
  528. package/src/workspace/links.test.ts +121 -2
  529. package/src/workspace/links.ts +97 -6
  530. package/src/workspace/ls-contract.test.ts +84 -1
  531. package/src/workspace/ls-generated.ts +111 -0
  532. package/src/workspace/ls.schema.json +19 -0
  533. package/src/workspace/ls.ts +8 -1
  534. package/src/workspace/member-commands.ts +3 -1
  535. package/src/workspace/nested-graph.test.ts +176 -0
  536. package/src/workspace/nested-graph.ts +169 -0
  537. package/src/workspace/nesting.ts +37 -0
  538. package/src/workspace/pin-cli.test.ts +71 -0
  539. package/src/workspace/pin-cli.ts +57 -0
  540. package/src/workspace/pin-integrity.test.ts +121 -0
  541. package/src/workspace/pin-integrity.ts +104 -0
  542. package/src/workspace/points-write.schema.json +2 -0
  543. package/src/workspace/principal-classes.test.ts +175 -0
  544. package/src/workspace/principal-classes.ts +307 -0
  545. package/src/workspace/read-contract.test.ts +24 -0
  546. package/src/workspace/reason-codes.test.ts +20 -2
  547. package/src/workspace/reason-codes.ts +53 -3
  548. package/src/workspace/record-sessions.ts +12 -14
  549. package/src/workspace/records-amend.schema.json +6 -0
  550. package/src/workspace/records-checkout.ts +106 -0
  551. package/src/workspace/records-cli.ts +195 -22
  552. package/src/workspace/records-close.schema.json +7 -2
  553. package/src/workspace/records-close.ts +10 -2
  554. package/src/workspace/records-formats.test.ts +13 -5
  555. package/src/workspace/records-new.schema.json +6 -0
  556. package/src/workspace/records-review.schema.json +6 -0
  557. package/src/workspace/records-sessions.test.ts +23 -6
  558. package/src/workspace/records-uncommitted.test.ts +171 -0
  559. package/src/workspace/records-write.test.ts +65 -8
  560. package/src/workspace/records-write.ts +92 -6
  561. package/src/workspace/records.schema.json +127 -4
  562. package/src/workspace/records.ts +123 -5
  563. package/src/workspace/returns.ts +328 -0
  564. package/src/workspace/runs-cli.ts +381 -0
  565. package/src/workspace/runs-write.schema.json +583 -0
  566. package/src/workspace/runs.schema.json +810 -0
  567. package/src/workspace/runs.test.ts +242 -0
  568. package/src/workspace/runs.ts +709 -0
  569. package/src/workspace/signers.schema.json +206 -0
  570. package/src/workspace/status-contract.test.ts +5 -0
  571. package/src/workspace/status-gates.ts +24 -9
  572. package/src/workspace/status.schema.json +127 -2
  573. package/src/workspace/status.ts +21 -1
  574. package/src/workspace/symbols.ts +180 -0
  575. package/src/workspace/template-manifest.ts +22 -4
  576. package/src/workspace/trailer-joins.test.ts +171 -0
  577. package/src/workspace/trailer-joins.ts +82 -0
  578. package/src/workspace/trailers.ts +148 -0
  579. package/src/workspace/trust/attestor.ts +15 -0
  580. package/src/workspace/trust/dsse.ts +134 -0
  581. package/src/workspace/trust/evidence-cli.ts +195 -0
  582. package/src/workspace/trust/evidence.test.ts +241 -0
  583. package/src/workspace/trust/evidence.ts +207 -0
  584. package/src/workspace/trust/policy.ts +110 -3
  585. package/src/workspace/trust/provenance.ts +41 -4
  586. package/src/workspace/trust/record-seal.test.ts +1 -1
  587. package/src/workspace/trust/rotation.test.ts +258 -0
  588. package/src/workspace/trust/rotation.ts +336 -0
  589. package/src/workspace/trust/seal.ts +63 -1
  590. package/src/workspace/trust/signers-cli.ts +178 -0
  591. package/src/workspace/trust/ssh-commit.ts +53 -4
  592. package/src/workspace/trust/test-repo.ts +18 -0
  593. package/src/workspace/trust/trust.test.ts +8 -2
  594. package/src/workspace/trust/verify-cli.ts +1 -0
  595. package/src/workspace/trust/verify.ts +22 -0
  596. package/src/workspace/work-cli.e2e.test.ts +28 -1
  597. package/src/workspace/work-cli.ts +133 -8
  598. package/src/workspace/work-evidence.schema.json +6 -0
  599. package/src/workspace/work-history.schema.json +73 -1
  600. package/src/workspace/work-history.test.ts +48 -5
  601. package/src/workspace/work-kind-fields.test.ts +154 -0
  602. package/src/workspace/work-lease.schema.json +14 -1
  603. package/src/workspace/work.ts +82 -0
  604. package/src/workspace/workspace-principals.schema.json +60 -0
  605. package/src/workspace/write-scope.test.ts +466 -0
  606. package/src/workspace/write-scope.ts +573 -0
  607. package/src/yaml.test.ts +111 -1
  608. package/src/yaml.ts +141 -549
@@ -165,6 +165,11 @@
165
165
  "source-harvest-not-proposed",
166
166
  "record-state-not-initial",
167
167
  "ratify-quorum-not-met",
168
+ "write-scope-member",
169
+ "write-scope-kind",
170
+ "write-scope-class-unknown",
171
+ "agent-unknown",
172
+ "principal-unidentified",
168
173
  "record-unparseable",
169
174
  "record-schema-invalid",
170
175
  "record-id-duplicate",
@@ -172,6 +177,7 @@
172
177
  "record-supersedes-conflict",
173
178
  "record-remediates-unknown",
174
179
  "record-remediates-not-closed",
180
+ "record-seal-mismatch",
175
181
  "session-seal-mismatch",
176
182
  "session-verdict-unknown-record"
177
183
  ]
@@ -259,6 +259,11 @@
259
259
  "review-sign-failed",
260
260
  "session-unknown",
261
261
  "session-not-open",
262
+ "write-scope-member",
263
+ "write-scope-kind",
264
+ "write-scope-class-unknown",
265
+ "agent-unknown",
266
+ "principal-unidentified",
262
267
  "record-unparseable",
263
268
  "record-schema-invalid",
264
269
  "record-id-duplicate",
@@ -266,6 +271,7 @@
266
271
  "record-supersedes-conflict",
267
272
  "record-remediates-unknown",
268
273
  "record-remediates-not-closed",
274
+ "record-seal-mismatch",
269
275
  "session-seal-mismatch",
270
276
  "session-verdict-unknown-record"
271
277
  ]
@@ -5,9 +5,10 @@
5
5
  * decision reviews that cite it.
6
6
  */
7
7
 
8
- import { execFileSync } from "node:child_process";
8
+ import { createHash } from "node:crypto";
9
9
  import { readFileSync, writeFileSync } from "node:fs";
10
10
  import { join } from "node:path";
11
+ import yaml from "js-yaml";
11
12
  import { afterAll, describe, expect, test } from "vitest";
12
13
  import { cleanScratch, commitAll, contract, REPO } from "./__fixtures__/contract-repo";
13
14
  import { reviewed, sessionText, SESSIONS_KIND as SESSIONS, sessionsRepo as fixture } from "./__fixtures__/sessions";
@@ -34,14 +35,30 @@ describe("the session kind (#2673)", () => {
34
35
  expect(doc.records[0].citedBy).toEqual([]);
35
36
  });
36
37
 
37
- test("the seal is the awk recipe the docs give", () => {
38
+ test("the seal is the whole-file JCS rule the docs give (#2546)", () => {
38
39
  const file = join(REPO, "reference-workspace", "design", "sessions", "S-0001-first-walk-of-the-reference-decisions.md");
39
- const awk = execFileSync("sh", ["-c", `awk 'NR==1&&/^---$/{f=1;print;next} f&&/^---$/{f=0} f&&/^closed_digest:/{next} {print}' "$1" | shasum -a 256`, "sh", file], { encoding: "utf-8" });
40
40
  const text = readFileSync(file, "utf-8");
41
- expect(awk.split(" ")[0]).toBe(sessionSeal(text, "closed_digest"));
42
- expect(text).toContain(`closed_digest: "${sessionSeal(text, "closed_digest")}"`);
41
+ // The rule worked by hand: the front matter as JSON without closed_digest, the text below it, keys sorted, no whitespace.
42
+ const [, front, body] = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text)!;
43
+ const { closed_digest: _seal, ...core } = yaml.load(front, { schema: yaml.JSON_SCHEMA }) as Record<string, unknown>;
44
+ const sorted = (v: unknown): unknown =>
45
+ v === null || typeof v !== "object" ? v : Array.isArray(v) ? v.map(sorted) : Object.fromEntries(Object.keys(v).sort().map((k) => [k, sorted((v as Record<string, unknown>)[k])]));
46
+ const byHand = `sha256:${createHash("sha256").update(JSON.stringify(sorted({ body, core })), "utf8").digest("hex")}`;
47
+ expect(sessionSeal(text, "closed_digest")).toBe(byHand);
48
+ expect(text).toContain(`closed_digest: "${byHand}"`);
43
49
  // CRLF line endings hash as LF.
44
- expect(sessionSeal(text.replace(/\n/g, "\r\n"), "closed_digest")).toBe(sessionSeal(text, "closed_digest"));
50
+ expect(sessionSeal(text.replace(/\n/g, "\r\n"), "closed_digest")).toBe(byHand);
51
+ });
52
+
53
+ test("a session sealed by the text rule before #2546 reads as session-seal-mismatch, naming the old rule", async () => {
54
+ const root = fixture();
55
+ const file = join(root, "design", "sessions", "S-0001-first-walk-of-the-reference-decisions.md");
56
+ const text = readFileSync(file, "utf-8");
57
+ const old = createHash("sha256").update(text.replace(/^closed_digest: .*\n/m, ""), "utf8").digest("hex");
58
+ writeFileSync(file, text.replace(/^closed_digest: .*$/m, `closed_digest: "${old}"`));
59
+ const s1 = (await read(root)).records[0];
60
+ expect(s1.reasons.map((r) => r.code)).toContain("session-seal-mismatch");
61
+ expect(s1.reasons.find((r) => r.code === "session-seal-mismatch")!.message).toMatch(/the text rule chant used before #2546/);
45
62
  });
46
63
 
47
64
  test("a closed session edited after it closed is session-seal-mismatch", async () => {
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Uncommitted records in the read contract (#3160): each record of a
3
+ * working-tree read says whether HEAD holds it as it is, the document names
4
+ * the branch, head and base, and `--uncommitted` lists only what HEAD
5
+ * doesn't hold. The documents validate against records.schema.json.
6
+ */
7
+
8
+ import { execFileSync } from "node:child_process";
9
+ import { cpSync, mkdtempSync, readFileSync, realpathSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+ import Ajv2020 from "ajv/dist/2020";
13
+ import { afterAll, describe, expect, test } from "vitest";
14
+ import { CONFORMANCE_FIXTURE_DIR, UNCOMMITTED_DECISION } from "./conformance";
15
+ import { queryRecords, type RecordsDocument } from "./records-cli";
16
+ import schema from "./records.schema.json";
17
+
18
+ const validate = new Ajv2020({ strict: true, allErrors: true }).compile(schema);
19
+ const KIND = "decisions/decision.kind.mjs";
20
+
21
+ type Result = Extract<RecordsDocument, { records: unknown }>;
22
+
23
+ function result(doc: RecordsDocument): Result {
24
+ expect(validate(doc), JSON.stringify(validate.errors, null, 2)).toBe(true);
25
+ if ("error" in doc) throw new Error(`${doc.error.code}: ${doc.error.message}`);
26
+ return doc;
27
+ }
28
+
29
+ const scratch: string[] = [];
30
+ afterAll(() => {
31
+ for (const d of scratch) rmSync(d, { recursive: true, force: true });
32
+ });
33
+
34
+ const GIT_ENV = { ...process.env, GIT_CONFIG_GLOBAL: "/dev/null", GIT_CONFIG_NOSYSTEM: "1" };
35
+ function git(cwd: string, ...args: string[]): string {
36
+ return execFileSync("git", ["-c", "user.name=t", "-c", "user.email=t@localhost", "-c", "commit.gpgsign=false", ...args], { cwd, env: GIT_ENV, encoding: "utf-8" }).trim();
37
+ }
38
+
39
+ const DECIDED = readFileSync(join(CONFORMANCE_FIXTURE_DIR, "decisions", "fix-001-how-the-app-is-deployed.md"), "utf-8");
40
+ /** A decided record with id `fix-<n>`. */
41
+ const decided = (n: string) => DECIDED.replace('id: "fix-001"', `id: "fix-${n}"`);
42
+ /** A proposed record with id `fix-<n>`. */
43
+ const proposed = (n: string) => UNCOMMITTED_DECISION.text.replace('id: "fix-002"', `id: "fix-${n}"`);
44
+ const file = (n: string) => `decisions/fix-${n}-a-record.md`;
45
+
46
+ /** A directory with the decision kind and its schema, and no records. */
47
+ function kindDir(): string {
48
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-records-uncommitted-")));
49
+ scratch.push(root);
50
+ cpSync(join(CONFORMANCE_FIXTURE_DIR, "decisions"), join(root, "decisions"), { recursive: true, filter: (src) => !src.endsWith(".md") });
51
+ return root;
52
+ }
53
+
54
+ /** A repository whose main holds fix-001, fix-003 and fix-005, on a work branch one commit past main. */
55
+ function repository(): { root: string; main: string } {
56
+ const root = kindDir();
57
+ for (const n of ["001", "003", "005"]) writeFileSync(join(root, file(n)), decided(n));
58
+ git(root, "init", "--quiet", "--initial-branch=main");
59
+ git(root, "add", "-A");
60
+ git(root, "commit", "--quiet", "-m", "records");
61
+ const main = git(root, "rev-parse", "HEAD");
62
+ git(root, "checkout", "--quiet", "-b", "chant/work/w-1");
63
+ writeFileSync(join(root, "notes.txt"), "work\n");
64
+ git(root, "add", "notes.txt");
65
+ git(root, "commit", "--quiet", "-m", "work");
66
+ return { root, main };
67
+ }
68
+
69
+ describe("records in the working tree (#3160)", () => {
70
+ test("each record is committed, modified or new against HEAD, and the checkout names its branch, head, base and deleted records", async () => {
71
+ const { root, main } = repository();
72
+ writeFileSync(join(root, file("001")), `${decided("001")}\nA line kept in the working tree.\n`);
73
+ writeFileSync(join(root, file("002")), proposed("002"));
74
+ writeFileSync(join(root, file("004")), proposed("004"));
75
+ git(root, "add", file("004"));
76
+ unlinkSync(join(root, file("003")));
77
+ const head = git(root, "rev-parse", "HEAD");
78
+
79
+ const doc = result(await queryRecords({ kind: KIND, cwd: root }));
80
+ expect(doc.uncommitted).toBe(false);
81
+ expect(doc.records.map((r) => [r.id, r.worktree])).toEqual([
82
+ ["fix-001", "modified"],
83
+ ["fix-002", "new"],
84
+ ["fix-004", "new"],
85
+ ["fix-005", "committed"],
86
+ ]);
87
+ expect(doc.checkout).toEqual({ branch: "chant/work/w-1", head, base: main, baseFrom: "main", deleted: [file("003")] });
88
+
89
+ const only = result(await queryRecords({ kind: KIND, cwd: root, uncommitted: true }));
90
+ expect(only.uncommitted).toBe(true);
91
+ expect(only.records.map((r) => [r.id, r.worktree])).toEqual([
92
+ ["fix-001", "modified"],
93
+ ["fix-002", "new"],
94
+ ["fix-004", "new"],
95
+ ]);
96
+ expect(only.checkout).toEqual(doc.checkout);
97
+ expect(only.summary).toEqual({ total: 3, valid: 3, invalid: 0, superseded: 0 });
98
+ });
99
+
100
+ test("a record changed back to what HEAD holds, or touched without a change, is committed, and the read leaves the index alone", async () => {
101
+ const { root } = repository();
102
+ const path = join(root, file("001"));
103
+ writeFileSync(path, decided("001"));
104
+ const index = join(root, ".git", "index");
105
+ const before = { bytes: readFileSync(index), mtime: statSync(index).mtimeMs };
106
+ const doc = result(await queryRecords({ kind: KIND, cwd: root, uncommitted: true }));
107
+ expect(doc.records).toEqual([]);
108
+ expect(doc.checkout?.deleted).toEqual([]);
109
+ expect(readFileSync(index).equals(before.bytes)).toBe(true);
110
+ expect(statSync(index).mtimeMs).toBe(before.mtime);
111
+ });
112
+
113
+ test("--base names the target the base is forked from", async () => {
114
+ const { root } = repository();
115
+ const head = git(root, "rev-parse", "HEAD");
116
+ const doc = result(await queryRecords({ kind: KIND, cwd: root, base: "HEAD" }));
117
+ expect(doc.checkout).toMatchObject({ head, base: head, baseFrom: "flag" });
118
+ });
119
+
120
+ test("a detached HEAD has no branch, and a repository with no commits has no head and only new records", async () => {
121
+ const { root } = repository();
122
+ git(root, "checkout", "--quiet", "--detach");
123
+ expect(result(await queryRecords({ kind: KIND, cwd: root })).checkout?.branch).toBeNull();
124
+
125
+ const empty = kindDir();
126
+ writeFileSync(join(empty, file("001")), decided("001"));
127
+ git(empty, "init", "--quiet", "--initial-branch=main");
128
+ const doc = result(await queryRecords({ kind: KIND, cwd: empty, uncommitted: true }));
129
+ expect(doc.checkout).toEqual({ branch: "main", head: null, base: null, baseFrom: null, deleted: [] });
130
+ expect(doc.records.map((r) => r.worktree)).toEqual(["new"]);
131
+ });
132
+
133
+ test("a read at a revision has no checkout and no worktree", async () => {
134
+ const { root } = repository();
135
+ writeFileSync(join(root, file("002")), proposed("002"));
136
+ const doc = result(await queryRecords({ kind: KIND, cwd: root, at: "HEAD" }));
137
+ expect(doc.checkout).toBeUndefined();
138
+ expect(doc.records.every((r) => r.worktree === undefined)).toBe(true);
139
+ await expect(queryRecords({ kind: KIND, cwd: root, at: "HEAD", uncommitted: true })).rejects.toThrow(/takes no --at/);
140
+ });
141
+
142
+ test("outside git a read has no checkout, and --uncommitted fails with not-a-git-repository", async () => {
143
+ const root = kindDir();
144
+ writeFileSync(join(root, file("001")), decided("001"));
145
+ const doc = result(await queryRecords({ kind: KIND, cwd: root }));
146
+ expect(doc.checkout).toBeUndefined();
147
+ expect(doc.records[0].worktree).toBeUndefined();
148
+ const failed = await queryRecords({ kind: KIND, cwd: root, uncommitted: true });
149
+ expect(validate(failed)).toBe(true);
150
+ expect(failed).toMatchObject({ error: { code: "not-a-git-repository" } });
151
+ });
152
+
153
+ test("the schema holds an --uncommitted document to its promise", () => {
154
+ const head = "a".repeat(40);
155
+ const doc = {
156
+ $schema: schema.$id,
157
+ contract: 1,
158
+ kind: { name: "decision", schema: "urn:x", file: KIND },
159
+ at: null,
160
+ current: false,
161
+ uncommitted: true,
162
+ checkout: { branch: "main", head, base: head, baseFrom: "main", deleted: [] },
163
+ records: [{ id: "fix-001", path: file("001"), state: "decided", valid: true, reasons: [], supersededBy: null, remediatedBy: [], data: {}, worktree: "new" }],
164
+ summary: { total: 1, valid: 1, invalid: 0, superseded: 0 },
165
+ };
166
+ expect(validate(doc)).toBe(true);
167
+ expect(validate({ ...doc, records: [{ ...doc.records[0], worktree: "committed" }] })).toBe(false);
168
+ const { checkout: _checkout, ...noCheckout } = doc;
169
+ expect(validate(noCheckout)).toBe(false);
170
+ });
171
+ });
@@ -10,7 +10,7 @@ import { cpSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync
10
10
  import { tmpdir } from "node:os";
11
11
  import { join } from "node:path";
12
12
  import { afterEach, beforeEach, describe, expect, test } from "vitest";
13
- import { parseFrontMatter, recordTextDigest as textDigest } from "./records";
13
+ import { parseFrontMatter, recordSeal, recordTextDigest as textDigest } from "./records";
14
14
  import { queryRecords } from "./records-cli";
15
15
  import { allocateId, amendRecord, newRecord, renderRecord, reviewRecord, toYaml } from "./records-write";
16
16
 
@@ -21,7 +21,7 @@ const KIND = "decisions/decision.kind.mjs";
21
21
  type Data = Record<string, unknown>;
22
22
 
23
23
  /** The digest of a decision: the kind names a ratified state, so the state leaves the digest with the reviews and seal (#2873). */
24
- const recordTextDigest = (text: string) => textDigest(text, ["reviews", "seal", "state"]);
24
+ const recordTextDigest = (text: string) => textDigest(text, ["reviews", "seal", "state", "closed_digest"]);
25
25
 
26
26
  const SAMPLE = (() => {
27
27
  const fm = parseFrontMatter(readFileSync(join(DECISIONS, "ws-003-seal-scope.md"), "utf-8"));
@@ -287,7 +287,7 @@ describe("records amend", () => {
287
287
  const evidence = [{ title: "A spec", url: "https://example.com/spec" }];
288
288
  expect(await amendRecord({ kind: KIND, id: "ws-001", fields: fields({ evidence }), cwd: dir })).toMatchObject({ changed: ["evidence"] });
289
289
  for (const by of ["alice", "bob"]) await reviewRecord({ kind: KIND, id: "ws-001", verdict: "agree", by, cwd: dir });
290
- expect(await amendRecord({ kind: KIND, id: "ws-001", fields: fields({ state: "ratified" }), cwd: dir })).toMatchObject({ changed: ["state"] });
290
+ expect(await amendRecord({ kind: KIND, id: "ws-001", fields: fields({ state: "ratified" }), cwd: dir })).toMatchObject({ changed: ["state", "closed_digest"] });
291
291
  put("ws-006-six.md", decision({ id: "ws-006", title: "Six" }));
292
292
  expect(code(await amendRecord({ kind: KIND, id: "ws-006", fields: fields({ state: "withdrawn" }), cwd: dir }))).toBe("amend-supersede-instead");
293
293
  });
@@ -353,12 +353,43 @@ describe("ratifying against the quorum (#2873)", () => {
353
353
  await agree("alice");
354
354
  await agree("bob");
355
355
  const judged = recordTextDigest(text("ws-001-one.md"));
356
- expect(await ratify()).toMatchObject({ changed: ["state"] });
356
+ expect(await ratify()).toMatchObject({ changed: ["state", "closed_digest"] });
357
357
  expect(data("ws-001-one.md").state).toBe("ratified");
358
358
  expect(recordTextDigest(text("ws-001-one.md"))).toBe(judged);
359
359
  expect(await quorumOf("ws-001")).toMatchObject({ need: 2, agreed: 2, met: true, notCounted: [] });
360
360
  });
361
361
 
362
+ test("ratifying writes the whole-file seal, which covers the reviews and the state the digest leaves out (#2546)", async () => {
363
+ await agree("alice");
364
+ await agree("bob");
365
+ const judged = textDigest(text("ws-001-one.md"), ["reviews", "seal", "state", "closed_digest"]);
366
+ await ratify();
367
+ const sealed = text("ws-001-one.md");
368
+ expect(data("ws-001-one.md").closed_digest).toMatch(/^sha256:[0-9a-f]{64}$/);
369
+ expect(data("ws-001-one.md").closed_digest).toBe(recordSeal(sealed, "closed_digest"));
370
+ // The closed_digest line goes last in the front matter, and the digest leaves it out.
371
+ expect(sealed).toMatch(/\nclosed_digest: "sha256:[0-9a-f]{64}"\n---\n/);
372
+ expect(textDigest(sealed, ["reviews", "seal", "state", "closed_digest"])).toBe(judged);
373
+ const read = async () => (await queryRecords({ kind: KIND, cwd: dir })) as { records: { id: string; valid: boolean; reasons: { code: string; message: string }[] }[] };
374
+ expect((await read()).records.find((r) => r.id === "ws-001")).toMatchObject({ valid: true, reasons: [] });
375
+ // A verdict, a state or a byte of the body edited by hand after the close breaks the seal.
376
+ for (const edit of [
377
+ (t: string) => t.replace('verdict: "agree"', 'verdict: "dissent"\n note: "no"'),
378
+ (t: string) => t.replace('state: "ratified"', 'state: "superseded"'),
379
+ (t: string) => `${t}\nAn afterthought.\n`,
380
+ ]) {
381
+ writeFileSync(join(dir, "decisions", "ws-001-one.md"), edit(sealed));
382
+ const r = (await read()).records.find((x) => x.id === "ws-001")!;
383
+ expect(r.reasons.map((x) => x.code)).toContain("record-seal-mismatch");
384
+ }
385
+ // CRLF line endings, or a value quoted another way, leave the seal as it was.
386
+ writeFileSync(join(dir, "decisions", "ws-001-one.md"), sealed.replace('area: "D4"', "area: 'D4'").replace(/\n/g, "\r\n"));
387
+ expect((await read()).records.find((r) => r.id === "ws-001")).toMatchObject({ valid: true });
388
+ // And a closed record is never written again, so the seal can't be set by hand either.
389
+ writeFileSync(join(dir, "decisions", "ws-001-one.md"), sealed);
390
+ expect(code(await amendRecord({ kind: KIND, id: "ws-002", fields: fields({ closed_digest: `sha256:${"0".repeat(64)}` }), cwd: dir }))).toBe("write-input-invalid");
391
+ });
392
+
362
393
  test("a met quorum with an open concern ratifies, and the concern stays listed", async () => {
363
394
  await agree("alice");
364
395
  await agree("bob");
@@ -379,7 +410,7 @@ describe("ratifying against the quorum (#2873)", () => {
379
410
  await agree("alice");
380
411
  const digest = recordTextDigest(text("ws-001-one.md"));
381
412
  const reviews = [...(data("ws-001-one.md").reviews as Data[]), { reviewer: "bob", verdict: "agree", on: "2026-09-26", digest }];
382
- expect(await amendRecord({ kind: KIND, id: "ws-001", fields: fields({ state: "ratified", reviews }), cwd: dir })).toMatchObject({ changed: ["state", "reviews"] });
413
+ expect(await amendRecord({ kind: KIND, id: "ws-001", fields: fields({ state: "ratified", reviews }), cwd: dir })).toMatchObject({ changed: ["state", "reviews", "closed_digest"] });
383
414
  put("ws-006-six.md", decision({ id: "ws-006", title: "Six" }));
384
415
  await agree("alice", "ws-006");
385
416
  await agree("bob", "ws-006");
@@ -403,11 +434,11 @@ describe("ratifying against the quorum (#2873)", () => {
403
434
  expect(withRatified).toContain('reviews: { field: "reviews", decider: "decided_by", ratified: "ratified" },');
404
435
  writeFileSync(kindFile, withRatified.replace(', ratified: "ratified" }', " }"));
405
436
  const judged = textDigest(text("ws-001-one.md"), ["reviews", "seal"]);
406
- expect(await ratify()).toMatchObject({ changed: ["state"] });
407
- expect(textDigest(text("ws-001-one.md"), ["reviews", "seal"])).not.toBe(judged);
437
+ expect(await ratify()).toMatchObject({ changed: ["state", "closed_digest"] });
438
+ expect(textDigest(text("ws-001-one.md"), ["reviews", "seal", "closed_digest"])).not.toBe(judged);
408
439
  writeFileSync(kindFile, withRatified.replace(' reviews: { field: "reviews", decider: "decided_by", ratified: "ratified" },\n', ""));
409
440
  put("ws-006-six.md", decision({ id: "ws-006", title: "Six" }));
410
- expect(await ratify("ws-006")).toMatchObject({ changed: ["state"] });
441
+ expect(await ratify("ws-006")).toMatchObject({ changed: ["state", "closed_digest"] });
411
442
  });
412
443
 
413
444
  test("a kind whose reviews.ratified is not one of its states is refused", async () => {
@@ -417,6 +448,32 @@ describe("ratifying against the quorum (#2873)", () => {
417
448
  expect(code(doc)).toBe("kind-invalid");
418
449
  expect("error" in doc && doc.error.message).toMatch(/reviews\.ratified must be one of states/);
419
450
  });
451
+
452
+ test("on a copy of the chant repo's own decisions, verdicts count through ratifying and sealing (#2555, ws-063)", async () => {
453
+ for (const f of readdirSync(join(dir, "decisions"))) if (f.endsWith(".md")) rmSync(join(dir, "decisions", f));
454
+ for (const f of readdirSync(DECISIONS)) if (/^ws-\d{3}-.+\.md$/.test(f)) cpSync(join(DECISIONS, f), join(dir, "decisions", f));
455
+ const read = async () => {
456
+ const doc = await queryRecords({ kind: KIND, cwd: dir });
457
+ if ("error" in doc) throw new Error(doc.error.message);
458
+ return doc.records;
459
+ };
460
+ const decided = (await read()).filter((r) => r.state === "decided");
461
+ expect(decided.length).toBeGreaterThan(50);
462
+ // Every sixth one, and the last, keeps the test inside the unit-test budget (#2817).
463
+ const sample = decided.filter((_, i) => i % 6 === 0 || i === decided.length - 1);
464
+ const judged = new Map(sample.map((r) => [r.id!, r.digest]));
465
+ for (const id of judged.keys()) {
466
+ await agree("alice", id);
467
+ await agree("Bob ", id);
468
+ expect(await ratify(id)).toMatchObject({ changed: ["state", "closed_digest"] });
469
+ }
470
+ for (const r of (await read()).filter((x) => judged.has(x.id!))) {
471
+ expect(r, r.id!).toMatchObject({ state: "ratified", valid: true, reasons: [], digest: judged.get(r.id!) });
472
+ expect(r.quorum, r.id!).toMatchObject({ agreed: 2, met: true, notCounted: [] });
473
+ expect(r.quorum!.counted.map((c) => c.principal), r.id!).toEqual(["alice", "bob"]);
474
+ expect(r.data?.closed_digest, r.id!).toBe(recordSeal(readFileSync(join(dir, r.path), "utf-8"), "closed_digest"));
475
+ }
476
+ });
420
477
  });
421
478
 
422
479
  describe("records review", () => {
@@ -20,7 +20,9 @@
20
20
  * `records close` is in `records-close.ts`.
21
21
  *
22
22
  * `new --sign` and `amend --sign` seal the record's author (#2688), and
23
- * `review --sign` seals a verdict (#2687): see `trust/seal.ts`.
23
+ * `review --sign` seals a verdict (#2687): see `trust/seal.ts`. A record of
24
+ * a kind that declares `seal` gets its whole-file seal when `new` or `amend`
25
+ * writes it into a closed state (#2546, ws-063).
24
26
  */
25
27
 
26
28
  import { readFileSync, statSync, writeFileSync } from "node:fs";
@@ -37,6 +39,7 @@ import {
37
39
  RecordReadError,
38
40
  RECORD_SEAL_FIELD,
39
41
  digestFields,
42
+ recordSeal,
40
43
  recordTextDigest,
41
44
  type LoadedRecordKind,
42
45
  type RecordEntry,
@@ -47,6 +50,9 @@ import { policyAtBase, resolveBase } from "./trust/provenance";
47
50
  import { WorkspaceReadError } from "./declaration";
48
51
  import { findSessionKinds, headCommit, sessionKindsFor } from "./session-kinds";
49
52
  import { workingTree } from "./tree";
53
+ import { AGENT_ENV, refuseRecordWrite, WRITE_SCOPE_CODES, WriteScopeError, type ScopeSource } from "./write-scope";
54
+ import { IDENTITY_CODES, IdentityError, refuseUnidentified } from "./identity";
55
+ import type { WriteVerb } from "./declaration";
50
56
 
51
57
  // ── Contract ─────────────────────────────────────────────────────────────────
52
58
 
@@ -72,6 +78,8 @@ export const NEW_ERROR_CODES = [
72
78
  "source-harvest-not-proposed",
73
79
  "record-state-not-initial",
74
80
  "ratify-quorum-not-met",
81
+ ...WRITE_SCOPE_CODES,
82
+ ...IDENTITY_CODES,
75
83
  ...RECORD_REASON_CODES,
76
84
  ] as const satisfies readonly ReasonCode[];
77
85
 
@@ -86,6 +94,8 @@ export const AMEND_ERROR_CODES = [
86
94
  "amend-supersede-instead",
87
95
  "record-sign-failed",
88
96
  "ratify-quorum-not-met",
97
+ ...WRITE_SCOPE_CODES,
98
+ ...IDENTITY_CODES,
89
99
  ...RECORD_REASON_CODES,
90
100
  ] as const satisfies readonly ReasonCode[];
91
101
 
@@ -100,6 +110,8 @@ export const REVIEW_ERROR_CODES = [
100
110
  "review-sign-failed",
101
111
  "session-unknown",
102
112
  "session-not-open",
113
+ ...WRITE_SCOPE_CODES,
114
+ ...IDENTITY_CODES,
103
115
  ...RECORD_REASON_CODES,
104
116
  ] as const satisfies readonly ReasonCode[];
105
117
 
@@ -348,6 +360,31 @@ export async function open(kind: string, cwd: string): Promise<Opened> {
348
360
  };
349
361
  }
350
362
 
363
+ /**
364
+ * Refuse a write outside the writer's scope (#2548): the kind `o` opened,
365
+ * written with `verb`, by the session `agent` names (`CHANT_AGENT`) or the
366
+ * principal `by` names. Throws a {@link WriteScopeError}.
367
+ */
368
+ export function refuseOutOfScope(o: Opened, verb: WriteVerb, cwd: string, writer: { agent?: string; by?: string }): ScopeSource {
369
+ const source = refuseRecordWrite(cwd, { kindName: o.loaded.kind.name, kindFile: o.loaded.file, recordsDir: o.loaded.dir, verb, agent: writer.agent, principal: writer.by });
370
+ // #3163: under identity.attribution "identified" at base, --by names a forge identity or a signer.
371
+ if (writer.by !== undefined) refuseUnidentified(source, [writer.by], "--by", { agent: writer.agent });
372
+ return source;
373
+ }
374
+
375
+ /**
376
+ * Refuse author fields (the kind's reviews.decider and proposedBy fields)
377
+ * that name a person by a bare name, under identity.attribution
378
+ * "identified" at base (#3163). `flag` is where the fields came from.
379
+ */
380
+ function refuseUnidentifiedAuthors(source: ScopeSource, kind: Opened["loaded"]["kind"], fields: Record<string, unknown>, flag: string, agent?: string): void {
381
+ const names = [kind.reviews?.decider, kind.proposedBy?.field]
382
+ .filter((f): f is string => f !== undefined)
383
+ .map((f) => fields[f])
384
+ .filter((v): v is string => typeof v === "string" && v.trim() !== "");
385
+ refuseUnidentified(source, names, `${flag} author`, { agent });
386
+ }
387
+
351
388
  /**
352
389
  * The kind's records as `records` reads them from `source`. A session kind's
353
390
  * subject records are read from the same source (#2693), so a write is
@@ -460,6 +497,33 @@ function refuseSealField(o: Opened, fields: Record<string, unknown>, flag: strin
460
497
  if (o.loaded.kind.reviews && RECORD_SEAL_FIELD in fields) {
461
498
  throw new RecordWriteError("write-input-invalid", `the fields given with ${flag} set ${RECORD_SEAL_FIELD}, and only --sign writes a record's seal`);
462
499
  }
500
+ const closed = o.loaded.kind.seal?.field;
501
+ if (closed !== undefined && !o.loaded.kind.session && closed in fields) {
502
+ throw new RecordWriteError("write-input-invalid", `the fields given with ${flag} set ${closed}, and chant writes it when the record closes`);
503
+ }
504
+ }
505
+
506
+ /**
507
+ * `text`, the record `data` holds, with its whole-file seal written into the
508
+ * kind's `seal.field` when the record is in a closed state (#2546, ws-063).
509
+ * The seal goes last, after the author seal, so it covers every other value
510
+ * and the body. `text` is returned as it is for a kind without `seal`, a
511
+ * session kind (whose seal `records close` writes), or an open record.
512
+ */
513
+ function sealClosed(o: Opened, text: string, data: Record<string, unknown>, id: string): { text: string; data: Record<string, unknown> } {
514
+ const { kind } = o.loaded;
515
+ if (!kind.seal || kind.session || kind.stateField === undefined) return { text, data };
516
+ const state = data[kind.stateField];
517
+ if (typeof state !== "string" || !(kind.closedStates ?? []).includes(state)) return { text, data };
518
+ const field = kind.seal.field;
519
+ const seal = recordSeal(text, field, kind.format);
520
+ if (seal === null) throw new RecordWriteError("record-unparseable", `${id} can't be read, so it can't be sealed`);
521
+ const sealedData = { ...data, [field]: seal };
522
+ const sealed = replaceFields(text, { [field]: seal }, sealedData);
523
+ if (sealed === undefined || recordSeal(sealed, field, kind.format) !== seal) {
524
+ throw new RecordWriteError("record-unparseable", `the ${field} field can't be written into ${id} without changing the text it seals`);
525
+ }
526
+ return { text: sealed, data: sealedData };
463
527
  }
464
528
 
465
529
  /**
@@ -584,7 +648,7 @@ function today(): string {
584
648
  }
585
649
 
586
650
  export function failure<C>(schema: string, err: unknown): WriteFailure<C> {
587
- if (err instanceof RecordWriteError || err instanceof RecordReadError) {
651
+ if (err instanceof RecordWriteError || err instanceof RecordReadError || err instanceof WriteScopeError || err instanceof IdentityError) {
588
652
  return { $schema: schema, contract: RECORDS_WRITE_CONTRACT_VERSION, error: { code: err.code as C, message: err.message } };
589
653
  }
590
654
  throw err;
@@ -644,6 +708,8 @@ export interface NewRecordOptions {
644
708
  * `reviews.decider` field, which the fields must set.
645
709
  */
646
710
  sign?: string | true;
711
+ /** The agent session the write is made in (`CHANT_AGENT`, #2548), which its write scope binds. */
712
+ agent?: string;
647
713
  }
648
714
 
649
715
  /**
@@ -727,8 +793,10 @@ export async function newRecord(opts: NewRecordOptions & ChannelOptions): Promis
727
793
  throw new RecordWriteError("write-usage-invalid", `--prefix takes letters and digits, starting with a letter, not ${JSON.stringify(opts.prefix)}`);
728
794
  }
729
795
  const o = await open(opts.kind, opts.cwd);
796
+ const scope = refuseOutOfScope(o, "new", opts.cwd, opts);
730
797
  const fields = applyChannel(o, parseFields(opts.fields, "--from"), opts, true, "--from");
731
798
  const { kind, schema } = o.loaded;
799
+ refuseUnidentifiedAuthors(scope, kind, fields, "--from", opts.agent);
732
800
  refuseSealField(o, fields, "--from");
733
801
  refuseRevisionFields(kind, fields, {}, "--from");
734
802
  const idField = kind.idField!;
@@ -758,6 +826,7 @@ export async function newRecord(opts: NewRecordOptions & ChannelOptions): Promis
758
826
  let text = renderRecord(data, title ? `\n# ${title}\n` : "");
759
827
  let seal: AuthorSeal | undefined;
760
828
  if (opts.sign !== undefined) ({ text, seal } = await sealAuthor(o, text, data, id, opts.sign, opts.cwd));
829
+ ({ text } = sealClosed(o, text, seal ? { ...data, [RECORD_SEAL_FIELD]: seal } : data, id));
761
830
  const written = await validatedEntry(o, before, path, text);
762
831
  await refuseRatifyBelowQuorum(o, written, id);
763
832
  const warnings = written.warnings;
@@ -789,6 +858,8 @@ export interface AmendRecordOptions {
789
858
  cwd: string;
790
859
  /** Seal the amended record's author, as `records new` does (#2688). */
791
860
  sign?: string | true;
861
+ /** The agent session the write is made in (#2548). */
862
+ agent?: string;
792
863
  }
793
864
 
794
865
  /**
@@ -808,8 +879,10 @@ export interface AmendRecordOptions {
808
879
  export async function amendRecord(opts: AmendRecordOptions & ChannelOptions): Promise<AmendDocument> {
809
880
  try {
810
881
  const o = await open(opts.kind, opts.cwd);
882
+ const scope = refuseOutOfScope(o, "amend", opts.cwd, opts);
811
883
  const given = applyChannel(o, parseFields(opts.fields, "--set"), opts, false, "--set");
812
884
  const { kind } = o.loaded;
885
+ refuseUnidentifiedAuthors(scope, kind, given, "--set", opts.agent);
813
886
  refuseSealField(o, given, "--set");
814
887
  const before = await readAll(o, o.source);
815
888
  const target = findRecord(before, opts.id, kind.name);
@@ -871,6 +944,12 @@ export async function amendRecord(opts: AmendRecordOptions & ChannelOptions): Pr
871
944
  sealDropped = `${opts.id} was sealed${typeof signer === "string" ? ` by ${signer}` : ""}, and the amendment moves its digest or its state, so the seal was removed: seal it again with records amend ${opts.id} --sign`;
872
945
  }
873
946
  if (stableJson(old[RECORD_SEAL_FIELD]) !== stableJson(merged[RECORD_SEAL_FIELD])) changed.push(RECORD_SEAL_FIELD);
947
+ // Entering a closed state seals the whole record, last (#2546, ws-063).
948
+ if (changed.length > 0 && kind.seal && !kind.session) {
949
+ const closedSeal = sealClosed(o, text, merged, opts.id);
950
+ text = closedSeal.text;
951
+ if (stableJson(old[kind.seal.field]) !== stableJson(closedSeal.data[kind.seal.field])) changed.push(kind.seal.field);
952
+ }
874
953
  let warnings = target.warnings;
875
954
  if (changed.length > 0) {
876
955
  const written = await validatedEntry(o, before, target.path, text);
@@ -916,6 +995,8 @@ export interface ReviewRecordOptions {
916
995
  * git's `user.signingkey`. Without it the verdict is written unsealed.
917
996
  */
918
997
  sign?: string | true;
998
+ /** The agent session the write is made in (#2548). */
999
+ agent?: string;
919
1000
  }
920
1001
 
921
1002
  /**
@@ -932,6 +1013,7 @@ export async function reviewRecord(opts: ReviewRecordOptions): Promise<ReviewDoc
932
1013
  if (opts.by.trim() === "") throw new RecordWriteError("write-usage-invalid", "--by needs the reviewer's name");
933
1014
  if (opts.session !== undefined && opts.session === "") throw new RecordWriteError("write-usage-invalid", "--session needs a session id");
934
1015
  const o = await open(opts.kind, opts.cwd);
1016
+ refuseOutOfScope(o, "review", opts.cwd, opts);
935
1017
  const { kind } = o.loaded;
936
1018
  if (!kind.reviews) {
937
1019
  throw new RecordWriteError("review-unsupported", `the ${kind.name} kind declares no reviews field, so its records take no review`);
@@ -946,6 +1028,8 @@ export async function reviewRecord(opts: ReviewRecordOptions): Promise<ReviewDoc
946
1028
  throw new RecordWriteError("review-note-required", `a dissent needs a reason: pass --note <text> with the concern`);
947
1029
  }
948
1030
  const inSession = opts.session !== undefined ? await openSession(o, opts.session, opts.cwd) : undefined;
1031
+ // The verdict is appended to the session too, so the session's kind must be in scope as well.
1032
+ if (inSession) refuseOutOfScope(inSession.so, "review", opts.cwd, opts);
949
1033
  const reviews = target.data[field] ?? [];
950
1034
  if (!Array.isArray(reviews)) throw new RecordWriteError("record-schema-invalid", `${target.path}: ${field} is not a list`);
951
1035
  const current = o.source.read(target.path);
@@ -1129,6 +1213,8 @@ export async function runRecordsWrite(ctx: CommandContext): Promise<number> {
1129
1213
  const { args } = ctx;
1130
1214
  const cwd = process.cwd();
1131
1215
  const verb = args.extraPositional;
1216
+ // The agent session the write is made in (#2548): set by the harness that runs the agent.
1217
+ const agent = process.env[AGENT_ENV] || undefined;
1132
1218
  const print = (doc: object): number => {
1133
1219
  console.log(JSON.stringify(doc, null, 2));
1134
1220
  return "error" in doc ? 1 : 0;
@@ -1140,7 +1226,7 @@ export async function runRecordsWrite(ctx: CommandContext): Promise<number> {
1140
1226
  if (!id) return print(usage(RECORDS_CLOSE_SCHEMA_ID, "close needs the session's id"));
1141
1227
  const kind = args.kind !== undefined ? resolveWriteKind(args.kind, cwd) : await declaredSessionKind(RECORDS_CLOSE_SCHEMA_ID, cwd);
1142
1228
  if (typeof kind !== "string") return print(kind);
1143
- return print(await closeRecord({ kind, id, dryRun: args.dryRun, cwd }));
1229
+ return print(await closeRecord({ kind, id, dryRun: args.dryRun, cwd, agent }));
1144
1230
  }
1145
1231
  if (verb === "new") {
1146
1232
  const named = args.extraPositional2 ?? args.kind;
@@ -1148,7 +1234,7 @@ export async function runRecordsWrite(ctx: CommandContext): Promise<number> {
1148
1234
  if (typeof kind !== "string") return print(kind);
1149
1235
  const input = readInput(RECORDS_NEW_SCHEMA_ID, "--from", args.migrateFrom, cwd);
1150
1236
  if (typeof input !== "string") return print(input);
1151
- return print(await newRecord({ kind, fields: input, prefix: args.prefix, by: args.by, sign: args.sign, dryRun: args.dryRun, cwd }));
1237
+ return print(await newRecord({ kind, fields: input, prefix: args.prefix, by: args.by, sign: args.sign, dryRun: args.dryRun, cwd, agent }));
1152
1238
  }
1153
1239
  const schema = verb === "amend" ? RECORDS_AMEND_SCHEMA_ID : RECORDS_REVIEW_SCHEMA_ID;
1154
1240
  const id = args.extraPositional2;
@@ -1159,11 +1245,11 @@ export async function runRecordsWrite(ctx: CommandContext): Promise<number> {
1159
1245
  if (args.by !== undefined) return print(usage(schema, "--by is not taken by amend: set decided_by, or a kind's proposedBy field, with --set"));
1160
1246
  const input = readInput(schema, "--set", args.set, cwd);
1161
1247
  if (typeof input !== "string") return print(input);
1162
- return print(await amendRecord({ kind, id, fields: input, sign: args.sign, dryRun: args.dryRun, cwd }));
1248
+ return print(await amendRecord({ kind, id, fields: input, sign: args.sign, dryRun: args.dryRun, cwd, agent }));
1163
1249
  }
1164
1250
  if (args.verdict === undefined) return print(usage(schema, "--verdict agree|dissent|abstain is required"));
1165
1251
  if (args.by === undefined) return print(usage(schema, "--by <principal> is required"));
1166
1252
  return print(
1167
- await reviewRecord({ kind, id, verdict: args.verdict, by: args.by, note: args.note, session: args.session, sign: args.sign, dryRun: args.dryRun, cwd }),
1253
+ await reviewRecord({ kind, id, verdict: args.verdict, by: args.by, note: args.note, session: args.session, sign: args.sign, dryRun: args.dryRun, cwd, agent }),
1168
1254
  );
1169
1255
  }