@intentius/chant 0.89.0 → 0.91.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 (194) hide show
  1. package/dist/cli/handlers/components.d.ts +8 -0
  2. package/dist/cli/handlers/components.d.ts.map +1 -1
  3. package/dist/cli/handlers/operator.d.ts +14 -0
  4. package/dist/cli/handlers/operator.d.ts.map +1 -1
  5. package/dist/cli/handlers/run.d.ts.map +1 -1
  6. package/dist/cli/handlers/serve.d.ts.map +1 -1
  7. package/dist/cli/main.d.ts.map +1 -1
  8. package/dist/cli/mcp/server.d.ts +10 -5
  9. package/dist/cli/mcp/server.d.ts.map +1 -1
  10. package/dist/cli/mcp/types.d.ts +13 -5
  11. package/dist/cli/mcp/types.d.ts.map +1 -1
  12. package/dist/cli/mcp/workspace-tools.d.ts +55 -0
  13. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -0
  14. package/dist/cli/registry.d.ts +31 -0
  15. package/dist/cli/registry.d.ts.map +1 -1
  16. package/dist/components/verbs/vuln-scan.d.ts +72 -0
  17. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  18. package/dist/lifecycle/git.d.ts +40 -5
  19. package/dist/lifecycle/git.d.ts.map +1 -1
  20. package/dist/lifecycle/lease.d.ts +72 -16
  21. package/dist/lifecycle/lease.d.ts.map +1 -1
  22. package/dist/lifecycle/member-ledger.d.ts +3 -2
  23. package/dist/lifecycle/member-ledger.d.ts.map +1 -1
  24. package/dist/lifecycle/plan-ledger.d.ts +114 -0
  25. package/dist/lifecycle/plan-ledger.d.ts.map +1 -0
  26. package/dist/lifecycle/work-lease.d.ts +140 -0
  27. package/dist/lifecycle/work-lease.d.ts.map +1 -0
  28. package/dist/op/activities/activity-contracts.d.ts +2 -2
  29. package/dist/op/builders.d.ts.map +1 -1
  30. package/dist/op/discover.d.ts +25 -0
  31. package/dist/op/discover.d.ts.map +1 -1
  32. package/dist/op/index.d.ts +9 -3
  33. package/dist/op/index.d.ts.map +1 -1
  34. package/dist/op/lifecycle-receipt-store.d.ts +34 -0
  35. package/dist/op/lifecycle-receipt-store.d.ts.map +1 -0
  36. package/dist/op/local-executor.d.ts +19 -0
  37. package/dist/op/local-executor.d.ts.map +1 -1
  38. package/dist/op/local-output.d.ts.map +1 -1
  39. package/dist/op/op-ir.d.ts +10 -1
  40. package/dist/op/op-ir.d.ts.map +1 -1
  41. package/dist/op/op-verb-class.d.ts.map +1 -1
  42. package/dist/op/operator.d.ts +29 -0
  43. package/dist/op/operator.d.ts.map +1 -1
  44. package/dist/op/runtime.d.ts +9 -0
  45. package/dist/op/runtime.d.ts.map +1 -1
  46. package/dist/op/runtimes/local.d.ts.map +1 -1
  47. package/dist/op/step-output-ref.d.ts +2 -2
  48. package/dist/op/step-output-ref.d.ts.map +1 -1
  49. package/dist/op/steward.d.ts +140 -0
  50. package/dist/op/steward.d.ts.map +1 -0
  51. package/dist/op/types.d.ts +51 -0
  52. package/dist/op/types.d.ts.map +1 -1
  53. package/dist/op/work-lease-decl.d.ts +18 -0
  54. package/dist/op/work-lease-decl.d.ts.map +1 -0
  55. package/dist/op/work-lease-run.d.ts +173 -0
  56. package/dist/op/work-lease-run.d.ts.map +1 -0
  57. package/dist/workspace/box-isolation.d.ts +99 -0
  58. package/dist/workspace/box-isolation.d.ts.map +1 -0
  59. package/dist/workspace/checks/box-isolation.d.ts +18 -0
  60. package/dist/workspace/checks/box-isolation.d.ts.map +1 -0
  61. package/dist/workspace/checks/boxes.d.ts +71 -0
  62. package/dist/workspace/checks/boxes.d.ts.map +1 -0
  63. package/dist/workspace/checks/records.d.ts +1 -0
  64. package/dist/workspace/checks/records.d.ts.map +1 -1
  65. package/dist/workspace/checks.d.ts +10 -1
  66. package/dist/workspace/checks.d.ts.map +1 -1
  67. package/dist/workspace/conformance/index.d.ts +42 -2
  68. package/dist/workspace/conformance/index.d.ts.map +1 -1
  69. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  70. package/dist/workspace/decide.d.ts +184 -0
  71. package/dist/workspace/decide.d.ts.map +1 -0
  72. package/dist/workspace/decision-points.schema.json +137 -0
  73. package/dist/workspace/declaration.d.ts +51 -0
  74. package/dist/workspace/declaration.d.ts.map +1 -1
  75. package/dist/workspace/declaration.schema.json +172 -0
  76. package/dist/workspace/declared-kinds.d.ts +12 -0
  77. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  78. package/dist/workspace/points-cli.d.ts +113 -0
  79. package/dist/workspace/points-cli.d.ts.map +1 -0
  80. package/dist/workspace/points.d.ts +320 -0
  81. package/dist/workspace/points.d.ts.map +1 -0
  82. package/dist/workspace/reason-codes.d.ts +29 -0
  83. package/dist/workspace/reason-codes.d.ts.map +1 -1
  84. package/dist/workspace/record-assets.d.ts.map +1 -1
  85. package/dist/workspace/records-cli.d.ts +12 -0
  86. package/dist/workspace/records-cli.d.ts.map +1 -1
  87. package/dist/workspace/records-write.d.ts +35 -3
  88. package/dist/workspace/records-write.d.ts.map +1 -1
  89. package/dist/workspace/records.d.ts +12 -3
  90. package/dist/workspace/records.d.ts.map +1 -1
  91. package/dist/workspace/source-block.d.ts +85 -0
  92. package/dist/workspace/source-block.d.ts.map +1 -0
  93. package/dist/workspace/status-stewards.d.ts +121 -0
  94. package/dist/workspace/status-stewards.d.ts.map +1 -0
  95. package/dist/workspace/status.d.ts +52 -1
  96. package/dist/workspace/status.d.ts.map +1 -1
  97. package/dist/workspace/work-cli.d.ts +78 -0
  98. package/dist/workspace/work-cli.d.ts.map +1 -0
  99. package/package.json +1 -1
  100. package/src/cli/handlers/components.test.ts +93 -0
  101. package/src/cli/handlers/components.ts +44 -3
  102. package/src/cli/handlers/operator.ts +107 -2
  103. package/src/cli/handlers/run.test.ts +19 -0
  104. package/src/cli/handlers/run.ts +53 -1
  105. package/src/cli/handlers/serve.ts +2 -1
  106. package/src/cli/main.test.ts +40 -0
  107. package/src/cli/main.ts +78 -2
  108. package/src/cli/mcp/docs-parity.test.ts +20 -2
  109. package/src/cli/mcp/server.ts +23 -6
  110. package/src/cli/mcp/types.ts +15 -2
  111. package/src/cli/mcp/workspace-tools.test.ts +211 -0
  112. package/src/cli/mcp/workspace-tools.ts +449 -0
  113. package/src/cli/registry.ts +31 -0
  114. package/src/components/verbs/vuln-scan.test.ts +124 -1
  115. package/src/components/verbs/vuln-scan.ts +142 -1
  116. package/src/lifecycle/git.ts +65 -15
  117. package/src/lifecycle/lease.test.ts +22 -0
  118. package/src/lifecycle/lease.ts +133 -29
  119. package/src/lifecycle/member-ledger.ts +3 -2
  120. package/src/lifecycle/plan-ledger.test.ts +148 -0
  121. package/src/lifecycle/plan-ledger.ts +158 -0
  122. package/src/lifecycle/work-lease.test.ts +236 -0
  123. package/src/lifecycle/work-lease.ts +426 -0
  124. package/src/op/builders.ts +5 -0
  125. package/src/op/discover.ts +71 -0
  126. package/src/op/index.ts +16 -3
  127. package/src/op/lifecycle-receipt-store.test.ts +60 -0
  128. package/src/op/lifecycle-receipt-store.ts +61 -0
  129. package/src/op/local-executor.ts +216 -18
  130. package/src/op/local-output.ts +13 -0
  131. package/src/op/op-ir.ts +14 -0
  132. package/src/op/op-verb-class.ts +6 -0
  133. package/src/op/operator.ts +75 -4
  134. package/src/op/runtime.ts +6 -0
  135. package/src/op/runtimes/local.ts +3 -0
  136. package/src/op/step-output-ref.ts +6 -2
  137. package/src/op/steward.test.ts +212 -0
  138. package/src/op/steward.ts +253 -0
  139. package/src/op/types.ts +53 -0
  140. package/src/op/work-lease-decl.ts +80 -0
  141. package/src/op/work-lease-run.test.ts +326 -0
  142. package/src/op/work-lease-run.ts +395 -0
  143. package/src/workspace/box-isolation.test.ts +261 -0
  144. package/src/workspace/box-isolation.ts +205 -0
  145. package/src/workspace/check-contract.test.ts +3 -1
  146. package/src/workspace/check.schema.json +15 -7
  147. package/src/workspace/checks/box-isolation.ts +68 -0
  148. package/src/workspace/checks/boxes.test.ts +197 -0
  149. package/src/workspace/checks/boxes.ts +307 -0
  150. package/src/workspace/checks/records.ts +25 -0
  151. package/src/workspace/checks.test.ts +7 -0
  152. package/src/workspace/checks.ts +19 -2
  153. package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +4 -0
  154. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +100 -6
  155. package/src/workspace/conformance/index.mjs +3 -0
  156. package/src/workspace/conformance/index.ts +185 -7
  157. package/src/workspace/conformance/vitest.ts +17 -8
  158. package/src/workspace/decide.test.ts +224 -0
  159. package/src/workspace/decide.ts +576 -0
  160. package/src/workspace/decision-points.schema.json +137 -0
  161. package/src/workspace/declaration.schema.json +172 -0
  162. package/src/workspace/declaration.ts +137 -0
  163. package/src/workspace/declared-kinds.ts +25 -2
  164. package/src/workspace/intent.schema.json +4 -1
  165. package/src/workspace/point-answer.schema.json +95 -0
  166. package/src/workspace/points-cli.ts +273 -0
  167. package/src/workspace/points-write.schema.json +489 -0
  168. package/src/workspace/points.schema.json +710 -0
  169. package/src/workspace/points.test.ts +264 -0
  170. package/src/workspace/points.ts +564 -0
  171. package/src/workspace/read-contract.test.ts +18 -1
  172. package/src/workspace/reason-codes.test.ts +14 -1
  173. package/src/workspace/reason-codes.ts +36 -0
  174. package/src/workspace/record-assets.test.ts +3 -2
  175. package/src/workspace/record-assets.ts +4 -1
  176. package/src/workspace/records-amend.schema.json +2 -1
  177. package/src/workspace/records-cli.ts +15 -2
  178. package/src/workspace/records-close.schema.json +2 -1
  179. package/src/workspace/records-contract.test.ts +3 -2
  180. package/src/workspace/records-new.schema.json +4 -1
  181. package/src/workspace/records-review.schema.json +2 -1
  182. package/src/workspace/records-write.ts +85 -7
  183. package/src/workspace/records.schema.json +18 -0
  184. package/src/workspace/records.ts +61 -3
  185. package/src/workspace/source-block.test.ts +167 -0
  186. package/src/workspace/source-block.ts +129 -0
  187. package/src/workspace/status-contract.test.ts +178 -0
  188. package/src/workspace/status-stewards.ts +225 -0
  189. package/src/workspace/status.schema.json +230 -4
  190. package/src/workspace/status.ts +106 -5
  191. package/src/workspace/work-cli.test.ts +180 -0
  192. package/src/workspace/work-cli.ts +246 -0
  193. package/src/workspace/work-lease.schema.json +233 -0
  194. package/src/workspace/work-readiness-chud.test.ts +145 -0
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Stewards in `chant workspace status --json` (#2731): which steward runs a
3
+ * member's operational work, the form it takes in the environment asked for,
4
+ * its Ops with their schedules, and each Op's last run.
5
+ *
6
+ * The steward itself is read from its declaration, the same `*.op.ts` files
7
+ * `chant operator --steward` reads (`../op/discover.ts`), and only for a member
8
+ * of kind `chant` that is a chant project of its own (a `chant.config.ts` or
9
+ * `.json` in its directory), so a member never lists a parent project's
10
+ * steward. Everything
11
+ * else is read from the local `chant/lifecycle` branch and refs, never
12
+ * fetched, as the rest of status is:
13
+ *
14
+ * - each Op's last run is the newest record in its run ledger,
15
+ * `<env>/runs__<op>.jsonl` under the member's ledger prefix, where `<env>`
16
+ * is the Op's own `labels.Env` or `local`;
17
+ * - `lease` is the local steward's own lease ref
18
+ * (`refs/chant/lease/[<prefix>]_stewards/<name>`), present while a
19
+ * `chant operator --steward` holds it or until it expires;
20
+ * - an Op's `workLease.held` is the work leases its turns hold (#2748): those
21
+ * whose holder is `<steward>/<op>@...` (`stewardWorkHolder`), read from the
22
+ * ledger of the Op's work kind, or the member's own.
23
+ */
24
+
25
+ import { existsSync, realpathSync } from "node:fs";
26
+ import { dirname, join, relative, resolve } from "node:path";
27
+ import { discoverStewards } from "../op/discover";
28
+ import { stewardFormFor, stewardLeaseName, type StewardForm } from "../op/steward";
29
+ import { readRunLedger, runEnvOf } from "../lifecycle/run-ledger";
30
+ import { leaseRef } from "../lifecycle/lease";
31
+ import { readBlobBySha, readRefSha } from "../lifecycle/git";
32
+ import { resolveMemberLedger } from "../lifecycle/member-ledger";
33
+ import { listWorkLeases } from "../lifecycle/work-lease";
34
+ import { stewardWorkHolder } from "../op/work-lease-run";
35
+ import type { OpConfig } from "../op/types";
36
+ import type { OpRunRecord } from "../op/runtime";
37
+ import type { ReasonCode } from "./reason-codes";
38
+
39
+ /** Why a member's stewards can't be fully listed. Closed: a new code is a contract change. */
40
+ export const STEWARD_REASON_CODES = [
41
+ /** An `*.op.ts` file could not be imported, so a steward it declares may be missing. */
42
+ "stewards-unreadable",
43
+ /** A steward was dropped: its name, or an Op it lists, belongs to another steward. */
44
+ "stewards-conflict",
45
+ /** Reading an Op's run ledger failed, so its last run is null. */
46
+ "steward-runs-unreadable",
47
+ ] as const satisfies readonly ReasonCode[];
48
+ export type StewardReasonCode = (typeof STEWARD_REASON_CODES)[number];
49
+
50
+ export interface StatusStewardRun {
51
+ id: string;
52
+ status: OpRunRecord["status"];
53
+ started: string;
54
+ ended: string;
55
+ /** The gate the run stopped at, for a `gated` run. */
56
+ gate: { name: string; since: string } | null;
57
+ }
58
+
59
+ export interface StatusStewardOp {
60
+ name: string;
61
+ /** The Op's cadence, or null for an Op the steward runs only when asked. */
62
+ schedule: { cron: string; overlap: "skip" } | null;
63
+ /** The environment its runs are recorded under: its `labels.Env`, or `local`. */
64
+ env: string;
65
+ /** The newest run in its run ledger, or null when it has none. */
66
+ lastRun: StatusStewardRun | null;
67
+ /** Whether the Op changes the checkout, and so runs under a work lease on a branch of its own (#2748). */
68
+ changesCheckout: boolean;
69
+ /**
70
+ * The Op's work lease (#2748), or null when it declares none: the work kind
71
+ * file whose ledger holds it (null for the member's own), and the leases
72
+ * the steward's turns of this Op hold, a live one while a turn runs.
73
+ */
74
+ workLease: { kind: string | null; held: StatusStewardWorkLease[] } | null;
75
+ }
76
+
77
+ /** A work lease a steward's turn holds (#2748). */
78
+ export interface StatusStewardWorkLease {
79
+ item: string;
80
+ holder: string;
81
+ token: string;
82
+ acquiredAt: string;
83
+ expiresAt: string;
84
+ state: "active" | "expired";
85
+ }
86
+
87
+ export interface StatusSteward {
88
+ name: string;
89
+ /** The `*.op.ts` file declaring it, relative to the member's directory. */
90
+ file: string;
91
+ /** Its form in the environment status was asked for. */
92
+ form: StewardForm;
93
+ /** The declared form: a default and the environments that differ from it. */
94
+ forms: { default: StewardForm; environments: Record<string, StewardForm> };
95
+ /** The vault the steward holds on Fountain, by name, or null. */
96
+ vault: string | null;
97
+ /**
98
+ * The box capabilities the steward reaches through a broker (#2726), joined
99
+ * with the member's box block: `broker` is the block's, and `declared` is
100
+ * false when the block doesn't list the capability (or there is no block).
101
+ */
102
+ capabilities: { name: string; broker: string | null; declared: boolean }[];
103
+ /** The local steward's own lease, or null when no local operator has held it. */
104
+ lease: { holder: string; acquiredAt: string; expiresAt: string; live: boolean } | null;
105
+ ops: StatusStewardOp[];
106
+ }
107
+
108
+ export interface MemberStewards {
109
+ stewards: StatusSteward[];
110
+ reasons: { code: StewardReasonCode; message: string }[];
111
+ }
112
+
113
+ /** Whether a directory is a chant project of its own. */
114
+ function isChantProject(dir: string): boolean {
115
+ return existsSync(join(dir, "chant.config.ts")) || existsSync(join(dir, "chant.config.json"));
116
+ }
117
+
118
+ async function readStewardLease(name: string, memberDir: string, now: string): Promise<StatusSteward["lease"]> {
119
+ try {
120
+ const { prefix } = await resolveMemberLedger(memberDir);
121
+ const sha = await readRefSha(leaseRef(stewardLeaseName(name), prefix), { cwd: memberDir });
122
+ if (!sha) return null;
123
+ const record = JSON.parse((await readBlobBySha(sha, { cwd: memberDir })) ?? "") as Record<string, unknown>;
124
+ if (typeof record.holder !== "string" || typeof record.expiresAt !== "string" || typeof record.acquiredAt !== "string") return null;
125
+ return {
126
+ holder: record.holder,
127
+ acquiredAt: record.acquiredAt,
128
+ expiresAt: record.expiresAt,
129
+ live: new Date(record.expiresAt).getTime() > new Date(now).getTime(),
130
+ };
131
+ } catch {
132
+ return null;
133
+ }
134
+ }
135
+
136
+ /** The work leases `steward`'s turns of `op` hold, from the ledger of the Op's kind or the member's. */
137
+ async function readHeldWorkLeases(steward: string, op: OpConfig, memberDir: string, now: string): Promise<StatusStewardWorkLease[]> {
138
+ try {
139
+ const kind = op.workLease?.kind;
140
+ const cwd = kind ? dirname(resolve(memberDir, kind)) : memberDir;
141
+ const { prefix } = await resolveMemberLedger(cwd);
142
+ const mine = stewardWorkHolder(steward, op.name, "");
143
+ return (await listWorkLeases({ cwd, memberPrefix: prefix, now: new Date(now) }))
144
+ .filter((l) => l.holder.startsWith(mine))
145
+ .map((l) => ({ item: l.item, holder: l.holder, token: l.token, acquiredAt: l.acquiredAt, expiresAt: l.expiresAt, state: l.state }));
146
+ } catch {
147
+ return [];
148
+ }
149
+ }
150
+
151
+ /**
152
+ * The stewards declared in one member, for `env`. `memberDir` is absolute.
153
+ * Only a member of kind `chant` with a config of its own is read.
154
+ */
155
+ export async function readMemberStewards(
156
+ memberDir: string,
157
+ env: string,
158
+ now: string,
159
+ kind = "chant",
160
+ box: { capabilities: { name: string; broker: string | null }[] } | null = null,
161
+ ): Promise<MemberStewards> {
162
+ const reasons: MemberStewards["reasons"] = [];
163
+ if (kind !== "chant" || !isChantProject(memberDir)) return { stewards: [], reasons };
164
+
165
+ let discovered: Awaited<ReturnType<typeof discoverStewards>>;
166
+ try {
167
+ discovered = await discoverStewards({ cwd: memberDir });
168
+ } catch (err) {
169
+ reasons.push({ code: "stewards-unreadable", message: err instanceof Error ? err.message.split("\n")[0] : String(err) });
170
+ return { stewards: [], reasons };
171
+ }
172
+ const { stewards: found, errors, conflicts } = discovered;
173
+ for (const message of errors) reasons.push({ code: "stewards-unreadable", message });
174
+ for (const message of conflicts) reasons.push({ code: "stewards-conflict", message });
175
+
176
+ const stewards: StatusSteward[] = [];
177
+ for (const { declaration, filePath } of [...found.values()].sort((a, b) => a.declaration.name.localeCompare(b.declaration.name))) {
178
+ const ops: StatusStewardOp[] = [];
179
+ for (const op of declaration.ops) {
180
+ const opEnv = runEnvOf(op);
181
+ let lastRun: StatusStewardRun | null = null;
182
+ try {
183
+ const newest = (await readRunLedger(opEnv, op.name, { cwd: memberDir })).records.at(-1);
184
+ if (newest) {
185
+ lastRun = {
186
+ id: newest.id,
187
+ status: newest.status,
188
+ started: newest.started,
189
+ ended: newest.ended,
190
+ gate: newest.gate ? { name: newest.gate.name, since: newest.gate.since } : null,
191
+ };
192
+ }
193
+ } catch (err) {
194
+ reasons.push({
195
+ code: "steward-runs-unreadable",
196
+ message: `${opEnv}/runs__${op.name}.jsonl: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
197
+ });
198
+ }
199
+ ops.push({
200
+ name: op.name,
201
+ schedule: op.schedule ? { cron: op.schedule.cron, overlap: "skip" } : null,
202
+ env: opEnv,
203
+ lastRun,
204
+ changesCheckout: op.changesCheckout === true,
205
+ workLease: op.workLease
206
+ ? { kind: op.workLease.kind ?? null, held: await readHeldWorkLeases(declaration.name, op, memberDir, now) }
207
+ : null,
208
+ });
209
+ }
210
+ stewards.push({
211
+ name: declaration.name,
212
+ file: relative(realpathSync(memberDir), realpathSync(filePath)).split("\\").join("/"),
213
+ form: stewardFormFor(declaration, env),
214
+ forms: { default: declaration.form.default, environments: { ...declaration.form.environments } },
215
+ vault: typeof declaration.vault === "string" ? declaration.vault : null,
216
+ capabilities: (Array.isArray(declaration.capabilities) ? declaration.capabilities : []).map((name) => {
217
+ const declared = box?.capabilities.find((c) => c.name === name);
218
+ return { name, broker: declared?.broker ?? null, declared: declared !== undefined };
219
+ }),
220
+ lease: await readStewardLease(declaration.name, memberDir, now),
221
+ ops,
222
+ });
223
+ }
224
+ return { stewards, reasons };
225
+ }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://intentius.io/chant/schemas/workspace/status/v1/status.schema.json",
4
4
  "title": "chant workspace status output",
5
- "description": "What `chant workspace status <env> --json` prints, version 1 of the read contract for releases across members (#2524 D15, D19, #2544). Readers ignore fields they do not know; a field is only ever added within a version. The reason and error codes are closed lists: a new code is a new contract version. Contract version 1 is written by chant 0.81.0 and newer; a reader that needs it refuses output whose `contract` it doesn't know. Every code is in the one closed list of `reason-codes.ts`. Each member's gates, read from its gate ledger on the same branch, are in the JSON only (#2674).",
5
+ "description": "What `chant workspace status <env> --json` prints, version 1 of the read contract for releases across members (#2524 D15, D19, #2544). Readers ignore fields they do not know; a field is only ever added within a version. The reason and error codes are closed lists: a new code is a new contract version. Contract version 1 is written by chant 0.81.0 and newer; a reader that needs it refuses output whose `contract` it doesn't know. Every code is in the one closed list of `reason-codes.ts`. Each member's gates, read from its gate ledger on the same branch, are in the JSON only (#2674), and so is each member's box block, from the declaration (#2726), with its isolation resolved (#2727). So are its stewards, their Ops and each Op's last run (#2731), and the work lease each Op's turn holds (#2748).",
6
6
  "oneOf": [{ "$ref": "#/$defs/result" }, { "$ref": "#/$defs/failure" }],
7
7
  "$defs": {
8
8
  "result": {
@@ -45,6 +45,11 @@
45
45
  "type": "array",
46
46
  "items": { "$ref": "#/$defs/member" }
47
47
  },
48
+ "leases": {
49
+ "description": "Added in contract 1 by #2732. Every work lease (chant workspace work claim) in the flat ledger and each member's, active and expired, by member (flat first) and then item. Read from the local lease refs, refs/chant/lease/[_members/<member>/]work/<id> and the remote's as last fetched; the command never fetches. A released lease has no ref and is not listed; its history is _leases/<id>.jsonl on chant/lifecycle.",
50
+ "type": "array",
51
+ "items": { "$ref": "#/$defs/lease" }
52
+ },
48
53
  "summary": {
49
54
  "type": "object",
50
55
  "required": ["members", "released", "unreadable", "differing"],
@@ -61,13 +66,27 @@
61
66
  }
62
67
  }
63
68
  },
69
+ "lease": {
70
+ "type": "object",
71
+ "required": ["item", "member", "ref", "holder", "token", "acquiredAt", "expiresAt", "state"],
72
+ "properties": {
73
+ "item": { "type": "string", "description": "The work item's id." },
74
+ "member": { "type": ["string", "null"], "description": "The member whose ledger holds the lease: the one owning the work kind file, or null for the flat ledger." },
75
+ "ref": { "type": "string", "description": "The lease ref, refs/chant/lease/work/<id> or refs/chant/lease/_members/<member>/work/<id>." },
76
+ "holder": { "type": "string" },
77
+ "token": { "type": "string", "description": "The fencing token: new on every claim, kept by every renew." },
78
+ "acquiredAt": { "type": "string" },
79
+ "expiresAt": { "type": "string" },
80
+ "state": { "enum": ["active", "expired"], "description": "expired once expiresAt has passed: the item can be claimed again, with a new token." }
81
+ }
82
+ },
64
83
  "env": {
65
84
  "type": "string",
66
85
  "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$"
67
86
  },
68
87
  "member": {
69
88
  "type": "object",
70
- "required": ["name", "dir", "kind", "environments", "compare", "readable", "gateLedger", "gates"],
89
+ "required": ["name", "dir", "kind", "environments", "compare", "readable", "gateLedger", "gates", "box", "stewards", "stewardReasons"],
71
90
  "properties": {
72
91
  "name": { "type": "string" },
73
92
  "dir": { "type": "string", "description": "Relative to the workspace root, with / separators; \".\" is the root member." },
@@ -85,10 +104,213 @@
85
104
  },
86
105
  "readable": { "type": "boolean", "description": "True exactly when no environment has a reason. A gate ledger reason doesn't change it." },
87
106
  "gateLedger": { "$ref": "#/$defs/gateLedger" },
107
+ "box": {
108
+ "description": "The member's box block from the declaration (#2726), or null when it declares none. A broker reads the scopes it enforces here, and chant workspace check fails on a capability with a null broker (WSP122).",
109
+ "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/box" }]
110
+ },
88
111
  "gates": {
89
112
  "description": "Each gate the member's gate ledger records, one per gate and environment, for env and compareTo. A gate that records no environment (an Op gate) is listed whatever the environment. Sorted by component, then gate. Empty when gateLedger has a reason.",
90
113
  "type": "array",
91
114
  "items": { "$ref": "#/$defs/gate" }
115
+ },
116
+ "stewards": {
117
+ "description": "The stewards the member declares in its *.op.ts files (#2731), sorted by name. Empty for a member that is not of kind chant, or has no chant.config.ts or .json in its own directory.",
118
+ "type": "array",
119
+ "items": { "$ref": "#/$defs/steward" }
120
+ },
121
+ "stewardReasons": {
122
+ "description": "Why a steward or an Op's last run may be missing from stewards. Empty when every one was read.",
123
+ "type": "array",
124
+ "items": {
125
+ "type": "object",
126
+ "required": ["code", "message"],
127
+ "properties": {
128
+ "code": { "enum": ["stewards-unreadable", "stewards-conflict", "steward-runs-unreadable"] },
129
+ "message": { "type": "string" }
130
+ }
131
+ }
132
+ }
133
+ }
134
+ },
135
+ "stewardForm": { "enum": ["local", "fountain"] },
136
+ "steward": {
137
+ "type": "object",
138
+ "required": ["name", "file", "form", "forms", "vault", "capabilities", "lease", "ops"],
139
+ "properties": {
140
+ "name": { "type": "string", "description": "The steward's name; on Fountain, its Agent's and Teammate's." },
141
+ "file": { "type": "string", "description": "The *.op.ts file that declares it, relative to the member's directory." },
142
+ "form": { "$ref": "#/$defs/stewardForm", "description": "Its form in env: fountain (the fountain lexicon's Agent and Teammate) or local (chant operator --steward in the box)." },
143
+ "forms": {
144
+ "type": "object",
145
+ "required": ["default", "environments"],
146
+ "properties": {
147
+ "default": { "$ref": "#/$defs/stewardForm" },
148
+ "environments": {
149
+ "description": "The environments whose form differs from default.",
150
+ "type": "object",
151
+ "additionalProperties": { "$ref": "#/$defs/stewardForm" }
152
+ }
153
+ }
154
+ },
155
+ "vault": { "type": ["string", "null"], "description": "The vault the steward holds on Fountain, by name, or null. Never set when capabilities is non-empty." },
156
+ "capabilities": {
157
+ "description": "The box capabilities the steward reaches through a broker (#2726), in declaration order, joined with the member's box block. broker is the block's; declared is false when the block doesn't list the capability or the member has no block.",
158
+ "type": "array",
159
+ "items": {
160
+ "type": "object",
161
+ "required": ["name", "broker", "declared"],
162
+ "properties": {
163
+ "name": { "type": "string" },
164
+ "broker": { "type": ["string", "null"] },
165
+ "declared": { "type": "boolean" }
166
+ }
167
+ }
168
+ },
169
+ "lease": {
170
+ "description": "The local steward's own lease ref, read locally and never fetched, or null when no local operator has held it. live is false once expiresAt has passed.",
171
+ "oneOf": [
172
+ { "type": "null" },
173
+ {
174
+ "type": "object",
175
+ "required": ["holder", "acquiredAt", "expiresAt", "live"],
176
+ "properties": {
177
+ "holder": { "type": "string" },
178
+ "acquiredAt": { "type": "string" },
179
+ "expiresAt": { "type": "string" },
180
+ "live": { "type": "boolean" }
181
+ }
182
+ }
183
+ ]
184
+ },
185
+ "ops": {
186
+ "description": "The Ops it runs, in declaration order.",
187
+ "type": "array",
188
+ "items": {
189
+ "type": "object",
190
+ "required": ["name", "schedule", "env", "lastRun", "changesCheckout", "workLease"],
191
+ "properties": {
192
+ "name": { "type": "string" },
193
+ "schedule": {
194
+ "description": "The Op's cadence, or null for an Op the steward runs only when asked.",
195
+ "oneOf": [
196
+ { "type": "null" },
197
+ {
198
+ "type": "object",
199
+ "required": ["cron", "overlap"],
200
+ "properties": { "cron": { "type": "string" }, "overlap": { "const": "skip" } }
201
+ }
202
+ ]
203
+ },
204
+ "env": { "type": "string", "description": "The environment its runs are recorded under: its labels.Env, or local." },
205
+ "lastRun": {
206
+ "description": "The newest record in the Op's run ledger on chant/lifecycle, or null when it has none.",
207
+ "oneOf": [
208
+ { "type": "null" },
209
+ {
210
+ "type": "object",
211
+ "required": ["id", "status", "started", "ended", "gate"],
212
+ "properties": {
213
+ "id": { "type": "string" },
214
+ "status": { "enum": ["ok", "fail", "gated"] },
215
+ "started": { "type": "string" },
216
+ "ended": { "type": "string" },
217
+ "gate": {
218
+ "oneOf": [
219
+ { "type": "null" },
220
+ { "type": "object", "required": ["name", "since"], "properties": { "name": { "type": "string" }, "since": { "type": "string" } } }
221
+ ]
222
+ }
223
+ }
224
+ }
225
+ ]
226
+ },
227
+ "changesCheckout": { "type": "boolean", "description": "Whether the Op changes the checkout, and so runs under a work lease on a branch of its own, chant/work/<item> (#2748)." },
228
+ "workLease": {
229
+ "description": "The Op's work lease (#2748), or null when it declares none. kind is the work kind file whose ledger holds it, relative to the member, or null for the member's own ledger. held is the work leases the steward's turns of this Op hold (holder <steward>/<op>@<process>), read locally and never fetched: an active one while a turn runs, an expired one left by a turn that stopped renewing.",
230
+ "oneOf": [
231
+ { "type": "null" },
232
+ {
233
+ "type": "object",
234
+ "required": ["kind", "held"],
235
+ "properties": {
236
+ "kind": { "type": ["string", "null"] },
237
+ "held": {
238
+ "type": "array",
239
+ "items": {
240
+ "type": "object",
241
+ "required": ["item", "holder", "token", "acquiredAt", "expiresAt", "state"],
242
+ "properties": {
243
+ "item": { "type": "string" },
244
+ "holder": { "type": "string" },
245
+ "token": { "type": "string" },
246
+ "acquiredAt": { "type": "string" },
247
+ "expiresAt": { "type": "string" },
248
+ "state": { "enum": ["active", "expired"] }
249
+ }
250
+ }
251
+ }
252
+ }
253
+ }
254
+ ]
255
+ }
256
+ }
257
+ }
258
+ }
259
+ }
260
+ },
261
+ "box": {
262
+ "type": "object",
263
+ "required": ["capabilities", "isolation"],
264
+ "properties": {
265
+ "isolation": {
266
+ "description": "The box's isolation resolved from its identity, its host, the member's name and its slot (#2727), or null when the block declares no host. A runtime that plants or starts the box sets these values instead of choosing its own. The same for every environment.",
267
+ "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/isolation" }]
268
+ },
269
+ "capabilities": {
270
+ "description": "Each capability the box reaches through a broker, in declaration order.",
271
+ "type": "array",
272
+ "items": {
273
+ "type": "object",
274
+ "required": ["name", "broker", "scope"],
275
+ "properties": {
276
+ "name": { "type": "string", "description": "The capability, such as inference or fountain." },
277
+ "broker": { "type": ["string", "null"], "description": "What brokers it, such as lobby, or null when the declaration names none." },
278
+ "scope": { "type": "array", "items": { "type": "string" }, "description": "What of the box's own the broker lets it reach, such as agent, vault, conversations and sandboxes. Empty when none is declared." }
279
+ }
280
+ }
281
+ }
282
+ }
283
+ },
284
+ "isolation": {
285
+ "type": "object",
286
+ "required": ["host", "slot", "portRange", "ports", "stateDir", "state", "cookies"],
287
+ "properties": {
288
+ "host": { "type": "string", "description": "The host the box runs on. Boxes on different hosts share nothing." },
289
+ "slot": { "type": "integer", "minimum": 0 },
290
+ "portRange": {
291
+ "type": "object",
292
+ "description": "The box's block of its host's range, inclusive: from + slot * perBox to from + (slot + 1) * perBox - 1.",
293
+ "required": ["from", "to"],
294
+ "properties": {
295
+ "from": { "type": "integer", "minimum": 1, "maximum": 65535 },
296
+ "to": { "type": "integer", "minimum": 1, "maximum": 65535 }
297
+ }
298
+ },
299
+ "ports": {
300
+ "type": "object",
301
+ "description": "Each declared port's number: the block's first port plus the port's offset.",
302
+ "additionalProperties": { "type": "integer", "minimum": 1, "maximum": 65535 }
303
+ },
304
+ "stateDir": { "type": "string", "description": "<stateRoot>/<member name>. Its environment reference, such as ${XDG_STATE_HOME}, is left for the runtime to expand." },
305
+ "state": {
306
+ "type": "object",
307
+ "description": "Each declared state entry's path under stateDir, keyed as declared, such as HUD_IDENTITY_PATH.",
308
+ "additionalProperties": { "type": "string" }
309
+ },
310
+ "cookies": {
311
+ "type": "object",
312
+ "description": "Each declared cookie's name for this box: <cookie>_<member name>.",
313
+ "additionalProperties": { "type": "string" }
92
314
  }
93
315
  }
94
316
  },
@@ -191,7 +413,7 @@
191
413
  },
192
414
  "release": {
193
415
  "type": "object",
194
- "required": ["component", "digest", "gitSha", "inputDigest", "runId", "timestamp", "actor", "flags"],
416
+ "required": ["component", "digest", "gitSha", "inputDigest", "runId", "timestamp", "actor", "flags", "plan"],
195
417
  "properties": {
196
418
  "component": { "type": "string" },
197
419
  "digest": { "type": "string", "description": "The artifact digest the release deployed." },
@@ -200,7 +422,11 @@
200
422
  "runId": { "type": "string" },
201
423
  "timestamp": { "type": "string" },
202
424
  "actor": { "type": "string" },
203
- "flags": { "type": "array", "items": { "enum": ["legacy-digest"] } }
425
+ "flags": { "type": "array", "items": { "enum": ["legacy-digest"] } },
426
+ "plan": {
427
+ "type": ["object", "null"],
428
+ "description": "The release plan digest names, read from _plans/<digest>.json on chant/lifecycle (ws-055, #2733): the work items and evidence this release shipped. Null when this checkout has no plan stored under that digest — most releases carry no plan, and a plan not yet fetched also reads null."
429
+ }
204
430
  }
205
431
  },
206
432
  "compare": {