@intentius/chant 0.100.0 → 0.101.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/dist/build.d.ts +6 -0
  2. package/dist/build.d.ts.map +1 -1
  3. package/dist/cli/build-options.d.ts +2 -0
  4. package/dist/cli/build-options.d.ts.map +1 -1
  5. package/dist/cli/commands/build.d.ts.map +1 -1
  6. package/dist/cli/commands/import.d.ts.map +1 -1
  7. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  8. package/dist/cli/main.d.ts.map +1 -1
  9. package/dist/cli/mcp/workspace-tools.d.ts +8 -0
  10. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  11. package/dist/cli/registry.d.ts +22 -0
  12. package/dist/cli/registry.d.ts.map +1 -1
  13. package/dist/config.d.ts +11 -0
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/lexicon.d.ts +24 -1
  16. package/dist/lexicon.d.ts.map +1 -1
  17. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  18. package/dist/lifecycle/plan-digest.d.ts +26 -5
  19. package/dist/lifecycle/plan-digest.d.ts.map +1 -1
  20. package/dist/lint/config.d.ts +4 -4
  21. package/dist/op/activities/activity-contracts.d.ts +1 -0
  22. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  23. package/dist/op/activities/propose-upgrade.d.ts +2 -0
  24. package/dist/op/activities/propose-upgrade.d.ts.map +1 -1
  25. package/dist/op/index.d.ts +1 -1
  26. package/dist/op/index.d.ts.map +1 -1
  27. package/dist/serializer.d.ts +8 -0
  28. package/dist/serializer.d.ts.map +1 -1
  29. package/dist/telemetry-attribution.d.ts +77 -0
  30. package/dist/telemetry-attribution.d.ts.map +1 -0
  31. package/dist/workspace/agent-cli.d.ts +83 -0
  32. package/dist/workspace/agent-cli.d.ts.map +1 -0
  33. package/dist/workspace/changes-cli.d.ts.map +1 -1
  34. package/dist/workspace/changes.d.ts +8 -1
  35. package/dist/workspace/changes.d.ts.map +1 -1
  36. package/dist/workspace/checks/links.d.ts +1 -0
  37. package/dist/workspace/checks/links.d.ts.map +1 -1
  38. package/dist/workspace/checks/live.d.ts +40 -0
  39. package/dist/workspace/checks/live.d.ts.map +1 -0
  40. package/dist/workspace/checks.d.ts +21 -2
  41. package/dist/workspace/checks.d.ts.map +1 -1
  42. package/dist/workspace/compose-graph.d.ts +63 -0
  43. package/dist/workspace/compose-graph.d.ts.map +1 -1
  44. package/dist/workspace/decide.d.ts +1 -1
  45. package/dist/workspace/decide.d.ts.map +1 -1
  46. package/dist/workspace/declaration.d.ts +32 -0
  47. package/dist/workspace/declaration.d.ts.map +1 -1
  48. package/dist/workspace/declaration.schema.json +138 -3
  49. package/dist/workspace/export-cli.d.ts +12 -0
  50. package/dist/workspace/export-cli.d.ts.map +1 -0
  51. package/dist/workspace/export.d.ts +145 -0
  52. package/dist/workspace/export.d.ts.map +1 -0
  53. package/dist/workspace/graph-cli.d.ts.map +1 -1
  54. package/dist/workspace/import.d.ts +73 -0
  55. package/dist/workspace/import.d.ts.map +1 -0
  56. package/dist/workspace/kinds.d.ts +6 -2
  57. package/dist/workspace/kinds.d.ts.map +1 -1
  58. package/dist/workspace/lineage-adopt-cli.d.ts +15 -0
  59. package/dist/workspace/lineage-adopt-cli.d.ts.map +1 -0
  60. package/dist/workspace/lineage-adopt.d.ts +106 -0
  61. package/dist/workspace/lineage-adopt.d.ts.map +1 -0
  62. package/dist/workspace/lineage-check.d.ts +9 -2
  63. package/dist/workspace/lineage-check.d.ts.map +1 -1
  64. package/dist/workspace/lineage-cli.d.ts +6 -1
  65. package/dist/workspace/lineage-cli.d.ts.map +1 -1
  66. package/dist/workspace/lineage-hash-index.d.ts +110 -0
  67. package/dist/workspace/lineage-hash-index.d.ts.map +1 -0
  68. package/dist/workspace/lineage-init.d.ts +10 -0
  69. package/dist/workspace/lineage-init.d.ts.map +1 -1
  70. package/dist/workspace/lineage-lock.d.ts +147 -0
  71. package/dist/workspace/lineage-lock.d.ts.map +1 -1
  72. package/dist/workspace/lineage-migrations.d.ts +15 -3
  73. package/dist/workspace/lineage-migrations.d.ts.map +1 -1
  74. package/dist/workspace/lineage-provenance.d.ts +18 -0
  75. package/dist/workspace/lineage-provenance.d.ts.map +1 -0
  76. package/dist/workspace/lineage-upgrade-cli.d.ts +2 -0
  77. package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -1
  78. package/dist/workspace/lineage-upgrade.d.ts +30 -1
  79. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  80. package/dist/workspace/lineage-versions.d.ts +116 -0
  81. package/dist/workspace/lineage-versions.d.ts.map +1 -0
  82. package/dist/workspace/links.d.ts +31 -5
  83. package/dist/workspace/links.d.ts.map +1 -1
  84. package/dist/workspace/ls-generated.d.ts +37 -0
  85. package/dist/workspace/ls-generated.d.ts.map +1 -0
  86. package/dist/workspace/ls.d.ts +4 -0
  87. package/dist/workspace/ls.d.ts.map +1 -1
  88. package/dist/workspace/member-commands.d.ts.map +1 -1
  89. package/dist/workspace/nested-graph.d.ts +56 -0
  90. package/dist/workspace/nested-graph.d.ts.map +1 -0
  91. package/dist/workspace/nesting.d.ts +21 -0
  92. package/dist/workspace/nesting.d.ts.map +1 -0
  93. package/dist/workspace/pin-cli.d.ts +10 -0
  94. package/dist/workspace/pin-cli.d.ts.map +1 -0
  95. package/dist/workspace/pin-integrity.d.ts +51 -0
  96. package/dist/workspace/pin-integrity.d.ts.map +1 -0
  97. package/dist/workspace/reason-codes.d.ts +26 -2
  98. package/dist/workspace/reason-codes.d.ts.map +1 -1
  99. package/dist/workspace/record-sessions.d.ts +7 -11
  100. package/dist/workspace/record-sessions.d.ts.map +1 -1
  101. package/dist/workspace/records-cli.d.ts +30 -1
  102. package/dist/workspace/records-cli.d.ts.map +1 -1
  103. package/dist/workspace/records-close.d.ts +5 -2
  104. package/dist/workspace/records-close.d.ts.map +1 -1
  105. package/dist/workspace/records-write.d.ts +22 -4
  106. package/dist/workspace/records-write.d.ts.map +1 -1
  107. package/dist/workspace/records.d.ts +43 -5
  108. package/dist/workspace/records.d.ts.map +1 -1
  109. package/dist/workspace/returns.d.ts +129 -0
  110. package/dist/workspace/returns.d.ts.map +1 -0
  111. package/dist/workspace/status-gates.d.ts.map +1 -1
  112. package/dist/workspace/template-manifest.d.ts +11 -3
  113. package/dist/workspace/template-manifest.d.ts.map +1 -1
  114. package/dist/workspace/trust/attestor.d.ts +8 -0
  115. package/dist/workspace/trust/attestor.d.ts.map +1 -1
  116. package/dist/workspace/trust/dsse.d.ts +58 -0
  117. package/dist/workspace/trust/dsse.d.ts.map +1 -0
  118. package/dist/workspace/trust/evidence-cli.d.ts +66 -0
  119. package/dist/workspace/trust/evidence-cli.d.ts.map +1 -0
  120. package/dist/workspace/trust/evidence.d.ts +93 -0
  121. package/dist/workspace/trust/evidence.d.ts.map +1 -0
  122. package/dist/workspace/trust/policy.d.ts +54 -1
  123. package/dist/workspace/trust/policy.d.ts.map +1 -1
  124. package/dist/workspace/trust/provenance.d.ts +21 -1
  125. package/dist/workspace/trust/provenance.d.ts.map +1 -1
  126. package/dist/workspace/trust/rotation.d.ts +132 -0
  127. package/dist/workspace/trust/rotation.d.ts.map +1 -0
  128. package/dist/workspace/trust/seal.d.ts.map +1 -1
  129. package/dist/workspace/trust/signers-cli.d.ts +49 -0
  130. package/dist/workspace/trust/signers-cli.d.ts.map +1 -0
  131. package/dist/workspace/trust/ssh-commit.d.ts +12 -0
  132. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
  133. package/dist/workspace/trust/test-repo.d.ts +13 -0
  134. package/dist/workspace/trust/test-repo.d.ts.map +1 -1
  135. package/dist/workspace/trust/verify.d.ts +15 -0
  136. package/dist/workspace/trust/verify.d.ts.map +1 -1
  137. package/dist/workspace/work-evidence.d.ts +1 -1
  138. package/dist/workspace/work-evidence.d.ts.map +1 -1
  139. package/dist/workspace/write-scope.d.ts +199 -0
  140. package/dist/workspace/write-scope.d.ts.map +1 -0
  141. package/package.json +1 -1
  142. package/src/build.ts +8 -0
  143. package/src/cli/build-options.ts +4 -1
  144. package/src/cli/commands/build.ts +2 -0
  145. package/src/cli/commands/import-live.test.ts +69 -1
  146. package/src/cli/commands/import.ts +48 -22
  147. package/src/cli/handlers/fan-out.test.ts +6 -6
  148. package/src/cli/handlers/fan-out.ts +2 -1
  149. package/src/cli/handlers/graph.test.ts +42 -0
  150. package/src/cli/handlers/graph.ts +22 -0
  151. package/src/cli/handlers/operator.ts +1 -1
  152. package/src/cli/main.test.ts +32 -0
  153. package/src/cli/main.ts +103 -6
  154. package/src/cli/mcp/workspace-tools.test.ts +1 -1
  155. package/src/cli/mcp/workspace-tools.ts +33 -2
  156. package/src/cli/registry.ts +22 -0
  157. package/src/cli/serve-mcp-workspace.test.ts +1 -1
  158. package/src/codegen/release-wiring.test.ts +5 -1
  159. package/src/components/fan-out-output.test.ts +1 -1
  160. package/src/components/fan-out.test.ts +1 -1
  161. package/src/components/promote.test.ts +1 -1
  162. package/src/config.ts +12 -0
  163. package/src/content-digest.test.ts +2 -2
  164. package/src/lexicon.ts +25 -1
  165. package/src/lifecycle/gate-ledger.test.ts +14 -0
  166. package/src/lifecycle/gate-ledger.ts +2 -1
  167. package/src/lifecycle/plan-digest.test.ts +54 -3
  168. package/src/lifecycle/plan-digest.ts +38 -8
  169. package/src/op/activities/activity-contracts.ts +1 -0
  170. package/src/op/activities/propose-upgrade.ts +8 -5
  171. package/src/op/gate-approval.test.ts +17 -0
  172. package/src/op/gate.ts +3 -3
  173. package/src/op/index.ts +1 -1
  174. package/src/serializer.ts +9 -0
  175. package/src/telemetry-attribution.test.ts +91 -0
  176. package/src/telemetry-attribution.ts +145 -0
  177. package/src/workspace/agent-cli.ts +134 -0
  178. package/src/workspace/agent.schema.json +356 -0
  179. package/src/workspace/behold-kinds.test.ts +1 -1
  180. package/src/workspace/changes-cli.ts +5 -0
  181. package/src/workspace/changes.schema.json +179 -1
  182. package/src/workspace/changes.ts +58 -4
  183. package/src/workspace/check-live.test.ts +192 -0
  184. package/src/workspace/check.schema.json +64 -0
  185. package/src/workspace/checks/links.ts +23 -2
  186. package/src/workspace/checks/live.ts +113 -0
  187. package/src/workspace/checks.test.ts +2 -2
  188. package/src/workspace/checks.ts +20 -3
  189. package/src/workspace/compose-graph.test.ts +47 -0
  190. package/src/workspace/compose-graph.ts +120 -3
  191. package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +7 -1
  192. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +5 -0
  193. package/src/workspace/declaration.schema.json +138 -3
  194. package/src/workspace/declaration.ts +106 -1
  195. package/src/workspace/declared-kinds.test.ts +33 -0
  196. package/src/workspace/evidence.schema.json +279 -0
  197. package/src/workspace/export-cli.ts +180 -0
  198. package/src/workspace/export-import.test.ts +282 -0
  199. package/src/workspace/export.ts +486 -0
  200. package/src/workspace/graph-cli.ts +47 -1
  201. package/src/workspace/graph-contract.test.ts +89 -4
  202. package/src/workspace/graph.schema.json +206 -1
  203. package/src/workspace/import.ts +325 -0
  204. package/src/workspace/kinds.test.ts +5 -5
  205. package/src/workspace/kinds.ts +25 -3
  206. package/src/workspace/lineage-adopt-cli.ts +103 -0
  207. package/src/workspace/lineage-adopt.test.ts +552 -0
  208. package/src/workspace/lineage-adopt.ts +452 -0
  209. package/src/workspace/lineage-check.ts +26 -5
  210. package/src/workspace/lineage-cli.ts +12 -2
  211. package/src/workspace/lineage-hash-index.ts +305 -0
  212. package/src/workspace/lineage-init.test.ts +9 -0
  213. package/src/workspace/lineage-init.ts +29 -9
  214. package/src/workspace/lineage-lock.ts +54 -0
  215. package/src/workspace/lineage-migrations.test.ts +27 -0
  216. package/src/workspace/lineage-migrations.ts +44 -14
  217. package/src/workspace/lineage-provenance.ts +40 -0
  218. package/src/workspace/lineage-upgrade-cli.ts +9 -5
  219. package/src/workspace/lineage-upgrade.test.ts +92 -1
  220. package/src/workspace/lineage-upgrade.ts +111 -16
  221. package/src/workspace/lineage-versions.ts +348 -0
  222. package/src/workspace/links.test.ts +121 -2
  223. package/src/workspace/links.ts +97 -6
  224. package/src/workspace/ls-contract.test.ts +84 -1
  225. package/src/workspace/ls-generated.ts +111 -0
  226. package/src/workspace/ls.schema.json +19 -0
  227. package/src/workspace/ls.ts +8 -1
  228. package/src/workspace/member-commands.ts +3 -1
  229. package/src/workspace/nested-graph.test.ts +176 -0
  230. package/src/workspace/nested-graph.ts +169 -0
  231. package/src/workspace/nesting.ts +37 -0
  232. package/src/workspace/pin-cli.test.ts +71 -0
  233. package/src/workspace/pin-cli.ts +57 -0
  234. package/src/workspace/pin-integrity.test.ts +121 -0
  235. package/src/workspace/pin-integrity.ts +104 -0
  236. package/src/workspace/points-write.schema.json +1 -0
  237. package/src/workspace/read-contract.test.ts +24 -0
  238. package/src/workspace/reason-codes.test.ts +10 -0
  239. package/src/workspace/reason-codes.ts +29 -2
  240. package/src/workspace/record-sessions.ts +12 -14
  241. package/src/workspace/records-amend.schema.json +4 -0
  242. package/src/workspace/records-cli.ts +93 -12
  243. package/src/workspace/records-close.schema.json +6 -2
  244. package/src/workspace/records-close.ts +10 -2
  245. package/src/workspace/records-formats.test.ts +13 -5
  246. package/src/workspace/records-new.schema.json +4 -0
  247. package/src/workspace/records-review.schema.json +4 -0
  248. package/src/workspace/records-sessions.test.ts +23 -6
  249. package/src/workspace/records-write.test.ts +65 -8
  250. package/src/workspace/records-write.ts +70 -6
  251. package/src/workspace/records.schema.json +55 -3
  252. package/src/workspace/records.ts +96 -5
  253. package/src/workspace/returns.ts +328 -0
  254. package/src/workspace/signers.schema.json +206 -0
  255. package/src/workspace/status-gates.ts +2 -1
  256. package/src/workspace/template-manifest.ts +22 -4
  257. package/src/workspace/trust/attestor.ts +15 -0
  258. package/src/workspace/trust/dsse.ts +134 -0
  259. package/src/workspace/trust/evidence-cli.ts +195 -0
  260. package/src/workspace/trust/evidence.test.ts +241 -0
  261. package/src/workspace/trust/evidence.ts +207 -0
  262. package/src/workspace/trust/policy.ts +110 -3
  263. package/src/workspace/trust/provenance.ts +41 -4
  264. package/src/workspace/trust/record-seal.test.ts +1 -1
  265. package/src/workspace/trust/rotation.test.ts +258 -0
  266. package/src/workspace/trust/rotation.ts +336 -0
  267. package/src/workspace/trust/seal.ts +11 -0
  268. package/src/workspace/trust/signers-cli.ts +178 -0
  269. package/src/workspace/trust/ssh-commit.ts +53 -4
  270. package/src/workspace/trust/test-repo.ts +18 -0
  271. package/src/workspace/trust/trust.test.ts +8 -2
  272. package/src/workspace/trust/verify-cli.ts +1 -0
  273. package/src/workspace/trust/verify.ts +22 -0
  274. package/src/workspace/work-evidence.schema.json +4 -0
  275. package/src/workspace/write-scope.test.ts +340 -0
  276. package/src/workspace/write-scope.ts +448 -0
