@intentius/chant 0.85.0 → 0.87.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 (129) hide show
  1. package/dist/cli/main.d.ts.map +1 -1
  2. package/dist/cli/registry.d.ts +13 -1
  3. package/dist/cli/registry.d.ts.map +1 -1
  4. package/dist/lifecycle/gate-ledger.d.ts +13 -0
  5. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  6. package/dist/workspace/__fixtures__/sessions.d.ts +23 -0
  7. package/dist/workspace/__fixtures__/sessions.d.ts.map +1 -0
  8. package/dist/workspace/checks/records.d.ts +1 -0
  9. package/dist/workspace/checks/records.d.ts.map +1 -1
  10. package/dist/workspace/checks.d.ts +4 -0
  11. package/dist/workspace/checks.d.ts.map +1 -1
  12. package/dist/workspace/composites.d.ts +14 -1
  13. package/dist/workspace/composites.d.ts.map +1 -1
  14. package/dist/workspace/conformance/index.d.ts +211 -0
  15. package/dist/workspace/conformance/index.d.ts.map +1 -0
  16. package/dist/workspace/conformance/vitest.d.ts +11 -0
  17. package/dist/workspace/conformance/vitest.d.ts.map +1 -0
  18. package/dist/workspace/declaration.d.ts +28 -0
  19. package/dist/workspace/declaration.d.ts.map +1 -1
  20. package/dist/workspace/declaration.schema.json +40 -0
  21. package/dist/workspace/declared-kinds.d.ts +43 -0
  22. package/dist/workspace/declared-kinds.d.ts.map +1 -0
  23. package/dist/workspace/graph-cli.d.ts +11 -0
  24. package/dist/workspace/graph-cli.d.ts.map +1 -1
  25. package/dist/workspace/intent-cli.d.ts +2 -1
  26. package/dist/workspace/intent-cli.d.ts.map +1 -1
  27. package/dist/workspace/intent-joins.d.ts +45 -8
  28. package/dist/workspace/intent-joins.d.ts.map +1 -1
  29. package/dist/workspace/intent.d.ts +71 -7
  30. package/dist/workspace/intent.d.ts.map +1 -1
  31. package/dist/workspace/ls.d.ts +31 -1
  32. package/dist/workspace/ls.d.ts.map +1 -1
  33. package/dist/workspace/reason-codes.d.ts +47 -4
  34. package/dist/workspace/reason-codes.d.ts.map +1 -1
  35. package/dist/workspace/record-sessions.d.ts +51 -0
  36. package/dist/workspace/record-sessions.d.ts.map +1 -0
  37. package/dist/workspace/record-source.d.ts +2 -0
  38. package/dist/workspace/record-source.d.ts.map +1 -1
  39. package/dist/workspace/records-cli.d.ts +71 -4
  40. package/dist/workspace/records-cli.d.ts.map +1 -1
  41. package/dist/workspace/records-since.d.ts +90 -0
  42. package/dist/workspace/records-since.d.ts.map +1 -0
  43. package/dist/workspace/records-write.d.ts +171 -0
  44. package/dist/workspace/records-write.d.ts.map +1 -0
  45. package/dist/workspace/records.d.ts +244 -15
  46. package/dist/workspace/records.d.ts.map +1 -1
  47. package/dist/workspace/runtimes.d.ts +60 -0
  48. package/dist/workspace/runtimes.d.ts.map +1 -0
  49. package/dist/workspace/status-gates.d.ts +90 -0
  50. package/dist/workspace/status-gates.d.ts.map +1 -0
  51. package/dist/workspace/status.d.ts +17 -0
  52. package/dist/workspace/status.d.ts.map +1 -1
  53. package/dist/workspace/trust/seal.d.ts +85 -0
  54. package/dist/workspace/trust/seal.d.ts.map +1 -0
  55. package/dist/workspace/trust/ssh-commit.d.ts +7 -0
  56. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
  57. package/dist/workspace/work.d.ts +56 -0
  58. package/dist/workspace/work.d.ts.map +1 -0
  59. package/package.json +19 -1
  60. package/src/cli/main.ts +55 -3
  61. package/src/cli/registry.ts +13 -1
  62. package/src/lifecycle/gate-ledger.ts +14 -0
  63. package/src/workspace/__fixtures__/sessions.ts +66 -0
  64. package/src/workspace/checks/records.ts +19 -0
  65. package/src/workspace/checks.test.ts +2 -0
  66. package/src/workspace/checks.ts +7 -1
  67. package/src/workspace/composites.schema.json +65 -3
  68. package/src/workspace/composites.test.ts +95 -5
  69. package/src/workspace/composites.ts +28 -7
  70. package/src/workspace/conformance/__fixture__/app/package.json +7 -0
  71. package/src/workspace/conformance/__fixture__/app/src/server.mjs +29 -0
  72. package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +32 -0
  73. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +376 -0
  74. package/src/workspace/conformance/__fixture__/decisions/fix-001-how-the-app-is-deployed.md +40 -0
  75. package/src/workspace/conformance/__fixture__/delivery/chant.config.ts +7 -0
  76. package/src/workspace/conformance/__fixture__/delivery/lexicon/index.ts +26 -0
  77. package/src/workspace/conformance/__fixture__/delivery/package.json +7 -0
  78. package/src/workspace/conformance/__fixture__/delivery/src/app.component.ts +14 -0
  79. package/src/workspace/conformance/__fixture__/delivery/src/app.ts +4 -0
  80. package/src/workspace/conformance/conformance.test.ts +149 -0
  81. package/src/workspace/conformance/index.mjs +31 -0
  82. package/src/workspace/conformance/index.ts +453 -0
  83. package/src/workspace/conformance/vitest.ts +62 -0
  84. package/src/workspace/declaration.schema.json +40 -0
  85. package/src/workspace/declaration.ts +62 -0
  86. package/src/workspace/declared-kinds.test.ts +321 -0
  87. package/src/workspace/declared-kinds.ts +76 -0
  88. package/src/workspace/graph-cli.ts +8 -0
  89. package/src/workspace/intent-cli.ts +29 -6
  90. package/src/workspace/intent-gaps.test.ts +217 -0
  91. package/src/workspace/intent-joins.test.ts +60 -0
  92. package/src/workspace/intent-joins.ts +71 -19
  93. package/src/workspace/intent.schema.json +304 -7
  94. package/src/workspace/intent.test.ts +99 -0
  95. package/src/workspace/intent.ts +365 -46
  96. package/src/workspace/ls.schema.json +34 -0
  97. package/src/workspace/ls.ts +69 -4
  98. package/src/workspace/read-contract.test.ts +30 -9
  99. package/src/workspace/reason-codes.test.ts +16 -4
  100. package/src/workspace/reason-codes.ts +55 -4
  101. package/src/workspace/record-assets.test.ts +3 -1
  102. package/src/workspace/record-sessions.ts +105 -0
  103. package/src/workspace/record-source.ts +14 -5
  104. package/src/workspace/records-amend.schema.json +167 -0
  105. package/src/workspace/records-cli.ts +308 -19
  106. package/src/workspace/records-contract.test.ts +57 -2
  107. package/src/workspace/records-formats.test.ts +640 -0
  108. package/src/workspace/records-new.schema.json +158 -0
  109. package/src/workspace/records-quorum.test.ts +196 -0
  110. package/src/workspace/records-review.schema.json +227 -0
  111. package/src/workspace/records-sessions.test.ts +108 -0
  112. package/src/workspace/records-since.schema.json +193 -0
  113. package/src/workspace/records-since.test.ts +174 -0
  114. package/src/workspace/records-since.ts +259 -0
  115. package/src/workspace/records-write-contract.test.ts +125 -0
  116. package/src/workspace/records-write.test.ts +373 -0
  117. package/src/workspace/records-write.ts +765 -0
  118. package/src/workspace/records.schema.json +202 -9
  119. package/src/workspace/records.ts +700 -41
  120. package/src/workspace/runtimes.ts +107 -0
  121. package/src/workspace/status-contract.test.ts +163 -0
  122. package/src/workspace/status-gates.ts +215 -0
  123. package/src/workspace/status.schema.json +69 -3
  124. package/src/workspace/status.ts +35 -2
  125. package/src/workspace/trust/seal.test.ts +232 -0
  126. package/src/workspace/trust/seal.ts +195 -0
  127. package/src/workspace/trust/ssh-commit.ts +2 -2
  128. package/src/workspace/work.test.ts +390 -0
  129. package/src/workspace/work.ts +163 -0
