@intentius/chant 0.124.13 → 0.124.15

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 (991) hide show
  1. package/bin/chant +11 -0
  2. package/dist/agents/checks.js +326 -0
  3. package/dist/agents/discover.js +885 -0
  4. package/dist/agents/importer.js +17 -0
  5. package/dist/agents/index.js +12 -0
  6. package/dist/agents/types.js +19 -0
  7. package/dist/apply.js +151 -0
  8. package/dist/attrref.js +70 -0
  9. package/dist/audit/catalog.js +350 -0
  10. package/dist/audit/core.js +252 -0
  11. package/dist/audit/discover.js +633 -0
  12. package/dist/audit/fetch.js +666 -0
  13. package/dist/audit/lineage.js +91 -0
  14. package/dist/audit/nginx.js +366 -0
  15. package/dist/audit/prior-art.js +132 -0
  16. package/dist/audit/proof.js +321 -0
  17. package/dist/audit/report-html.js +194 -0
  18. package/dist/audit/report-model.js +198 -0
  19. package/dist/audit/report.js +102 -0
  20. package/dist/audit/rules-doc.js +144 -0
  21. package/dist/audit/secrets.js +405 -0
  22. package/dist/audit/terraform-state.js +140 -0
  23. package/dist/audit/wrangler.js +262 -0
  24. package/dist/behaviour-delta.js +401 -0
  25. package/dist/behaviour-engine.js +357 -0
  26. package/dist/behaviour-graph.js +83 -0
  27. package/dist/behaviour-http.js +188 -0
  28. package/dist/behaviour-kinds.js +172 -0
  29. package/dist/behaviour-overlay.js +50 -0
  30. package/dist/behaviour-predict.js +188 -0
  31. package/dist/behaviour-request.js +194 -0
  32. package/dist/behaviour.js +1325 -0
  33. package/dist/build-params.js +108 -0
  34. package/dist/build.d.ts +6 -0
  35. package/dist/build.d.ts.map +1 -1
  36. package/dist/build.js +669 -0
  37. package/dist/builder.js +44 -0
  38. package/dist/cdk/advise.js +30 -0
  39. package/dist/cdk/assembly.js +122 -0
  40. package/dist/cdk/graph.js +444 -0
  41. package/dist/cdk/tier-map.js +64 -0
  42. package/dist/cdk/types.js +24 -0
  43. package/dist/change-set.d.ts +24 -2
  44. package/dist/change-set.d.ts.map +1 -1
  45. package/dist/change-set.js +361 -0
  46. package/dist/child-project.js +21 -0
  47. package/dist/ci-jobs.d.ts +227 -0
  48. package/dist/ci-jobs.d.ts.map +1 -0
  49. package/dist/ci-jobs.js +448 -0
  50. package/dist/claimed-fields.js +135 -0
  51. package/dist/cli/build-options.js +70 -0
  52. package/dist/cli/build-params-cli.js +139 -0
  53. package/dist/cli/command-group.js +123 -0
  54. package/dist/cli/commands/audit-agents.js +326 -0
  55. package/dist/cli/commands/audit.js +571 -0
  56. package/dist/cli/commands/build.js +920 -0
  57. package/dist/cli/commands/carve-apply.js +153 -0
  58. package/dist/cli/commands/carve-bridge.js +201 -0
  59. package/dist/cli/commands/carve-emit.js +332 -0
  60. package/dist/cli/commands/carve-status.js +119 -0
  61. package/dist/cli/commands/carve.js +204 -0
  62. package/dist/cli/commands/check-lexicon-docs.js +79 -0
  63. package/dist/cli/commands/check-lexicon-examples.js +177 -0
  64. package/dist/cli/commands/check-lexicon-intrinsics.js +291 -0
  65. package/dist/cli/commands/check-lexicon-mcp.js +82 -0
  66. package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
  67. package/dist/cli/commands/check-lexicon-plugin.js +104 -0
  68. package/dist/cli/commands/check-lexicon.js +766 -0
  69. package/dist/cli/commands/describe.js +85 -0
  70. package/dist/cli/commands/diff.js +175 -0
  71. package/dist/cli/commands/doctor.js +350 -0
  72. package/dist/cli/commands/import-agents.js +155 -0
  73. package/dist/cli/commands/import.js +670 -0
  74. package/dist/cli/commands/init-lexicon/templates/codegen.js +194 -0
  75. package/dist/cli/commands/init-lexicon/templates/docs.js +100 -0
  76. package/dist/cli/commands/init-lexicon/templates/examples.js +29 -0
  77. package/dist/cli/commands/init-lexicon/templates/lint.js +34 -0
  78. package/dist/cli/commands/init-lexicon/templates/lsp.js +37 -0
  79. package/dist/cli/commands/init-lexicon/templates/plugin.js +108 -0
  80. package/dist/cli/commands/init-lexicon/templates/project.js +240 -0
  81. package/dist/cli/commands/init-lexicon/templates/spec.js +56 -0
  82. package/dist/cli/commands/init-lexicon/templates/tests.js +66 -0
  83. package/dist/cli/commands/init-lexicon.js +172 -0
  84. package/dist/cli/commands/init.js +381 -0
  85. package/dist/cli/commands/lexicon-rolling-upgrade.js +145 -0
  86. package/dist/cli/commands/lexicon-surface-diff.js +145 -0
  87. package/dist/cli/commands/licenses.js +125 -0
  88. package/dist/cli/commands/lint.d.ts.map +1 -1
  89. package/dist/cli/commands/lint.js +948 -0
  90. package/dist/cli/commands/list.js +63 -0
  91. package/dist/cli/commands/migrate.d.ts.map +1 -1
  92. package/dist/cli/commands/migrate.js +423 -0
  93. package/dist/cli/commands/onboard.js +403 -0
  94. package/dist/cli/commands/pinned-upgrade.js +191 -0
  95. package/dist/cli/commands/update.js +194 -0
  96. package/dist/cli/commands/vendor.js +217 -0
  97. package/dist/cli/conflict-check.js +104 -0
  98. package/dist/cli/debug.js +8 -0
  99. package/dist/cli/format.js +104 -0
  100. package/dist/cli/handlers/build.js +267 -0
  101. package/dist/cli/handlers/carve-apply.js +30 -0
  102. package/dist/cli/handlers/carve-bridge.js +28 -0
  103. package/dist/cli/handlers/carve-emit.js +50 -0
  104. package/dist/cli/handlers/carve-status.js +26 -0
  105. package/dist/cli/handlers/carve.js +40 -0
  106. package/dist/cli/handlers/change-set.js +46 -0
  107. package/dist/cli/handlers/components.js +730 -0
  108. package/dist/cli/handlers/dev.js +113 -0
  109. package/dist/cli/handlers/emulator.js +233 -0
  110. package/dist/cli/handlers/explain.js +54 -0
  111. package/dist/cli/handlers/fan-out.js +422 -0
  112. package/dist/cli/handlers/gate-resume.js +275 -0
  113. package/dist/cli/handlers/generate.js +84 -0
  114. package/dist/cli/handlers/graph.js +969 -0
  115. package/dist/cli/handlers/init.js +102 -0
  116. package/dist/cli/handlers/lifecycle.js +1727 -0
  117. package/dist/cli/handlers/lint.js +62 -0
  118. package/dist/cli/handlers/migrate.js +48 -0
  119. package/dist/cli/handlers/misc.js +391 -0
  120. package/dist/cli/handlers/operator.js +1162 -0
  121. package/dist/cli/handlers/override-approve.js +102 -0
  122. package/dist/cli/handlers/pr.js +503 -0
  123. package/dist/cli/handlers/promote.js +342 -0
  124. package/dist/cli/handlers/run-generate.js +206 -0
  125. package/dist/cli/handlers/run-wave.js +66 -0
  126. package/dist/cli/handlers/run.js +1191 -0
  127. package/dist/cli/handlers/scenario.js +325 -0
  128. package/dist/cli/handlers/search.js +924 -0
  129. package/dist/cli/handlers/serve.js +32 -0
  130. package/dist/cli/handlers/vendor.js +154 -0
  131. package/dist/cli/index.js +3 -0
  132. package/dist/cli/is-entry-point.js +23 -0
  133. package/dist/cli/lsp/capabilities.js +28 -0
  134. package/dist/cli/lsp/diagnostics.js +34 -0
  135. package/dist/cli/lsp/server.js +329 -0
  136. package/dist/cli/main.d.ts.map +1 -1
  137. package/dist/cli/main.js +2540 -0
  138. package/dist/cli/mcp/lifecycle-tools.js +80 -0
  139. package/dist/cli/mcp/op-tools.js +306 -0
  140. package/dist/cli/mcp/resource-handlers.js +258 -0
  141. package/dist/cli/mcp/resources/context.js +85 -0
  142. package/dist/cli/mcp/server.js +353 -0
  143. package/dist/cli/mcp/tools/build.js +111 -0
  144. package/dist/cli/mcp/tools/composites.js +95 -0
  145. package/dist/cli/mcp/tools/explain.js +166 -0
  146. package/dist/cli/mcp/tools/import.js +49 -0
  147. package/dist/cli/mcp/tools/lint.js +41 -0
  148. package/dist/cli/mcp/tools/scaffold.js +111 -0
  149. package/dist/cli/mcp/tools/search.js +112 -0
  150. package/dist/cli/mcp/types.js +1 -0
  151. package/dist/cli/mcp/workspace-plugins.js +91 -0
  152. package/dist/cli/mcp/workspace-tools.d.ts +1 -1
  153. package/dist/cli/mcp/workspace-tools.js +564 -0
  154. package/dist/cli/mcp-config.js +70 -0
  155. package/dist/cli/plugins.js +313 -0
  156. package/dist/cli/registry.js +23 -0
  157. package/dist/cli/reporters/stylish.js +329 -0
  158. package/dist/cli/version.js +15 -0
  159. package/dist/cli/watch.js +79 -0
  160. package/dist/codegen/bound-property-types.js +82 -0
  161. package/dist/codegen/coverage.js +119 -0
  162. package/dist/codegen/docs-file-markers.js +60 -0
  163. package/dist/codegen/docs-pages.js +115 -0
  164. package/dist/codegen/docs-rule-scanning.js +164 -0
  165. package/dist/codegen/docs-rules.js +76 -0
  166. package/dist/codegen/docs-sections.js +117 -0
  167. package/dist/codegen/docs-sidebar.js +65 -0
  168. package/dist/codegen/docs-types.js +4 -0
  169. package/dist/codegen/docs.js +356 -0
  170. package/dist/codegen/fetch.js +340 -0
  171. package/dist/codegen/generate-post-synth-barrel.js +63 -0
  172. package/dist/codegen/generate-registry.js +71 -0
  173. package/dist/codegen/generate-runtime-index.js +56 -0
  174. package/dist/codegen/generate-typescript.js +119 -0
  175. package/dist/codegen/generate.js +115 -0
  176. package/dist/codegen/json-patch.js +141 -0
  177. package/dist/codegen/json-schema-to-ts.js +344 -0
  178. package/dist/codegen/json-schema.js +246 -0
  179. package/dist/codegen/lexicon-regen.js +412 -0
  180. package/dist/codegen/naming.js +217 -0
  181. package/dist/codegen/okf-lexicon.js +245 -0
  182. package/dist/codegen/package.js +150 -0
  183. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  184. package/dist/codegen/pinned-upgrade.js +494 -0
  185. package/dist/codegen/registry.js +60 -0
  186. package/dist/codegen/rehype-base-url.mjs +68 -0
  187. package/dist/codegen/rolling-upgrade.js +217 -0
  188. package/dist/codegen/surface-snapshot.js +537 -0
  189. package/dist/codegen/testdata/postgres-tags.json +242 -0
  190. package/dist/codegen/topo-sort.js +36 -0
  191. package/dist/codegen/typecheck.js +115 -0
  192. package/dist/codegen/validate.js +200 -0
  193. package/dist/codegen/version-bump.js +35 -0
  194. package/dist/components/auto-release.js +143 -0
  195. package/dist/components/builders.js +47 -0
  196. package/dist/components/capability-plugin-loader.js +175 -0
  197. package/dist/components/capability-plugin-schema.js +46 -0
  198. package/dist/components/capability-plugin.js +99 -0
  199. package/dist/components/capability.js +60 -0
  200. package/dist/components/changed-files.js +72 -0
  201. package/dist/components/cli-support.js +427 -0
  202. package/dist/components/component.js +120 -0
  203. package/dist/components/component.schema.json +271 -0
  204. package/dist/components/config-defaults.js +130 -0
  205. package/dist/components/deploy-units.js +60 -0
  206. package/dist/components/discover.d.ts.map +1 -1
  207. package/dist/components/discover.js +189 -0
  208. package/dist/components/driver-output.js +86 -0
  209. package/dist/components/driver.js +728 -0
  210. package/dist/components/fan-out-output.js +165 -0
  211. package/dist/components/fan-out-record.js +94 -0
  212. package/dist/components/fan-out-run.js +373 -0
  213. package/dist/components/fan-out-support.js +60 -0
  214. package/dist/components/fan-out.js +273 -0
  215. package/dist/components/gate-plan.js +41 -0
  216. package/dist/components/gated-wave-pipeline.js +46 -0
  217. package/dist/components/index.js +24 -0
  218. package/dist/components/layers.js +28 -0
  219. package/dist/components/manifest-persistence.js +108 -0
  220. package/dist/components/pilots/alb-ecs.pilot.js +59 -0
  221. package/dist/components/pilots/dynamodb.pilot.js +51 -0
  222. package/dist/components/pilots/jar-emr.pilot.js +65 -0
  223. package/dist/components/pilots/lambda.pilot.js +52 -0
  224. package/dist/components/pilots/neo4j-fanout.pilot.js +68 -0
  225. package/dist/components/pilots/project.js +12 -0
  226. package/dist/components/pr-pipeline.d.ts.map +1 -1
  227. package/dist/components/pr-pipeline.js +135 -0
  228. package/dist/components/pr-run.js +363 -0
  229. package/dist/components/presets/ecs-fargate.js +72 -0
  230. package/dist/components/presets/index.js +28 -0
  231. package/dist/components/presets/lambda.js +34 -0
  232. package/dist/components/presets/single-host-compose.js +41 -0
  233. package/dist/components/promote.js +549 -0
  234. package/dist/components/registry.js +34 -0
  235. package/dist/components/run-progress.js +33 -0
  236. package/dist/components/sandbox/driver.d.ts.map +1 -1
  237. package/dist/components/sandbox/driver.js +93 -0
  238. package/dist/components/sandbox/run.js +115 -0
  239. package/dist/components/starter-plugin.js +104 -0
  240. package/dist/components/verbs/__tests__/mock-cloud-executor.js +68 -0
  241. package/dist/components/verbs/__tests__/mock-process-runner.js +45 -0
  242. package/dist/components/verbs/__tests__/mock-sbom-generator.js +56 -0
  243. package/dist/components/verbs/bom-writer.js +287 -0
  244. package/dist/components/verbs/build-archive.js +188 -0
  245. package/dist/components/verbs/build.js +142 -0
  246. package/dist/components/verbs/cloud-executor.js +110 -0
  247. package/dist/components/verbs/component-bom.js +125 -0
  248. package/dist/components/verbs/ensure-secret.js +50 -0
  249. package/dist/components/verbs/index.js +61 -0
  250. package/dist/components/verbs/license-policy.js +75 -0
  251. package/dist/components/verbs/lockfile-sbom-generator.js +191 -0
  252. package/dist/components/verbs/process-runner.js +78 -0
  253. package/dist/components/verbs/r2-sync.js +93 -0
  254. package/dist/components/verbs/reproducibility.js +62 -0
  255. package/dist/components/verbs/run-agent.js +577 -0
  256. package/dist/components/verbs/sbom-generator.js +119 -0
  257. package/dist/components/verbs/sbom.js +97 -0
  258. package/dist/components/verbs/shell.js +32 -0
  259. package/dist/components/verbs/sign.js +221 -0
  260. package/dist/components/verbs/stub.js +28 -0
  261. package/dist/components/verbs/tool-sbom-generator.js +154 -0
  262. package/dist/components/verbs/verify.js +112 -0
  263. package/dist/components/verbs/vex.js +131 -0
  264. package/dist/components/verbs/vuln-gate.js +152 -0
  265. package/dist/components/verbs/vuln-scan.js +288 -0
  266. package/dist/components/verbs/wait-verify.js +106 -0
  267. package/dist/components/verbs/wrangler.js +179 -0
  268. package/dist/components/wave-plan.js +102 -0
  269. package/dist/composite.d.ts +10 -2
  270. package/dist/composite.d.ts.map +1 -1
  271. package/dist/composite.js +313 -0
  272. package/dist/config-import.d.ts.map +1 -1
  273. package/dist/config-import.js +22 -0
  274. package/dist/config-sandbox.js +122 -0
  275. package/dist/config-static.js +206 -0
  276. package/dist/config.js +605 -0
  277. package/dist/content-digest.js +23 -0
  278. package/dist/declarable.js +114 -0
  279. package/dist/deep-observation.js +437 -0
  280. package/dist/detectLexicon.js +54 -0
  281. package/dist/discovery/cache.js +70 -0
  282. package/dist/discovery/collect.d.ts +2 -1
  283. package/dist/discovery/collect.d.ts.map +1 -1
  284. package/dist/discovery/collect.js +223 -0
  285. package/dist/discovery/cycles.js +95 -0
  286. package/dist/discovery/entity-wire-codec.js +391 -0
  287. package/dist/discovery/entity-wire.js +42 -0
  288. package/dist/discovery/files.js +121 -0
  289. package/dist/discovery/fold-import.d.ts.map +1 -1
  290. package/dist/discovery/fold-import.js +4073 -0
  291. package/dist/discovery/fold-rank.js +292 -0
  292. package/dist/discovery/graph.js +115 -0
  293. package/dist/discovery/host-composite.d.ts +35 -0
  294. package/dist/discovery/host-composite.d.ts.map +1 -0
  295. package/dist/discovery/host-composite.js +93 -0
  296. package/dist/discovery/import.d.ts.map +1 -1
  297. package/dist/discovery/import.js +65 -0
  298. package/dist/discovery/index.d.ts +9 -0
  299. package/dist/discovery/index.d.ts.map +1 -1
  300. package/dist/discovery/index.js +333 -0
  301. package/dist/discovery/param-deps.js +487 -0
  302. package/dist/discovery/resolve.js +39 -0
  303. package/dist/discovery/sandbox/bundle.js +214 -0
  304. package/dist/discovery/sandbox/child-errors.js +26 -0
  305. package/dist/discovery/sandbox/config-run.js +100 -0
  306. package/dist/discovery/sandbox/config-wire.js +183 -0
  307. package/dist/discovery/sandbox/driver.d.ts.map +1 -1
  308. package/dist/discovery/sandbox/driver.js +333 -0
  309. package/dist/discovery/sandbox/fork.js +229 -0
  310. package/dist/discovery/sandbox/policy-run.js +142 -0
  311. package/dist/discovery/sandbox/policy-wire.js +152 -0
  312. package/dist/discovery/sandbox/run.js +77 -0
  313. package/dist/discovery/specifier-resolve.js +320 -0
  314. package/dist/discovery/walk.js +284 -0
  315. package/dist/effect-receipt.js +340 -0
  316. package/dist/env.js +55 -0
  317. package/dist/errors.js +62 -0
  318. package/dist/fold/fold.js +1434 -0
  319. package/dist/fold/foldable-helpers.js +229 -0
  320. package/dist/fold/subset.js +696 -0
  321. package/dist/fold-provenance.js +414 -0
  322. package/dist/gate-decision.d.ts +20 -9
  323. package/dist/gate-decision.d.ts.map +1 -1
  324. package/dist/gate-decision.js +313 -0
  325. package/dist/gate-resume-run.js +154 -0
  326. package/dist/gated-waves.js +165 -0
  327. package/dist/governance.js +43 -0
  328. package/dist/graph-declared.js +45 -0
  329. package/dist/graph-detail.js +207 -0
  330. package/dist/graph-dot.js +54 -0
  331. package/dist/graph-effective.js +107 -0
  332. package/dist/graph-ir.js +623 -0
  333. package/dist/graph-layout.js +184 -0
  334. package/dist/graph-lens.js +105 -0
  335. package/dist/graph-mermaid.js +83 -0
  336. package/dist/graph-ops.js +39 -0
  337. package/dist/graph-refs.js +156 -0
  338. package/dist/held-elsewhere.js +87 -0
  339. package/dist/identity.js +251 -0
  340. package/dist/import/base-parser.js +34 -0
  341. package/dist/import/embedded.js +255 -0
  342. package/dist/import/generator.js +1 -0
  343. package/dist/import/ir-utils.js +85 -0
  344. package/dist/import/parser-options.js +45 -0
  345. package/dist/import/parser.js +1 -0
  346. package/dist/index.js +118 -0
  347. package/dist/intrinsic-interpolation.js +73 -0
  348. package/dist/intrinsic.js +31 -0
  349. package/dist/join-key.js +35 -0
  350. package/dist/kubectl-context.js +150 -0
  351. package/dist/lexicon-config.js +58 -0
  352. package/dist/lexicon-integrity.js +47 -0
  353. package/dist/lexicon-manifest.js +70 -0
  354. package/dist/lexicon-module.d.ts.map +1 -1
  355. package/dist/lexicon-module.js +241 -0
  356. package/dist/lexicon-output.js +218 -0
  357. package/dist/lexicon-plugin-helpers.js +104 -0
  358. package/dist/lexicon-schema.js +136 -0
  359. package/dist/lexicon.js +130 -0
  360. package/dist/lifecycle/affected.js +224 -0
  361. package/dist/lifecycle/assert-live.js +110 -0
  362. package/dist/lifecycle/build-ledger-store.js +160 -0
  363. package/dist/lifecycle/build-ledger.js +155 -0
  364. package/dist/lifecycle/change-set.js +356 -0
  365. package/dist/lifecycle/converge-ledger.js +161 -0
  366. package/dist/lifecycle/deep-diff.js +233 -0
  367. package/dist/lifecycle/deep-observe.js +147 -0
  368. package/dist/lifecycle/digest.js +143 -0
  369. package/dist/lifecycle/disruption.js +135 -0
  370. package/dist/lifecycle/gate-applied.js +113 -0
  371. package/dist/lifecycle/gate-audit.d.ts +69 -2
  372. package/dist/lifecycle/gate-audit.d.ts.map +1 -1
  373. package/dist/lifecycle/gate-audit.js +415 -0
  374. package/dist/lifecycle/gate-ledger-select.js +89 -0
  375. package/dist/lifecycle/gate-ledger.d.ts +24 -0
  376. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  377. package/dist/lifecycle/gate-ledger.js +201 -0
  378. package/dist/lifecycle/gate-origin.js +117 -0
  379. package/dist/lifecycle/gate-pending.js +58 -0
  380. package/dist/lifecycle/git.js +1182 -0
  381. package/dist/lifecycle/identity.js +58 -0
  382. package/dist/lifecycle/index.js +26 -0
  383. package/dist/lifecycle/lease.js +283 -0
  384. package/dist/lifecycle/ledger-port.js +1 -0
  385. package/dist/lifecycle/legacy-digest.js +91 -0
  386. package/dist/lifecycle/live-diff.js +274 -0
  387. package/dist/lifecycle/local-note.js +15 -0
  388. package/dist/lifecycle/member-ledger.js +122 -0
  389. package/dist/lifecycle/observation-baseline.js +155 -0
  390. package/dist/lifecycle/observe.js +375 -0
  391. package/dist/lifecycle/oras-referrer-lookup.js +82 -0
  392. package/dist/lifecycle/override-ledger.js +54 -0
  393. package/dist/lifecycle/plan-digest.js +110 -0
  394. package/dist/lifecycle/plan-ledger.js +129 -0
  395. package/dist/lifecycle/receipt-plan.js +192 -0
  396. package/dist/lifecycle/release-ledger.js +155 -0
  397. package/dist/lifecycle/release-read.js +64 -0
  398. package/dist/lifecycle/replay.js +193 -0
  399. package/dist/lifecycle/rollback.js +102 -0
  400. package/dist/lifecycle/run-ledger.js +183 -0
  401. package/dist/lifecycle/scenario-eval.js +204 -0
  402. package/dist/lifecycle/scenario.js +214 -0
  403. package/dist/lifecycle/snapshot.js +239 -0
  404. package/dist/lifecycle/status.js +447 -0
  405. package/dist/lifecycle/symptoms.js +117 -0
  406. package/dist/lifecycle/teardown.js +255 -0
  407. package/dist/lifecycle/types.js +1 -0
  408. package/dist/lifecycle/unobserved-gate.js +33 -0
  409. package/dist/lifecycle/wave-plans.js +68 -0
  410. package/dist/lifecycle/work-item-id.js +36 -0
  411. package/dist/lifecycle/work-lease.js +393 -0
  412. package/dist/lint/component-checks.js +87 -0
  413. package/dist/lint/config.js +483 -0
  414. package/dist/lint/declarative.js +122 -0
  415. package/dist/lint/discover.d.ts.map +1 -1
  416. package/dist/lint/discover.js +138 -0
  417. package/dist/lint/engine.js +246 -0
  418. package/dist/lint/knowledge-checks.js +64 -0
  419. package/dist/lint/named-checks.js +22 -0
  420. package/dist/lint/output-checks.js +87 -0
  421. package/dist/lint/output-docs.js +163 -0
  422. package/dist/lint/parser.js +33 -0
  423. package/dist/lint/pipeline-change-gate.js +70 -0
  424. package/dist/lint/policy-import.d.ts +1 -1
  425. package/dist/lint/policy-import.d.ts.map +1 -1
  426. package/dist/lint/policy-import.js +62 -0
  427. package/dist/lint/policy-sandbox.js +86 -0
  428. package/dist/lint/policy.js +122 -0
  429. package/dist/lint/post-synth.js +87 -0
  430. package/dist/lint/presets/relaxed.json +26 -0
  431. package/dist/lint/presets/strict.json +24 -24
  432. package/dist/lint/receipt-checks.js +83 -0
  433. package/dist/lint/rule-loader.d.ts.map +1 -1
  434. package/dist/lint/rule-loader.js +89 -0
  435. package/dist/lint/rule-registry.js +86 -0
  436. package/dist/lint/rule.js +1 -0
  437. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.js +7 -0
  438. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.js +7 -0
  439. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.js +9 -0
  440. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.js +11 -0
  441. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.js +9 -0
  442. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.js +25 -0
  443. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.js +8 -0
  444. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.js +21 -0
  445. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.js +8 -0
  446. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.js +24 -0
  447. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.js +17 -0
  448. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.js +8 -0
  449. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.js +18 -0
  450. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.js +20 -0
  451. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.js +8 -0
  452. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.js +12 -0
  453. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.js +16 -0
  454. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.js +8 -0
  455. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.js +12 -0
  456. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.js +11 -0
  457. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.js +11 -0
  458. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.js +10 -0
  459. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.js +16 -0
  460. package/dist/lint/rules/comp/comp001-publishes-never-applies.js +69 -0
  461. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.js +84 -0
  462. package/dist/lint/rules/comp/comp003-mutating-no-rollback.js +103 -0
  463. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.js +70 -0
  464. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.js +109 -0
  465. package/dist/lint/rules/comp/comp006-shell-needs-reason.js +49 -0
  466. package/dist/lint/rules/comp/comp007-composition-sprawl.js +64 -0
  467. package/dist/lint/rules/comp/index.js +33 -0
  468. package/dist/lint/rules/comp/support.js +129 -0
  469. package/dist/lint/rules/composite-scope.js +51 -0
  470. package/dist/lint/rules/cor017-composite-name-match.js +93 -0
  471. package/dist/lint/rules/cor018-composite-prefer-lexicon-type.js +136 -0
  472. package/dist/lint/rules/cor021-env-literal-name.js +100 -0
  473. package/dist/lint/rules/cor022-receipt-leaf.js +118 -0
  474. package/dist/lint/rules/cor024-receipt-secret-pointer.js +199 -0
  475. package/dist/lint/rules/declarable-naming-convention.js +62 -0
  476. package/dist/lint/rules/evl001-non-literal-expression.js +66 -0
  477. package/dist/lint/rules/evl002-control-flow-resource.js +69 -0
  478. package/dist/lint/rules/evl003-dynamic-property-access.js +44 -0
  479. package/dist/lint/rules/evl004-spread-non-const.js +94 -0
  480. package/dist/lint/rules/evl005-resource-block-body.js +41 -0
  481. package/dist/lint/rules/evl007-invalid-siblings.js +105 -0
  482. package/dist/lint/rules/evl009-composite-no-constant.js +158 -0
  483. package/dist/lint/rules/evl010-composite-no-transform.js +59 -0
  484. package/dist/lint/rules/evl011-symbolic-in-template.js +106 -0
  485. package/dist/lint/rules/export-required.js +115 -0
  486. package/dist/lint/rules/file-declarable-limit.js +88 -0
  487. package/dist/lint/rules/flat-declarations.js +62 -0
  488. package/dist/lint/rules/index.js +90 -0
  489. package/dist/lint/rules/no-cyclic-declarable-ref.js +138 -0
  490. package/dist/lint/rules/no-redundant-type-import.js +76 -0
  491. package/dist/lint/rules/no-redundant-value-cast.js +41 -0
  492. package/dist/lint/rules/no-string-ref.js +59 -0
  493. package/dist/lint/rules/no-unused-declarable-import.js +75 -0
  494. package/dist/lint/rules/no-unused-declarable.js +142 -0
  495. package/dist/lint/rules/op/index.js +28 -0
  496. package/dist/lint/rules/op/ops012-activity-contract.js +54 -0
  497. package/dist/lint/rules/op/ops013-step-output-ref.js +88 -0
  498. package/dist/lint/rules/op/ops014-converge-rule-refusals.js +114 -0
  499. package/dist/lint/rules/op/ops015-gate-approval.js +45 -0
  500. package/dist/lint/rules/op/support.js +11 -0
  501. package/dist/lint/rules/op/sys010-key-over-plain-http.js +100 -0
  502. package/dist/lint/rules/property-kind.js +56 -0
  503. package/dist/lint/rules/single-concern-file.js +71 -0
  504. package/dist/lint/rules/sys001-no-literal-key.js +60 -0
  505. package/dist/lint/selectors.js +168 -0
  506. package/dist/lint/suppressions.js +129 -0
  507. package/dist/lint/typescript.js +17 -0
  508. package/dist/live-endpoint.js +113 -0
  509. package/dist/live-read-session.js +76 -0
  510. package/dist/lsp/lexicon-providers.js +163 -0
  511. package/dist/lsp/types.js +5 -0
  512. package/dist/managed-fields.js +293 -0
  513. package/dist/mcp/types.js +4 -0
  514. package/dist/notify-verify.js +0 -1
  515. package/dist/notify.js +0 -1
  516. package/dist/notify.schema.json +72 -0
  517. package/dist/observation.js +266 -0
  518. package/dist/okf-read.js +134 -0
  519. package/dist/okf.js +275 -0
  520. package/dist/op/__fixtures__/decide-stub-backend.js +125 -0
  521. package/dist/op/activities/activity-contracts.d.ts +1 -1
  522. package/dist/op/activities/activity-contracts.js +313 -0
  523. package/dist/op/activities/apply.js +582 -0
  524. package/dist/op/activities/build.js +18 -0
  525. package/dist/op/activities/change-coverage.js +40 -0
  526. package/dist/op/activities/change-set.d.ts +8 -3
  527. package/dist/op/activities/change-set.d.ts.map +1 -1
  528. package/dist/op/activities/change-set.js +67 -0
  529. package/dist/op/activities/converge.js +492 -0
  530. package/dist/op/activities/decide.js +211 -0
  531. package/dist/op/activities/env-teardown.js +110 -0
  532. package/dist/op/activities/factory.js +591 -0
  533. package/dist/op/activities/forge-pull-request.js +82 -0
  534. package/dist/op/activities/gh-host.js +78 -0
  535. package/dist/op/activities/guard-validate.js +165 -0
  536. package/dist/op/activities/http-check.js +40 -0
  537. package/dist/op/activities/index.js +59 -0
  538. package/dist/op/activities/lexicon-upgrade.js +629 -0
  539. package/dist/op/activities/lifecycle.js +109 -0
  540. package/dist/op/activities/pipeline-audit.js +147 -0
  541. package/dist/op/activities/policy.js +26 -0
  542. package/dist/op/activities/predict-behaviour.js +341 -0
  543. package/dist/op/activities/propose-upgrade.js +165 -0
  544. package/dist/op/activities/reconcile.d.ts +28 -16
  545. package/dist/op/activities/reconcile.d.ts.map +1 -1
  546. package/dist/op/activities/reconcile.js +1011 -0
  547. package/dist/op/activities/shell.js +118 -0
  548. package/dist/op/activities/source-release.js +97 -0
  549. package/dist/op/activities/source-rollback.js +165 -0
  550. package/dist/op/activities/teardown.js +17 -0
  551. package/dist/op/activities/util.js +6 -0
  552. package/dist/op/activities/wait.js +51 -0
  553. package/dist/op/activities/work-evidence.js +35 -0
  554. package/dist/op/activities/workflow-audit.js +133 -0
  555. package/dist/op/activity-contract-registry.js +106 -0
  556. package/dist/op/activity-contract.js +232 -0
  557. package/dist/op/activity-failure.js +31 -0
  558. package/dist/op/activity-profiles.js +129 -0
  559. package/dist/op/activity-registry.js +105 -0
  560. package/dist/op/activity-runtime.js +28 -0
  561. package/dist/op/builders.d.ts +1 -1
  562. package/dist/op/builders.d.ts.map +1 -1
  563. package/dist/op/builders.js +801 -0
  564. package/dist/op/change-signal.js +118 -0
  565. package/dist/op/composites/apply-op.js +151 -0
  566. package/dist/op/composites/behaviour-op.js +65 -0
  567. package/dist/op/composites/converge-op.js +141 -0
  568. package/dist/op/composites/index.js +19 -0
  569. package/dist/op/composites/lexicon-upgrade-op.js +107 -0
  570. package/dist/op/composites/pipeline-audit-op.js +50 -0
  571. package/dist/op/composites/reconcile-op.js +84 -0
  572. package/dist/op/composites/watch-op.js +69 -0
  573. package/dist/op/composites/workflow-audit-op.js +57 -0
  574. package/dist/op/converge-rule.js +205 -0
  575. package/dist/op/cron.js +146 -0
  576. package/dist/op/decide-backend.js +181 -0
  577. package/dist/op/decide-config.js +68 -0
  578. package/dist/op/decide-read-inputs.js +141 -0
  579. package/dist/op/discover.d.ts.map +1 -1
  580. package/dist/op/discover.js +213 -0
  581. package/dist/op/duration.js +21 -0
  582. package/dist/op/emulator-freshness.js +80 -0
  583. package/dist/op/emulator-lifecycle.js +269 -0
  584. package/dist/op/factory.js +83 -0
  585. package/dist/op/gate-approval.js +172 -0
  586. package/dist/op/gate-name.js +86 -0
  587. package/dist/op/gate-plan-context.js +167 -0
  588. package/dist/op/gate-point-run.js +67 -0
  589. package/dist/op/gate-point.js +111 -0
  590. package/dist/op/gate-resume-plan.js +155 -0
  591. package/dist/op/gate-resume.js +31 -0
  592. package/dist/op/gate-review.js +290 -0
  593. package/dist/op/gate-run-locator.js +85 -0
  594. package/dist/op/gate-summary.js +100 -0
  595. package/dist/op/gate-wait.js +56 -0
  596. package/dist/op/gate.js +449 -0
  597. package/dist/op/generate-pipeline.js +124 -0
  598. package/dist/op/index.js +51 -0
  599. package/dist/op/lifecycle-receipt-store.js +45 -0
  600. package/dist/op/local-executor.js +1164 -0
  601. package/dist/op/local-output.js +140 -0
  602. package/dist/op/op-ir.js +317 -0
  603. package/dist/op/op-verb-class.js +131 -0
  604. package/dist/op/op-waves-notify.js +136 -0
  605. package/dist/op/op-waves-run.d.ts +1 -1
  606. package/dist/op/op-waves-run.d.ts.map +1 -1
  607. package/dist/op/op-waves-run.js +1657 -0
  608. package/dist/op/op-waves.js +409 -0
  609. package/dist/op/operator.js +743 -0
  610. package/dist/op/receipt-store.js +94 -0
  611. package/dist/op/resource.js +23 -0
  612. package/dist/op/run-context.js +28 -0
  613. package/dist/op/run-live.js +256 -0
  614. package/dist/op/runtime.js +21 -0
  615. package/dist/op/runtimes/local.js +195 -0
  616. package/dist/op/source-archive.js +131 -0
  617. package/dist/op/step-output-ref.js +397 -0
  618. package/dist/op/steward-beside.js +169 -0
  619. package/dist/op/steward-points.js +138 -0
  620. package/dist/op/steward-turn.js +59 -0
  621. package/dist/op/steward.js +317 -0
  622. package/dist/op/types.js +25 -0
  623. package/dist/op/work-lease-decl.js +81 -0
  624. package/dist/op/work-lease-run.js +393 -0
  625. package/dist/override-gate.js +284 -0
  626. package/dist/owner-chain.js +74 -0
  627. package/dist/ownership.js +92 -0
  628. package/dist/params.js +61 -0
  629. package/dist/plan-summary.js +711 -0
  630. package/dist/point-decision.js +211 -0
  631. package/dist/pr-forge.js +810 -0
  632. package/dist/pr-loop.js +203 -0
  633. package/dist/project-codegen.js +191 -0
  634. package/dist/project-root.js +74 -0
  635. package/dist/project-validation.js +50 -0
  636. package/dist/provenance.js +157 -0
  637. package/dist/pseudo-parameter.js +40 -0
  638. package/dist/reconcile.js +450 -0
  639. package/dist/resource-attributes.js +33 -0
  640. package/dist/runtime-adapter.js +77 -0
  641. package/dist/runtime.js +66 -0
  642. package/dist/secret-materialization.js +153 -0
  643. package/dist/secret-provenance.js +250 -0
  644. package/dist/serializer-walker.js +112 -0
  645. package/dist/serializer.js +1 -0
  646. package/dist/sort.js +45 -0
  647. package/dist/stack-output.js +115 -0
  648. package/dist/telemetry-attribution.js +149 -0
  649. package/dist/terraform/__fixtures__/build-graph.js +63 -0
  650. package/dist/terraform/adopt-state.js +40 -0
  651. package/dist/terraform/aws-resources.js +384 -0
  652. package/dist/terraform/bridge.js +204 -0
  653. package/dist/terraform/carve-provider.js +155 -0
  654. package/dist/terraform/carve.js +138 -0
  655. package/dist/terraform/data-source-shape.js +57 -0
  656. package/dist/terraform/emit-source.js +36 -0
  657. package/dist/terraform/excise.js +123 -0
  658. package/dist/terraform/graduate.js +128 -0
  659. package/dist/terraform/graph.js +435 -0
  660. package/dist/terraform/hcl2json.js +60 -0
  661. package/dist/terraform/manifest.js +149 -0
  662. package/dist/terraform/parse.js +81 -0
  663. package/dist/terraform/providers/aws.js +146 -0
  664. package/dist/terraform/providers/gcp.js +294 -0
  665. package/dist/terraform/providers/index.js +17 -0
  666. package/dist/terraform/providers/kubernetes.js +253 -0
  667. package/dist/terraform/score.js +151 -0
  668. package/dist/terraform/state.js +67 -0
  669. package/dist/terraform/tier-map.js +74 -0
  670. package/dist/terraform/types.js +13 -0
  671. package/dist/terraform/unified-diff.js +132 -0
  672. package/dist/testing.js +295 -0
  673. package/dist/toml-emit.js +153 -0
  674. package/dist/toml-parse.js +355 -0
  675. package/dist/toml-utils.js +60 -0
  676. package/dist/toml.js +428 -0
  677. package/dist/ts-loader.d.ts +46 -0
  678. package/dist/ts-loader.d.ts.map +1 -0
  679. package/dist/ts-loader.js +103 -0
  680. package/dist/types.js +1 -0
  681. package/dist/utils.js +61 -0
  682. package/dist/validation.js +20 -0
  683. package/dist/vendor/js-yaml-load.js +1662 -0
  684. package/dist/workspace/__fixtures__/contract-repo.d.ts +7 -0
  685. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  686. package/dist/workspace/__fixtures__/contract-repo.js +119 -0
  687. package/dist/workspace/__fixtures__/decide-workspace.js +112 -0
  688. package/dist/workspace/__fixtures__/sessions.d.ts +1 -1
  689. package/dist/workspace/__fixtures__/sessions.d.ts.map +1 -1
  690. package/dist/workspace/__fixtures__/sessions.js +91 -0
  691. package/dist/workspace/__fixtures__/two-member-workspace.js +34 -0
  692. package/dist/workspace/agent-cli.js +99 -0
  693. package/dist/workspace/agent.schema.json +390 -0
  694. package/dist/workspace/answers-ledger.js +89 -0
  695. package/dist/workspace/box-cli.js +142 -0
  696. package/dist/workspace/box-factory-write.js +196 -0
  697. package/dist/workspace/box-factory-write.schema.json +342 -0
  698. package/dist/workspace/box-factory.js +88 -0
  699. package/dist/workspace/box-intent.js +126 -0
  700. package/dist/workspace/box-isolation.js +169 -0
  701. package/dist/workspace/box-listing-write.schema.json +204 -0
  702. package/dist/workspace/box-listing.js +298 -0
  703. package/dist/workspace/box-publish.js +352 -0
  704. package/dist/workspace/box-publish.schema.json +498 -0
  705. package/dist/workspace/box-services.js +39 -0
  706. package/dist/workspace/broker-protocol.js +359 -0
  707. package/dist/workspace/broker-protocol.schema.json +228 -0
  708. package/dist/workspace/change-set.schema.json +462 -0
  709. package/dist/workspace/changes-cli.js +56 -0
  710. package/dist/workspace/changes.js +446 -0
  711. package/dist/workspace/changes.schema.json +642 -0
  712. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.js +198 -0
  713. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.js +155 -0
  714. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.js +214 -0
  715. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.js +180 -0
  716. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.js +99 -0
  717. package/dist/workspace/chant-migrations/chud-lexicon-exit.js +847 -0
  718. package/dist/workspace/chant-migrations.js +113 -0
  719. package/dist/workspace/check.schema.json +433 -0
  720. package/dist/workspace/checks/box-isolation.js +62 -0
  721. package/dist/workspace/checks/boxes.js +491 -0
  722. package/dist/workspace/checks/diagrams.js +104 -0
  723. package/dist/workspace/checks/generated.js +419 -0
  724. package/dist/workspace/checks/ledgers.js +212 -0
  725. package/dist/workspace/checks/links.js +191 -0
  726. package/dist/workspace/checks/live.js +100 -0
  727. package/dist/workspace/checks/pipelines.js +188 -0
  728. package/dist/workspace/checks/records.js +169 -0
  729. package/dist/workspace/checks.js +426 -0
  730. package/dist/workspace/ci-green-cli.js +125 -0
  731. package/dist/workspace/ci-green-forge.js +83 -0
  732. package/dist/workspace/ci-green-workflow.d.ts +3 -1
  733. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  734. package/dist/workspace/ci-green-workflow.js +202 -0
  735. package/dist/workspace/ci-green.js +325 -0
  736. package/dist/workspace/ci-last-green.schema.json +158 -0
  737. package/dist/workspace/class-data.d.ts +84 -0
  738. package/dist/workspace/class-data.d.ts.map +1 -0
  739. package/dist/workspace/class-data.js +238 -0
  740. package/dist/workspace/compose-graph.js +235 -0
  741. package/dist/workspace/compose-reports.js +146 -0
  742. package/dist/workspace/composites.js +264 -0
  743. package/dist/workspace/composites.schema.json +617 -0
  744. package/dist/workspace/conformance/__fixture__/app/package.json +7 -0
  745. package/dist/workspace/conformance/__fixture__/app/src/server.mjs +29 -0
  746. package/dist/workspace/conformance/__fixture__/decisions/decision.kind.mjs +54 -0
  747. package/dist/workspace/conformance/__fixture__/decisions/decision.schema.json +512 -0
  748. package/dist/workspace/conformance/__fixture__/decisions/fix-001-how-the-app-is-deployed.md +40 -0
  749. package/dist/workspace/conformance/__fixture__/delivery/chant.config.ts +7 -0
  750. package/dist/workspace/conformance/__fixture__/delivery/lexicon/index.ts +26 -0
  751. package/dist/workspace/conformance/__fixture__/delivery/package.json +7 -0
  752. package/dist/workspace/conformance/__fixture__/delivery/src/app.component.ts +14 -0
  753. package/dist/workspace/conformance/__fixture__/delivery/src/app.ts +4 -0
  754. package/dist/workspace/conformance/__fixtures__/reference-broker.js +205 -0
  755. package/dist/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +32 -0
  756. package/dist/workspace/conformance/__writer_fixture__/answers/answer.schema.json +135 -0
  757. package/dist/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
  758. package/dist/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
  759. package/dist/workspace/conformance/__writer_fixture__/decisions/points.json +228 -0
  760. package/dist/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
  761. package/dist/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
  762. package/dist/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +54 -0
  763. package/dist/workspace/conformance/__writer_fixture__/sessions/session.schema.json +289 -0
  764. package/dist/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  765. package/dist/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  766. package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +603 -0
  767. package/dist/workspace/conformance/broker.js +736 -0
  768. package/dist/workspace/conformance/index.js +673 -0
  769. package/dist/workspace/conformance/vitest.js +108 -0
  770. package/dist/workspace/conformance/writer.js +1080 -0
  771. package/dist/workspace/decide.js +879 -0
  772. package/dist/workspace/decision-points.schema.json +135 -135
  773. package/dist/workspace/declaration-write-cli.js +74 -0
  774. package/dist/workspace/declaration-write.js +238 -0
  775. package/dist/workspace/declaration.js +938 -0
  776. package/dist/workspace/declaration.schema.json +1413 -1413
  777. package/dist/workspace/declared-kinds.js +106 -0
  778. package/dist/workspace/environments.js +164 -0
  779. package/dist/workspace/evidence.schema.json +279 -0
  780. package/dist/workspace/export-cli.js +185 -0
  781. package/dist/workspace/export.js +499 -0
  782. package/dist/workspace/factory-rules.js +175 -0
  783. package/dist/workspace/generated-files.js +92 -0
  784. package/dist/workspace/glob.js +45 -0
  785. package/dist/workspace/graph-cache.js +352 -0
  786. package/dist/workspace/graph-cli.js +348 -0
  787. package/dist/workspace/graph.schema.json +1338 -0
  788. package/dist/workspace/identity-gate-rule.js +75 -0
  789. package/dist/workspace/identity.js +126 -0
  790. package/dist/workspace/import.js +283 -0
  791. package/dist/workspace/init.js +620 -0
  792. package/dist/workspace/intent-cli.js +263 -0
  793. package/dist/workspace/intent-joins.js +201 -0
  794. package/dist/workspace/intent-record.js +352 -0
  795. package/dist/workspace/intent-record.schema.json +864 -0
  796. package/dist/workspace/intent.js +1582 -0
  797. package/dist/workspace/intent.schema.json +2420 -0
  798. package/dist/workspace/json-edit.js +221 -0
  799. package/dist/workspace/jsonc.js +235 -0
  800. package/dist/workspace/kind-readers.js +103 -0
  801. package/dist/workspace/kinds.d.ts +3 -7
  802. package/dist/workspace/kinds.d.ts.map +1 -1
  803. package/dist/workspace/kinds.js +553 -0
  804. package/dist/workspace/lexicon-symbols.js +112 -0
  805. package/dist/workspace/lineage-adopt-cli.js +102 -0
  806. package/dist/workspace/lineage-adopt.js +360 -0
  807. package/dist/workspace/lineage-check.js +282 -0
  808. package/dist/workspace/lineage-cli.js +121 -0
  809. package/dist/workspace/lineage-hash-index.js +284 -0
  810. package/dist/workspace/lineage-init.js +507 -0
  811. package/dist/workspace/lineage-lock.js +387 -0
  812. package/dist/workspace/lineage-merge.js +45 -0
  813. package/dist/workspace/lineage-migrations.d.ts.map +1 -1
  814. package/dist/workspace/lineage-migrations.js +329 -0
  815. package/dist/workspace/lineage-provenance.js +36 -0
  816. package/dist/workspace/lineage-update.js +138 -0
  817. package/dist/workspace/lineage-upgrade-cli.js +131 -0
  818. package/dist/workspace/lineage-upgrade.js +889 -0
  819. package/dist/workspace/lineage-vendor.js +171 -0
  820. package/dist/workspace/lineage-version.js +128 -0
  821. package/dist/workspace/lineage-versions.js +275 -0
  822. package/dist/workspace/links.js +330 -0
  823. package/dist/workspace/ls-generated.js +95 -0
  824. package/dist/workspace/ls.js +277 -0
  825. package/dist/workspace/ls.schema.json +333 -0
  826. package/dist/workspace/member-commands.js +508 -0
  827. package/dist/workspace/member-handles.js +216 -0
  828. package/dist/workspace/member-pipeline.js +258 -0
  829. package/dist/workspace/member-run.js +117 -0
  830. package/dist/workspace/member-write.schema.json +193 -0
  831. package/dist/workspace/nested-graph.js +156 -0
  832. package/dist/workspace/nesting.js +38 -0
  833. package/dist/workspace/patch.js +301 -0
  834. package/dist/workspace/patch.schema.json +321 -0
  835. package/dist/workspace/pin-cli.js +55 -0
  836. package/dist/workspace/pin-integrity.js +100 -0
  837. package/dist/workspace/plan-summary.schema.json +155 -0
  838. package/dist/workspace/point-answer.schema.json +129 -129
  839. package/dist/workspace/points-cli.js +291 -0
  840. package/dist/workspace/points-write.schema.json +660 -0
  841. package/dist/workspace/points.js +521 -0
  842. package/dist/workspace/points.schema.json +916 -0
  843. package/dist/workspace/pr-report.schema.json +108 -0
  844. package/dist/workspace/principal-classes.d.ts +4 -11
  845. package/dist/workspace/principal-classes.d.ts.map +1 -1
  846. package/dist/workspace/principal-classes.js +162 -0
  847. package/dist/workspace/reason-codes.js +326 -0
  848. package/dist/workspace/record-assets.js +151 -0
  849. package/dist/workspace/record-decided.js +105 -0
  850. package/dist/workspace/record-sessions.js +96 -0
  851. package/dist/workspace/record-source.js +114 -0
  852. package/dist/workspace/records-amend.schema.json +284 -0
  853. package/dist/workspace/records-checkout.js +76 -0
  854. package/dist/workspace/records-cli.js +707 -0
  855. package/dist/workspace/records-close.js +116 -0
  856. package/dist/workspace/records-close.schema.json +278 -0
  857. package/dist/workspace/records-new.schema.json +202 -0
  858. package/dist/workspace/records-review.schema.json +367 -0
  859. package/dist/workspace/records-since.js +317 -0
  860. package/dist/workspace/records-since.schema.json +218 -0
  861. package/dist/workspace/records-write.js +1229 -0
  862. package/dist/workspace/records.js +1345 -0
  863. package/dist/workspace/records.schema.json +604 -0
  864. package/dist/workspace/reference-kinds/app/package.json +10 -0
  865. package/dist/workspace/reference-kinds/app/workspace-kinds.json +45 -0
  866. package/dist/workspace/returns.js +303 -0
  867. package/dist/workspace/root-refusal.js +75 -0
  868. package/dist/workspace/run-statement.schema.json +622 -0
  869. package/dist/workspace/runs-cli.js +584 -0
  870. package/dist/workspace/runs-write.schema.json +994 -0
  871. package/dist/workspace/runs.js +708 -0
  872. package/dist/workspace/runs.schema.json +1257 -0
  873. package/dist/workspace/runtimes.js +76 -0
  874. package/dist/workspace/session-kinds.js +83 -0
  875. package/dist/workspace/signers.schema.json +206 -0
  876. package/dist/workspace/source-block.js +125 -0
  877. package/dist/workspace/squash.js +177 -0
  878. package/dist/workspace/status-gates.js +162 -0
  879. package/dist/workspace/status-stewards.js +330 -0
  880. package/dist/workspace/status.js +514 -0
  881. package/dist/workspace/status.schema.json +998 -0
  882. package/dist/workspace/steward-index.js +191 -0
  883. package/dist/workspace/symbols.js +188 -0
  884. package/dist/workspace/template-manifest.js +227 -0
  885. package/dist/workspace/template-pins.js +115 -0
  886. package/dist/workspace/trailer-joins.js +65 -0
  887. package/dist/workspace/trailers.js +111 -0
  888. package/dist/workspace/tree.js +136 -0
  889. package/dist/workspace/trust/attestor.js +63 -0
  890. package/dist/workspace/trust/dsse.js +108 -0
  891. package/dist/workspace/trust/evidence-cli.js +175 -0
  892. package/dist/workspace/trust/evidence.js +161 -0
  893. package/dist/workspace/trust/gate-seal.js +226 -0
  894. package/dist/workspace/trust/policy.js +190 -0
  895. package/dist/workspace/trust/provenance.js +180 -0
  896. package/dist/workspace/trust/rotation.js +298 -0
  897. package/dist/workspace/trust/run-commits.js +101 -0
  898. package/dist/workspace/trust/run-statement.js +182 -0
  899. package/dist/workspace/trust/seal.js +272 -0
  900. package/dist/workspace/trust/signers-cli.js +152 -0
  901. package/dist/workspace/trust/ssh-commit.js +178 -0
  902. package/dist/workspace/trust/test-repo.js +118 -0
  903. package/dist/workspace/trust/trust-files.js +193 -0
  904. package/dist/workspace/trust/verify-cli.js +63 -0
  905. package/dist/workspace/trust/verify.js +192 -0
  906. package/dist/workspace/which-chant.js +141 -0
  907. package/dist/workspace/wip-cli.js +95 -0
  908. package/dist/workspace/wip-write.schema.json +504 -0
  909. package/dist/workspace/wip.js +631 -0
  910. package/dist/workspace/wip.schema.json +334 -0
  911. package/dist/workspace/work-cli.js +448 -0
  912. package/dist/workspace/work-evidence.js +122 -0
  913. package/dist/workspace/work-evidence.schema.json +222 -0
  914. package/dist/workspace/work-history.schema.json +421 -0
  915. package/dist/workspace/work-lease.schema.json +246 -0
  916. package/dist/workspace/work-size.js +77 -0
  917. package/dist/workspace/work.js +283 -0
  918. package/dist/workspace/workspace-kinds.schema.json +259 -259
  919. package/dist/workspace/workspace-principals.schema.json +53 -53
  920. package/dist/workspace/write-lock-cli.js +105 -0
  921. package/dist/workspace/write-lock.js +424 -0
  922. package/dist/workspace/write-lock.schema.json +172 -0
  923. package/dist/workspace/write-scope.js +484 -0
  924. package/dist/yaml.js +459 -0
  925. package/package.json +2 -2
  926. package/src/build.ts +8 -0
  927. package/src/change-set.test.ts +48 -0
  928. package/src/change-set.ts +52 -2
  929. package/src/ci-jobs.test.ts +49 -0
  930. package/src/ci-jobs.ts +515 -0
  931. package/src/cli/commands/check-lexicon-plugin.ts +2 -0
  932. package/src/cli/commands/lint.ts +3 -0
  933. package/src/cli/commands/migrate.ts +2 -0
  934. package/src/cli/compiled-cli.e2e.test.ts +165 -0
  935. package/src/cli/main.ts +121 -27
  936. package/src/cli/mcp/workspace-tools.ts +1 -1
  937. package/src/codegen/pinned-upgrade.ts +2 -0
  938. package/src/components/discover.ts +2 -0
  939. package/src/components/pr-pipeline.ts +3 -2
  940. package/src/components/sandbox/driver.ts +3 -1
  941. package/src/composite.ts +15 -3
  942. package/src/config-import.ts +3 -0
  943. package/src/discovery/collect.ts +15 -1
  944. package/src/discovery/fold-build-parity.test.ts +71 -0
  945. package/src/discovery/fold-import.ts +25 -127
  946. package/src/discovery/fold-project-provenance.test.ts +21 -4
  947. package/src/discovery/host-composite.ts +101 -0
  948. package/src/discovery/import.ts +2 -0
  949. package/src/discovery/index.ts +11 -1
  950. package/src/discovery/sandbox/driver.ts +3 -1
  951. package/src/gate-decision-bundle.test.ts +5 -4
  952. package/src/gate-decision-identity.test.ts +59 -6
  953. package/src/gate-decision.ts +81 -21
  954. package/src/lexicon-module.ts +3 -0
  955. package/src/lifecycle/gate-audit.test.ts +55 -1
  956. package/src/lifecycle/gate-audit.ts +116 -3
  957. package/src/lifecycle/gate-ledger.test.ts +20 -1
  958. package/src/lifecycle/gate-ledger.ts +33 -1
  959. package/src/lint/config.ts +1 -1
  960. package/src/lint/discover.ts +28 -8
  961. package/src/lint/policy-import.ts +4 -1
  962. package/src/lint/rule-loader.ts +2 -0
  963. package/src/op/activities/activity-contracts.ts +1 -1
  964. package/src/op/activities/change-set.ts +11 -3
  965. package/src/op/activities/lexicon-upgrade.test.ts +2 -0
  966. package/src/op/activities/reconcile.test.ts +62 -2
  967. package/src/op/activities/reconcile.ts +46 -22
  968. package/src/op/builders.ts +1 -1
  969. package/src/op/discover.ts +3 -0
  970. package/src/op/op-waves-run.ts +4 -3
  971. package/src/op/op-waves.test.ts +2 -0
  972. package/src/op/work-lease-run.test.ts +12 -4
  973. package/src/ts-loader.test.ts +23 -0
  974. package/src/ts-loader.ts +122 -0
  975. package/src/workspace/__fixtures__/contract-repo.ts +17 -1
  976. package/src/workspace/__fixtures__/sessions.ts +3 -3
  977. package/src/workspace/change-set.schema.json +5 -3
  978. package/src/workspace/chant-migrations/chud-lexicon-exit.ts +1 -1
  979. package/src/workspace/ci-green-workflow.test.ts +4 -4
  980. package/src/workspace/ci-green-workflow.ts +5 -2
  981. package/src/workspace/class-data.test.ts +64 -0
  982. package/src/workspace/class-data.ts +236 -0
  983. package/src/workspace/declaration.ts +1 -1
  984. package/src/workspace/kinds.ts +7 -36
  985. package/src/workspace/lineage-migrations.ts +2 -0
  986. package/src/workspace/points.ts +1 -1
  987. package/src/workspace/principal-classes.ts +12 -95
  988. package/src/workspace/records-formats.test.ts +7 -2
  989. package/src/workspace/records-sessions-write.test.ts +3 -3
  990. package/src/workspace/status-contract.e2e.test.ts +37 -0
  991. package/src/workspace/status-contract.test.ts +23 -21