@@ -36,6 +36,19 @@ export declare class TestRepo {
36
36
  sshSign(key: Key, payload: Buffer, namespace: string): string;
37
37
  cleanup(): void;
38
38
  }
39
+ /**
40
+ * Write `<signers>.rotation.json` for the signers file now in the working
41
+ * tree, as the next version after `previousText`, signed by `by` in the
42
+ * rotation namespace (#2553).
43
+ */
44
+ export declare function writeRotation(repo: TestRepo, opts: {
45
+ previousText: string;
46
+ version: number;
47
+ threshold?: number;
48
+ by: Array<[string, Key]>;
49
+ namespace?: string;
50
+ signersPath?: string;
51
+ }): void;
39
52
  /** A record kind reading `records/*.md`, and its schema, written into `repo`. */
40
53
  export declare function writeRecordKind(repo: TestRepo): string;
41
54
  export declare function note(id: string, body?: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"test-repo.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/test-repo.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,eAAO,MAAM,YAAY,SAA2E,CAAC;AAErG,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,qBAAa,QAAQ;IACnB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,KAAK,EAAE,MAAM;IASzB,GAAG,CAAC,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,MAAM,CAAC,UAAU;IAc1D,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAO,GAAG,MAAM;IAIjG,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG;IAOtB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAKvC,wFAAwF;IACxF,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,MAAM;IAO5E,IAAI,IAAI,MAAM;IAId;;;;OAIG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM;IAgBvE,gEAAgE;IAChE,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAO7D,OAAO,IAAI,IAAI;CAGhB;AAED,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAqBtD;AAED,wBAAgB,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,MAAM,CAElD"}
