@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
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The live link checks, WSP141 and WSP142 (#2549; #2524 D6, ws-062).
3
+ *
4
+ * `chant workspace check --live --env <env>` resolves each declared member
5
+ * link against what its producer's estate publishes now. It reads the live
6
+ * graph the way `chant workspace graph --live --env <env>` does: each `chant`
7
+ * member runs `chant graph --live` under its own toolchain, and the outputs of
8
+ * the live graph (the stack outputs a lexicon's live read reports) are the
9
+ * names a link may match, exactly. That is the one place this command reaches
10
+ * a network, through the member's own chant, with the member's own
11
+ * credentials. Without `--live` none of this runs, and the source checks
12
+ * (`./links.ts`) stay offline.
13
+ *
14
+ * | Id | Fails when |
15
+ * |---|---|
16
+ * | WSP141 | the producer was read live and publishes no output of that name |
17
+ * | WSP142 | the link could not be resolved live: the producer's kind is not read live, its read failed, or the read returned no outputs at all |
18
+ *
19
+ * Only declared links of kind `output` are resolved live. A `telemetry` link
20
+ * names a collector's pipeline or exporter, which the graph reports from the
21
+ * member's source, so it is not part of this check.
22
+ */
23
+
24
+ import type { WorkspaceCheck, WorkspaceDiagnostic } from "../checks";
25
+ import type { Declaration } from "../declaration";
26
+ import type { KindRegistry } from "../kinds";
27
+ import { DEFAULT_LINK_KIND, irMemberHandles, resolveLinks, type LinkRow } from "../links";
28
+
29
+ /** What a live read of the workspace found, for the live link checks. */
30
+ export interface LiveLinkFacts {
31
+ env: string;
32
+ /** Every declared `output` link, resolved against the live outputs. */
33
+ rows: LinkRow[];
34
+ }
35
+
36
+ const describeLink = (row: LinkRow) => `${row.consumer}'s link to ${row.producer} output ${row.output}`;
37
+
38
+ /**
39
+ * Read the live graph of the workspace at `cwd` for `env` and resolve the
40
+ * declared links against it. `onStderr` gets each member's stderr, which is
41
+ * where a lexicon's live read reports an account it could not reach.
42
+ */
43
+ export async function gatherLiveLinkFacts(cwd: string, declaration: Declaration, kinds: KindRegistry, env: string, onStderr?: (text: string) => void): Promise<LiveLinkFacts> {
44
+ const { workspaceGraph } = await import("../graph-cli");
45
+ const { doc } = await workspaceGraph({ cwd, args: { live: true, env }, noCache: true, ...(onStderr ? { onStderr } : {}) });
46
+ if ("error" in doc) {
47
+ return { env, rows: unreadRows(declaration, `the live graph could not be read: ${doc.error.code}: ${doc.error.message}`) };
48
+ }
49
+ const read = new Map(doc.members.map((m) => [m.name, m]));
50
+ const composed = doc.members.filter((m) => m.status === "composed").map((m) => m.name);
51
+ const handles = irMemberHandles({ composed, exports: doc.exports, imports: doc.imports });
52
+ const table = resolveLinks(declaration, handles);
53
+ const rows: LinkRow[] = [];
54
+ for (const r of table) {
55
+ if (r.status === "ambiguous" || r.origin !== "declared" || r.kind !== DEFAULT_LINK_KIND) continue;
56
+ const row: LinkRow = { ...r, resolves: "live" };
57
+ if (row.status !== "invalid") {
58
+ const producer = read.get(row.producer);
59
+ const kind = kinds.get(declaration.members.find((m) => m.name === row.producer)?.kind ?? "");
60
+ if (!producer || producer.status !== "composed") {
61
+ row.status = "unresolved";
62
+ row.reason =
63
+ producer?.reason?.message ??
64
+ (kind ? `${row.producer} is kind ${kind.name}, which chant does not read live` : `${row.producer} was not read live`);
65
+ } else if (row.status === "missing" && !doc.exports.some((e) => e.member === row.producer)) {
66
+ row.status = "unresolved";
67
+ row.reason = `the live read of ${row.producer} returned no outputs: the account may be unreachable, or its lexicon reports none`;
68
+ } else if (row.status === "missing") {
69
+ row.reason = row.reason?.replace(/^(\S+) has no output/, "$1 publishes no output") ?? row.reason;
70
+ }
71
+ }
72
+ rows.push(row);
73
+ }
74
+ return { env, rows };
75
+ }
76
+
77
+ /** Every declared output link as unresolved, when the live graph could not be read at all. */
78
+ function unreadRows(declaration: Declaration, reason: string): LinkRow[] {
79
+ return resolveLinks(declaration, new Map())
80
+ .filter((r): r is LinkRow => r.status !== "ambiguous" && r.origin === "declared" && r.kind === DEFAULT_LINK_KIND)
81
+ .map((r) => ({ ...r, resolves: "live", ...(r.status === "invalid" ? {} : { status: "unresolved" as const, reason }) }));
82
+ }
83
+
84
+ function liveFinding(check: WorkspaceCheck, row: LinkRow, message: string): WorkspaceDiagnostic {
85
+ return { checkId: check.id, severity: check.severity, message, entity: row.consumer, pointer: `${row.pointer}/output` };
86
+ }
87
+
88
+ export const LIVE_CHECKS: readonly WorkspaceCheck[] = [
89
+ {
90
+ id: "WSP141",
91
+ name: "link-live-missing",
92
+ description: "With --live, a member link names an output its producer's estate publishes now, matched exactly. A producer whose declared output isn't deployed yet, or was renamed in the estate, fails this for every consumer.",
93
+ severity: "warning",
94
+ configurable: true,
95
+ check(ctx) {
96
+ return (ctx.facts?.live?.rows ?? [])
97
+ .filter((r) => r.status === "missing")
98
+ .map((r) => liveFinding(this, r, `${describeLink(r)} does not resolve live in ${ctx.facts!.live!.env}: ${r.reason}`));
99
+ },
100
+ },
101
+ {
102
+ id: "WSP142",
103
+ name: "link-live-unresolved",
104
+ description: "With --live, a member link that could not be resolved against a live read is kept, unresolved, and reported: the producer's kind is not read live, its read failed, or the read returned no outputs.",
105
+ severity: "info",
106
+ configurable: true,
107
+ check(ctx) {
108
+ return (ctx.facts?.live?.rows ?? [])
109
+ .filter((r) => r.status === "unresolved")
110
+ .map((r) => liveFinding(this, r, `${describeLink(r)} is kept unresolved in ${ctx.facts!.live!.env}: ${r.reason}`));
111
+ },
112
+ },
113
+ ];
@@ -75,7 +75,7 @@ describe("the WSP catalog", () => {
75
75
  expect(list.filter((id) => /^WSP(07|08|09|10)\d$/.test(id))).toEqual([
76
76
  "WSP071", "WSP072", "WSP073",
77
77
  "WSP081", "WSP082", "WSP083",
78
- "WSP091", "WSP092", "WSP093", "WSP094", "WSP095", "WSP096", "WSP097",
78
+ "WSP091", "WSP092", "WSP093", "WSP094", "WSP095", "WSP096", "WSP097", "WSP098",
79
79
  "WSP101", "WSP102", "WSP103", "WSP104", "WSP105", "WSP106",
80
80
  ]);
81
81
  });
@@ -111,7 +111,7 @@ describe("declaration checks (#2535)", () => {
111
111
  column: 15,
112
112
  ruleId: "WSP003",
113
113
  severity: "error",
114
- message: "member infra has kind terraform, which no built-in kind or pinned package supplies; known kinds: chant, other, workspace",
114
+ message: "member infra has kind terraform, which no built-in kind or pinned package supplies; known kinds: chant, design, other, workspace",
115
115
  entity: "infra",
116
116
  },
117
117
  ]);
@@ -24,7 +24,7 @@
24
24
  * | WSP001 to WSP011 | the declaration and member kinds (this file) |
25
25
  * | WSP071, WSP072 | member ledgers |
26
26
  * | WSP081 to WSP083 | recorded pipelines |
27
- * | WSP091 to WSP097 | member links (#2539) |
27
+ * | WSP091 to WSP098 | member links (#2539, #2558) |
28
28
  * | WSP101 to WSP106 | generated files |
29
29
  * | WSP111 to WSP114 | records read with `--kind` (#2549) |
30
30
  * | WSP115 | the record kinds the declaration names (#2680) |
@@ -34,6 +34,7 @@
34
34
  * | WSP123, WSP124 | box isolation: no shared port, state path or cookie, no literal machine path (#2727) |
35
35
  * | WSP125 | boxes: a fountain Box declares the callback token fountain gives its sandbox (#2780) |
36
36
  * | WSP126, WSP127 | box intent: the decision record a box names exists, and constrains a member or path of this workspace (#2850, #2857) |
37
+ * | WSP141, WSP142 | member links resolved against a live read, with `--live` (#2549) |
37
38
  * | WSP131 to WSP133 | diagram artifacts: source and render exist, a recorded source hash still matches (#2764) |
38
39
  */
39
40
 
@@ -47,10 +48,11 @@ import { parseJsonText, pointerToken, type TextLocation } from "./jsonc";
47
48
  import { loadKindRegistry, probeKind, resolveKind, type KindLoadProblem, type KindRegistry } from "./kinds";
48
49
  import { gitTop, workingTree, type WorkspaceTree } from "./tree";
49
50
  import { LINK_CHECKS, linkTable } from "./checks/links";
50
- import type { LinkTableRow } from "./links";
51
+ import type { LinkRow, LinkTableRow } from "./links";
51
52
  import { gatherGeneratedFacts, GENERATED_CHECKS, type GeneratedFileFacts } from "./checks/generated";
52
53
  import { gatherLedgerFacts, LEDGER_CHECKS, type MemberLedgerFacts } from "./checks/ledgers";
53
54
  import { gatherPipelineFacts, PIPELINE_CHECKS, type MemberPipelineFacts } from "./checks/pipelines";
55
+ import { gatherLiveLinkFacts, LIVE_CHECKS, type LiveLinkFacts } from "./checks/live";
54
56
  import { RECORD_CHECKS, type RecordFacts } from "./checks/records";
55
57
  import { BOX_CHECKS } from "./checks/boxes";
56
58
  import { DIAGRAM_CHECKS } from "./checks/diagrams";
@@ -73,6 +75,8 @@ export interface WorkspaceFacts {
73
75
  records?: RecordFacts;
74
76
  /** The record kinds the declaration names, each loaded, or only looked for under `--at` (#2680). */
75
77
  declaredKinds?: readonly DeclaredKind[];
78
+ /** The declared links resolved against a live read, with `--live` (#2549). */
79
+ live?: LiveLinkFacts;
76
80
  /** The decision record each box's intent names, read from the working tree (#2850). */
77
81
  boxIntents?: readonly ResolvedBoxIntent[];
78
82
  }
@@ -137,6 +141,8 @@ export interface DeclarationCheckReport {
137
141
  suppressed: SuppressedFinding[];
138
142
  /** The member links, declared and inferred, as resolved in source (#2539). */
139
143
  links: LinkTableRow[];
144
+ /** With `--live`, the declared links as resolved against a live read of `env` (#2549). */
145
+ live?: { env: string; links: LinkRow[] };
140
146
  /** Whether an error-severity finding is active. */
141
147
  ok: boolean;
142
148
  }
@@ -319,7 +325,7 @@ export const WORKSPACE_CHECKS: readonly WorkspaceCheck[] = [
319
325
  ...LEDGER_CHECKS,
320
326
  // Recorded pipelines (#2542).
321
327
  ...PIPELINE_CHECKS,
322
- // Member links (#2539), WSP091 to WSP097.
328
+ // Member links (#2539, #2558), WSP091 to WSP098.
323
329
  ...LINK_CHECKS,
324
330
  // Generated files (#2541).
325
331
  ...GENERATED_CHECKS,
@@ -329,6 +335,8 @@ export const WORKSPACE_CHECKS: readonly WorkspaceCheck[] = [
329
335
  ...BOX_CHECKS,
330
336
  // Diagram artifacts (#2764).
331
337
  ...DIAGRAM_CHECKS,
338
+ // Links resolved against a live read, with --live (#2549), WSP141 and WSP142.
339
+ ...LIVE_CHECKS,
332
340
  ];
333
341
 
334
342
  const BY_ID = new Map(WORKSPACE_CHECKS.map((c) => [c.id, c]));
@@ -406,6 +414,13 @@ export interface DeclarationCheckOptions {
406
414
  tree?: WorkspaceTree;
407
415
  /** The records read with `--kind`, in the same tree (#2549). Given with or without `tree`. */
408
416
  records?: RecordFacts;
417
+ /**
418
+ * Resolve the declared links against a live read of this environment
419
+ * (`--live --env`, #2549). Each `chant` member runs `chant graph --live`, so
420
+ * this reaches whatever those reach. Not with `tree`: a live read is of the
421
+ * account now. `onStderr` gets each member's stderr.
422
+ */
423
+ live?: { env: string; onStderr?: (text: string) => void };
409
424
  }
410
425
 
411
426
  /**
@@ -480,6 +495,7 @@ export async function runDeclarationChecks(
480
495
  ...(options.records ? { records: options.records } : {}),
481
496
  ...(declaredKinds ? { declaredKinds } : {}),
482
497
  ...(boxIntents ? { boxIntents } : {}),
498
+ ...(options.live && !options.tree ? { live: await gatherLiveLinkFacts(root, declaration, registry, options.live.env, options.live.onStderr) } : {}),
483
499
  };
484
500
  const ctx: WorkspaceCheckContext = { declaration, tree, groups, kinds: registry, kindProblems: problems, facts };
485
501
  const findings = runWorkspaceChecks(ctx);
@@ -507,6 +523,7 @@ export async function runDeclarationChecks(
507
523
  diagnostics,
508
524
  suppressed: suppressed.map((s) => ({ ...toFinding(s), reason: s.reason })).sort(order),
509
525
  links: linkTable(ctx),
526
+ ...(facts.live ? { live: { env: facts.live.env, links: facts.live.rows } } : {}),
510
527
  ok: !diagnostics.some((d) => d.severity === "error"),
511
528
  };
512
529
  }
@@ -146,3 +146,50 @@ describe("the links section (#2539)", () => {
146
146
  ]);
147
147
  });
148
148
  });
149
+
150
+ describe("collectors (#2559)", () => {
151
+ const topology = {
152
+ pipelines: [{ id: "traces", signal: "traces", receivers: ["otlp"], processors: ["batch"], exporters: ["otlphttp/honeycomb"] }],
153
+ components: [
154
+ { id: "otlp", kind: "receiver", type: "otlp", builtin: true, endpoints: ["0.0.0.0:4317"], protocols: ["grpc"], pipelines: ["traces"] },
155
+ { id: "otlphttp/honeycomb", kind: "exporter", type: "otlphttp", builtin: true, endpoints: ["https://api.honeycomb.io"], protocols: ["http/protobuf"], pipelines: ["traces"] },
156
+ ],
157
+ exporters: [{ id: "otlphttp/honeycomb", type: "otlphttp", endpoints: ["https://api.honeycomb.io"], pipelines: ["traces"], signals: ["traces"] }],
158
+ edges: [],
159
+ semconv: [],
160
+ };
161
+ const withMeta = (meta: Record<string, unknown>): GraphIR => ({ ...structuredClone(db), meta });
162
+
163
+ test("lifts a member's meta.collector into a section and keeps the rest of its meta", () => {
164
+ const inputs = [
165
+ { member: member("web", "web"), ir: structuredClone(web) },
166
+ { member: member("ops", "ops"), ir: withMeta({ collector: topology, other: 1 }) },
167
+ ];
168
+ const doc = composeWorkspaceGraph({ name: "acme", root: "/w" }, inputs);
169
+ expect(doc.collectors).toEqual([
170
+ {
171
+ member: "ops",
172
+ pipelines: topology.pipelines,
173
+ components: topology.components.map(({ builtin: _b, ...c }) => c),
174
+ exporters: topology.exporters,
175
+ },
176
+ ]);
177
+ expect(doc.members.find((m) => m.name === "ops")!.meta).toEqual({ other: 1 });
178
+ });
179
+
180
+ test("is empty when no member reports a collector, and leaves meta alone", () => {
181
+ const doc = composeWorkspaceGraph({ name: "acme", root: "/w" }, [
182
+ { member: member("web", "web"), ir: structuredClone(web) },
183
+ { member: member("ops", "ops"), ir: withMeta({ _behaviour: { engine: "x" } }) },
184
+ ]);
185
+ expect(doc.collectors).toEqual([]);
186
+ expect(doc.members.find((m) => m.name === "ops")!.meta).toEqual({ _behaviour: { engine: "x" } });
187
+ });
188
+
189
+ test("ignores a meta.collector that is not a topology", () => {
190
+ for (const bad of ["x", 1, null, {}, { pipelines: "no" }]) {
191
+ const doc = composeWorkspaceGraph({ name: "acme", root: "/w" }, [{ member: member("ops", "ops"), ir: withMeta({ collector: bad }) }]);
192
+ expect(doc.collectors, JSON.stringify(bad)).toEqual([]);
193
+ }
194
+ });
195
+ });
@@ -87,12 +87,75 @@ export interface ComposedMember {
87
87
  cached?: boolean;
88
88
  /** The member's stamp (#2876): what the cache keys the read on, or null when none could be taken. */
89
89
  stamp?: string | null;
90
+ /**
91
+ * For a member of kind `workspace` (#2551, ws-071): the nested workspace's
92
+ * own view, read through its own `chant workspace graph`. Its nodes are in
93
+ * the outer `nodes` with `outer/inner/id` ids.
94
+ */
95
+ nested?: NestedWorkspace;
90
96
  /** Whole-read facts the member's IR carried (`meta`, `pipeline`), kept apart from the composed sections. */
91
97
  meta?: Record<string, unknown>;
92
98
  pipeline?: unknown;
93
99
  }
94
100
 
95
- export type ComposedNode = IRNode & { member: string };
101
+ /** A nested workspace as the outer graph shows it (#2551, ./nested-graph.ts). */
102
+ export interface NestedWorkspace {
103
+ /** The nested declaration's name. */
104
+ name: string;
105
+ /** The contract version of the document the nested read printed. */
106
+ contract: number;
107
+ /** The nested workspace's members, as its own graph lists them. */
108
+ members: ComposedMember[];
109
+ /** The nested workspace's links, with member names as the nested side writes them. */
110
+ links: (LinkTableRow | RecordLinkRow)[];
111
+ }
112
+
113
+ /**
114
+ * A collector pipeline as a member's IR reports it (#2559). Plain data, so
115
+ * core never imports the lexicon that computes it.
116
+ */
117
+ export interface CollectorPipeline {
118
+ /** The id under `service.pipelines`, such as `traces` or `traces/backend`. */
119
+ id: string;
120
+ signal: string;
121
+ receivers: string[];
122
+ processors: string[];
123
+ exporters: string[];
124
+ }
125
+
126
+ /** One collector component with the addresses its config states. */
127
+ export interface CollectorComponent {
128
+ id: string;
129
+ kind: string;
130
+ type: string;
131
+ endpoints: string[];
132
+ /** The wire protocols it speaks (`grpc`, `http/protobuf`, `http/json`); empty when unknown (#2558). */
133
+ protocols: string[];
134
+ pipelines: string[];
135
+ }
136
+
137
+ /** An exporter and the signals that reach it: where a member's telemetry goes. */
138
+ export interface CollectorExporter {
139
+ id: string;
140
+ type: string;
141
+ endpoints: string[];
142
+ pipelines: string[];
143
+ signals: string[];
144
+ }
145
+
146
+ /** The topology of one member's collector config, from the lexicon that types it. */
147
+ export interface MemberCollector {
148
+ member: string;
149
+ pipelines: CollectorPipeline[];
150
+ components: CollectorComponent[];
151
+ exporters: CollectorExporter[];
152
+ }
153
+
154
+ /** The meta key a lexicon's `graphMeta` hook answers the collector under. */
155
+ export const COLLECTOR_META_KEY = "collector";
156
+
157
+ /** `nested` names the nested workspace's member a node was read through (#2551); absent for the outer workspace's own nodes. */
158
+ export type ComposedNode = IRNode & { member: string; nested?: string };
96
159
  export type ComposedEdge = IREdge & { member: string };
97
160
 
98
161
  export interface WorkspaceGraph {
@@ -110,6 +173,11 @@ export interface WorkspaceGraph {
110
173
  * the records read with `--kind` (#2549). Empty when no declaration is given.
111
174
  */
112
175
  links: (LinkTableRow | RecordLinkRow)[];
176
+ /**
177
+ * The collector pipelines and exporters of each member that declares a
178
+ * collector, in declaration order (#2559). Empty when none does.
179
+ */
180
+ collectors: MemberCollector[];
113
181
  /** The records read with `--kind` (#2524 D4, #2549). Empty without it. */
114
182
  records: GraphRecord[];
115
183
  }
@@ -163,6 +231,46 @@ export interface ComposeInput {
163
231
 
164
232
  const prefix = (member: string, id: string): string => `${member}/${id}`;
165
233
 
234
+ const strings = (v: unknown): string[] => (Array.isArray(v) ? v.filter((x): x is string => typeof x === "string") : []);
235
+ const records = (v: unknown): Record<string, unknown>[] =>
236
+ Array.isArray(v) ? v.filter((x): x is Record<string, unknown> => typeof x === "object" && x !== null) : [];
237
+
238
+ /**
239
+ * A member's collector topology from its IR meta, or null when the meta holds
240
+ * none or something that isn't one. Read defensively: the topology comes from
241
+ * a lexicon the member's own toolchain loaded, which may be newer or older
242
+ * than this chant.
243
+ */
244
+ function readCollector(member: string, value: unknown): MemberCollector | null {
245
+ if (typeof value !== "object" || value === null || !Array.isArray((value as { pipelines?: unknown }).pipelines)) return null;
246
+ const v = value as Record<string, unknown>;
247
+ return {
248
+ member,
249
+ pipelines: records(v.pipelines).map((p) => ({
250
+ id: String(p.id),
251
+ signal: String(p.signal),
252
+ receivers: strings(p.receivers),
253
+ processors: strings(p.processors),
254
+ exporters: strings(p.exporters),
255
+ })),
256
+ components: records(v.components).map((c) => ({
257
+ id: String(c.id),
258
+ kind: String(c.kind),
259
+ type: String(c.type),
260
+ endpoints: strings(c.endpoints),
261
+ protocols: strings(c.protocols),
262
+ pipelines: strings(c.pipelines),
263
+ })),
264
+ exporters: records(v.exporters).map((e) => ({
265
+ id: String(e.id),
266
+ type: String(e.type),
267
+ endpoints: strings(e.endpoints),
268
+ pipelines: strings(e.pipelines),
269
+ signals: strings(e.signals),
270
+ })),
271
+ };
272
+ }
273
+
166
274
  function prefixRefs(value: unknown, member: string): unknown {
167
275
  if (Array.isArray(value)) return value.map((v) => prefixRefs(v, member));
168
276
  if (value === null || typeof value !== "object") return value;
@@ -202,6 +310,7 @@ export function composeWorkspaceGraph(
202
310
  const shared: Record<"byLexicon" | "byComposite", Record<string, string[]>> = { byLexicon: {}, byComposite: {} };
203
311
  const scoped: Record<"byStack" | "byContainer" | "byWave", Record<string, string[]>> = { byStack: {}, byContainer: {}, byWave: {} };
204
312
  const derivedAttrs: Record<string, Set<string>> = {};
313
+ const collectors: MemberCollector[] = [];
205
314
 
206
315
  for (const { member, ir } of inputs) {
207
316
  if (!ir) continue;
@@ -225,7 +334,14 @@ export function composeWorkspaceGraph(
225
334
  for (const e of ir.exports ?? []) exports.push({ ...e, ...(e.node ? { node: prefix(m, e.node) } : {}), member: m });
226
335
  for (const i of ir.imports ?? []) imports.push({ ...i, node: prefix(m, i.node), member: m });
227
336
  for (const [kind, attrs] of Object.entries(ir.derivedAttrs ?? {})) for (const a of attrs) (derivedAttrs[kind] ??= new Set()).add(a);
228
- if (ir.meta) member.meta = ir.meta;
337
+ if (ir.meta) {
338
+ // The collector topology has a section of its own; the rest of the meta stays on the member.
339
+ const { [COLLECTOR_META_KEY]: collector, ...rest } = ir.meta;
340
+ const read = readCollector(m, collector);
341
+ if (read) collectors.push(read);
342
+ if (!read) member.meta = ir.meta;
343
+ else if (Object.keys(rest).length > 0) member.meta = rest;
344
+ }
229
345
  if (ir.pipeline) member.pipeline = ir.pipeline;
230
346
  }
231
347
 
@@ -247,8 +363,9 @@ export function composeWorkspaceGraph(
247
363
  exports,
248
364
  imports,
249
365
  links: links
250
- ? graphLinks(links.declaration, { composed: inputs.filter((i) => i.ir).map((i) => i.member.name), exports, imports }, links.kinds)
366
+ ? graphLinks(links.declaration, { composed: inputs.filter((i) => i.ir).map((i) => i.member.name), collectors, exports, imports }, links.kinds)
251
367
  : [],
368
+ collectors,
252
369
  records: [],
253
370
  };
254
371
  if (Object.keys(derivedAttrs).length) {
@@ -14,8 +14,14 @@ export const recordKind = {
14
14
  idField: "id",
15
15
  stateField: "state",
16
16
  states: ["proposed", "decided", "ratified", "superseded", "withdrawn"],
17
- // Sealed once reached (#2555).
17
+ // Sealed once reached (#2555): records amend and new write the whole-file
18
+ // seal into closed_digest when a decision enters one, and records checks it
19
+ // on every read (#2546, ws-063).
18
20
  closedStates: ["ratified", "superseded"],
21
+ seal: { field: "closed_digest" },
22
+ // The current decisions, with the files they pin, are the workspace's spec:
23
+ // records --current --json lists them under spec (#2524 D20, #2546).
24
+ spec: true,
19
25
  supersedes: { field: "supersedes", key: "decision" },
20
26
  // A remediates link names a closed decision this one fixes the consequence
21
27
  // of, without replacing it: the target's state never changes (#2774).
@@ -227,6 +227,11 @@
227
227
  "signature": { "description": "The armored ssh signature, as ssh-keygen -Y sign prints it.", "type": "string", "pattern": "^-----BEGIN SSH SIGNATURE-----\\n" }
228
228
  }
229
229
  },
230
+ "closed_digest": {
231
+ "description": "The decision's whole-file seal (#2546, ws-063): sha256: and the lowercase hex SHA-256 of the JCS form of {body, core}, where core is this front matter without closed_digest and body is the text below it, line endings LF. chant workspace records amend and new write it when the decision enters a closed state (ratified or superseded), and records reports record-seal-mismatch if the file changes afterwards. It covers the reviews, the author seal and the state, which the review digest leaves out, and the digest leaves this field out.",
232
+ "type": "string",
233
+ "pattern": "^sha256:[0-9a-f]{64}$"
234
+ },
230
235
  "constrains": {
231
236
  "description": "What this decision governs: issues (owner/repo#n), other decisions (their ids), workspace members (member:<name>) or files and directories in the workspace (path:<path>, the grammar of an evidence path, #2549). At least one entry: a decision that governs nothing is refused.",
232
237
  "type": "array",
@@ -101,6 +101,36 @@
101
101
  }
102
102
  }
103
103
  }
104
+ },
105
+ "writeScope": {
106
+ "type": "object",
107
+ "additionalProperties": false,
108
+ "description": "Who may write what (#2524 D5, #2548, ws-067): for each principal class, the members whose files it may write and the record kinds it may write, with which verbs. A class with no entry is not restricted. An agent is always bound to its session's member (agents); its entry can only narrow the record kinds and verbs. chant workspace records new, amend, review and close and the MCP record tools refuse a write outside the writer's scope with write-scope-member or write-scope-kind, and chant workspace check --changes reports each commit that writes outside its author's scope, read from the declaration and the trust policy at the range's base. Added in schema 1 by chant 0.101.0, so a declaration that uses it sets minReader to 0.101.0 or newer.",
109
+ "properties": {
110
+ "human": {
111
+ "$ref": "#/$defs/classScope",
112
+ "description": "Principals holding none of the agent, runner and service roles in the trust policy at base."
113
+ },
114
+ "agent": {
115
+ "$ref": "#/$defs/agentScope",
116
+ "description": "An agent session (agents), or a principal holding the agent role in the trust policy at base."
117
+ },
118
+ "runner": {
119
+ "$ref": "#/$defs/classScope",
120
+ "description": "Principals holding the runner role in the trust policy at base, such as a CI job's signing identity."
121
+ },
122
+ "service": {
123
+ "$ref": "#/$defs/classScope",
124
+ "description": "Principals holding the service role in the trust policy at base."
125
+ }
126
+ }
127
+ },
128
+ "agents": {
129
+ "type": "array",
130
+ "description": "The agent sessions the workspace declares (#2524 D20, #2548, ws-067). Each is bound to one member: it writes that member's files and the records of kinds that member or the workspace declares, as writeScope.agent allows. A session resumes from chant workspace agent <name> --json, which prints its member, its scope and the spec, read from the repository alone. A write names its session with the CHANT_AGENT environment variable, or a commit with a Chant-Agent trailer. Added in schema 1 by chant 0.101.0, so a declaration that uses it sets minReader to 0.101.0 or newer.",
131
+ "items": {
132
+ "$ref": "#/$defs/agent"
133
+ }
104
134
  }
105
135
  },
106
136
  "patternProperties": {
@@ -173,7 +203,7 @@
173
203
  },
174
204
  "link": {
175
205
  "type": "object",
176
- "description": "A member link: this member reads output of member (#2539).",
206
+ "description": "A member link: this member reads output of member (#2539), or sends its telemetry to a pipeline of its collector (#2558).",
177
207
  "required": [
178
208
  "member",
179
209
  "output"
@@ -185,11 +215,19 @@
185
215
  },
186
216
  "output": {
187
217
  "$ref": "#/$defs/outputName",
188
- "description": "The producer's output, matched exactly. It must be one the producer's kind exposes."
218
+ "description": "The producer's output, matched exactly. It must be one the producer's kind exposes. For a telemetry link, the id of a pipeline or an exporter in the producer's collector."
189
219
  },
190
220
  "kind": {
191
221
  "$ref": "#/$defs/kindName",
192
- "description": "The link kind. output, the default, is the only one so far: the consumer reads the named output."
222
+ "description": "The link kind. output, the default: the consumer reads the named output. telemetry (#2558): the consumer sends its telemetry to the named pipeline or exporter of the producer's collector, and chant workspace graph checks that it exists and, when protocol is given, that it speaks it. A declaration that uses telemetry sets minReader to 0.101.0 or newer."
223
+ },
224
+ "protocol": {
225
+ "enum": [
226
+ "grpc",
227
+ "http/protobuf",
228
+ "http/json"
229
+ ],
230
+ "description": "For a telemetry link, the OTLP protocol the consumer sends, as OTEL_EXPORTER_OTLP_PROTOCOL names it. Refused on any other kind. Added in schema 1 by chant 0.101.0."
193
231
  }
194
232
  },
195
233
  "patternProperties": {
@@ -320,6 +358,10 @@
320
358
  "minLength": 1,
321
359
  "description": "Where an embedded member's files come from. Versions live in the lock."
322
360
  },
361
+ "travel": {
362
+ "type": "boolean",
363
+ "description": "Whether the member goes with an export (#2524 D10, #2552). chant workspace export writes only members that set travel to true into the export member, and refuses to name one that does not. Added in schema 1 by chant 0.101.0, so a declaration that uses it sets minReader to 0.101.0 or newer."
364
+ },
323
365
  "because": {
324
366
  "type": "string",
325
367
  "minLength": 1,
@@ -858,6 +900,99 @@
858
900
  "type": "string",
859
901
  "description": "A glob over file paths, relative to the workspace root: * and ? within one segment, ** across segments, [...] classes and {a,b} alternatives. No leading /, no . or .. segments.",
860
902
  "pattern": "^(?!\\.\\.?(/|$))[^/\\\\]+(/(?!\\.\\.?(/|$))[^/\\\\]+)*$"
903
+ },
904
+ "recordScope": {
905
+ "type": "object",
906
+ "description": "Record kind names, as the kind file's recordKind.name or the name the declaration gives it, each with the verbs the class may write it with: new, amend, review and close (a review session). A kind left out may not be written at all. A record file a commit deletes is never in scope.",
907
+ "propertyNames": {
908
+ "$ref": "#/$defs/kindName"
909
+ },
910
+ "additionalProperties": {
911
+ "type": "array",
912
+ "uniqueItems": true,
913
+ "items": {
914
+ "enum": [
915
+ "new",
916
+ "amend",
917
+ "review",
918
+ "close"
919
+ ]
920
+ }
921
+ }
922
+ },
923
+ "classScope": {
924
+ "type": "object",
925
+ "description": "A principal class's write scope.",
926
+ "properties": {
927
+ "members": {
928
+ "description": "The members whose files the class may write, by name, or * for every path. Without it, every path. A path in no member is in scope only under *.",
929
+ "oneOf": [
930
+ {
931
+ "const": "*"
932
+ },
933
+ {
934
+ "type": "array",
935
+ "uniqueItems": true,
936
+ "items": {
937
+ "$ref": "#/$defs/name"
938
+ }
939
+ }
940
+ ]
941
+ },
942
+ "records": {
943
+ "$ref": "#/$defs/recordScope",
944
+ "description": "The record kinds the class may write, and how. Without it, every kind in reach, with every verb. A kind a member declares is in reach when that member is; the workspace's own kinds always are."
945
+ }
946
+ },
947
+ "patternProperties": {
948
+ "^x-": true
949
+ },
950
+ "additionalProperties": false
951
+ },
952
+ "agentScope": {
953
+ "type": "object",
954
+ "description": "The agent class's write scope. Its members are always its session's one member, so only the record kinds can be set.",
955
+ "properties": {
956
+ "records": {
957
+ "$ref": "#/$defs/recordScope",
958
+ "description": "The record kinds an agent may write, and how. Without it, every kind in reach of its member, with every verb."
959
+ }
960
+ },
961
+ "patternProperties": {
962
+ "^x-": true
963
+ },
964
+ "additionalProperties": false
965
+ },
966
+ "agent": {
967
+ "type": "object",
968
+ "description": "An agent session (#2548).",
969
+ "required": [
970
+ "name",
971
+ "member"
972
+ ],
973
+ "properties": {
974
+ "name": {
975
+ "$ref": "#/$defs/name",
976
+ "description": "The session's name, unique among agents: what CHANT_AGENT and a Chant-Agent trailer give."
977
+ },
978
+ "member": {
979
+ "$ref": "#/$defs/name",
980
+ "description": "The one member the session is bound to. It must be a declared member, not an example group."
981
+ },
982
+ "principals": {
983
+ "type": "array",
984
+ "uniqueItems": true,
985
+ "items": {
986
+ "type": "string",
987
+ "minLength": 1
988
+ },
989
+ "description": "Principals that write only as this session: a commit attested by one, or a write whose by names one, is judged as the session. A principal is listed by one session at most."
990
+ }
991
+ },
992
+ "patternProperties": {
993
+ "^x-": true
994
+ },
995
+ "additionalProperties": false
861
996
  }
862
997
  }
863
998
  }