@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
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The checkout a working-tree read is made in, and which records in it are
3
+ * uncommitted (#3160).
4
+ *
5
+ * Work in progress lives as uncommitted files in a work branch's working tree
6
+ * (#3158, #3145). A reader such as hud or studio shows "kept, not yet
7
+ * applied" from `records --json` alone: each record says whether it is
8
+ * committed, modified or new against `HEAD`, and the document names the
9
+ * branch, the commit `HEAD` names and the base that branch forked from. The
10
+ * reader never diffs git itself.
11
+ *
12
+ * Every git call here reads. None takes the index lock or refreshes the
13
+ * index (`--no-optional-locks`), so a read leaves the checkout as it was,
14
+ * `.git` included, which the reader conformance suite checks.
15
+ */
16
+
17
+ import { execFileSync } from "node:child_process";
18
+ import { resolveBase, type BaseSource } from "./trust/provenance";
19
+
20
+ /** Where a record's file stands against `HEAD`. */
21
+ export type WorktreeState = "committed" | "modified" | "new";
22
+
23
+ /** The branch, head and base of a checkout (#3160). `records` and `status` both print it. */
24
+ export interface CheckoutHead {
25
+ /** The checked-out branch's short name, such as `chant/work/w-12`, or null when `HEAD` is detached. */
26
+ branch: string | null;
27
+ /** The commit `HEAD` names, or null before the first commit. Each record's `worktree` is judged against it. */
28
+ head: string | null;
29
+ /** The merge base of `head` and the target branch tip: the commit the branch forked from. Null without a head, a target, or common history. */
30
+ base: string | null;
31
+ /** Where the target branch was found: `flag` for `--base`, else `origin/HEAD`, `main` or `master`. Null when there is none. */
32
+ baseFrom: BaseSource | null;
33
+ }
34
+
35
+ /** {@link CheckoutHead} for one record kind, with the record files `HEAD` has and the working tree does not. */
36
+ export interface CheckoutView extends CheckoutHead {
37
+ /** Record files of the kind at `head` that the working tree no longer has, from the repository root, sorted. */
38
+ deleted: string[];
39
+ }
40
+
41
+ function git(top: string, args: string[]): string {
42
+ return execFileSync("git", ["--no-optional-locks", "--literal-pathspecs", ...args], {
43
+ cwd: top,
44
+ encoding: "utf-8",
45
+ stdio: ["ignore", "pipe", "pipe"],
46
+ maxBuffer: 256 * 1024 * 1024,
47
+ });
48
+ }
49
+
50
+ function tryGit(top: string, args: string[]): string | undefined {
51
+ try {
52
+ return git(top, args);
53
+ } catch {
54
+ return undefined;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * The branch, head and base of the checkout at `top`. `explicitBase` is
60
+ * `--base`, as the trust policy takes it; otherwise the target branch is the
61
+ * one the policy is read at (`origin/HEAD`, then `main`, then `master`).
62
+ */
63
+ export function readCheckoutHead(top: string, explicitBase?: string): CheckoutHead {
64
+ const branch = tryGit(top, ["symbolic-ref", "--quiet", "--short", "HEAD"])?.trim() || null;
65
+ const head = tryGit(top, ["rev-parse", "--verify", "--quiet", "HEAD^{commit}"])?.trim() || null;
66
+ const target = resolveBase(top, explicitBase);
67
+ const base = head && target.commit ? tryGit(top, ["merge-base", head, target.commit])?.trim() || null : null;
68
+ return { branch, head, base, baseFrom: target.from };
69
+ }
70
+
71
+ /** NUL-separated git output as a list. */
72
+ function zList(out: string | undefined): string[] {
73
+ return (out ?? "").split("\0").filter(Boolean);
74
+ }
75
+
76
+ /**
77
+ * Each of `paths` (record files directly in `dir`, from the repository root)
78
+ * as committed, modified or new against `head`, and the files directly in
79
+ * `dir` at `head` whose names `match` and that `paths` leaves out: the
80
+ * records deleted in the working tree. With no head every path is new.
81
+ *
82
+ * Staged and unstaged changes count alike: anything `HEAD` does not hold is
83
+ * uncommitted. A rename is a new record and a deleted one.
84
+ */
85
+ export function worktreeStates(
86
+ top: string,
87
+ head: string | null,
88
+ dir: string,
89
+ paths: readonly string[],
90
+ match: RegExp,
91
+ ): { states: Map<string, WorktreeState>; deleted: string[] } {
92
+ const states = new Map<string, WorktreeState>();
93
+ if (head === null) {
94
+ for (const p of paths) states.set(p, "new");
95
+ return { states, deleted: [] };
96
+ }
97
+ const prefix = dir === "." ? "" : `${dir}/`;
98
+ const direct = (p: string) => p.startsWith(prefix) && !p.slice(prefix.length).includes("/");
99
+ const spec = dir === "." ? "." : dir;
100
+ const atHead = new Set(zList(tryGit(top, ["ls-tree", "-r", "-z", "--name-only", head, "--", spec])).filter(direct));
101
+ const changed = new Set(zList(tryGit(top, ["diff", "--name-only", "-z", "--no-renames", head, "--", spec])));
102
+ for (const p of paths) states.set(p, !atHead.has(p) ? "new" : changed.has(p) ? "modified" : "committed");
103
+ const present = new Set(paths);
104
+ const deleted = [...atHead].filter((p) => !present.has(p) && match.test(p.slice(prefix.length))).sort();
105
+ return { states, deleted };
106
+ }
@@ -15,6 +15,11 @@
15
15
  *
16
16
  * With `--since <rev>` it prints what changed between two revisions instead,
17
17
  * through `records-since.ts` (#2673).
18
+ *
19
+ * A working-tree read in git says, for each record, whether it is committed,
20
+ * modified or new against `HEAD`, and names the branch, head and base of the
21
+ * checkout; `--uncommitted` lists only the records `HEAD` doesn't hold
22
+ * (#3160, `records-checkout.ts`).
18
23
  */
19
24
 
20
25
  import { execFileSync } from "node:child_process";
@@ -48,11 +53,12 @@ import {
48
53
  type SealInput,
49
54
  type VerdictAttestation,
50
55
  } from "./records";
56
+ import { readCheckoutHead, worktreeStates, type CheckoutView, type WorktreeState } from "./records-checkout";
51
57
  import { gitTree, workingTree, type WorkspaceTree } from "./tree";
52
- import type { DecisionWork } from "./work";
53
- import { activeAttestors, type ProvenanceLevel } from "./trust/attestor";
58
+ import type { DecisionWork, WorkAnswer } from "./work";
59
+ import { activeAttestors, type CommitAttestor, type ProvenanceLevel } from "./trust/attestor";
54
60
  import { policyAtBase, recordProvenance, resolveBase, type BaseSource, type RecordProvenance } from "./trust/provenance";
55
- import type { TrustPolicy } from "./trust/policy";
61
+ import { returnedPolicy, type TrustPolicy } from "./trust/policy";
56
62
  import type { SealedRecord } from "./trust/seal";
57
63
 
58
64
  /** The version of the `records` output this chant writes. */
@@ -62,7 +68,7 @@ export const RECORDS_CONTRACT_VERSION = 1;
62
68
  export const RECORDS_OUTPUT_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/records/v1/records.schema.json";
63
69
 
64
70
  const USAGE =
65
- "chant workspace records [--kind <kind file>] [--current] [--at <rev>] [--base <rev>] [--require attested] [--json] | chant workspace records [--kind <kind file>] --since <rev|session id> [--at <rev>] [--json] | chant workspace records pin <path> | chant workspace records new|amend|review|close (#2670, #2693)";
71
+ "chant workspace records [--kind <kind file>] [--current] [--uncommitted | --at <rev>] [--base <rev>] [--require attested] [--json] | chant workspace records [--kind <kind file>] --since <rev|session id> [--at <rev>] [--json] | chant workspace records pin <path> | chant workspace records new|amend|review|close (#2670, #2693)";
66
72
 
67
73
  /** Exit code when the read worked and a record falls below `--require`. */
68
74
  export const EXIT_BELOW_REQUIRED = 2;
@@ -81,6 +87,8 @@ export interface RecordsQuery {
81
87
  * false; the intent graph passes false, since it raises the warning itself.
82
88
  */
83
89
  workGaps?: boolean;
90
+ /** List only the records that are modified or new against `HEAD` (#3160). Needs git and a working-tree read. */
91
+ uncommitted?: boolean;
84
92
  }
85
93
 
86
94
  /**
@@ -108,6 +116,20 @@ export type RecordView = RecordEntry & {
108
116
  * (`record-decided.ts`). The intent graph's windows open at this commit.
109
117
  */
110
118
  decidedIn?: DecidedIn | null;
119
+ /**
120
+ * For a read in the working tree of a git repository (#3160): whether the
121
+ * record's file is as `HEAD` holds it (`committed`), changed from it
122
+ * (`modified`), or not in it (`new`), staged or not. Absent under `--at`.
123
+ */
124
+ worktree?: WorktreeState;
125
+ /**
126
+ * For a work kind whose work block names an answer kind (#3147): the
127
+ * decision-point answers about the item, the answer records whose
128
+ * `constrains` names its id, joined by id and never copied onto the item.
129
+ * Read at the same revision; in the working tree, with the questions a
130
+ * steward keeps on the lifecycle ledger (#2786) laid over it.
131
+ */
132
+ answers?: WorkAnswer[];
111
133
  };
112
134
 
113
135
  /** The role in the trust policy whose holders' verdicts the quorum does not count (#2671). */
@@ -129,11 +151,15 @@ export type RecordsDocument =
129
151
  | {
130
152
  $schema: string;
131
153
  contract: number;
132
- kind: { name: string; schema: string; file: string; format: RecordFormat };
154
+ kind: { name: string; schema: string; file: string; format: RecordFormat; spec: boolean };
133
155
  at: string | null;
134
156
  /** The directory pinned paths resolve in, from the repository root: the workspace holding the kind file, or the repository root (#2549). */
135
157
  workspaceRoot: string;
136
158
  current: boolean;
159
+ /** Whether only the records `HEAD` doesn't hold were listed (`--uncommitted`, #3160). */
160
+ uncommitted: boolean;
161
+ /** For a read in the working tree of a git repository (#3160): the branch, head and base it is on, and the records deleted from it. Absent under `--at`. */
162
+ checkout?: CheckoutView;
137
163
  trust: TrustView;
138
164
  records: RecordView[];
139
165
  summary: { total: number; valid: number; invalid: number; superseded: number };
@@ -161,6 +187,47 @@ export interface RecordsSetDocument {
161
187
  $schema: string;
162
188
  contract: number;
163
189
  kinds: (RecordsDocument & { declared: DeclaredKindView })[];
190
+ /** With `--current`: the workspace's spec (#2524 D20, #2546), from the kinds marked `spec: true`. */
191
+ spec?: SpecView;
192
+ }
193
+
194
+ /**
195
+ * The spec (#2524 D20): the current records of every declared kind marked
196
+ * `spec: true`, each with the workspace files it pins. `records --current
197
+ * --json` prints it beside the kinds, so an agent resumes from the
198
+ * declaration and this one read.
199
+ */
200
+ export interface SpecView {
201
+ /** The spec kinds, as the declaration names them, in its order. */
202
+ kinds: DeclaredKindView[];
203
+ records: SpecRecord[];
204
+ }
205
+
206
+ /** One current record of a spec kind. */
207
+ export interface SpecRecord {
208
+ /** The kind's name, such as decision. */
209
+ kind: string;
210
+ id: string | null;
211
+ path: string;
212
+ state: string | null;
213
+ valid: boolean;
214
+ /** The digest a verdict names (#2672). */
215
+ digest: string;
216
+ /** Each workspace file the record pins: its path from the workspace root, the sha256 pinned, and how it compares with the tree read (#2549). */
217
+ assets: RecordEntry["assets"];
218
+ }
219
+
220
+ /** The spec of a set of declared kinds already read with `--current`. */
221
+ export function specOf(kinds: RecordsSetDocument["kinds"]): SpecView {
222
+ const spec: SpecView = { kinds: [], records: [] };
223
+ for (const doc of kinds) {
224
+ if ("error" in doc || !doc.kind.spec) continue;
225
+ spec.kinds.push(doc.declared);
226
+ for (const r of doc.records) {
227
+ spec.records.push({ kind: doc.kind.name, id: r.id, path: r.path, state: r.state, valid: r.valid, digest: r.digest, assets: r.assets });
228
+ }
229
+ }
230
+ return spec;
164
231
  }
165
232
 
166
233
  /**
@@ -186,7 +253,8 @@ export async function queryDeclaredRecords(kinds: { declared: RecordKindDeclarat
186
253
  const doc = await queryRecords({ ...query, kind: k.file });
187
254
  out.push({ ...doc, declared: { member: k.declared.member, path: k.declared.path, name: k.declared.name } });
188
255
  }
189
- return { $schema: RECORDS_OUTPUT_SCHEMA_ID, contract: RECORDS_CONTRACT_VERSION, kinds: out };
256
+ // The spec is every current spec record; an --uncommitted read lists only some, so it has none.
257
+ return { $schema: RECORDS_OUTPUT_SCHEMA_ID, contract: RECORDS_CONTRACT_VERSION, kinds: out, ...(query.current && !query.uncommitted ? { spec: specOf(out) } : {}) };
190
258
  }
191
259
 
192
260
  /** A records read, before provenance. */
@@ -315,26 +383,37 @@ export async function readRecordsFor(
315
383
 
316
384
  /** Run the query and build the document `--json` prints. Never throws a {@link RecordReadError}. */
317
385
  export async function queryRecords(query: RecordsQuery): Promise<RecordsDocument> {
386
+ // A revision has no working tree to hold anything uncommitted; the CLI and the MCP tool refuse the pair first.
387
+ if (query.uncommitted && query.at !== undefined) throw new Error("--uncommitted reads the working tree and takes no --at");
318
388
  try {
319
389
  const { loaded, root, top, at, workspaceRoot, tree, result } = await readRecordsFor(query);
320
390
  // Provenance, judged by the policy at base and never by the tree read (#2547).
321
391
  const base = top ? resolveBase(top, query.base) : { commit: null, from: null };
322
392
  const policy = top ? policyAtBase(top, base) : policyAtBase(root, base);
393
+ const attestors = policy.active ? await activeAttestors() : [];
323
394
  const provenance = recordProvenance({
324
395
  repo: top,
325
396
  policy,
326
397
  at,
327
398
  paths: result.records.map((r) => r.path),
328
- attestors: policy.active ? await activeAttestors() : [],
399
+ attestors,
400
+ ...(policy.active ? { revisit: await returnedRevisit(tree, workspaceRoot, policy, attestors) } : {}),
329
401
  });
330
402
  const seals = loaded.kind.reviews ? await import("./trust/seal") : undefined;
331
403
  const quorumOptions = await quorumOptionsFor(loaded, tree, policy);
332
- const records: RecordView[] = result.records.map((r) => ({
333
- ...r,
334
- provenance: provenance.get(r.path)!,
335
- ...(quorumOptions ? { quorum: computeQuorum(loaded.kind, r, quorumOptions) } : {}),
336
- ...(seals ? authorSeal(loaded.kind, r, policy, seals.checkRecordSeal) : {}),
337
- }));
404
+ const records: RecordView[] = [];
405
+ for (const r of result.records) {
406
+ const p = provenance.get(r.path)!;
407
+ // Returned work (#2552): its seals verify against the signers admitted for its return too.
408
+ const recordPolicy = p.returned ? returnedPolicy(policy, p.returned.id) : policy;
409
+ const options = p.returned ? await quorumOptionsFor(loaded, tree, recordPolicy) : quorumOptions;
410
+ records.push({
411
+ ...r,
412
+ provenance: p,
413
+ ...(options ? { quorum: computeQuorum(loaded.kind, r, options) } : {}),
414
+ ...(seals ? authorSeal(loaded.kind, r, recordPolicy, seals.checkRecordSeal) : {}),
415
+ });
416
+ }
338
417
  if (loaded.kind.approval && !loaded.kind.work && top) {
339
418
  const decided = decidedCommits(top, at ?? "HEAD", records.map((r) => r.path), loaded.kind);
340
419
  for (const r of records) r.decidedIn = decided.get(r.path) ?? null;
@@ -345,6 +424,41 @@ export async function queryRecords(query: RecordsQuery): Promise<RecordsDocument
345
424
  const leases = await activeWorkLeases(loaded.file);
346
425
  for (const r of records) if (r.id !== null) r.lease = leases.get(r.id) ?? null;
347
426
  }
427
+ if (loaded.kind.work?.answers) {
428
+ // The answers about each item, joined by id (#3147). An answer kind that can't be read leaves answers out rather than failing the work read.
429
+ const [{ workAnswers }, { readLedgerAnswers, withLedgerAnswers }] = await Promise.all([import("./work"), import("./answers-ledger")]);
430
+ const answersRead = await readRecordsFor({
431
+ kind: resolve(dirname(loaded.file), loaded.kind.work.answers),
432
+ cwd: query.cwd,
433
+ ...(query.at !== undefined ? { at: query.at } : {}),
434
+ overlay: async (base, { loaded: l, root: r }) =>
435
+ withLedgerAnswers(base, await readLedgerAnswers({ file: l.file, name: l.kind.name, dirRel: relative(r, l.dir).split(sep).join("/") || "." })),
436
+ }).catch((err: unknown) => {
437
+ if (err instanceof RecordReadError) return undefined;
438
+ throw err;
439
+ });
440
+ if (answersRead) {
441
+ const byItem = workAnswers(answersRead.result.records, records.flatMap((r) => (r.id !== null ? [r.id] : [])));
442
+ for (const r of records) if (r.id !== null) r.answers = byItem.get(r.id) ?? [];
443
+ }
444
+ }
445
+ // Which records the checkout holds uncommitted, judged after every record is read, so links still resolve across the whole set (#3160).
446
+ let checkout: CheckoutView | undefined;
447
+ let listed = records;
448
+ let summary = result.summary;
449
+ if (at === null && top) {
450
+ const { head, ...rest } = readCheckoutHead(top, query.base);
451
+ const dir = relative(root, loaded.dir).split(sep).join("/") || ".";
452
+ const { states, deleted } = worktreeStates(top, head, dir, records.map((r) => r.path), new RegExp(loaded.kind.location.match));
453
+ for (const r of records) r.worktree = states.get(r.path) ?? "new";
454
+ checkout = { branch: rest.branch, head, base: rest.base, baseFrom: rest.baseFrom, deleted };
455
+ if (query.uncommitted) {
456
+ listed = records.filter((r) => r.worktree !== "committed");
457
+ summary = { ...summary, total: listed.length, valid: listed.filter((r) => r.valid).length, invalid: listed.filter((r) => !r.valid).length };
458
+ }
459
+ } else if (query.uncommitted && !top) {
460
+ throw new RecordReadError("not-a-git-repository", "--uncommitted compares the working tree with HEAD, and this directory is not in a git repository");
461
+ }
348
462
  return {
349
463
  $schema: RECORDS_OUTPUT_SCHEMA_ID,
350
464
  contract: RECORDS_CONTRACT_VERSION,
@@ -353,13 +467,16 @@ export async function queryRecords(query: RecordsQuery): Promise<RecordsDocument
353
467
  schema: loaded.kind.schema.id,
354
468
  file: relative(root, loaded.file).split("\\").join("/"),
355
469
  format: loaded.kind.format,
470
+ spec: loaded.kind.spec === true,
356
471
  },
357
472
  at,
358
473
  workspaceRoot,
359
474
  current: !!query.current,
475
+ uncommitted: !!query.uncommitted,
476
+ ...(checkout ? { checkout } : {}),
360
477
  trust: { base: base.commit, baseFrom: base.from, active: policy.active, signersPath: policy.signersPath, problems: policy.problems },
361
- records,
362
- summary: result.summary,
478
+ records: listed,
479
+ summary,
363
480
  ...(result.decisions ? { decisions: result.decisions } : {}),
364
481
  };
365
482
  } catch (err) {
@@ -368,6 +485,36 @@ export async function queryRecords(query: RecordsQuery): Promise<RecordsDocument
368
485
  }
369
486
  }
370
487
 
488
+ /**
489
+ * The second look recordProvenance takes at each record (#2552): a record
490
+ * whose bytes came back in a return is judged by the commit it was made in,
491
+ * which the return carries, rather than by the import here. `tree` is the
492
+ * workspace root's tree, as read; record paths are from the repository root.
493
+ */
494
+ async function returnedRevisit(
495
+ tree: WorkspaceTree,
496
+ workspaceRoot: string,
497
+ policy: TrustPolicy,
498
+ attestors: readonly CommitAttestor[],
499
+ ): Promise<((path: string, host: RecordProvenance) => RecordProvenance | undefined) | undefined> {
500
+ const { readReturns, returnedProvenance } = await import("./returns");
501
+ const { returns } = readReturns(tree);
502
+ if (returns.length === 0) return undefined;
503
+ const prefix = workspaceRoot === "." ? "" : `${workspaceRoot}/`;
504
+ return (path, host) => {
505
+ if (!path.startsWith(prefix)) return undefined;
506
+ const rel = path.slice(prefix.length);
507
+ const holding = returns.filter((r) => r.paths[rel] !== undefined);
508
+ if (holding.length === 0 || tree.stat(rel) !== "file") return undefined;
509
+ const content = Buffer.from(tree.bytes ? tree.bytes(rel) : tree.read(rel));
510
+ for (const r of holding) {
511
+ const p = returnedProvenance(policy, attestors, r, rel, content, host);
512
+ if (p) return p;
513
+ }
514
+ return undefined;
515
+ };
516
+ }
517
+
371
518
  /**
372
519
  * A record's author seal checked against the policy at base (#2688), for a
373
520
  * kind with a reviews list: its author is the kind's `reviews.decider` field.
@@ -480,6 +627,11 @@ export async function runWorkspaceRecords(ctx: CommandContext): Promise<number>
480
627
  console.error(formatError({ message: `chant workspace records takes no argument but pin, new, amend, review or close (got ${args.extraPositional})`, hint: USAGE }));
481
628
  return 1;
482
629
  }
630
+ const pairing = uncommittedPairing(args);
631
+ if (pairing) {
632
+ console.error(formatError({ message: pairing, hint: USAGE }));
633
+ return 1;
634
+ }
483
635
  if (!args.kind) return runDeclaredRecords(args);
484
636
  if (args.require !== undefined && args.require !== "attested") {
485
637
  console.error(formatError({ message: `--require takes one level, attested, not ${JSON.stringify(args.require)}`, hint: USAGE }));
@@ -498,13 +650,13 @@ export async function runWorkspaceRecords(ctx: CommandContext): Promise<number>
498
650
  else console.log(formatSince(since));
499
651
  return "error" in since ? 1 : 0;
500
652
  }
501
- const doc = await queryRecords({ kind: args.kind, current: args.current, at: args.at, base: args.base, cwd: process.cwd() });
653
+ const doc = await queryRecords({ kind: args.kind, current: args.current, uncommitted: args.uncommitted, at: args.at, base: args.base, cwd: process.cwd() });
502
654
  if (args.json) {
503
655
  console.log(JSON.stringify(doc, null, 2));
504
656
  } else if ("error" in doc) {
505
657
  console.error(formatError({ message: `${doc.error.code}: ${doc.error.message}`, hint: USAGE }));
506
658
  } else {
507
- console.log(formatRecords(doc.records, doc.summary, doc.at));
659
+ console.log(formatRecords(doc.records, doc.summary, doc.at, doc.checkout));
508
660
  }
509
661
  if ("error" in doc) return 1;
510
662
  if (args.require) {
@@ -562,7 +714,7 @@ async function runDeclaredRecords(args: CommandContext["args"]): Promise<number>
562
714
  return 1;
563
715
  }
564
716
  if (args.since !== undefined) return runDeclaredSince(declared, args);
565
- const set = await queryDeclaredRecords(declared, { current: args.current, at: args.at, base: args.base, cwd: process.cwd() });
717
+ const set = await queryDeclaredRecords(declared, { current: args.current, uncommitted: args.uncommitted, at: args.at, base: args.base, cwd: process.cwd() });
566
718
  if (args.json) console.log(JSON.stringify(set, null, 2));
567
719
  for (const doc of args.json ? [] : set.kinds) {
568
720
  if ("error" in doc) {
@@ -570,7 +722,7 @@ async function runDeclaredRecords(args: CommandContext["args"]): Promise<number>
570
722
  continue;
571
723
  }
572
724
  console.log(`${doc.declared.name ?? doc.kind.name} (${doc.declared.path})`);
573
- console.log(formatRecords(doc.records, doc.summary, doc.at));
725
+ console.log(formatRecords(doc.records, doc.summary, doc.at, doc.checkout));
574
726
  }
575
727
  if (set.kinds.some((d) => "error" in d)) return 1;
576
728
  if (args.require) {
@@ -636,7 +788,20 @@ export function belowRequired(records: RecordView[], required: ProvenanceLevel):
636
788
  return records.filter((r) => r.provenance.level !== required);
637
789
  }
638
790
 
639
- function formatRecords(records: RecordView[], summary: { total: number; valid: number; invalid: number; superseded: number }, at: string | null): string {
791
+ /** Why `--uncommitted` can't be given with the other flags given, or undefined when it can (#3160). */
792
+ function uncommittedPairing(args: CommandContext["args"]): string | undefined {
793
+ if (!args.uncommitted) return undefined;
794
+ if (args.at !== undefined) return "--uncommitted reads the working tree against HEAD, and takes no --at";
795
+ if (args.since !== undefined) return "--uncommitted takes no --since: --since compares two revisions";
796
+ return undefined;
797
+ }
798
+
799
+ function formatRecords(
800
+ records: RecordView[],
801
+ summary: { total: number; valid: number; invalid: number; superseded: number },
802
+ at: string | null,
803
+ checkout?: CheckoutView,
804
+ ): string {
640
805
  const lines: string[] = [];
641
806
  const idWidth = Math.max(2, ...records.map((r) => (r.id ?? "-").length));
642
807
  const stateWidth = Math.max(5, ...records.map((r) => (r.state ?? "-").length));
@@ -647,7 +812,8 @@ function formatRecords(records: RecordView[], summary: { total: number; valid: n
647
812
  const remediated = r.remediatedBy.length > 0 ? ` remediated by ${r.remediatedBy.join(", ")}` : "";
648
813
  const attested = r.provenance.level === "attested" ? ` attested by ${r.provenance.principal}` : "";
649
814
  const sealed = r.attested === true ? ` sealed by ${(r.data?.[RECORD_SEAL_FIELD] as { signer: string }).signer}` : "";
650
- lines.push(`${(r.id ?? "-").padEnd(idWidth)} ${(r.state ?? "-").padEnd(stateWidth)} ${title}${superseded}${remediated}${attested}${sealed}${flag}`);
815
+ const uncommitted = r.worktree === "new" || r.worktree === "modified" ? ` (${r.worktree}, uncommitted)` : "";
816
+ lines.push(`${(r.id ?? "-").padEnd(idWidth)} ${(r.state ?? "-").padEnd(stateWidth)} ${title}${superseded}${remediated}${attested}${sealed}${uncommitted}${flag}`);
651
817
  if (r.ready !== undefined) {
652
818
  const blocked = (r.blockedBy ?? []).map((b) => `${b.id} (${b.state ?? "unknown"})`).join(", ");
653
819
  const implemented = (r.implements ?? []).map((d) => `${d.id} (${d.state ?? "unknown"})`).join(", ");
@@ -664,9 +830,16 @@ function formatRecords(records: RecordView[], summary: { total: number; valid: n
664
830
  lines.push(`${" ".repeat(idWidth + 2)}quorum ${q.agreed} of ${q.need} agreed, ${verdict}; ${q.notCounted.length} not counted${concerns}`);
665
831
  }
666
832
  }
833
+ for (const path of checkout?.deleted ?? []) lines.push(`${"-".padEnd(idWidth)} ${"-".padEnd(stateWidth)} ${path} (deleted, uncommitted)`);
667
834
  lines.push(
668
835
  `${summary.total} records${at ? ` at ${at.slice(0, 8)}` : ""}: ${summary.valid} valid, ${summary.invalid} invalid, ${summary.superseded} superseded`,
669
836
  );
837
+ if (checkout) {
838
+ const branch = checkout.branch ?? "a detached HEAD";
839
+ const head = checkout.head ? ` at ${checkout.head.slice(0, 8)}` : " with no commits";
840
+ const base = checkout.base ? `, forked from ${checkout.base.slice(0, 8)} of ${checkout.baseFrom}` : "";
841
+ lines.push(`working tree on ${branch}${head}${base}`);
842
+ }
670
843
  return lines.join("\n");
671
844
  }
672
845
 
@@ -676,7 +849,7 @@ export async function runWorkspaceUnknown(ctx: CommandContext): Promise<number>
676
849
  console.error(
677
850
  formatError({
678
851
  message: sub ? `Unknown workspace subcommand: ${sub}` : "chant workspace needs a subcommand",
679
- hint: `Workspace subcommands: audit, build, check, graph, init, lineage, lint, ls, records, status, upgrade, verify, work. Run "chant --help" for their options.`,
852
+ hint: `Workspace subcommands: adopt-lineage, audit, build, check, evidence, graph, hash-index, init, lineage, lint, ls, pin, records, signers, status, upgrade, verify, versions, work. Run "chant --help" for their options.`,
680
853
  }),
681
854
  );
682
855
  return 1;
@@ -84,8 +84,8 @@
84
84
  },
85
85
  "digest": {
86
86
  "type": "string",
87
- "pattern": "^[0-9a-f]{64}$",
88
- "description": "The seal written: the lowercase hex SHA-256 of the file with LF line endings and without its seal line, which records checks on every read (session-seal-mismatch)."
87
+ "pattern": "^sha256:[0-9a-f]{64}$",
88
+ "description": "The seal written: sha256: and the lowercase hex SHA-256 of the JCS form of the whole session without its seal field (its front matter as core and the text below it as body, line endings LF), which records checks on every read (session-seal-mismatch). Before #2546 it was the bare hex SHA-256 of the file's text without the seal line."
89
89
  }
90
90
  }
91
91
  },
@@ -171,6 +171,10 @@
171
171
  "write-usage-invalid",
172
172
  "record-not-found",
173
173
  "record-closed",
174
+ "write-scope-member",
175
+ "write-scope-kind",
176
+ "write-scope-class-unknown",
177
+ "agent-unknown",
174
178
  "record-unparseable",
175
179
  "record-schema-invalid",
176
180
  "record-id-duplicate",
@@ -178,6 +182,7 @@
178
182
  "record-supersedes-conflict",
179
183
  "record-remediates-unknown",
180
184
  "record-remediates-not-closed",
185
+ "record-seal-mismatch",
181
186
  "session-seal-mismatch",
182
187
  "session-verdict-unknown-record"
183
188
  ]
@@ -3,7 +3,8 @@
3
3
  * session in one write. It sets the session's state to its kind's closed
4
4
  * state, the time it closed and the commit it closed at (the fields the
5
5
  * kind's `session` block names in `closedOn` and `closedRev`), and then the
6
- * seal by chant's rule ({@link sessionSeal}), so a UI never computes a seal.
6
+ * seal by chant's whole-file rule ({@link sessionSeal}, #2546), so a UI never
7
+ * computes a seal.
7
8
  *
8
9
  * Like `new`, `amend` and `review`, it reads the records again with the file
9
10
  * in place and writes only when the session comes back valid: a verdict
@@ -26,6 +27,7 @@ import {
26
27
  readAll,
27
28
  RECORDS_WRITE_CONTRACT_VERSION,
28
29
  RecordWriteError,
30
+ refuseOutOfScope,
29
31
  replaceFields,
30
32
  stableJson,
31
33
  validateWrite,
@@ -33,6 +35,7 @@ import {
33
35
  type WriteResult,
34
36
  } from "./records-write";
35
37
  import { headCommit } from "./session-kinds";
38
+ import { WRITE_SCOPE_CODES } from "./write-scope";
36
39
 
37
40
  export const RECORDS_CLOSE_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/records-close/v1/records-close.schema.json";
38
41
 
@@ -42,6 +45,7 @@ export const CLOSE_ERROR_CODES = [
42
45
  "write-usage-invalid",
43
46
  "record-not-found",
44
47
  "record-closed",
48
+ ...WRITE_SCOPE_CODES,
45
49
  ...RECORD_REASON_CODES,
46
50
  ] as const satisfies readonly ReasonCode[];
47
51
  export type CloseErrorCode = (typeof CLOSE_ERROR_CODES)[number];
@@ -65,6 +69,8 @@ export interface CloseRecordOptions {
65
69
  cwd: string;
66
70
  /** The close time. Defaults to now. */
67
71
  now?: Date;
72
+ /** The agent session the write is made in (#2548). */
73
+ agent?: string;
68
74
  }
69
75
 
70
76
  /** An ISO 8601 time in UTC to the second, as the reference session schema's dateTime takes it. */
@@ -76,6 +82,7 @@ function isoSeconds(d: Date): string {
76
82
  export async function closeRecord(opts: CloseRecordOptions): Promise<CloseDocument> {
77
83
  try {
78
84
  const o = await open(opts.kind, opts.cwd);
85
+ refuseOutOfScope(o, "close", opts.cwd, opts);
79
86
  const { kind } = o.loaded;
80
87
  const decl = kind.session;
81
88
  if (!decl || kind.stateField === undefined) {
@@ -97,12 +104,13 @@ export async function closeRecord(opts: CloseRecordOptions): Promise<CloseDocume
97
104
  ...(decl.closedOn ? { [decl.closedOn]: isoSeconds(opts.now ?? new Date()) } : {}),
98
105
  ...(decl.closedRev ? { [decl.closedRev]: closedRev } : {}),
99
106
  };
100
- // The seal line is written last, into text that already holds everything else, so the seal is the digest of the file without it.
107
+ // The seal is written last, into text that already holds everything else: the whole-file seal of the session without its seal field (#2546).
101
108
  const merged = { ...old, ...set };
102
109
  delete merged[decl.seal];
103
110
  const unsealed = replaceFields(o.source.read(target.path), set, merged);
104
111
  if (unsealed === undefined) throw new RecordWriteError("record-unparseable", `${target.path}: its fields can't be rewritten in place without changing the rest of the file`);
105
112
  const digest = sessionSeal(unsealed, decl.seal, kind.format);
113
+ if (digest === null) throw new RecordWriteError("record-unparseable", `${target.path} can't be read, so it can't be sealed`);
106
114
  const text = replaceFields(unsealed, { [decl.seal]: digest }, { ...merged, [decl.seal]: digest });
107
115
  if (text === undefined || sessionSeal(text, decl.seal, kind.format) !== digest) {
108
116
  throw new RecordWriteError("record-unparseable", `${target.path}: the ${decl.seal} line can't be added without changing the text it seals`);
@@ -154,7 +154,8 @@ const SESSION_SCHEMA = {
154
154
  comments: { type: "array" },
155
155
  follow_ups: { type: "array", items: ref("contractId") },
156
156
  closed_at: ref("datetime"),
157
- seal: ref("sha256"),
157
+ // The whole-file seal (#2546): sha256: and the hex SHA-256 of the session's JCS form without it.
158
+ seal: { type: "string", pattern: "^sha256:[0-9a-f]{64}$" },
158
159
  },
159
160
  };
160
161
 
@@ -294,7 +295,7 @@ const SESSION = {
294
295
  comments: [],
295
296
  follow_ups: ["C-002"],
296
297
  closed_at: "2026-09-24T11:00:00.000Z",
297
- seal: "e".repeat(64),
298
+ seal: `sha256:${"e".repeat(64)}`,
298
299
  };
299
300
 
300
301
  // ── 1. The json format ───────────────────────────────────────────────────────
@@ -372,11 +373,16 @@ describe("format json", () => {
372
373
  });
373
374
 
374
375
  describe("a JSON session kind (#2673)", () => {
375
- test("its seal is the digest without the seal member, by the JSON rule", async () => {
376
+ test("its seal is the whole-file JCS seal without the seal member (#2546)", async () => {
376
377
  write("design/session.kind.mjs", kindFile({ ...KINDS.session, session: { verdicts: "approvals", seal: "seal", subjects: { kind: "contract.kind.mjs" } } }));
377
378
  write("design/contracts/C-001-first.md", '---\nid: "C-001"\nstatus: "approved"\n---\n');
379
+ // JCS of the object without its seal: keys sorted, no whitespace. The member order and indentation of the file play no part.
380
+ const sorted = (v: unknown): unknown =>
381
+ 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])]));
382
+ const jcsSeal = (v: object): string => `sha256:${sha256(JSON.stringify(sorted(v)))}`;
378
383
  const { seal: _drop, ...unsealed } = SESSION;
379
- const seal = sha256(json(unsealed));
384
+ const seal = jcsSeal(unsealed);
385
+ // Sealed over SESSION, then changed: a verdict added, or the id.
380
386
  write("design/sessions/S-0001.json", json({ ...SESSION, approvals: [{ record: "C-001" }], seal }));
381
387
  write("design/sessions/S-0002.json", json({ ...SESSION, id: "S-0002", seal }));
382
388
  const doc = await query("session");
@@ -385,7 +391,7 @@ describe("a JSON session kind (#2673)", () => {
385
391
  ["S-0002", ["session-seal-mismatch"]],
386
392
  ]);
387
393
  const good = { ...unsealed, id: "S-0003" };
388
- write("design/sessions/S-0003.json", json({ ...good, seal: sha256(json(good)) }));
394
+ write("design/sessions/S-0003.json", `${JSON.stringify({ seal: jcsSeal(good), ...Object.fromEntries(Object.entries(good).reverse()) })}\n`);
389
395
  const [, , third] = (await query("session")).records;
390
396
  expect([third.id, codes(third)]).toEqual(["S-0003", []]);
391
397
  });
@@ -547,6 +553,8 @@ describe("kinds without a lifecycle", () => {
547
553
  ["neither idField nor idFrom", { ...KINDS.closure, idField: undefined }, /idField: a kind names its id with exactly one of idField and idFrom/],
548
554
  ["an unknown format", { ...KINDS.closure, format: "yaml" }, /format/],
549
555
  ["a session block without states", { ...KINDS.closure, session: { verdicts: "approvals", seal: "seal", subjects: { kind: "contract.kind.mjs" } } }, /session: a session kind must have states/],
556
+ ["a seal without states (#2546)", { ...KINDS.closure, seal: { field: "seal" } }, /seal: a kind without states cannot have seal/],
557
+ ["a seal naming another field than session.seal (#2546)", { ...KINDS.session, session: { verdicts: "approvals", seal: "seal", subjects: { kind: "contract.kind.mjs" } }, seal: { field: "closed_digest" } }, /seal: seal.field and session.seal name the field/],
550
558
  ])("a kind with %s is kind-invalid, naming the field", async (_label, kind, why) => {
551
559
  write("design/closure.kind.mjs", kindFile(kind));
552
560
  const error = await failure("closure");