@@ -7,7 +7,7 @@
7
7
  * (`read-contract.test.ts`).
8
8
  */
9
9
 
10
- import { writeFileSync } from "node:fs";
10
+ import { mkdirSync, writeFileSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  import { afterAll, describe, expect, test } from "vitest";
13
13
  import type { GraphIR, IRNode } from "../graph-ir";
@@ -18,6 +18,7 @@ import {
18
18
  COMPOSITES_ERROR_CODES,
19
19
  COMPOSITES_OUTPUT_SCHEMA_ID,
20
20
  COMPOSITES_REASON_CODES,
21
+ COMPOSITES_RUNTIME_REASON_CODES,
21
22
  compositeInstances,
22
23
  joinComponents,
23
24
  workspaceComposites,
@@ -54,6 +55,9 @@ const componentIr = (components: { name: string; composites?: string[]; archetyp
54
55
  { groups: { byWave: { "wave-1": components.map((c) => c.name) } } },
55
56
  );
56
57
 
58
+ /** The built-in runtime every component has. */
59
+ const local = (name: string) => ({ name: "local", lexicon: null, default: true, command: `chant run --components ${name}` });
60
+
57
61
  /**
58
62
  * app declares three instances and exports ImageUri; delivery links to it and
59
63
  * declares two components; jobs declares an instance and a component of its
@@ -106,6 +110,7 @@ describe("composites output schema", () => {
106
110
  expect(schema.$defs.failure.properties.error.properties.code.enum).toEqual([...COMPOSITES_ERROR_CODES]);
107
111
  expect(COMPOSITES_ERROR_CODES).toEqual(GRAPH_ERROR_CODES);
108
112
  expect(schema.$defs.member.properties.reason.oneOf[1].properties!.code.enum).toEqual([...MEMBER_RUN_REASON_CODES]);
113
+ expect(schema.$defs.member.properties.runtimeReasons.items.properties.code.enum).toEqual([...COMPOSITES_RUNTIME_REASON_CODES]);
109
114
  });
110
115
  });
111
116
 
@@ -117,7 +122,7 @@ describe("the join", () => {
117
122
  { ...composite("a/billingTable", "Table", "a/billing"), member: "a" },
118
123
  ],
119
124
  });
120
- const component = (member: string, name: string, composites: string[] | null = null): ComponentEntry => ({ id: `${member}/${name}`, name, member, archetype: null, composites, file: null });
125
+ const component = (member: string, name: string, composites: string[] | null = null): ComponentEntry => ({ id: `${member}/${name}`, name, member, archetype: null, composites, file: null, runtimes: [] });
121
126
 
122
127
  test("an instance carries every kind and lexicon of its nodes", () => {
123
128
  expect(instances.map((i) => [i.id, i.instance, i.kinds, i.lexicons])).toEqual([
@@ -160,9 +165,9 @@ describe("chant workspace graph --composites on a built workspace", () => {
160
165
  ["docs", "skipped", "kind-not-run"],
161
166
  ]);
162
167
  expect(g.components).toEqual([
163
- { id: "delivery/edge", name: "edge", member: "delivery", archetype: "infra", composites: ["StaticSite"], file: "delivery/src/edge.component.ts" },
164
- { id: "delivery/loom-backend", name: "loom-backend", member: "delivery", archetype: "service", composites: null, file: "delivery/src/loom-backend.component.ts" },
165
- { id: "jobs/queue-runner", name: "queue-runner", member: "jobs", archetype: null, composites: ["WorkQueue", "CacheCluster"], file: "jobs/src/queue-runner.component.ts" },
168
+ { id: "delivery/edge", name: "edge", member: "delivery", archetype: "infra", composites: ["StaticSite"], file: "delivery/src/edge.component.ts", runtimes: [local("edge")] },
169
+ { id: "delivery/loom-backend", name: "loom-backend", member: "delivery", archetype: "service", composites: null, file: "delivery/src/loom-backend.component.ts", runtimes: [local("loom-backend")] },
170
+ { id: "jobs/queue-runner", name: "queue-runner", member: "jobs", archetype: null, composites: ["WorkQueue", "CacheCluster"], file: "jobs/src/queue-runner.component.ts", runtimes: [local("queue-runner")] },
166
171
  ]);
167
172
  const rows = Object.fromEntries(g.composites.map((c) => [c.id, c]));
168
173
  expect(Object.keys(rows)).toEqual(["app/backend", "app/cache", "app/site", "jobs/queue"]);
@@ -242,3 +247,88 @@ describe("why the list is empty", () => {
242
247
  expect("error" in doc && doc.error.code).toBe("declaration-missing");
243
248
  });
244
249
  });
250
+
251
+ /** A lexicon module: a LexiconPlugin export, with `opRuntime` when `runtime` says what it hosts. */
252
+ function lexiconModule(name: string, runtime: "components" | "ops" | "none"): string {
253
+ const opRuntime =
254
+ runtime === "none"
255
+ ? ""
256
+ : `opRuntime: { name: ${JSON.stringify(name)}, start: async () => { throw new Error("stub"); }${runtime === "components" ? ", runComponents: async () => ({ success: true })" : ""} },`;
257
+ return `const noop = async () => {};
258
+ export const plugin = { name: ${JSON.stringify(name)}, serializer: {}, generate: noop, validate: noop, coverage: noop, package: noop, ${opRuntime} };
259
+ `;
260
+ }
261
+
262
+ describe("the runtimes each component can deploy on (#2674)", () => {
263
+ /** delivery configures four lexicons: one hosts component runs, one hosts only Op runs, one hosts nothing, one can't load. */
264
+ function hosting(): string {
265
+ const root = fixture();
266
+ writeFileSync(
267
+ join(root, "delivery", "chant.config.ts"),
268
+ `export default {
269
+ lexicons: [
270
+ { name: "fleet", module: "./lexicons/fleet.ts" },
271
+ { name: "oponly", module: "./lexicons/oponly.ts" },
272
+ { name: "plain", module: "./lexicons/plain.ts" },
273
+ { name: "gone", module: "./lexicons/gone.ts" },
274
+ ],
275
+ };
276
+ `,
277
+ );
278
+ mkdirSync(join(root, "delivery", "lexicons"));
279
+ writeFileSync(join(root, "delivery", "lexicons", "fleet.ts"), lexiconModule("fleet", "components"));
280
+ writeFileSync(join(root, "delivery", "lexicons", "oponly.ts"), lexiconModule("oponly", "ops"));
281
+ writeFileSync(join(root, "delivery", "lexicons", "plain.ts"), lexiconModule("plain", "none"));
282
+ return root;
283
+ }
284
+
285
+ test("local is the default, and a configured lexicon whose opRuntime hosts component runs is listed with its --on line", async () => {
286
+ const g = result((await workspaceComposites({ cwd: hosting() })).doc);
287
+ expectValid(g);
288
+ const edge = g.components.find((c) => c.id === "delivery/edge")!;
289
+ expect(edge.runtimes).toEqual([
290
+ { name: "local", lexicon: null, default: true, command: "chant run --components edge" },
291
+ { name: "fleet", lexicon: "fleet", default: false, command: "chant run --components edge --on fleet" },
292
+ ]);
293
+ expect(g.components.find((c) => c.id === "delivery/loom-backend")!.runtimes.map((r) => r.command)).toEqual([
294
+ "chant run --components loom-backend",
295
+ "chant run --components loom-backend --on fleet",
296
+ ]);
297
+ // jobs configures no lexicon, so only local.
298
+ expect(g.components.find((c) => c.id === "jobs/queue-runner")!.runtimes).toEqual([local("queue-runner")]);
299
+ const delivery = g.members.find((m) => m.name === "delivery")!;
300
+ expect(delivery.runtimeReasons.map((r) => r.code)).toEqual(["runtimes-lexicon-unreadable"]);
301
+ expect(delivery.runtimeReasons[0].message).toContain('lexicon "gone"');
302
+ expect(g.members.find((m) => m.name === "docs")!.runtimeReasons).toEqual([]);
303
+ });
304
+
305
+ test("a member whose config can't be read lists local only, with a reason", async () => {
306
+ const root = fixture();
307
+ writeFileSync(join(root, "jobs", "chant.config.ts"), 'throw new Error("no config here");\nexport default {};\n');
308
+ const { doc, failed } = await workspaceComposites({ cwd: root, loadPlugin: async () => { throw new Error("not called"); } });
309
+ const g = result(doc);
310
+ expectValid(g);
311
+ expect(failed).toBe(false);
312
+ const jobs = g.members.find((m) => m.name === "jobs")!;
313
+ expect(jobs.runtimeReasons).toEqual([{ code: "runtimes-config-unreadable", message: expect.stringContaining("no config here") }]);
314
+ expect(g.components.find((c) => c.id === "jobs/queue-runner")!.runtimes).toEqual([local("queue-runner")]);
315
+ });
316
+
317
+ test("the plugin loader decides from opRuntime.runComponents alone", async () => {
318
+ const root = fixture();
319
+ writeFileSync(join(root, "jobs", "chant.config.ts"), 'export default { lexicons: ["hosted", "bare"] };\n');
320
+ const g = result(
321
+ (
322
+ await workspaceComposites({
323
+ cwd: root,
324
+ loadPlugin: async (name) => (name === "hosted" ? { opRuntime: { runComponents: () => undefined } } : {}),
325
+ })
326
+ ).doc,
327
+ );
328
+ expectValid(g);
329
+ expect(g.components.find((c) => c.id === "jobs/queue-runner")!.runtimes.map((r) => [r.name, r.lexicon, r.default])).toEqual([
330
+ ["local", null, true],
331
+ ["hosted", "hosted", false],
332
+ ]);
333
+ });
334
+ });
@@ -28,6 +28,10 @@
28
28
  *
29
29
  * Each member runs under its own toolchain, as for `workspace graph`, and at
30
30
  * the same revision with `--at`. Nothing is written and no listener starts.
31
+ *
32
+ * Each component also lists the runtimes it can deploy on (#2674,
33
+ * `runtimes.ts`): `local`, and each lexicon its member configures that hosts
34
+ * `chant run --components`, with the command line for each.
31
35
  */
32
36
 
33
37
  import { formatError } from "../cli/format";
@@ -40,6 +44,7 @@ import type { LinkRow } from "./links";
40
44
  import { emitDocument, type UnitResult } from "./member-commands";
41
45
  import { readerVersion, type ErrorLocation, type WorkspaceErrorCode } from "./declaration";
42
46
  import type { ReasonCode } from "./reason-codes";
47
+ import { componentRuntimes, readRuntimesIn, RUNTIME_REASON_CODES, type ComponentRuntime, type MemberRuntimes, type PluginLoader, type RuntimeReason } from "./runtimes";
43
48
 
44
49
  /** `$id` of the JSON Schema for the document, shipped beside this file. */
45
50
  export const COMPOSITES_OUTPUT_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/composites/v1/composites.schema.json";
@@ -61,6 +66,9 @@ export const COMPOSITES_REASON_CODES = [
61
66
  ] as const satisfies readonly ReasonCode[];
62
67
  export type CompositesReasonCode = (typeof COMPOSITES_REASON_CODES)[number];
63
68
 
69
+ /** Why a member's runtimes are only `local`, or leave out a lexicon its config lists (#2674). Closed. */
70
+ export const COMPOSITES_RUNTIME_REASON_CODES = RUNTIME_REASON_CODES;
71
+
64
72
  export interface CompositesMember {
65
73
  name: string;
66
74
  dir: string;
@@ -69,6 +77,8 @@ export interface CompositesMember {
69
77
  status: "read" | "skipped" | "failed";
70
78
  reason: MemberReason | null;
71
79
  chant: string | null;
80
+ /** Why its components' runtimes leave something out: its config, or a lexicon, couldn't be read. Empty for a member not of kind chant. */
81
+ runtimeReasons: RuntimeReason[];
72
82
  }
73
83
 
74
84
  export interface ComponentEntry {
@@ -82,6 +92,8 @@ export interface ComponentEntry {
82
92
  composites: string[] | null;
83
93
  /** The component's file from the workspace root, when the member's chant names it. */
84
94
  file: string | null;
95
+ /** The runtimes it can deploy on: `local` first, then each hosting lexicon its member configures. */
96
+ runtimes: ComponentRuntime[];
85
97
  }
86
98
 
87
99
  export interface ComponentMatch {
@@ -133,7 +145,7 @@ export interface CompositesResult {
133
145
  }
134
146
 
135
147
  /** Read one member's component graph run into components, or the reason it can't be read. */
136
- function readComponents(member: string, dir: string, run: UnitResult): { components: ComponentEntry[] } | { reason: MemberReason } {
148
+ function readComponents(member: string, dir: string, run: UnitResult, runtimes: MemberRuntimes | undefined): { components: ComponentEntry[] } | { reason: MemberReason } {
137
149
  if (run.exitCode !== 0) {
138
150
  const tail = run.stderr.trim().split("\n").slice(-5).join("\n");
139
151
  return { reason: { code: "command-failed", message: `chant graph --components exited ${run.exitCode}${tail ? `: ${tail}` : ""}` } };
@@ -153,6 +165,7 @@ function readComponents(member: string, dir: string, run: UnitResult): { compone
153
165
  archetype: typeof attrs.archetype === "string" ? attrs.archetype : null,
154
166
  composites: composites && composites.length > 0 ? composites : null,
155
167
  file: file ? (dir === "." ? file : `${dir}/${file}`) : null,
168
+ runtimes: componentRuntimes(n.id, runtimes),
156
169
  });
157
170
  }
158
171
  return { components };
@@ -220,16 +233,24 @@ export function joinComponents(instances: CompositeInstanceRow[], components: Co
220
233
  });
221
234
  }
222
235
 
223
- function memberEntry(m: ComposedMember, componentReason: MemberReason | null): CompositesMember {
236
+ function memberEntry(m: ComposedMember, componentReason: MemberReason | null, runtimes: MemberRuntimes | undefined): CompositesMember {
224
237
  const reason = m.reason ?? componentReason;
225
238
  const status = m.status === "composed" ? (componentReason ? "failed" : "read") : m.status;
226
- return { name: m.name, dir: m.dir, kind: m.kind, status, reason, chant: m.chant };
239
+ return { name: m.name, dir: m.dir, kind: m.kind, status, reason, chant: m.chant, runtimeReasons: runtimes?.reasons ?? [] };
227
240
  }
228
241
 
229
242
  /** Read the workspace's composites and components, and build the document. Never throws a `WorkspaceReadError`. */
230
- export async function workspaceComposites(query: Omit<GraphQuery, "kind" | "components">): Promise<CompositesResult> {
243
+ export async function workspaceComposites(query: Omit<GraphQuery, "kind" | "components" | "inTree"> & { loadPlugin?: PluginLoader }): Promise<CompositesResult> {
231
244
  const head: Head = { $schema: COMPOSITES_OUTPUT_SCHEMA_ID, contract: COMPOSITES_CONTRACT_VERSION, chant: readerVersion() };
232
- const { doc: graph, failed, components: runs } = await workspaceGraph({ ...query, components: true });
245
+ let runtimes = new Map<string, MemberRuntimes>();
246
+ const { loadPlugin, ...graphQuery } = query;
247
+ const { doc: graph, failed, components: runs } = await workspaceGraph({
248
+ ...graphQuery,
249
+ components: true,
250
+ inTree: async (root, members) => {
251
+ runtimes = await readRuntimesIn(root, members, loadPlugin);
252
+ },
253
+ });
233
254
  if ("error" in graph) return { doc: { ...head, error: graph.error }, failed: true };
234
255
 
235
256
  const byMember = new Map((runs ?? []).map((r) => [r.unit.member, r]));
@@ -240,13 +261,13 @@ export async function workspaceComposites(query: Omit<GraphQuery, "kind" | "comp
240
261
  const run = m.status === "composed" ? byMember.get(m.name) : undefined;
241
262
  let reason: MemberReason | null = null;
242
263
  if (run) {
243
- const read = readComponents(m.name, m.dir, run);
264
+ const read = readComponents(m.name, m.dir, run, runtimes.get(m.name));
244
265
  if ("reason" in read) {
245
266
  reason = read.reason;
246
267
  componentsFailed = true;
247
268
  } else components.push(...read.components);
248
269
  }
249
- members.push(memberEntry(m, reason));
270
+ members.push(memberEntry(m, reason, runtimes.get(m.name)));
250
271
  }
251
272
  components.sort((a, b) => a.id.localeCompare(b.id));
252
273
 
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "app",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "The reader conformance workspace's product: the handler of a small web app."
7
+ }
@@ -0,0 +1,29 @@
1
+ // The reader conformance workspace's product (#2679): the request handler of
2
+ // a small web app, with no dependencies. The suite reads
3
+ // `graph --intent app/src/server.mjs:19`, so line 19 is the home page's
4
+ // status line, as it is in chant's reference-workspace/app/src/server.mjs.
5
+ // Nothing here opens a port: chant's own packages never listen (#2657), and
6
+ // this file ships in one.
7
+
8
+ /** The app's display name. */
9
+ export const APP_NAME = "Conformance app";
10
+
11
+ /** The home page. */
12
+ export const HOME = `<!doctype html>
13
+ <html lang="en">
14
+ <head><meta charset="utf-8"><title>${APP_NAME}</title></head>
15
+ <body>
16
+ <header><h1>${APP_NAME}</h1></header>
17
+ <main>
18
+ <!-- the status line -->
19
+ <p id="status">Running.</p>
20
+ </main>
21
+ </body>
22
+ </html>
23
+ `;
24
+
25
+ /** Answer one request with a status and a body. */
26
+ export function handle(url) {
27
+ if (url === "/healthz") return { status: 200, type: "application/json", body: JSON.stringify({ ok: true }) };
28
+ return { status: 200, type: "text/html; charset=utf-8", body: HOME };
29
+ }
@@ -0,0 +1,32 @@
1
+ // The decision record kind (#2555), data only: no imports, no code.
2
+ //
3
+ // chant workspace records --kind decisions/decision.kind.mjs --current --json
4
+ //
5
+ // A copy of docs/design/decisions/decision.kind.mjs in the chant repo, with
6
+ // decision.schema.json beside it, so a workspace made from this one reads its
7
+ // decisions without the chant repo. chant's test/reference-workspace.test.ts fails
8
+ // when this kind's data or the schema drifts from chant's.
9
+ export const recordKind = {
10
+ name: "decision",
11
+ location: { dir: ".", match: "^[a-z][a-z0-9]{0,15}-[0-9]{3,}-.+\\.md$" },
12
+ format: "markdown-front-matter",
13
+ schema: { id: "urn:intentius:chant:decision:1", path: "decision.schema.json" },
14
+ idField: "id",
15
+ stateField: "state",
16
+ states: ["proposed", "decided", "ratified", "superseded", "withdrawn"],
17
+ // Sealed once reached (#2555).
18
+ closedStates: ["ratified", "superseded"],
19
+ supersedes: { field: "supersedes", key: "decision" },
20
+ // A supersedes link takes effect under an equal or stricter approval rule
21
+ // (#2524 D4): from a record ranked above 0 and at least as high as the one it
22
+ // names. A decided record supersedes a decided or proposed one, a ratified
23
+ // record supersedes any, and a proposed or withdrawn record none.
24
+ approval: { proposed: 0, withdrawn: 0, decided: 1, ratified: 2, superseded: 2 },
25
+ // Evidence entries with a path pin a workspace file by the hash of its bytes (#2549).
26
+ pins: { field: "evidence" },
27
+ // member:<name> and path:<path> entries are the record's links in workspace graph (#2549).
28
+ constrains: { field: "constrains" },
29
+ // Verdicts, and the field naming the decider, for each record's digest and
30
+ // quorum (#2671, #2672).
31
+ reviews: { field: "reviews", decider: "decided_by" },
32
+ };