@@ -0,0 +1,1345 @@
1
+ /**
2
+ * Records read through a record kind (#2524 D4), first-test slice (#2546).
3
+ *
4
+ * A record kind is data. It says where its records live, which JSON Schema
5
+ * they follow and which of their states are closed. This module reads every
6
+ * record a kind locates, parses its structured core (the front matter, as the
7
+ * JSON subset of YAML, or the whole file as one I-JSON object, ws-053),
8
+ * validates it against the kind's schema and derives supersession from the
9
+ * records' own `supersedes` links, and remediation from their `remediates`
10
+ * links (#2774): a later record fixing a closed one's consequence without
11
+ * replacing it, which leaves the closed record's status unchanged. It never
12
+ * writes a record; `records-write.ts` does, through the rules here (#2670).
13
+ *
14
+ * A record that fails any of that is still returned, with reason codes, and the
15
+ * read succeeds. Only a failure to read the kind, its schema or the revision is
16
+ * an error. A closed record of a kind that declares `seal` is checked against
17
+ * its whole-file seal ({@link recordSeal}, #2546); author and verdict seals
18
+ * are checked by `records-cli.ts` against the policy at base. Nothing here
19
+ * needs a `chant.workspace.json`.
20
+ *
21
+ * Everything under `workspace/` loads only when a `chant workspace` command
22
+ * runs. The level-0 goldens (#2526) fail if a level-0 command loads it.
23
+ */
24
+ import { createHash } from "node:crypto";
25
+ import { contentDigest, sha256Hex } from "../content-digest.js";
26
+ import { canonicalJson } from "../effect-receipt.js";
27
+ import { readFileSync, statSync } from "node:fs";
28
+ import { dirname, posix, relative, resolve, sep } from "node:path";
29
+ import yaml from "js-yaml";
30
+ import { z } from "zod";
31
+ import { importLexiconModule, registerLexiconDeclarations } from "../lexicon-module.js";
32
+ import { checkPins, pinEntries } from "./record-assets.js";
33
+ import { joinSessions } from "./record-sessions.js";
34
+ import { sourceBlock, sourceBlockProblems, transcriptDrift } from "./source-block.js";
35
+ // ── Reason codes ─────────────────────────────────────────────────────────────
36
+ /**
37
+ * Why one record is not valid. The list is closed: a reader may switch on it,
38
+ * and a new code is a contract change (#2536).
39
+ */
40
+ export const RECORD_REASON_CODES = [
41
+ /** No front matter, a YAML error or a value outside the JSON subset of YAML, or for a JSON kind a file that is not one object or repeats a member name. */
42
+ "record-unparseable",
43
+ /** The record's front matter, or its JSON object, does not match the kind's schema. */
44
+ "record-schema-invalid",
45
+ /** Another record earlier in path order has the same id. */
46
+ "record-id-duplicate",
47
+ /** A `supersedes` link names an id no record has. */
48
+ "record-supersedes-unknown",
49
+ /** A second closed record supersedes a record another one already superseded. */
50
+ "record-supersedes-conflict",
51
+ /** A `remediates` link names an id no record has (#2774). */
52
+ "record-remediates-unknown",
53
+ /** A `remediates` link names a record that isn't closed: a record still open is amended instead (#2774). */
54
+ "record-remediates-not-closed",
55
+ /** A closed record's seal is not the whole-file seal of its text now: it changed after it closed (#2546, ws-063). */
56
+ "record-seal-mismatch",
57
+ /** A closed session's seal is not the digest of its text: it changed after it closed (#2673). */
58
+ "session-seal-mismatch",
59
+ /** A session's verdict names a record the kind's subject records do not have (#2673). */
60
+ "session-verdict-unknown-record",
61
+ ];
62
+ /**
63
+ * Why a record carries a warning. Closed, like the reason codes. A warning
64
+ * never makes a record invalid, and `--current` still lists the record.
65
+ */
66
+ export const RECORD_WARNING_CODES = [
67
+ /** A pinned file's bytes no longer hash to the pinned sha256 (#2549). */
68
+ "asset-drift",
69
+ /** A pinned file does not exist in the tree read (#2549). */
70
+ "asset-missing",
71
+ /**
72
+ * A pinned file is unchanged since a record this one supersedes pinned it at
73
+ * the same hash: the decision changed and the artifact did not follow (#2549).
74
+ */
75
+ "asset-stale",
76
+ /** A supersedes link from a record whose state is weaker than the one it names, so it has no effect yet (#2524 D4). */
77
+ "record-supersedes-pending",
78
+ /**
79
+ * The kind's pins field is an empty list: the record cites no evidence and
80
+ * pins no file. Information for a reviewer, such as a decision made in a
81
+ * product's own design flow with nothing to cite (#2654).
82
+ */
83
+ "record-no-evidence",
84
+ /**
85
+ * A verdict in the kind's reviews list names no `digest`, so it is not bound
86
+ * to the text it judged. It still counts toward the quorum, and an
87
+ * amendment does not stop it counting (#2672).
88
+ */
89
+ "review-undigested",
90
+ /**
91
+ * The kind's source block pins a transcript by hash, the file it names can
92
+ * be read here, and its bytes hash to something else: it is not the
93
+ * transcript the record means (#2708).
94
+ */
95
+ "source-transcript-drift",
96
+ ];
97
+ /**
98
+ * Why a verdict does not count toward a record's quorum (#2671). Closed, like
99
+ * the reason codes. A verdict with none of these counts.
100
+ */
101
+ export const REVIEW_REASON_CODES = [
102
+ /** The reviewer is the record's decider. */
103
+ "review-decider",
104
+ /** The reviewer holds the agent role in the trust policy at base. */
105
+ "review-agent",
106
+ /** A later verdict by the same principal, after normalising, replaces this one. */
107
+ "review-duplicate",
108
+ /** The verdict's digest is not the digest of the record's text now: the record changed after the verdict (#2672). */
109
+ "review-older-digest",
110
+ /**
111
+ * An attestation policy is active at base, and the verdict's seal does not
112
+ * verify for its reviewer: it has none, the reviewer has no key in the
113
+ * signers file, or the signature fails (#2687).
114
+ */
115
+ "review-unattested",
116
+ ];
117
+ /**
118
+ * Why a seal is not attested: a verdict's (#2687), or a record's author seal
119
+ * (#2688). Closed, like the reason codes. Every verdict in the quorum, and
120
+ * every parsed record of a kind with a reviews list, carries one of these in
121
+ * its `attestation`, unless its seal verified.
122
+ */
123
+ export const SEAL_REASON_CODES = [
124
+ /** The verdict or record carries no seal. */
125
+ "seal-missing",
126
+ /** The reviewer or author has no key in the signers file at base. */
127
+ "seal-signer-unlisted",
128
+ /** The seal is malformed, names another signer, or its signature does not verify over the verdict or record. */
129
+ "seal-signature-invalid",
130
+ /** Nothing here can say whose seal it is: there is no signers file at base, or ssh-keygen is not installed. */
131
+ "seal-unverifiable",
132
+ ];
133
+ /**
134
+ * A warning about a record's own seal (#2688), raised by `records` with the
135
+ * policy at base, never by a write. Closed, like the reason codes.
136
+ */
137
+ export const SEAL_WARNING_CODES = [
138
+ /**
139
+ * A signers file is active at base, the record names its author (the
140
+ * kind's `reviews.decider` field), and its seal does not verify for that
141
+ * author: it has none, the author has no key in the file, or the signature
142
+ * fails. The record is still read, and still valid.
143
+ */
144
+ "record-unattested",
145
+ ];
146
+ /**
147
+ * The top-level field that holds a record's author seal (#2688), on a kind
148
+ * with a reviews list. Like the reviews block, the record's digest leaves it
149
+ * out, so sealing a record never moves its digest.
150
+ */
151
+ export const RECORD_SEAL_FIELD = "seal";
152
+ /**
153
+ * The top-level fields {@link recordTextDigest} leaves out for a kind: its
154
+ * reviews list and {@link RECORD_SEAL_FIELD} when it has a reviews list, and
155
+ * nothing when it has none (#2672, #2688). A kind that names a ratified
156
+ * state (`reviews.ratified`, #2873) leaves its state field out too, so
157
+ * moving a record to that state keeps the verdicts that ratified it
158
+ * counting.
159
+ */
160
+ export function digestFields(kind) {
161
+ if (!kind.reviews)
162
+ return null;
163
+ const fields = [kind.reviews.field, RECORD_SEAL_FIELD];
164
+ if (kind.reviews.ratified !== undefined && kind.stateField !== undefined)
165
+ fields.push(kind.stateField);
166
+ // The whole-file seal is written when the record closes (#2546, ws-063), so
167
+ // the verdicts that ratified it keep counting on the sealed record.
168
+ if (kind.seal !== undefined)
169
+ fields.push(kind.seal.field);
170
+ return fields;
171
+ }
172
+ /** The field holding a closed record's whole-file seal: the kind's `seal.field`, or a session kind's `session.seal` (#2546, #2673). */
173
+ export function sealFieldOf(kind) {
174
+ return kind.seal?.field ?? kind.session?.seal;
175
+ }
176
+ /**
177
+ * Why the read as a whole failed. Also closed. The command exits 1 with one of
178
+ * these and returns no records.
179
+ */
180
+ export const READ_ERROR_CODES = [
181
+ /** The kind file is missing or could not be imported. */
182
+ "kind-unreadable",
183
+ /** The kind file exports no `recordKind`, or its shape is wrong. */
184
+ "kind-invalid",
185
+ /** The schema file the kind names is missing or is not JSON. */
186
+ "schema-unreadable",
187
+ /** The schema's `$id` differs from the id the kind names. */
188
+ "schema-id-mismatch",
189
+ /** The schema itself does not compile. */
190
+ "schema-invalid",
191
+ /** The records directory does not exist, in the tree or at the revision. */
192
+ "location-missing",
193
+ /** `--at` was given outside a git repository. */
194
+ "not-a-git-repository",
195
+ /** `--at` names no commit. */
196
+ "revision-unknown",
197
+ ];
198
+ export class RecordReadError extends Error {
199
+ constructor(code, message) {
200
+ super(message);
201
+ this.code = code;
202
+ this.name = "RecordReadError";
203
+ }
204
+ }
205
+ // ── The kind ─────────────────────────────────────────────────────────────────
206
+ const idPattern = /^[a-z][a-z0-9-]*$/;
207
+ /** The formats a kind file may name. Closed. */
208
+ export const RECORD_FORMATS = ["markdown-front-matter", "json"];
209
+ /** The data a kind file exports as `recordKind`. */
210
+ export const recordKindSchema = z
211
+ .object({
212
+ /** The kind's name, such as `decision`. */
213
+ name: z.string().regex(idPattern),
214
+ /** Where the records are, relative to the kind file's directory. */
215
+ location: z
216
+ .object({
217
+ dir: z.string().min(1),
218
+ /** A regular expression the file name must match. Only that directory is read; subdirectories are not. */
219
+ match: z.string().min(1),
220
+ })
221
+ .strict(),
222
+ /**
223
+ * How a file holds its record's structured core: `markdown-front-matter`,
224
+ * the YAML front matter of a Markdown file, or `json`, the whole file as
225
+ * one JSON object (ws-053).
226
+ */
227
+ format: z.enum(RECORD_FORMATS),
228
+ schema: z
229
+ .object({
230
+ /** The schema's `$id`. A schema file with a different `$id` is refused. */
231
+ id: z.string().min(1),
232
+ /** The schema file, relative to the kind file's directory. */
233
+ path: z.string().min(1),
234
+ /**
235
+ * Schema files the schema `$ref`s, each by its `$id` and path relative
236
+ * to the kind file's directory (ws-053). Each is checked for its `$id`
237
+ * and added to the validator before the schema compiles. Optional.
238
+ */
239
+ refs: z.array(z.object({ id: z.string().min(1), path: z.string().min(1) }).strict()).optional(),
240
+ })
241
+ .strict(),
242
+ /** The field holding the record's id. A kind has this or `idFrom`, never both. */
243
+ idField: z.string().min(1).optional(),
244
+ /**
245
+ * `sha256`: the record's id is the lowercase hex SHA-256 of the file's
246
+ * bytes, and the file name's stem (up to its first `.`) is the hash the
247
+ * name claims (ws-053). In place of `idField`.
248
+ */
249
+ idFrom: z.literal("sha256").optional(),
250
+ /**
251
+ * The field holding the record's state. `stateField`, `states` and
252
+ * `closedStates` are given together or not at all; a kind without them
253
+ * has no lifecycle, and its records have state null (ws-053).
254
+ */
255
+ stateField: z.string().min(1).optional(),
256
+ states: z.array(z.string().min(1)).min(1).optional(),
257
+ /** States whose records are final. A `supersedes` link takes effect only from a record in one of them. */
258
+ closedStates: z.array(z.string().min(1)).optional(),
259
+ /**
260
+ * The field of links to superseded records. With `key`, a list of objects
261
+ * whose `key` holds the target id; without it, one id or a list of ids
262
+ * (ws-053). Optional, and only on a kind with states.
263
+ */
264
+ supersedes: z.object({ field: z.string().min(1), key: z.string().min(1).optional() }).strict().optional(),
265
+ /**
266
+ * The field of links to records this one remediates: a later fix to a
267
+ * closed record's consequence, that leaves the closed record's status
268
+ * unchanged (#2774). Shaped like `supersedes`: with `key`, a list of
269
+ * objects whose `key` holds the target id; without it, one id or a list
270
+ * of ids. Optional, and only on a kind with states. A link is valid only
271
+ * when the target exists and is in one of `closedStates`; a target still
272
+ * open is amended instead, never remediated.
273
+ */
274
+ remediates: z.object({ field: z.string().min(1), key: z.string().min(1).optional() }).strict().optional(),
275
+ /**
276
+ * How strongly each state is approved (#2524 D4). With it, a supersedes
277
+ * link takes effect when the new record's rank is above 0 and at least the
278
+ * old record's, "under an equal or stricter approval rule". A state it
279
+ * leaves out ranks 0. Without it, a link takes effect only from a record
280
+ * in a closed state.
281
+ */
282
+ approval: z.record(z.string(), z.number().int().min(0)).optional(),
283
+ /**
284
+ * The front-matter list whose entries may pin a workspace file, as
285
+ * `{path, sha256}` (#2549). Optional: a kind without it pins nothing.
286
+ */
287
+ pins: z.object({ field: z.string().min(1) }).strict().optional(),
288
+ /**
289
+ * The front-matter list of what a record governs. Its `member:<name>` and
290
+ * `path:<path>` entries are the record's links in `chant workspace graph`
291
+ * (#2549). Optional.
292
+ */
293
+ constrains: z.object({ field: z.string().min(1) }).strict().optional(),
294
+ /**
295
+ * The front-matter list of workspace paths a record puts out of scope
296
+ * (#2773): a file or a directory, from the workspace root. `chant
297
+ * workspace check --changes` reports a change to one as
298
+ * `change-out-of-scope` when the record is in hand for the change, and
299
+ * the record does not cover it even where its constrains would.
300
+ * Optional.
301
+ */
302
+ outOfScope: z.object({ field: z.string().min(1) }).strict().optional(),
303
+ /**
304
+ * The front-matter list of review verdicts and the field naming the
305
+ * decider (#2671, #2672). With it, each record gets a digest of its text
306
+ * without that list, and `records --json` computes its quorum. Each
307
+ * entry holds `reviewer`, `verdict` (agree, dissent or abstain) and
308
+ * optionally `digest`, `note`, `proposes`, `addressed_by` and
309
+ * `withdrawn_on`. Optional.
310
+ *
311
+ * `ratified` names the state a record enters only once its quorum is met
312
+ * (#2873): `records new` and `records amend` refuse it below the quorum,
313
+ * with ratify-quorum-not-met. With it the digest leaves the state field
314
+ * out, so the move to that state keeps the verdicts counting. It must be
315
+ * one of `states`.
316
+ */
317
+ reviews: z.object({ field: z.string().min(1), decider: z.string().min(1), ratified: z.string().min(1).optional() }).strict().optional(),
318
+ /**
319
+ * The front-matter field a new record's proposer is named on, when the
320
+ * kind opts in (#2756). `records new --by` and the MCP `records-new`
321
+ * tool's `by` write it for a record that opens in the kind's first
322
+ * state — a proposal naming who proposed it — instead of
323
+ * `reviews.decider`'s field, which `by` fills only when a new record
324
+ * opens straight into another state. Requires `states`. Optional: a kind
325
+ * without it keeps `by` writing `reviews.decider` for every new record,
326
+ * as before #2756.
327
+ */
328
+ proposedBy: z.object({ field: z.string().min(1) }).strict().optional(),
329
+ /**
330
+ * The front-matter object that says where a record came from, opted in
331
+ * to the stored source block (#2708): `via`, `client`, `harness`,
332
+ * `model`, `session`, `turns` and `transcript`, beside the kind's own
333
+ * fields in the same object. With it, those fields are checked for their
334
+ * shapes, a write with `via: "harvest"` must open in the kind's first
335
+ * state, and `records` warns `source-transcript-drift`. Optional.
336
+ */
337
+ source: z.object({ field: z.string().min(1) }).strict().optional(),
338
+ /**
339
+ * A review-session kind (#2673, #2650 C10): the front-matter list of the
340
+ * verdicts a session produced, the field that seals a closed session, and
341
+ * the records its verdicts name, as the kind file that locates them
342
+ * (relative to this kind file's directory), or as `kinds`, several kind
343
+ * files, when a session judges records of more than one kind, such as
344
+ * decisions and contracts (#3148, ws-082). The entries of each such
345
+ * kind's reviews list (its `reviews.field`, or `reviews`) name a session
346
+ * in `session`. Optional.
347
+ *
348
+ * `openedRev`, `closedRev` and `closedOn` (#2693) name the fields that
349
+ * hold the commit a session opened at, the commit it closed at, and when
350
+ * it closed. `records new` writes the first, `records close` the other
351
+ * two, and `records --since <session id>` reads the revisions. Each is
352
+ * optional: without it, nothing writes that field.
353
+ */
354
+ session: z
355
+ .object({
356
+ verdicts: z.string().min(1),
357
+ seal: z.string().min(1),
358
+ subjects: z.union([
359
+ z.object({ kind: z.string().min(1) }).strict(),
360
+ z
361
+ .object({ kinds: z.array(z.string().min(1)).min(1) })
362
+ .strict()
363
+ .refine((x) => new Set(x.kinds).size === x.kinds.length, { message: "session.subjects.kinds lists each kind file once", path: ["kinds"] }),
364
+ ]),
365
+ openedRev: z.string().min(1).optional(),
366
+ closedRev: z.string().min(1).optional(),
367
+ closedOn: z.string().min(1).optional(),
368
+ })
369
+ .strict()
370
+ .optional(),
371
+ /**
372
+ * A work kind (#2683): the front-matter lists of the work ids a record
373
+ * needs and the decision ids it implements, the decision kind file those
374
+ * ids name (relative to this kind file), the state a ready record is in,
375
+ * the state that satisfies a need, and the field holding the closing
376
+ * date. With it, `work.ts` gives each record `ready`, `blockedBy` and
377
+ * `implements`, and the read lists each decision with `implementedBy`.
378
+ * Optional.
379
+ *
380
+ * `acceptance` (#2772) names the front-matter list of a record's
381
+ * acceptance criteria, each an `id`, `text` and the `verification` it
382
+ * expects (`unit`, `integration`, `e2e`, `runtime` or `manual`), and the
383
+ * field naming the record's implementer, whose own `manual` verdict never
384
+ * counts. With it, each record gets `acceptance` (met and total), and a
385
+ * done record with a criterion that no passing evidence meets is warned
386
+ * `work-acceptance-unmet`, which `check` fails on (WSP117). Optional, and
387
+ * a record without the list is read as before.
388
+ *
389
+ * #3147 adds four, each optional:
390
+ *
391
+ * - `contract`: the front-matter field naming the contract record the
392
+ * item builds, and the kind file of those records (relative to this
393
+ * kind file). Each record gets `contract` with that record's state,
394
+ * warned `work-contract-unknown` or `work-contract-undecided`.
395
+ * - `tier`: the field holding the item's builder tier, and the tiers the
396
+ * workspace allows, declared once here. A tier outside them is warned
397
+ * `work-tier-unknown`.
398
+ * - `attempts`: the field holding an item's own attempt limit, and the
399
+ * default `max` for items without one. `work history` counts attempts
400
+ * against it.
401
+ * - `answers`: the answer kind file (relative to this kind file) whose
402
+ * records answer decision points about an item. Each record gets
403
+ * `answers`, the answers whose `constrains` names its id, joined by id
404
+ * rather than copied onto the item.
405
+ */
406
+ work: z
407
+ .object({
408
+ needs: z.string().min(1),
409
+ implements: z.string().min(1),
410
+ decisions: z.string().min(1),
411
+ open: z.string().min(1),
412
+ done: z.string().min(1),
413
+ closedOn: z.string().min(1),
414
+ acceptance: z.object({ field: z.string().min(1), implementer: z.string().min(1) }).strict().optional(),
415
+ contract: z.object({ field: z.string().min(1), kind: z.string().min(1) }).strict().optional(),
416
+ tier: z
417
+ .object({
418
+ field: z.string().min(1),
419
+ tiers: z.array(z.string().min(1)).min(1),
420
+ // What each tier may hold, for the slice-tier point's fits_<tier> inputs (#3150, work-size.ts).
421
+ limits: z.record(z.string().min(1), z.object({ criteria: z.number().int().min(0).optional(), files: z.number().int().min(0).optional(), words: z.number().int().min(0).optional() }).strict()).optional(),
422
+ })
423
+ .strict()
424
+ .refine((t) => new Set(t.tiers).size === t.tiers.length, { message: "work.tier.tiers lists each tier once", path: ["tiers"] })
425
+ .optional(),
426
+ attempts: z.object({ field: z.string().min(1), max: z.number().int().min(1) }).strict().optional(),
427
+ answers: z.string().min(1).optional(),
428
+ })
429
+ .strict()
430
+ .optional(),
431
+ /**
432
+ * An answer kind (ws-058, #2739): the decision points file its records
433
+ * answer, relative to this kind file. The records are written by `chant
434
+ * workspace points ask` and `points answer` in the shape of
435
+ * `point-answer.schema.json`, with an `id`, the states `escalated`,
436
+ * `proposed` and `answered`, and `answered` closed. With it, `records`
437
+ * warns about an answer whose point is gone or changed, `check` validates
438
+ * the points file (WSP116), and `points` lists the open questions.
439
+ * Optional.
440
+ */
441
+ answers: z.object({ points: z.string().min(1) }).strict().optional(),
442
+ /**
443
+ * The top-level field that holds a closed record's seal (#2546, ws-003,
444
+ * ws-063): {@link recordSeal}, the whole-file JCS/SHA-256 of the record
445
+ * without that field. `records amend` and `records new` write it when a
446
+ * record enters one of `closedStates`, and `records` reports a closed
447
+ * record whose seal does not match as `record-seal-mismatch`. Requires
448
+ * states. A session kind's `session.seal` names the same field for a
449
+ * session, and when both are given they must agree. Optional.
450
+ */
451
+ seal: z.object({ field: z.string().min(1) }).strict().optional(),
452
+ /**
453
+ * The kind's current records, with the files they pin, are part of the
454
+ * workspace's spec (#2524 D20): `chant workspace records --current
455
+ * --json` lists them under `spec`. Optional, and false when left out.
456
+ */
457
+ spec: z.boolean().optional(),
458
+ })
459
+ .strict()
460
+ .refine((k) => k.states !== undefined || k.seal === undefined, {
461
+ message: "a kind without states cannot have seal: a record is sealed when it enters a closed state",
462
+ path: ["seal"],
463
+ })
464
+ .refine((k) => k.seal === undefined || k.session === undefined || k.seal.field === k.session.seal, {
465
+ message: "seal.field and session.seal name the field that holds a closed session's seal, and must be the same",
466
+ path: ["seal"],
467
+ })
468
+ .refine((k) => (k.idField === undefined) !== (k.idFrom === undefined), {
469
+ message: "a kind names its id with exactly one of idField and idFrom",
470
+ path: ["idField"],
471
+ })
472
+ .refine((k) => new Set([k.stateField === undefined, k.states === undefined, k.closedStates === undefined]).size === 1, {
473
+ message: "stateField, states and closedStates are given together or not at all",
474
+ path: ["states"],
475
+ })
476
+ .refine((k) => k.states !== undefined || k.supersedes === undefined, {
477
+ message: "a kind without states cannot have supersedes: a link takes effect only from a closed or ranked state",
478
+ path: ["supersedes"],
479
+ })
480
+ .refine((k) => k.states !== undefined || k.remediates === undefined, {
481
+ message: "a kind without states cannot have remediates: a link only ever targets a record in a closed state",
482
+ path: ["remediates"],
483
+ })
484
+ .refine((k) => k.states !== undefined || k.session === undefined, {
485
+ message: "a session kind must have states: a session is sealed when it reaches a closed state",
486
+ path: ["session"],
487
+ })
488
+ .refine((k) => k.states !== undefined || k.approval === undefined, {
489
+ message: "a kind without states cannot have approval ranks",
490
+ path: ["approval"],
491
+ })
492
+ .refine((k) => k.states !== undefined || k.proposedBy === undefined, {
493
+ message: "a kind without states cannot have proposedBy: there is no first state for a record to open in",
494
+ path: ["proposedBy"],
495
+ })
496
+ .refine((k) => k.reviews?.ratified === undefined || (k.states ?? []).includes(k.reviews.ratified), {
497
+ message: "reviews.ratified must be one of states: it names the state a record enters once its quorum is met",
498
+ path: ["reviews", "ratified"],
499
+ })
500
+ .refine((k) => (k.closedStates ?? []).every((s) => (k.states ?? []).includes(s)), {
501
+ message: "every closed state must be listed in states",
502
+ path: ["closedStates"],
503
+ })
504
+ .refine((k) => Object.keys(k.approval ?? {}).every((s) => (k.states ?? []).includes(s)), {
505
+ message: "every state approval ranks must be listed in states",
506
+ path: ["approval"],
507
+ })
508
+ .refine((k) => !k.work || (k.states !== undefined && k.states.includes(k.work.open) && k.states.includes(k.work.done)), {
509
+ message: "a work kind must have states, and its work open and done states must be listed in them",
510
+ path: ["work"],
511
+ })
512
+ .refine((k) => !k.work?.acceptance || k.pins !== undefined, {
513
+ message: "a work kind with acceptance criteria must have pins: a criterion is met by the evidence in its pins field",
514
+ path: ["work", "acceptance"],
515
+ })
516
+ .refine((k) => !k.answers ||
517
+ (k.format === "markdown-front-matter" &&
518
+ k.idField === "id" &&
519
+ k.stateField === "state" &&
520
+ ANSWER_STATES.every((s) => k.states?.includes(s)) &&
521
+ (k.closedStates ?? []).includes("answered")), {
522
+ message: 'an answer kind is markdown-front-matter with idField "id", stateField "state", the states escalated, proposed and answered, and answered closed',
523
+ path: ["answers"],
524
+ });
525
+ /** The states of an answer kind's records (ws-058): open for people, proposed by a model, answered by a table, people, or a confirmed proposal. */
526
+ export const ANSWER_STATES = ["escalated", "proposed", "answered"];
527
+ /** The kind files a session kind's verdicts name records of, relative to the session kind file: `subjects.kind`, or each of `subjects.kinds` (ws-082). */
528
+ export function sessionSubjectKinds(session) {
529
+ return "kinds" in session.subjects ? session.subjects.kinds : [session.subjects.kind];
530
+ }
531
+ /**
532
+ * A session kind's subject records, each subject kind read from `source`
533
+ * (#2673, ws-082), for {@link readRecords}' `subjects`. Undefined for a kind
534
+ * that is not a session kind.
535
+ */
536
+ export async function readSessionSubjects(loaded, root, source) {
537
+ if (!loaded.kind.session)
538
+ return undefined;
539
+ const out = [];
540
+ for (const file of sessionSubjectKinds(loaded.kind.session)) {
541
+ const subjectKind = await loadRecordKind(resolve(dirname(loaded.file), file), root);
542
+ out.push({ kind: file, records: (await readRecords(subjectKind, { root, source })).records, reviews: subjectKind.kind.reviews?.field ?? "reviews" });
543
+ }
544
+ return out;
545
+ }
546
+ /**
547
+ * Import the kind file at `path` through the path loader lexicons use (#2520).
548
+ * The kind is registered under a name no lexicon package can have (npm names
549
+ * hold no `:`), imported and unregistered again, so no lexicon lookup sees it.
550
+ */
551
+ export async function importKindModule(path) {
552
+ const name = `record-kind:${path}`;
553
+ registerLexiconDeclarations([{ name, module: path }], dirname(path));
554
+ try {
555
+ const mod = await importLexiconModule(name);
556
+ if (!mod)
557
+ throw new Error(`${path} was not registered`);
558
+ return mod;
559
+ }
560
+ finally {
561
+ registerLexiconDeclarations([name], dirname(path));
562
+ }
563
+ }
564
+ /** Load a kind file and the schema it names. `path` is resolved against `cwd`. */
565
+ export async function loadRecordKind(path, cwd = process.cwd()) {
566
+ const file = resolve(cwd, path);
567
+ let mod;
568
+ try {
569
+ statSync(file);
570
+ mod = await importKindModule(file);
571
+ }
572
+ catch (err) {
573
+ throw new RecordReadError("kind-unreadable", `kind file ${path} could not be loaded: ${message(err)}`);
574
+ }
575
+ const parsed = recordKindSchema.safeParse(mod.recordKind);
576
+ if (!parsed.success) {
577
+ const detail = mod.recordKind === undefined
578
+ ? "it has no recordKind export"
579
+ : parsed.error.issues.map((i) => `${i.path.join(".") || "recordKind"}: ${i.message}`).join("; ");
580
+ throw new RecordReadError("kind-invalid", `kind file ${path} is not a record kind: ${detail}`);
581
+ }
582
+ const kind = parsed.data;
583
+ const base = dirname(file);
584
+ const schema = readSchemaFile(kind, base, path, kind.schema);
585
+ const refs = (kind.schema.refs ?? []).map((ref) => readSchemaFile(kind, base, path, ref));
586
+ return { kind, file, dir: resolve(base, kind.location.dir), schema, refs };
587
+ }
588
+ /** A schema file a kind names, refused when it can't be read or its `$id` is not the one the kind names. */
589
+ function readSchemaFile(kind, base, kindPath, named) {
590
+ let schema;
591
+ try {
592
+ schema = JSON.parse(readFileSync(resolve(base, named.path), "utf-8"));
593
+ }
594
+ catch (err) {
595
+ throw new RecordReadError("schema-unreadable", `schema ${named.path} named by ${kindPath} could not be read: ${message(err)}`);
596
+ }
597
+ if (schema === null || typeof schema !== "object" || schema.$id !== named.id) {
598
+ throw new RecordReadError("schema-id-mismatch", `kind ${kind.name} names schema ${named.id}, but ${named.path} has $id ${JSON.stringify(schema?.$id)}`);
599
+ }
600
+ return schema;
601
+ }
602
+ /**
603
+ * The front matter of a Markdown file, limited to what YAML and JSON share
604
+ * (#2524 D4). Line endings are normalised first. Aliases, and any value JSON
605
+ * cannot hold, are refused, so the same text always reads as the same JSON.
606
+ */
607
+ export function parseFrontMatter(text) {
608
+ const normalised = text.replace(/\r\n?/g, "\n");
609
+ const m = normalised.match(/^---\n([\s\S]*?)\n---(?:\n|$)/);
610
+ if (!m)
611
+ return { ok: false, message: "no front matter: the file must start with a --- line and close it with another" };
612
+ let value;
613
+ try {
614
+ value = yaml.load(m[1], { schema: yaml.JSON_SCHEMA });
615
+ }
616
+ catch (err) {
617
+ return { ok: false, message: `front matter is not valid YAML: ${message(err).split("\n")[0]}` };
618
+ }
619
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
620
+ return { ok: false, message: "front matter must be a mapping" };
621
+ }
622
+ const problem = nonJson(value, "", new Set());
623
+ if (problem)
624
+ return { ok: false, message: problem };
625
+ return { ok: true, value: value };
626
+ }
627
+ // ── JSON records ─────────────────────────────────────────────────────────────
628
+ /**
629
+ * A JSON record: the whole file is one object, read as I-JSON (RFC 7493)
630
+ * requires on the two points JSON.parse lets through (ws-053). A top-level
631
+ * value other than an object is refused, and so is a member name that repeats
632
+ * within one object, at any depth, which JSON.parse accepts by keeping the
633
+ * last value. Names compare after their escapes are decoded, so `"a"` and
634
+ * `"\u0061"` are the same name. A number too large for a double is refused as
635
+ * front matter's is.
636
+ */
637
+ export function parseJsonRecord(text) {
638
+ let value;
639
+ try {
640
+ value = JSON.parse(text);
641
+ }
642
+ catch (err) {
643
+ return { ok: false, message: `not valid JSON: ${message(err).split("\n")[0]}` };
644
+ }
645
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
646
+ return { ok: false, message: "a JSON record must be one object at the top level" };
647
+ }
648
+ const scan = scanJson(text);
649
+ if (scan.duplicate)
650
+ return { ok: false, message: `${scan.duplicate.at || "/"}: member name ${JSON.stringify(scan.duplicate.name)} repeats, which I-JSON refuses` };
651
+ const problem = nonJson(value, "", new Set());
652
+ if (problem)
653
+ return { ok: false, message: problem };
654
+ return { ok: true, value: value };
655
+ }
656
+ /** A record's structured core, parsed as the kind's format says. */
657
+ export function parseRecord(format, text) {
658
+ return format === "json" ? parseJsonRecord(text) : parseFrontMatter(text);
659
+ }
660
+ /**
661
+ * One pass over text JSON.parse accepted: the top-level object's members, with
662
+ * where each starts and ends, and the first member name that repeats within
663
+ * one object at any depth.
664
+ */
665
+ function scanJson(text) {
666
+ const members = [];
667
+ // One frame per open object or array: an object's names so far, or null for an array.
668
+ const stack = [];
669
+ let pendingTop;
670
+ let valueStart = -1;
671
+ const closeValue = (end) => {
672
+ // A value just ended at `end`; if it is a top-level member's value, record the member.
673
+ if (stack.length === 1 && pendingTop) {
674
+ members.push({ ...pendingTop, end });
675
+ pendingTop = undefined;
676
+ }
677
+ };
678
+ let i = 0;
679
+ while (i < text.length) {
680
+ const c = text[i];
681
+ if (c === '"') {
682
+ let j = i + 1;
683
+ while (text[j] !== '"')
684
+ j += text[j] === "\\" ? 2 : 1;
685
+ const raw = text.slice(i, j + 1);
686
+ let k = j + 1;
687
+ while (k < text.length && /[ \t\n\r]/.test(text[k]))
688
+ k++;
689
+ const top = stack[stack.length - 1];
690
+ if (text[k] === ":" && top?.names) {
691
+ const name = JSON.parse(raw);
692
+ if (top.names.has(name))
693
+ return { members, duplicate: { name, at: top.path } };
694
+ top.names.add(name);
695
+ top.key = name;
696
+ if (stack.length === 1)
697
+ pendingTop = { name, start: i };
698
+ i = k + 1;
699
+ continue;
700
+ }
701
+ closeValue(j + 1);
702
+ i = j + 1;
703
+ continue;
704
+ }
705
+ if (c === "{" || c === "[") {
706
+ const parent = stack[stack.length - 1];
707
+ const at = parent ? `${parent.path}/${parent.names ? parent.key : parent.index}` : "";
708
+ stack.push({ names: c === "{" ? new Set() : null, path: at, index: 0 });
709
+ i++;
710
+ continue;
711
+ }
712
+ if (c === "}" || c === "]") {
713
+ stack.pop();
714
+ closeValue(i + 1);
715
+ i++;
716
+ continue;
717
+ }
718
+ if (c === ",") {
719
+ const top = stack[stack.length - 1];
720
+ if (top && !top.names)
721
+ top.index++;
722
+ i++;
723
+ continue;
724
+ }
725
+ if (/[ \t\n\r:]/.test(c)) {
726
+ i++;
727
+ continue;
728
+ }
729
+ // A number, true, false or null.
730
+ valueStart = i;
731
+ while (i < text.length && !/[ \t\n\r,\]}]/.test(text[i]))
732
+ i++;
733
+ if (valueStart < i)
734
+ closeValue(i);
735
+ }
736
+ return { members };
737
+ }
738
+ /** The first thing in `v` that JSON cannot say, or that only an alias can produce. */
739
+ function nonJson(v, at, seen) {
740
+ if (v === null || typeof v === "string" || typeof v === "boolean")
741
+ return undefined;
742
+ if (typeof v === "number")
743
+ return Number.isFinite(v) ? undefined : `${at || "/"}: ${v} is not a JSON number`;
744
+ if (typeof v !== "object")
745
+ return `${at || "/"}: a ${typeof v} is not a JSON value`;
746
+ if (seen.has(v))
747
+ return `${at || "/"}: YAML aliases are not allowed`;
748
+ seen.add(v);
749
+ if (Array.isArray(v)) {
750
+ for (let i = 0; i < v.length; i++) {
751
+ const p = nonJson(v[i], `${at}/${i}`, seen);
752
+ if (p)
753
+ return p;
754
+ }
755
+ return undefined;
756
+ }
757
+ if (Object.getPrototypeOf(v) !== Object.prototype)
758
+ return `${at || "/"}: not a plain mapping`;
759
+ for (const [k, x] of Object.entries(v)) {
760
+ const p = nonJson(x, `${at}/${k}`, seen);
761
+ if (p)
762
+ return p;
763
+ }
764
+ return undefined;
765
+ }
766
+ // ── Record digest ────────────────────────────────────────────────────────────
767
+ /**
768
+ * The digest a review verdict names: the lowercase hex SHA-256 of the record
769
+ * file's text with its reviews block taken out of the front matter (#2672), or
770
+ * for a JSON record its reviews member taken out of the object (ws-053).
771
+ * Adding, changing or removing a verdict leaves it as it was; any other edit
772
+ * to the file changes it, so a verdict given before an amendment stops
773
+ * counting.
774
+ *
775
+ * `field` names the top-level fields left out: one name, or a list. For a
776
+ * kind with a reviews list the list is the reviews field and `seal`, the
777
+ * record's author seal (#2688; {@link digestFields}), so sealing a record
778
+ * leaves its digest where it was too. A record with no `seal` hashes exactly
779
+ * as it did before author seals existed. A kind with `reviews.ratified` adds
780
+ * its state field (#2873), and a kind with `seal` its seal field (#2546), so
781
+ * the whole-file seal written at the close moves no digest. The digest is not
782
+ * that seal: {@link recordSeal} covers the whole record.
783
+ *
784
+ * The rule, which a hand-editor can follow with a text editor and
785
+ * `sha256sum`:
786
+ *
787
+ * 1. Line endings become LF (CRLF and a lone CR each become one LF).
788
+ * 2. When the text starts with a `---` line and a later line is exactly
789
+ * `---`, the lines between them are the front matter. In it, each line
790
+ * that starts, at column 0, with one of the keys (bare, or in single or
791
+ * double quotes), optional spaces or tabs and a `:`, is removed, and so
792
+ * is every line after it, up to the closing `---`, that is empty or
793
+ * starts with a space, a tab, `#` or `-`. Removal stops at the first
794
+ * other line. Everything else, the `---` lines and the body included, is
795
+ * kept byte for byte. Which key is removed first makes no difference.
796
+ * 3. The digest is the SHA-256 of the result's UTF-8 bytes.
797
+ *
798
+ * Text with no front matter, or none of the keys, is hashed after step 1
799
+ * alone. A writer that adds a verdict must change only the reviews block: a
800
+ * reformatted front matter is a new digest, and every earlier verdict stops
801
+ * counting.
802
+ */
803
+ export function recordTextDigest(text, field = "reviews", format = "markdown-front-matter") {
804
+ const lf = text.replace(/\r\n?/g, "\n");
805
+ const fields = field === null ? [] : typeof field === "string" ? [field] : field;
806
+ const kept = format === "json" ? fields.reduce(withoutMember, lf) : withoutBlocks(lf, fields);
807
+ return createHash("sha256").update(kept, "utf8").digest("hex");
808
+ }
809
+ // ── Record seal ──────────────────────────────────────────────────────────────
810
+ /**
811
+ * The seal of a closed record (#2546, ws-003, ws-063): `sha256:` and the
812
+ * lowercase hex SHA-256 of the RFC 8785 (JCS) form of the whole record, with
813
+ * only the field that holds the seal left out. It is a separate hash from
814
+ * {@link recordTextDigest}, which a review verdict names: the digest leaves
815
+ * out the reviews, the author seal and the state so verdicts keep counting,
816
+ * and the seal covers all of them.
817
+ *
818
+ * 1. Line endings become LF (CRLF and a lone CR each become one LF).
819
+ * 2. The structured core is parsed as the kind's format says: the front
820
+ * matter, as the JSON subset of YAML, or the whole JSON object. The
821
+ * top-level member `field` is removed from it.
822
+ * 3. For a Markdown record the value hashed is the object
823
+ * `{"body": <the text after the closing --- line>, "core": <the core>}`;
824
+ * for a JSON record it is the core itself.
825
+ * 4. That value is written in JCS (keys sorted by UTF-16 code units, no
826
+ * whitespace, ECMAScript number and string forms) and hashed as UTF-8.
827
+ *
828
+ * So every value in the record and every byte of its body is sealed, the
829
+ * reviews, the author seal and the state included, while the key order,
830
+ * quoting and indentation of the front matter are not: the same record
831
+ * written another way has the same seal. A YAML comment in the front matter
832
+ * is not part of the record, so it is not sealed either (#3066). Returns null when
833
+ * the core can't be parsed.
834
+ */
835
+ export function recordSeal(text, field, format = "markdown-front-matter") {
836
+ const lf = text.replace(/\r\n?/g, "\n");
837
+ const parsed = parseRecord(format, lf);
838
+ if (!parsed.ok)
839
+ return null;
840
+ const core = { ...parsed.value };
841
+ delete core[field];
842
+ const sealed = format === "json" ? core : { body: bodyText(lf), core };
843
+ return contentDigest(canonicalJson(sealed));
844
+ }
845
+ /** The text below a Markdown file's front matter, with LF line endings: everything after the closing `---` line. */
846
+ export function bodyText(text) {
847
+ const normalised = text.replace(/\r\n?/g, "\n");
848
+ const m = normalised.match(/^---\n[\s\S]*?\n---(?:\n|$)/);
849
+ return m ? normalised.slice(m[0].length) : "";
850
+ }
851
+ /**
852
+ * `text` with the top-level member `field` removed from a JSON record, by the
853
+ * rule a hand-editor follows for a JSON record (ws-053). With several fields
854
+ * (the reviews member, then `seal`, #2688) the rule is applied to each in
855
+ * turn, to the text the previous one left:
856
+ *
857
+ * 1. Line endings become LF, as for Markdown.
858
+ * 2. When the text is a JSON record (one object, no repeated member name) and
859
+ * its top-level object has a member named `field`, that member is deleted:
860
+ * the whitespace right before its name, the name, the colon, the value,
861
+ * and one comma with the whitespace right before that comma. The comma is
862
+ * the one after the value when another member follows, or else the one
863
+ * before the member, when a member precedes it. Nothing else changes: other
864
+ * members, their order, the indentation and the final newline stay byte
865
+ * for byte.
866
+ * 3. The digest is the SHA-256 of the result's UTF-8 bytes.
867
+ *
868
+ * So in a pretty-printed file, deleting the `"reviews": [...]` lines and the
869
+ * comma that separated the member from its neighbour gives the text to hash.
870
+ * Text that is not a JSON record, or has no such member, is hashed after step 1
871
+ * alone. A writer that adds a verdict changes only that member's value.
872
+ */
873
+ function withoutMember(text, field) {
874
+ const parsed = parseJsonRecord(text);
875
+ if (!parsed.ok)
876
+ return text;
877
+ const { members } = scanJson(text);
878
+ const at = members.findIndex((m) => m.name === field);
879
+ if (at < 0)
880
+ return text;
881
+ const m = members[at];
882
+ let start = m.start;
883
+ while (start > 0 && /[ \t\n]/.test(text[start - 1]))
884
+ start--;
885
+ let end = m.end;
886
+ if (at + 1 < members.length) {
887
+ // The comma after the value, and the whitespace before it.
888
+ let k = end;
889
+ while (/[ \t\n]/.test(text[k]))
890
+ k++;
891
+ end = k + 1;
892
+ }
893
+ else if (at > 0) {
894
+ // The comma before the member, and the whitespace between it and the previous value.
895
+ let k = start - 1;
896
+ while (k > 0 && text[k] !== ",")
897
+ k--;
898
+ while (k > 0 && /[ \t\n]/.test(text[k - 1]))
899
+ k--;
900
+ start = k;
901
+ }
902
+ return text.slice(0, start) + text.slice(end);
903
+ }
904
+ /** `text` with the top-level blocks of `fields` removed from its front matter, by the rule {@link recordTextDigest} states. */
905
+ function withoutBlocks(text, fields) {
906
+ if (fields.length === 0)
907
+ return text;
908
+ const lines = text.split("\n");
909
+ if (lines[0] !== "---")
910
+ return text;
911
+ const close = lines.indexOf("---", 1);
912
+ if (close < 0)
913
+ return text;
914
+ const keys = fields.map((f) => f.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).map((k) => `${k}|"${k}"|'${k}'`);
915
+ const starts = new RegExp(`^(?:${keys.join("|")})[ \\t]*:(?:[ \\t]|$)`);
916
+ const out = [];
917
+ for (let i = 0; i < lines.length; i++) {
918
+ if (i > 0 && i < close && starts.test(lines[i])) {
919
+ while (i + 1 < close && /^(?:$|[ \t#-])/.test(lines[i + 1]))
920
+ i++;
921
+ continue;
922
+ }
923
+ out.push(lines[i]);
924
+ }
925
+ return out.join("\n");
926
+ }
927
+ // ── Quorum ───────────────────────────────────────────────────────────────────
928
+ /** The quorum a workspace sets when its declaration names none: two verdicts besides the decider's (#2555). */
929
+ export const DEFAULT_QUORUM = 2;
930
+ /** A principal as the quorum compares it: NFKC, trimmed and lower-cased, so `alice` and `Alice ` are one reviewer (#2671). */
931
+ export function normalisePrincipal(name) {
932
+ return name.normalize("NFKC").trim().toLowerCase();
933
+ }
934
+ /** The check used when {@link QuorumOptions.verifySeal} is not given. */
935
+ function uncheckedSeal(attestation, v) {
936
+ if (v.seal === undefined || v.seal === null) {
937
+ return { attested: attestation ? false : null, code: "seal-missing", message: `the verdict by ${v.reviewer} carries no seal` };
938
+ }
939
+ return { attested: null, code: "seal-unverifiable", message: `the seal by ${v.reviewer} was not checked` };
940
+ }
941
+ const VERDICTS = new Set(["agree", "dissent", "abstain"]);
942
+ /**
943
+ * The quorum of one record, read from its reviews list, or null when the
944
+ * kind has no reviews list or the front matter could not be read (#2671).
945
+ * Malformed entries are skipped; the schema reports them.
946
+ *
947
+ * A verdict is not counted when its reviewer is the decider, holds the agent
948
+ * role, names a digest other than the record's own now, or, under an active
949
+ * attestation policy, carries no seal that verifies for its reviewer (#2687),
950
+ * in that order. Of the rest, the latest verdict per principal counts and
951
+ * each earlier one is a duplicate. Every verdict reports its seal's check in
952
+ * `attested` and `attestation`, whether it counts or not.
953
+ */
954
+ export function computeQuorum(kind, entry, options) {
955
+ if (!kind.reviews || entry.data === null)
956
+ return null;
957
+ const list = entry.data[kind.reviews.field];
958
+ const decidedBy = entry.data[kind.reviews.decider];
959
+ const decider = typeof decidedBy === "string" ? normalisePrincipal(decidedBy) : null;
960
+ const idValue = entry.id !== undefined ? entry.id : kind.idField !== undefined ? entry.data[kind.idField] : null;
961
+ const record = typeof idValue === "string" ? idValue : null;
962
+ const verdicts = [];
963
+ const openConcerns = [];
964
+ (Array.isArray(list) ? list : []).forEach((raw, index) => {
965
+ if (raw === null || typeof raw !== "object")
966
+ return;
967
+ const r = raw;
968
+ if (typeof r.reviewer !== "string" || typeof r.verdict !== "string" || !VERDICTS.has(r.verdict))
969
+ return;
970
+ const digest = typeof r.digest === "string" ? r.digest : null;
971
+ const input = { record, reviewer: r.reviewer, verdict: r.verdict, on: r.on, digest, seal: r.seal };
972
+ const { attested, ...attestation } = options.verifySeal ? options.verifySeal(input) : uncheckedSeal(options.attestation, input);
973
+ const v = {
974
+ index,
975
+ principal: normalisePrincipal(r.reviewer),
976
+ reviewer: r.reviewer,
977
+ verdict: r.verdict,
978
+ digest,
979
+ attested,
980
+ attestation,
981
+ };
982
+ if (v.principal === decider) {
983
+ v.reason = { code: "review-decider", message: `${v.reviewer} decided this record, and the quorum counts verdicts besides the decider's` };
984
+ }
985
+ else if (options.agents.has(v.principal)) {
986
+ v.reason = { code: "review-agent", message: `${v.reviewer} holds the agent role in the trust policy at base, and an agent's verdict does not count` };
987
+ }
988
+ else if (v.digest !== null && v.digest !== entry.digest) {
989
+ v.reason = { code: "review-older-digest", message: `${v.reviewer} judged the text at digest ${v.digest.slice(0, 12)}, and the record's text is now at ${entry.digest.slice(0, 12)}` };
990
+ }
991
+ else if (options.attestation && attested !== true) {
992
+ v.reason = { code: "review-unattested", message: `an attestation policy is active at base, and ${attestation.message}` };
993
+ }
994
+ verdicts.push(v);
995
+ if (v.verdict === "dissent" && r.addressed_by == null && r.withdrawn_on == null) {
996
+ openConcerns.push({
997
+ index,
998
+ principal: v.principal,
999
+ reviewer: v.reviewer,
1000
+ note: typeof r.note === "string" ? r.note : null,
1001
+ ...(typeof r.proposes === "string" ? { proposes: r.proposes } : {}),
1002
+ });
1003
+ }
1004
+ });
1005
+ // The latest verdict per principal stands; earlier ones are duplicates.
1006
+ const latest = new Map();
1007
+ for (const v of verdicts)
1008
+ if (!v.reason)
1009
+ latest.set(v.principal, v);
1010
+ for (const v of verdicts) {
1011
+ if (v.reason || latest.get(v.principal) === v)
1012
+ continue;
1013
+ const later = latest.get(v.principal);
1014
+ v.reason = { code: "review-duplicate", message: `the later verdict by ${JSON.stringify(later.reviewer)} (entry ${later.index}) replaces this one, and a principal counts once` };
1015
+ }
1016
+ const counted = verdicts.filter((v) => !v.reason);
1017
+ const agreed = counted.filter((v) => v.verdict === "agree").length;
1018
+ const met = agreed >= options.need;
1019
+ return {
1020
+ need: options.need,
1021
+ needFrom: options.needFrom,
1022
+ agreed,
1023
+ counted,
1024
+ notCounted: verdicts.filter((v) => v.reason),
1025
+ openConcerns,
1026
+ met,
1027
+ metWithObjections: met && openConcerns.length > 0,
1028
+ };
1029
+ }
1030
+ /**
1031
+ * ajv's errors as `<path> <message>` lines. A failed `if` whose `then` or
1032
+ * `else` branch has a `description` is reported by that description alone, in
1033
+ * place of the branch's own errors, with each `{field}` filled from the value
1034
+ * the branch checked. The decision schema words its dissent rule this way, so
1035
+ * the message names the reviewer (#2652). The schema stays plain JSON Schema,
1036
+ * with no keyword a strict validator would refuse.
1037
+ */
1038
+ function renderSchemaErrors(errors) {
1039
+ const replaced = [];
1040
+ const described = new Map();
1041
+ for (const e of errors) {
1042
+ const branch = e.keyword === "if" ? e.params?.failingKeyword : undefined;
1043
+ const text = branch ? e.parentSchema?.[branch]?.description : undefined;
1044
+ if (!branch || typeof text !== "string")
1045
+ continue;
1046
+ const at = e.data !== null && typeof e.data === "object" ? e.data : {};
1047
+ described.set(e, text.replace(/\{([A-Za-z0-9_]+)\}/g, (all, key) => (typeof at[key] === "string" ? at[key] : all)));
1048
+ replaced.push(`${e.schemaPath.replace(/\/if$/, "")}/${branch}/`);
1049
+ }
1050
+ return errors
1051
+ .filter((e) => described.has(e) || !replaced.some((prefix) => e.schemaPath.startsWith(prefix)))
1052
+ .map((e) => `${e.instancePath || "/"} ${described.get(e) ?? e.message ?? "is invalid"}`);
1053
+ }
1054
+ async function compileSchema(schema, refs = []) {
1055
+ const mod = (await import("ajv"));
1056
+ // ajv is CommonJS; its class is the default export, or that export's own default.
1057
+ const Ajv = (mod.default.default ?? mod.default);
1058
+ let validate;
1059
+ try {
1060
+ const ajv = new Ajv({ allErrors: true, strict: false, verbose: true });
1061
+ // The files the schema $refs, registered by their $id first (ws-053).
1062
+ for (const ref of refs)
1063
+ ajv.addSchema(ref);
1064
+ validate = ajv.compile(schema);
1065
+ }
1066
+ catch (err) {
1067
+ throw new RecordReadError("schema-invalid", `the kind's schema does not compile: ${message(err)}`);
1068
+ }
1069
+ return (data) => (validate(data) ? { ok: true } : { ok: false, errors: renderSchemaErrors(validate.errors ?? []) });
1070
+ }
1071
+ /**
1072
+ * The ids a record's supersedes field names (ws-053). With the kind's `key`,
1073
+ * the field is a list of objects and each one's `key` holds an id; without
1074
+ * it, the field holds one id or a list of ids. Anything else names none.
1075
+ */
1076
+ export function supersedesTargets(kind, data) {
1077
+ if (!kind.supersedes || data === null)
1078
+ return [];
1079
+ const { field, key } = kind.supersedes;
1080
+ const value = data[field];
1081
+ if (key === undefined && typeof value === "string")
1082
+ return [value];
1083
+ if (!Array.isArray(value))
1084
+ return [];
1085
+ const ids = key === undefined ? value : value.map((l) => (l !== null && typeof l === "object" ? l[key] : undefined));
1086
+ return ids.filter((x) => typeof x === "string");
1087
+ }
1088
+ /**
1089
+ * The ids a record's remediates field names (#2774), read the same way
1090
+ * {@link supersedesTargets} reads `supersedes`.
1091
+ */
1092
+ export function remediatesTargets(kind, data) {
1093
+ if (!kind.remediates || data === null)
1094
+ return [];
1095
+ const { field, key } = kind.remediates;
1096
+ const value = data[field];
1097
+ if (key === undefined && typeof value === "string")
1098
+ return [value];
1099
+ if (!Array.isArray(value))
1100
+ return [];
1101
+ const ids = key === undefined ? value : value.map((l) => (l !== null && typeof l === "object" ? l[key] : undefined));
1102
+ return ids.filter((x) => typeof x === "string");
1103
+ }
1104
+ /** The stem of a file name: the name up to its first `.`, the hash a content-addressed record's name claims (ws-053). */
1105
+ function nameStem(path) {
1106
+ const name = path.slice(path.lastIndexOf("/") + 1);
1107
+ const dot = name.indexOf(".");
1108
+ return dot < 0 ? name : name.slice(0, dot);
1109
+ }
1110
+ const SHA256_HEX = /^[0-9a-f]{64}$/;
1111
+ /** Read every record `loaded` locates, through `options.source`. */
1112
+ export async function readRecords(loaded, options) {
1113
+ const { kind } = loaded;
1114
+ const dirRel = toPosix(relative(options.root, loaded.dir)) || ".";
1115
+ const names = options.source.list(dirRel);
1116
+ if (names === undefined) {
1117
+ throw new RecordReadError("location-missing", `records directory ${dirRel} does not exist${options.source.label}`);
1118
+ }
1119
+ const match = new RegExp(kind.location.match);
1120
+ const validate = await compileSchema(loaded.schema, loaded.refs);
1121
+ const workspaceRoot = options.workspaceRoot ?? ".";
1122
+ // Where a relative transcript path resolves (#2708): the workspace root in the working tree.
1123
+ const workspaceDir = workspaceRoot === "." ? options.root : resolve(options.root, ...workspaceRoot.split("/"));
1124
+ const entries = [];
1125
+ const texts = new Map();
1126
+ for (const name of names.filter((n) => match.test(n)).sort()) {
1127
+ const path = dirRel === "." ? name : `${dirRel}/${name}`;
1128
+ const text = options.source.read(path);
1129
+ const entry = {
1130
+ id: null,
1131
+ path,
1132
+ state: null,
1133
+ valid: true,
1134
+ reasons: [],
1135
+ supersededBy: null,
1136
+ remediatedBy: [],
1137
+ data: null,
1138
+ assets: [],
1139
+ warnings: [],
1140
+ digest: recordTextDigest(text, digestFields(kind), kind.format),
1141
+ };
1142
+ entries.push(entry);
1143
+ if (kind.session)
1144
+ texts.set(path, text);
1145
+ const fm = parseRecord(kind.format, text);
1146
+ if (!fm.ok) {
1147
+ entry.reasons.push({ code: "record-unparseable", message: fm.message });
1148
+ continue;
1149
+ }
1150
+ entry.data = fm.value;
1151
+ if (kind.idFrom === "sha256") {
1152
+ // The id is the hash of the bytes; the name's stem is the hash it claims.
1153
+ // A name that claims another is the record pinning itself, drifted (ws-053).
1154
+ entry.id = sha256Hex(options.source.bytes(path));
1155
+ const stem = nameStem(path);
1156
+ if (stem !== entry.id) {
1157
+ const self = workspaceRoot === "." ? path : path.startsWith(`${workspaceRoot}/`) ? path.slice(workspaceRoot.length + 1) : path;
1158
+ if (SHA256_HEX.test(stem))
1159
+ entry.assets.push({ path: self, sha256: stem, actual: entry.id, state: "drifted" });
1160
+ entry.warnings.push({
1161
+ code: "asset-drift",
1162
+ message: SHA256_HEX.test(stem)
1163
+ ? `${self} is named for sha256 ${stem.slice(0, 12)}, and its bytes${options.source.label} hash to ${entry.id.slice(0, 12)}`
1164
+ : `${self} is content-addressed, and its name claims no sha256: its bytes${options.source.label} hash to ${entry.id.slice(0, 12)}`,
1165
+ });
1166
+ }
1167
+ }
1168
+ else {
1169
+ const id = fm.value[kind.idField];
1170
+ if (typeof id === "string")
1171
+ entry.id = id;
1172
+ }
1173
+ const state = kind.stateField === undefined ? undefined : fm.value[kind.stateField];
1174
+ if (typeof state === "string")
1175
+ entry.state = state;
1176
+ const result = validate(fm.value);
1177
+ if (!result.ok) {
1178
+ entry.reasons.push({ code: "record-schema-invalid", message: result.errors.join("; ") });
1179
+ }
1180
+ if (kind.source) {
1181
+ // The stored source block (#2708): its proposal fields' shapes, and the transcript it pins.
1182
+ const block = sourceBlock(fm.value, kind.source.field);
1183
+ if (block) {
1184
+ const problems = sourceBlockProblems(block, kind.source.field);
1185
+ if (problems.length > 0)
1186
+ entry.reasons.push({ code: "record-schema-invalid", message: problems.join("; ") });
1187
+ const drift = transcriptDrift(block, kind.source.field, workspaceDir);
1188
+ if (drift)
1189
+ entry.warnings.push({ code: "source-transcript-drift", message: drift });
1190
+ }
1191
+ }
1192
+ if (kind.seal && !kind.session && entry.state !== null && (kind.closedStates ?? []).includes(entry.state)) {
1193
+ // A closed record's whole-file seal (#2546, ws-063). A session's is checked with its verdicts (#2673).
1194
+ const seal = fm.value[kind.seal.field];
1195
+ const actual = typeof seal === "string" ? recordSeal(text, kind.seal.field, kind.format) : null;
1196
+ if (typeof seal === "string" && actual !== seal) {
1197
+ entry.reasons.push({ code: "record-seal-mismatch", message: `${kind.seal.field} is ${seal}, but the record seals to ${actual}: it changed after it closed` });
1198
+ }
1199
+ }
1200
+ if (kind.pins) {
1201
+ const cited = fm.value[kind.pins.field];
1202
+ if (Array.isArray(cited) && cited.length === 0) {
1203
+ entry.warnings.push({ code: "record-no-evidence", message: `${kind.pins.field} is empty: the record cites nothing and pins no file` });
1204
+ }
1205
+ if (options.assets) {
1206
+ const checked = checkPins(pinEntries(fm.value, kind.pins.field), options.assets);
1207
+ entry.assets.push(...checked.assets);
1208
+ entry.warnings.push(...checked.warnings);
1209
+ }
1210
+ }
1211
+ if (kind.reviews) {
1212
+ const list = fm.value[kind.reviews.field];
1213
+ const undigested = (Array.isArray(list) ? list : [])
1214
+ .filter((r) => r !== null && typeof r === "object" && !Array.isArray(r) && r.digest === undefined)
1215
+ .map((r) => (typeof r.reviewer === "string" ? r.reviewer : "an unnamed reviewer"));
1216
+ if (undigested.length > 0) {
1217
+ entry.warnings.push({
1218
+ code: "review-undigested",
1219
+ message: `the ${undigested.length === 1 ? "verdict" : "verdicts"} by ${undigested.join(", ")} name no digest: ${undigested.length === 1 ? "it counts" : "they count"}, and an amendment will not stop ${undigested.length === 1 ? "it" : "them"} counting`,
1220
+ });
1221
+ }
1222
+ }
1223
+ }
1224
+ // Ids: the first file in path order keeps an id; later ones are flagged.
1225
+ const byId = new Map();
1226
+ for (const e of entries) {
1227
+ if (e.id === null)
1228
+ continue;
1229
+ const first = byId.get(e.id);
1230
+ if (first)
1231
+ e.reasons.push({ code: "record-id-duplicate", message: `id ${e.id} is already used by ${first.path}` });
1232
+ else
1233
+ byId.set(e.id, e);
1234
+ }
1235
+ // Supersession comes from the new record's links, never from the old record.
1236
+ // With approval ranks, a link takes effect under an equal or stricter
1237
+ // approval rule: from a record ranked above 0 and at least as high as the
1238
+ // one it names (#2524 D4). Without them, only from a closed record (#2555).
1239
+ // A record is superseded at most once.
1240
+ const closed = new Set(kind.closedStates ?? []);
1241
+ const rank = (state) => (state === null ? 0 : (kind.approval?.[state] ?? 0));
1242
+ const takesEffect = (from, to) => kind.approval ? rank(from.state) > 0 && rank(from.state) >= rank(to.state) : from.state !== null && closed.has(from.state);
1243
+ for (const e of entries) {
1244
+ for (const target of supersedesTargets(kind, e.data)) {
1245
+ const old = byId.get(target);
1246
+ if (!old) {
1247
+ e.reasons.push({ code: "record-supersedes-unknown", message: `supersedes ${target}, which no record has` });
1248
+ continue;
1249
+ }
1250
+ if (old === e)
1251
+ continue;
1252
+ if (!takesEffect(e, old)) {
1253
+ if (kind.approval) {
1254
+ e.warnings.push({
1255
+ code: "record-supersedes-pending",
1256
+ message: `supersedes ${target}, which is ${old.state ?? "stateless"}; a ${e.state ?? "stateless"} record can't supersede it, so the link takes effect once this record is approved at least as strongly`,
1257
+ });
1258
+ }
1259
+ continue;
1260
+ }
1261
+ if (old.supersededBy !== null && old.supersededBy !== e.id) {
1262
+ e.reasons.push({
1263
+ code: "record-supersedes-conflict",
1264
+ message: `supersedes ${target}, which ${old.supersededBy} already supersedes`,
1265
+ });
1266
+ continue;
1267
+ }
1268
+ old.supersededBy = e.id;
1269
+ }
1270
+ }
1271
+ // Remediation (#2774): a later record fixes a consequence of a closed one
1272
+ // without replacing it. The target's status, and its supersededBy, never
1273
+ // change; several records may remediate the same one. Unlike supersedes,
1274
+ // this needs no approval rank: the target is closed or it isn't, and a
1275
+ // still-open target is amended instead of remediated.
1276
+ for (const e of entries) {
1277
+ for (const target of remediatesTargets(kind, e.data)) {
1278
+ const old = byId.get(target);
1279
+ if (!old) {
1280
+ e.reasons.push({ code: "record-remediates-unknown", message: `remediates ${target}, which no record has` });
1281
+ continue;
1282
+ }
1283
+ if (old === e)
1284
+ continue;
1285
+ if (old.state === null || !closed.has(old.state)) {
1286
+ e.reasons.push({
1287
+ code: "record-remediates-not-closed",
1288
+ message: `remediates ${target}, which is ${old.state ?? "stateless"}, not closed; a record still open is amended instead`,
1289
+ });
1290
+ continue;
1291
+ }
1292
+ if (e.id !== null && !old.remediatedBy.includes(e.id))
1293
+ old.remediatedBy.push(e.id);
1294
+ }
1295
+ }
1296
+ // A pin that still matches, at the hash a record this one supersedes
1297
+ // pinned, while the file has not changed since this record was recorded:
1298
+ // the decision moved on and the artifact did not (#2549).
1299
+ for (const e of entries) {
1300
+ for (const a of e.assets) {
1301
+ if (a.state !== "pinned")
1302
+ continue;
1303
+ const old = entries.find((o) => o.supersededBy !== null && o.supersededBy === e.id && o.assets.some((p) => p.path === a.path && p.sha256 === a.sha256));
1304
+ if (!old)
1305
+ continue;
1306
+ const changed = options.history?.fileChanged(a.path) ?? null;
1307
+ const recorded = options.history ? options.history.recorded(e.path) : null;
1308
+ // A record not yet committed is recorded now, after every commit.
1309
+ if (changed !== null && recorded !== null && changed > recorded)
1310
+ continue;
1311
+ a.state = "stale";
1312
+ e.warnings.push({
1313
+ code: "asset-stale",
1314
+ message: `${a.path} is pinned at the hash ${old.id} pinned, and it has not changed since: ${e.id} supersedes ${old.id}, and the artifact did not follow`,
1315
+ });
1316
+ }
1317
+ }
1318
+ // A session's seal and its verdicts' records (#2673).
1319
+ joinSessions(kind, entries, texts, options.subjects ?? null);
1320
+ // A work kind's links, ready and blocked (#2683), from every record before --current.
1321
+ const work = kind.work ? await (await import("./work.js")).applyWork(loaded, entries, options) : undefined;
1322
+ // An answer kind's points (ws-058): an answer whose point is gone or changed is warned about.
1323
+ if (kind.answers)
1324
+ (await import("./points.js")).applyAnswers(loaded, entries, options);
1325
+ for (const e of entries)
1326
+ e.valid = e.reasons.length === 0;
1327
+ const records = options.current ? entries.filter((e) => e.supersededBy === null) : entries;
1328
+ return {
1329
+ records,
1330
+ summary: {
1331
+ total: records.length,
1332
+ valid: records.filter((e) => e.valid).length,
1333
+ invalid: records.filter((e) => !e.valid).length,
1334
+ superseded: entries.filter((e) => e.supersededBy !== null).length,
1335
+ },
1336
+ ...(work ? { decisions: work.decisions } : {}),
1337
+ };
1338
+ }
1339
+ // ── Helpers ──────────────────────────────────────────────────────────────────
1340
+ function message(err) {
1341
+ return err instanceof Error ? err.message : String(err);
1342
+ }
1343
+ function toPosix(p) {
1344
+ return sep === "/" ? p : p.split(sep).join(posix.sep);
1345
+ }