1
+ {"version":3,"file":"test-repo.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/test-repo.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,eAAO,MAAM,YAAY,SAA2E,CAAC;AAErG,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,qBAAa,QAAQ;IACnB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,KAAK,EAAE,MAAM;IASzB,GAAG,CAAC,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,MAAM,CAAC,UAAU;IAc1D,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAO,GAAG,MAAM;IAIjG,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG;IAOtB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAKvC,wFAAwF;IACxF,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,MAAM;IAO5E,IAAI,IAAI,MAAM;IAId;;;;OAIG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM;IAgBvE,gEAAgE;IAChE,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAO7D,OAAO,IAAI,IAAI;CAGhB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACtI,IAAI,CAON;AAED,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAqBtD;AAED,wBAAgB,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,MAAM,CAElD"}
@@ -14,6 +14,7 @@
14
14
  import { type CommitAttestor, type ProvenanceLevel } from "./attestor.js";
15
15
  import { type BaseSource, type RecordProvenance } from "./provenance.js";
16
16
  import { type ExcludedSigner } from "./policy.js";
17
+ import { type RotationRefusalCode } from "./rotation.js";
17
18
  export interface CommitVerdict extends RecordProvenance {
18
19
  commit: string;
19
20
  subject: string;
@@ -46,6 +47,20 @@ export interface ChangeReport {
46
47
  /** Protected paths whose content differs between the merge base and head. */
47
48
  protectedChanged: string[];
48
49
  protectedWrites: ProtectedWrite[];
50
+ /**
51
+ * When the change edits the signer set (#2553): the version it proposes and
52
+ * who signed it, or, when the new set is refused, the version it had to
53
+ * follow and why.
54
+ */
55
+ rotation: {
56
+ from: number;
57
+ to: number;
58
+ signedBy: string[];
59
+ } | {
60
+ from: number;
61
+ code: RotationRefusalCode;
62
+ reason: string;
63
+ } | null;
49
64
  notes: string[];
50
65
  failures: string[];
51
66
  ok: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAA+C,KAAK,UAAU,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACnH,OAAO,EAAiC,KAAK,cAAc,EAAoB,MAAM,UAAU,CAAC;AAEhG,MAAM,WAAW,aAAc,SAAQ,gBAAgB;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,eAAe,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,UAAU,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE;QACN,MAAM,EAAE,OAAO,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,QAAQ,EAAE,cAAc,EAAE,CAAC;QAC3B,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IACF,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IAChC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,6EAA6E;IAC7E,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,EAAE,EAAE,OAAO,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,SAAS,EAAE,SAAS,cAAc,EAAE,CAAC;CACtC;AAMD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAUnG;AAQD,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,CAqG9D"}
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAA+C,KAAK,UAAU,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACnH,OAAO,EAAiC,KAAK,cAAc,EAAoB,MAAM,UAAU,CAAC;AAChG,OAAO,EAAoD,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAExG,MAAM,WAAW,aAAc,SAAQ,gBAAgB;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,eAAe,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,UAAU,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE;QACN,MAAM,EAAE,OAAO,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,QAAQ,EAAE,cAAc,EAAE,CAAC;QAC3B,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IACF,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IAChC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,6EAA6E;IAC7E,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC;;;;OAIG;IACH,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,mBAAmB,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAChI,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,EAAE,EAAE,OAAO,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,SAAS,EAAE,SAAS,cAAc,EAAE,CAAC;CACtC;AAMD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAUnG;AAQD,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,CAoH9D"}
@@ -20,7 +20,7 @@
20
20
  */
21
21
  import { type EvidenceResult, type WorkAcceptance } from "./work.js";
22
22
  /** Why evidence was not attached. Closed. Nothing is written with any of them. */
23
- export declare const WORK_EVIDENCE_ERROR_CODES: readonly ["kind-unreadable", "kind-invalid", "schema-unreadable", "schema-id-mismatch", "schema-invalid", "location-missing", "write-usage-invalid", "write-input-invalid", "record-not-found", "amend-id-immutable", "record-closed", "amend-supersede-instead", "record-sign-failed", "ratify-quorum-not-met", "record-unparseable", "record-schema-invalid", "record-id-duplicate", "record-supersedes-unknown", "record-supersedes-conflict", "record-remediates-unknown", "record-remediates-not-closed", "session-seal-mismatch", "session-verdict-unknown-record", "work-kind-missing", "work-kind-ambiguous", "work-item-unknown", "work-item-closed", "work-criterion-unknown", "work-acceptance-self-verified", "lease-held", "lease-not-held", "lease-token-mismatch", "not-a-git-repository"];
23
+ export declare const WORK_EVIDENCE_ERROR_CODES: readonly ["kind-unreadable", "kind-invalid", "schema-unreadable", "schema-id-mismatch", "schema-invalid", "location-missing", "write-usage-invalid", "write-input-invalid", "record-not-found", "amend-id-immutable", "record-closed", "amend-supersede-instead", "record-sign-failed", "ratify-quorum-not-met", "write-scope-member", "write-scope-kind", "agent-unknown", "record-unparseable", "record-schema-invalid", "record-id-duplicate", "record-supersedes-unknown", "record-supersedes-conflict", "record-remediates-unknown", "record-remediates-not-closed", "record-seal-mismatch", "session-seal-mismatch", "session-verdict-unknown-record", "work-kind-missing", "work-kind-ambiguous", "work-item-unknown", "work-item-closed", "work-criterion-unknown", "work-acceptance-self-verified", "lease-held", "lease-not-held", "lease-token-mismatch", "not-a-git-repository"];
24
24
  export type WorkEvidenceErrorCode = (typeof WORK_EVIDENCE_ERROR_CODES)[number];
25
25
  export interface AttachWorkEvidenceRequest {
26
26
  /** Where the workspace is found, and what `path` and `kind` resolve against. */
@@ -1 +1 @@
1
- {"version":3,"file":"work-evidence.d.ts","sourceRoot":"","sources":["../../src/workspace/work-evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAOH,OAAO,EAAwD,KAAK,cAAc,EAAyB,KAAK,cAAc,EAAE,MAAM,QAAQ,CAAC;AAG/I,kFAAkF;AAClF,eAAO,MAAM,yBAAyB,2wBAYI,CAAC;AAC3C,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/E,MAAM,WAAW,yBAAyB;IACxC,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAC;IACZ,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,4DAA4D;AAC5D,eAAO,MAAM,8BAA8B,IAAI,CAAC;AAEhD,kFAAkF;AAClF,eAAO,MAAM,8BAA8B,4FAA4F,CAAC;AAExI,UAAU,OAAO;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,sFAAsF;AACtF,MAAM,MAAM,oBAAoB,GAAG,OAAO,GACxC,CACI;IACE,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,wDAAwD;IACxD,UAAU,EAAE,cAAc,CAAC;CAC5B,GACD;IAAE,KAAK,EAAE;QAAE,IAAI,EAAE,qBAAqB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAC9D,CAAC;AAWJ,sGAAsG;AACtG,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,yBAAyB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA+DtG"}
1
+ {"version":3,"file":"work-evidence.d.ts","sourceRoot":"","sources":["../../src/workspace/work-evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAOH,OAAO,EAAwD,KAAK,cAAc,EAAyB,KAAK,cAAc,EAAE,MAAM,QAAQ,CAAC;AAG/I,kFAAkF;AAClF,eAAO,MAAM,yBAAyB,81BAYI,CAAC;AAC3C,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/E,MAAM,WAAW,yBAAyB;IACxC,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAC;IACZ,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,4DAA4D;AAC5D,eAAO,MAAM,8BAA8B,IAAI,CAAC;AAEhD,kFAAkF;AAClF,eAAO,MAAM,8BAA8B,4FAA4F,CAAC;AAExI,UAAU,OAAO;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,sFAAsF;AACtF,MAAM,MAAM,oBAAoB,GAAG,OAAO,GACxC,CACI;IACE,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,wDAAwD;IACxD,UAAU,EAAE,cAAc,CAAC;CAC5B,GACD;IAAE,KAAK,EAAE;QAAE,IAAI,EAAE,qBAAqB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAC9D,CAAC;AAWJ,sGAAsG;AACtG,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,yBAAyB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA+DtG"}
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Write scope per member and record kind, for each principal class, and the
3
+ * agent sessions bound to one member (#2524 D5, D20; #2548; ws-067).
4
+ *
5
+ * The declaration's `writeScope` block gives a restricted class the members
6
+ * whose files it may write and the record kinds it may write, with which
7
+ * verbs (`new`, `amend`, `review`, `close`). A class with no entry is not
8
+ * restricted. The declaration's `agents` list names agent sessions, each
9
+ * bound to one member: a session writes that member's files, and the records
10
+ * of kinds that member or the workspace declares, as `writeScope.agent`
11
+ * allows. An agent's members can't be widened.
12
+ *
13
+ * Two places apply it, both reading the scope from the base revision so a
14
+ * change can't widen its own scope (#2524 threat model):
15
+ *
16
+ * - The write paths: `chant workspace records new|amend|review|close` and the
17
+ * MCP record tools refuse a write outside the writer's scope with
18
+ * `write-scope-member` or `write-scope-kind`, and an unknown session with
19
+ * `agent-unknown`. The session comes from `CHANT_AGENT`.
20
+ * - The enforcement boundary: `chant workspace check --changes <range>`
21
+ * judges every commit in the range, by its `Chant-Agent` trailer, its
22
+ * attested principal, or its author, and reports each path written outside
23
+ * that writer's scope. Run in CI with an attestation policy, the principal
24
+ * is the signer the policy at base trusts; on a developer machine it is
25
+ * detection only (ws-002).
26
+ *
27
+ * A principal's class comes from the role grants in the trust policy at base,
28
+ * through {@link principalClass} alone (the agent, runner and service roles),
29
+ * or from naming an agent session. Claiming a session only ever narrows what
30
+ * a writer may do, so an unverified `CHANT_AGENT` or trailer is safe to honour.
31
+ */
32
+ import { type AgentDeclaration, type ClassScope, type Declaration, type PrincipalClass, type WriteVerb } from "./declaration.js";
33
+ import { type TrustPolicy } from "./trust/policy.js";
34
+ /** The environment variable naming the agent session a write is made in. */
35
+ export declare const AGENT_ENV = "CHANT_AGENT";
36
+ /** The commit trailer naming the agent session a commit was made in. */
37
+ export declare const AGENT_TRAILER = "Chant-Agent";
38
+ /** Why a write is outside its writer's scope. Closed. */
39
+ export declare const WRITE_SCOPE_CODES: readonly ["write-scope-member", "write-scope-kind", "agent-unknown"];
40
+ export type WriteScopeCode = (typeof WRITE_SCOPE_CODES)[number];
41
+ /**
42
+ * The role grants a class is read from. Every read of roles for write scope
43
+ * goes through here, so where the grants live (`.chant/trust.json` today,
44
+ * possibly the declaration, #2547) is decided in one place: the policy.
45
+ */
46
+ export declare function roleGrants(policy: TrustPolicy): Record<string, string[]>;
47
+ /** The class a principal is in: the first of agent, runner and service whose role it holds at base, or human. */
48
+ export declare function principalClass(policy: TrustPolicy, principal: string | null): PrincipalClass;
49
+ /** Who is writing, as the scope judges it. */
50
+ export interface Writer {
51
+ /** The principal named, or null when none is. */
52
+ principal: string | null;
53
+ class: PrincipalClass;
54
+ /** The agent session the writer is bound to, or null. */
55
+ agent: AgentDeclaration | null;
56
+ }
57
+ export declare class WriteScopeError extends Error {
58
+ readonly code: WriteScopeCode;
59
+ constructor(code: WriteScopeCode, message: string);
60
+ }
61
+ /**
62
+ * The writer: in the session `agent` names (refused with agent-unknown when
63
+ * the declaration declares none by that name), in the session that lists
64
+ * `principal`, or else in the class `principal`'s roles give it.
65
+ */
66
+ export declare function resolveWriter(declaration: Declaration | null, policy: TrustPolicy, given: {
67
+ agent?: string | null;
68
+ principal?: string | null;
69
+ }): Writer;
70
+ /** The scope that applies to `writer`, or null when its class is not restricted. An agent is always restricted to its member. */
71
+ export declare function scopeOf(declaration: Declaration | null, writer: Writer): ClassScope | null;
72
+ export type ScopeVerdict = {
73
+ ok: true;
74
+ } | {
75
+ ok: false;
76
+ code: Exclude<WriteScopeCode, "agent-unknown">;
77
+ message: string;
78
+ };
79
+ /** The record kind a write goes to, as the scope reads it. */
80
+ export interface ScopedKind {
81
+ /** The kind file's `recordKind.name`. */
82
+ name: string;
83
+ /** The name the declaration gives it, or null. */
84
+ declaredName: string | null;
85
+ /** The member that declares the kind, or null for the workspace's own kinds. */
86
+ member: string | null;
87
+ /** True when the declaration names the kind; an undeclared kind belongs to the member holding its records. */
88
+ declared: boolean;
89
+ }
90
+ /** Whether `writer` may write a record of `kind` with `verb`. A delete is never in a restricted scope. */
91
+ export declare function judgeRecord(declaration: Declaration | null, writer: Writer, kind: ScopedKind, verb: WriteVerb | "delete"): ScopeVerdict;
92
+ /** Whether `writer` may write the file at `path`, from the workspace root. */
93
+ export declare function judgePath(declaration: Declaration | null, writer: Writer, path: string): ScopeVerdict;
94
+ /** The declaration and policy a write on this machine is judged by. */
95
+ export interface ScopeSource {
96
+ declaration: Declaration | null;
97
+ /** Where the declaration was read: the base revision, or the working tree when there is no base or no declaration at it. */
98
+ from: "base" | "working-tree" | null;
99
+ /** The workspace root on disk, or null without a declaration. */
100
+ rootOnDisk: string | null;
101
+ /** The workspace root from the git root, "." for the git root, or null without a declaration. */
102
+ root: string | null;
103
+ policy: TrustPolicy;
104
+ }
105
+ /**
106
+ * The scope for a write from `cwd`: the declaration and the trust policy at
107
+ * the base revision (`origin/HEAD`, `main` or `master`), so a write can't
108
+ * widen its own scope by editing its copy of the declaration. Without a base,
109
+ * or without a declaration at it, the working tree's declaration.
110
+ */
111
+ export declare function scopeSource(cwd: string): ScopeSource;
112
+ /**
113
+ * The kind a write goes to, as {@link judgeRecord} reads it: the declared
114
+ * entry whose file is `kindFile`, or, for a kind the declaration does not
115
+ * name, the member holding `recordsDir`.
116
+ */
117
+ export declare function scopedKind(source: ScopeSource, kindName: string, kindFile: string, recordsDir: string): ScopedKind;
118
+ /**
119
+ * Refuse a record write outside the writer's scope (#2548): throws a
120
+ * {@link WriteScopeError}. `agent` is the session the write names
121
+ * (`CHANT_AGENT`), and `principal` who the write names as its author.
122
+ */
123
+ export declare function refuseRecordWrite(cwd: string, write: {
124
+ kindName: string;
125
+ kindFile: string;
126
+ recordsDir: string;
127
+ verb: WriteVerb;
128
+ agent?: string | null;
129
+ principal?: string | null;
130
+ }): void;
131
+ /** A commit as the scope check judged it. */
132
+ export interface ScopeCommit {
133
+ commit: string;
134
+ subject: string;
135
+ /** The attested principal, or the author's email when the commit is not attested. */
136
+ principal: string;
137
+ /** Whether the principal is a signer the policy at base attests, or only the author the commit claims. */
138
+ attested: boolean;
139
+ class: PrincipalClass;
140
+ /** The agent session, from the Chant-Agent trailer or the principal, or null. */
141
+ agent: string | null;
142
+ /** Paths in the workspace the commit writes. */
143
+ paths: number;
144
+ }
145
+ export interface ScopeFinding {
146
+ /** `finding:<code>:<commit>:<path>`, or `finding:agent-unknown:<commit>`. */
147
+ id: string;
148
+ code: WriteScopeCode;
149
+ commit: string;
150
+ /** From the workspace root, or null for a finding on the whole commit. */
151
+ path: string | null;
152
+ principal: string;
153
+ class: PrincipalClass;
154
+ agent: string | null;
155
+ /** For a record file: how it was written, as the records commands name it, or delete. */
156
+ verb: WriteVerb | "delete" | null;
157
+ message: string;
158
+ }
159
+ export interface ScopeReport {
160
+ /** The commit the declaration and the trust policy were read at. */
161
+ base: string;
162
+ /** The classes with a writeScope entry, and agent when any session is declared. */
163
+ restricted: PrincipalClass[];
164
+ agents: string[];
165
+ commits: ScopeCommit[];
166
+ findings: ScopeFinding[];
167
+ }
168
+ /** A record kind the check knows, to tell a record file from another path. */
169
+ export interface CheckKind {
170
+ scoped: ScopedKind;
171
+ /** The records directory, from the repository root. */
172
+ dir: string;
173
+ match: RegExp;
174
+ /** The reviews field, when the kind has one; the verdicts field of a session kind. */
175
+ reviewFields: string[];
176
+ /** The state field and closed states of a session kind, for telling a close. */
177
+ session: {
178
+ stateField: string;
179
+ closed: string[];
180
+ fills: string[];
181
+ } | null;
182
+ format: string;
183
+ }
184
+ /**
185
+ * Judge every commit in `base..head` against the write scope the declaration
186
+ * at `base` gives (#2548). Returns null when that declaration restricts no
187
+ * one: no writeScope block and no agent session. Merges are skipped; the
188
+ * commits they bring are judged instead.
189
+ */
190
+ export declare function checkWriteScope(q: {
191
+ top: string;
192
+ base: string;
193
+ head: string;
194
+ /** The workspace root from the repository root, "" for the top. */
195
+ prefix: string;
196
+ declaration: Declaration;
197
+ kinds: CheckKind[];
198
+ }): Promise<ScopeReport | null>;
199
+ //# sourceMappingURL=write-scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-scope.d.ts","sourceRoot":"","sources":["../../src/workspace/write-scope.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.100.0",
3
+ "version": "0.101.0",
4
4
  "description": "Declarative infrastructure-as-code toolkit — TypeScript on Node.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
package/src/build.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Declarable } from "./declarable";
2
2
  import type { Serializer, SerializerResult } from "./serializer";
3
3
  import type { OwnershipMarker } from "./ownership";
4
+ import type { TelemetryAttribution } from "./telemetry-attribution";
4
5
  import type { BuildError, DiscoveryErrorType } from "./errors";
5
6
  import type { IntrinsicDef, BuildRootContribution, BuildRootContributor } from "./lexicon";
6
7
  import { getProvenance, type BuildParamProvenance } from "./provenance";
@@ -194,6 +195,12 @@ export interface BuildOptions {
194
195
  */
195
196
  ownership?: OwnershipMarker;
196
197
 
198
+ /**
199
+ * Telemetry attribution facts (#2558), passed to each serializer's
200
+ * {@link SerializeContext}. Resolved by the build command; unset at level 0.
201
+ */
202
+ telemetry?: TelemetryAttribution;
203
+
197
204
  /**
198
205
  * The resolved project configuration, passed through to each serializer's
199
206
  * {@link SerializeContext} so a dialect can read its lexicon-scoped settings.
@@ -876,6 +883,7 @@ async function buildFromDiscoveryResult(
876
883
  const serialized = serializer.serialize(applyBound, lexiconLexiconOutputs, {
877
884
  ownership: options?.ownership,
878
885
  config: options?.config,
886
+ ...(options?.telemetry ? { telemetry: options.telemetry } : {}),
879
887
  ...(receipts.size > 0 ? { receipts } : {}),
880
888
  });
881
889
  // Collect any non-fatal serializer diagnostics into the build warnings.
@@ -64,6 +64,8 @@ export interface ProjectBuildOptionsInput {
64
64
  ownership?: OwnershipMarker;
65
65
  /** This build's resolved build-time parameters (#1064). */
66
66
  buildParams?: BuildParamProvenance[];
67
+ /** Telemetry attribution facts (#2558), from `resolveTelemetryAttribution`; unset at level 0. */
68
+ telemetry?: import("../telemetry-attribution").TelemetryAttribution;
67
69
  }
68
70
 
69
71
  /**
@@ -75,7 +77,7 @@ export interface ProjectBuildOptionsInput {
75
77
  * in this function, never at a call site.
76
78
  */
77
79
  export function resolveProjectBuildOptions(input: ProjectBuildOptionsInput): BuildOptions {
78
- const { config, configDir, plugins, modes, ownership, buildParams } = input;
80
+ const { config, configDir, plugins, modes, ownership, buildParams, telemetry } = input;
79
81
 
80
82
  // #1039 — each loaded plugin's registered intrinsics (e.g. AWS's `Sub`), so
81
83
  // a file using a registered intrinsic tagged template folds instead of
@@ -90,6 +92,7 @@ export function resolveProjectBuildOptions(input: ProjectBuildOptionsInput): Bui
90
92
 
91
93
  return {
92
94
  ownership,
95
+ ...(telemetry ? { telemetry } : {}),
93
96
  config: config as unknown as Record<string, unknown>,
94
97
  fold: modes.fold,
95
98
  sandbox: modes.sandbox,
@@ -8,6 +8,7 @@ import {
8
8
  resolveKnowledgeDir,
9
9
  } from "../../config";
10
10
  import { resolveBuildModes, resolveProjectBuildOptions } from "../build-options";
11
+ import { resolveTelemetryAttribution } from "../../telemetry-attribution";
11
12
  import { loadOkfBundle } from "../../okf-read";
12
13
  import { unknownEnvError } from "../../env";
13
14
  import type { OwnershipMarker } from "../../ownership";
@@ -320,6 +321,7 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
320
321
  modes,
321
322
  ownership,
322
323
  buildParams: paramsResolution.provenance,
324
+ telemetry: await resolveTelemetryAttribution(configDir, config as unknown as Record<string, unknown>, env),
323
325
  }),
324
326
  );
325
327
 
@@ -6,7 +6,8 @@ import { join } from "node:path";
6
6
  import { tmpdir } from "node:os";
7
7
  import type { LexiconPlugin, ExportedTemplate, ResourceSelector } from "../../lexicon";
8
8
  import type { TypeScriptGenerator } from "../../import/generator";
9
- import type { TemplateIR } from "../../import/parser";
9
+ import type { TemplateIR, ParseContext } from "../../import/parser";
10
+ import type { EmbeddedContentImporter } from "../../import/embedded";
10
11
 
11
12
  const generator: TypeScriptGenerator = {
12
13
  generate(ir: TemplateIR) {
@@ -151,4 +152,71 @@ describe("liveImportFromPlugins (#114)", () => {
151
152
  expect(result.success).toBe(true);
152
153
  expect(seenStack).toBe("loom-backend");
153
154
  });
155
+
156
+ // #2995: an export with `reparse` goes through the same embedded-content
157
+ // delegation as file import.
158
+ describe("embedded content (#2995)", () => {
159
+ const configText = "service:\n pipelines: {}\n";
160
+
161
+ function hostExporter(): LexiconPlugin {
162
+ const parse = (context?: ParseContext): TemplateIR => {
163
+ const ref = context?.embedded?.resolve({
164
+ host: "fake",
165
+ hostType: "Fake::ConfigMap",
166
+ location: "ConfigMap cfg data[\"config.yaml\"]",
167
+ directory: "cfg",
168
+ text: configText,
169
+ expectedOwner: { lexicon: "owner", what: "an owner config" },
170
+ });
171
+ return {
172
+ resources: [{ logicalId: "cfg", type: "Fake::ConfigMap", properties: { data: ref ?? configText } }],
173
+ parameters: [],
174
+ };
175
+ };
176
+ return {
177
+ ...fakeExporter("fake", { resources: [], parameters: [] }),
178
+ async exportResources(): Promise<ExportedTemplate> {
179
+ return { ...parse(), reparse: (context) => parse(context) };
180
+ },
181
+ };
182
+ }
183
+
184
+ const importer: EmbeddedContentImporter = {
185
+ what: "an owner config",
186
+ matches: (c) => typeof c.document === "object" && c.document !== null && "service" in c.document,
187
+ import: () => ({
188
+ files: [{ path: "config.ts", content: "export const config = {};\n" }],
189
+ value: { bindings: [{ from: "config.ts", name: "config" }], shape: "single" },
190
+ }),
191
+ };
192
+ const owner: LexiconPlugin = {
193
+ name: "owner",
194
+ serializer: {} as never,
195
+ generate: async () => {},
196
+ validate: async () => {},
197
+ coverage: async () => {},
198
+ package: async () => {},
199
+ embeddedImporters: () => [importer],
200
+ };
201
+
202
+ test("a loaded owner imports the content and its modules are written beside the host's", async () => {
203
+ const result = await liveImportFromPlugins([hostExporter(), owner], { environment: "prod", output: outputDir, force: true });
204
+ expect(result.success).toBe(true);
205
+ expect(result.warnings).toEqual([]);
206
+ expect(result.generatedFiles).toEqual(["main.ts", "cfg/config.ts"]);
207
+ const main = await readFile(join(outputDir, "main.ts"), "utf-8");
208
+ expect(main).toContain('"$embedded"');
209
+ expect(main).toContain('"from":"cfg/config.ts"');
210
+ expect(await readFile(join(outputDir, "cfg/config.ts"), "utf-8")).toContain("export const config");
211
+ });
212
+
213
+ test("with no owner the content is kept as read, with a warning", async () => {
214
+ const result = await liveImportFromPlugins([hostExporter()], { environment: "prod", output: outputDir, force: true });
215
+ expect(result.success).toBe(true);
216
+ expect(result.generatedFiles).toEqual(["main.ts"]);
217
+ expect(result.warnings.join("\n")).toContain("no installed lexicon imports it");
218
+ const main = await readFile(join(outputDir, "main.ts"), "utf-8");
219
+ expect(main).toContain(JSON.stringify(configText));
220
+ });
221
+ });
154
222
  });
@@ -1,10 +1,10 @@
1
1
  import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from "fs";
2
2
  import { join, resolve, basename, dirname } from "path";
3
3
  import { formatSuccess, formatWarning, formatError } from "../format";
4
- import type { TemplateIR, ResourceIR, ParameterIR, TemplateParser } from "../../import/parser";
4
+ import type { TemplateIR, ResourceIR, ParameterIR, ParseContext } from "../../import/parser";
5
5
  import type { GeneratedFile, TypeScriptGenerator } from "../../import/generator";
6
6
  import { listInstalledLexicons, loadPlugin, loadPlugins, resolveProjectLexicons } from "../plugins";
7
- import type { LexiconPlugin, ResourceSelector } from "../../lexicon";
7
+ import type { ExportedTemplate, LexiconPlugin, ResourceSelector } from "../../lexicon";
8
8
  import { parseYAMLDocument, splitYAMLDocuments } from "../../yaml";
9
9
  import { importLexiconPackage } from "../../lexicon-module";
10
10
  import { EmbeddedImports, type EmbeddedContent, type RegisteredEmbeddedImporter } from "../../import/embedded";
@@ -427,14 +427,15 @@ export async function importFromContent(options: ContentImportOptions): Promise<
427
427
 
428
428
  /**
429
429
  * The importers for the content a host's parser offered (#2962): those of the
430
- * project's lexicons, and of each installed lexicon whose `detectTemplate`
431
- * recognizes one of the offered documents. The recognizing is done with the
430
+ * plugins already loaded, of the project's lexicons, and of each installed
431
+ * lexicon whose `detectTemplate` recognizes one of the offered documents
432
+ * (#2995 adds the loaded ones). The recognizing is done with the
432
433
  * lexicon's light `/detect` module, so a lexicon is loaded in full only when
433
434
  * it may own something; one that fails to load is skipped.
434
435
  */
435
436
  async function embeddedImportersFor(
436
437
  offered: readonly EmbeddedContent[],
437
- host: LexiconPlugin,
438
+ loaded: readonly LexiconPlugin[],
438
439
  projectDir: string,
439
440
  ): Promise<RegisteredEmbeddedImporter[]> {
440
441
  const documents = offered.map((c) => c.document).filter((d) => typeof d === "object" && d !== null);
@@ -446,7 +447,8 @@ async function embeddedImportersFor(
446
447
  } catch {
447
448
  projectNames = [];
448
449
  }
449
- const candidates: string[] = [...projectNames];
450
+ // Plugins already loaded (the host, or live import's exporters) are asked too.
451
+ const candidates: string[] = [...new Set([...loaded.map((p) => p.name), ...projectNames])];
450
452
  for (const name of listInstalledLexicons(projectDir)) {
451
453
  if (candidates.includes(name)) continue;
452
454
  try {
@@ -471,7 +473,7 @@ async function embeddedImportersFor(
471
473
  for (const name of candidates) {
472
474
  let plugin: LexiconPlugin;
473
475
  try {
474
- plugin = name === host.name ? host : await loadPlugin(name);
476
+ plugin = loaded.find((p) => p.name === name) ?? (await loadPlugin(name));
475
477
  } catch {
476
478
  continue;
477
479
  }
@@ -488,22 +490,24 @@ async function embeddedImportersFor(
488
490
 
489
491
  /**
490
492
  * Parse, resolving embedded content (#2962). A first parse with a probe
491
- * collects what the parser offers; when it offers anything, the owners'
492
- * importers are loaded and the content is parsed again with them. A parser
493
- * that offers nothing is parsed once, and nothing is loaded.
493
+ * collects what the parsers offer; when they offer anything, the owners'
494
+ * importers are loaded and each is parsed again with them, through one
495
+ * resolver so their directories stay distinct. A parse is a template
496
+ * parser over a file's content, or an export's `reparse` (#2995). Parsers
497
+ * that offer nothing are parsed once, and nothing is loaded. `loaded` are
498
+ * plugins already loaded, reused when one of them owns content.
494
499
  */
495
500
  async function parseWithEmbedded(
496
- plugin: LexiconPlugin,
497
- parser: TemplateParser,
498
- content: string,
501
+ parses: ReadonlyArray<(context: ParseContext) => TemplateIR>,
502
+ loaded: readonly LexiconPlugin[],
499
503
  projectDir: string,
500
- ): Promise<{ ir: TemplateIR; embedded?: EmbeddedImports }> {
504
+ ): Promise<{ irs: TemplateIR[]; embedded?: EmbeddedImports }> {
501
505
  const probe = new EmbeddedImports([], { quiet: true });
502
- const ir = parser.parse(content, { embedded: probe });
503
- if (probe.offered.length === 0) return { ir };
504
- const importers = await embeddedImportersFor(probe.offered, plugin, projectDir);
506
+ const irs = parses.map((parse) => parse({ embedded: probe }));
507
+ if (probe.offered.length === 0) return { irs };
508
+ const importers = await embeddedImportersFor(probe.offered, loaded, projectDir);
505
509
  const embedded = new EmbeddedImports(importers);
506
- return { ir: parser.parse(content, { embedded }), embedded };
510
+ return { irs: parses.map((parse) => parse({ embedded })), embedded };
507
511
  }
508
512
 
509
513
  /** The shared tail of every template import: parse → generate → write. */
@@ -535,7 +539,9 @@ async function parseAndWrite(
535
539
  let embedded: EmbeddedImports | undefined;
536
540
  try {
537
541
  const parser = plugin.templateParser();
538
- ({ ir, embedded } = await parseWithEmbedded(plugin, parser, content, projectDir));
542
+ let irs: TemplateIR[];
543
+ ({ irs, embedded } = await parseWithEmbedded([(context) => parser.parse(content, context)], [plugin], projectDir));
544
+ ir = irs[0];
539
545
  } catch (err) {
540
546
  return {
541
547
  success: false,
@@ -701,6 +707,7 @@ export async function liveImportFromPlugins(
701
707
  options: LiveImportOptions,
702
708
  ): Promise<ImportResult> {
703
709
  const outputDir = resolve(options.output ?? "./infra/");
710
+ const projectDir = resolve(options.output ? dirname(options.output) : ".");
704
711
  const warnings: string[] = [];
705
712
 
706
713
  let exporters = plugins.filter((p) => p.exportResources && p.templateGenerator);
@@ -720,10 +727,10 @@ export async function liveImportFromPlugins(
720
727
  }
721
728
 
722
729
  // Collect IR from every exporter, tagging which lexicon produced output.
723
- const irParts: TemplateIR[] = [];
730
+ let irParts: ExportedTemplate[] = [];
724
731
  let generatorLexicon: LexiconPlugin | undefined;
725
732
  for (const plugin of exporters) {
726
- let ir: TemplateIR;
733
+ let ir: ExportedTemplate;
727
734
  try {
728
735
  ir = await plugin.exportResources!({
729
736
  environment: options.environment,
@@ -754,6 +761,23 @@ export async function liveImportFromPlugins(
754
761
  warnings.push("Multiple lexicons exported resources; generated with the first. Use --lexicon to target one.");
755
762
  }
756
763
 
764
+ // Content embedded in exported resources goes to the lexicon that owns it,
765
+ // as in file import (#2995). An export without `reparse` keeps it as read.
766
+ let embedded: EmbeddedImports | undefined;
767
+ if (irParts.some((part) => part.reparse)) {
768
+ try {
769
+ const parses = irParts.map((part) => part.reparse ?? (() => part));
770
+ let irs: TemplateIR[];
771
+ ({ irs, embedded } = await parseWithEmbedded(parses, plugins, projectDir));
772
+ irParts = irs;
773
+ } catch (err) {
774
+ warnings.push(
775
+ `Embedded content is kept as written, because resolving it failed: ${err instanceof Error ? err.message : String(err)}`,
776
+ );
777
+ }
778
+ if (embedded) warnings.push(...embedded.warnings);
779
+ }
780
+
757
781
  const ir = mergeIR(irParts);
758
782
  const generator = generatorLexicon.templateGenerator!();
759
783
 
@@ -761,8 +785,10 @@ export async function liveImportFromPlugins(
761
785
  mkdirSync(outputDir, { recursive: true });
762
786
  }
763
787
 
764
- const { files, warnings: layoutWarnings } = generateOrganizedFiles(ir, generator);
788
+ const { files: hostFiles, warnings: layoutWarnings } = generateOrganizedFiles(ir, generator);
765
789
  warnings.push(...layoutWarnings);
790
+ // The owners' modules for embedded content, each in a directory of its own.
791
+ const files = [...hostFiles, ...(embedded?.files ?? [])];
766
792
  const generatedFiles: string[] = [];
767
793
  for (const file of files) {
768
794
  const filePath = join(outputDir, file.path);