@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.
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +13 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +13 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/sessions.d.ts +23 -0
- package/dist/workspace/__fixtures__/sessions.d.ts.map +1 -0
- package/dist/workspace/checks/records.d.ts +1 -0
- package/dist/workspace/checks/records.d.ts.map +1 -1
- package/dist/workspace/checks.d.ts +4 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/composites.d.ts +14 -1
- package/dist/workspace/composites.d.ts.map +1 -1
- package/dist/workspace/conformance/index.d.ts +211 -0
- package/dist/workspace/conformance/index.d.ts.map +1 -0
- package/dist/workspace/conformance/vitest.d.ts +11 -0
- package/dist/workspace/conformance/vitest.d.ts.map +1 -0
- package/dist/workspace/declaration.d.ts +28 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +40 -0
- package/dist/workspace/declared-kinds.d.ts +43 -0
- package/dist/workspace/declared-kinds.d.ts.map +1 -0
- package/dist/workspace/graph-cli.d.ts +11 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/intent-cli.d.ts +2 -1
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-joins.d.ts +45 -8
- package/dist/workspace/intent-joins.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts +71 -7
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/ls.d.ts +31 -1
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/reason-codes.d.ts +47 -4
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-sessions.d.ts +51 -0
- package/dist/workspace/record-sessions.d.ts.map +1 -0
- package/dist/workspace/record-source.d.ts +2 -0
- package/dist/workspace/record-source.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +71 -4
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-since.d.ts +90 -0
- package/dist/workspace/records-since.d.ts.map +1 -0
- package/dist/workspace/records-write.d.ts +171 -0
- package/dist/workspace/records-write.d.ts.map +1 -0
- package/dist/workspace/records.d.ts +244 -15
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/runtimes.d.ts +60 -0
- package/dist/workspace/runtimes.d.ts.map +1 -0
- package/dist/workspace/status-gates.d.ts +90 -0
- package/dist/workspace/status-gates.d.ts.map +1 -0
- package/dist/workspace/status.d.ts +17 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/trust/seal.d.ts +85 -0
- package/dist/workspace/trust/seal.d.ts.map +1 -0
- package/dist/workspace/trust/ssh-commit.d.ts +7 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
- package/dist/workspace/work.d.ts +56 -0
- package/dist/workspace/work.d.ts.map +1 -0
- package/package.json +19 -1
- package/src/cli/main.ts +55 -3
- package/src/cli/registry.ts +13 -1
- package/src/lifecycle/gate-ledger.ts +14 -0
- package/src/workspace/__fixtures__/sessions.ts +66 -0
- package/src/workspace/checks/records.ts +19 -0
- package/src/workspace/checks.test.ts +2 -0
- package/src/workspace/checks.ts +7 -1
- package/src/workspace/composites.schema.json +65 -3
- package/src/workspace/composites.test.ts +95 -5
- package/src/workspace/composites.ts +28 -7
- package/src/workspace/conformance/__fixture__/app/package.json +7 -0
- package/src/workspace/conformance/__fixture__/app/src/server.mjs +29 -0
- package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +32 -0
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +376 -0
- package/src/workspace/conformance/__fixture__/decisions/fix-001-how-the-app-is-deployed.md +40 -0
- package/src/workspace/conformance/__fixture__/delivery/chant.config.ts +7 -0
- package/src/workspace/conformance/__fixture__/delivery/lexicon/index.ts +26 -0
- package/src/workspace/conformance/__fixture__/delivery/package.json +7 -0
- package/src/workspace/conformance/__fixture__/delivery/src/app.component.ts +14 -0
- package/src/workspace/conformance/__fixture__/delivery/src/app.ts +4 -0
- package/src/workspace/conformance/conformance.test.ts +149 -0
- package/src/workspace/conformance/index.mjs +31 -0
- package/src/workspace/conformance/index.ts +453 -0
- package/src/workspace/conformance/vitest.ts +62 -0
- package/src/workspace/declaration.schema.json +40 -0
- package/src/workspace/declaration.ts +62 -0
- package/src/workspace/declared-kinds.test.ts +321 -0
- package/src/workspace/declared-kinds.ts +76 -0
- package/src/workspace/graph-cli.ts +8 -0
- package/src/workspace/intent-cli.ts +29 -6
- package/src/workspace/intent-gaps.test.ts +217 -0
- package/src/workspace/intent-joins.test.ts +60 -0
- package/src/workspace/intent-joins.ts +71 -19
- package/src/workspace/intent.schema.json +304 -7
- package/src/workspace/intent.test.ts +99 -0
- package/src/workspace/intent.ts +365 -46
- package/src/workspace/ls.schema.json +34 -0
- package/src/workspace/ls.ts +69 -4
- package/src/workspace/read-contract.test.ts +30 -9
- package/src/workspace/reason-codes.test.ts +16 -4
- package/src/workspace/reason-codes.ts +55 -4
- package/src/workspace/record-assets.test.ts +3 -1
- package/src/workspace/record-sessions.ts +105 -0
- package/src/workspace/record-source.ts +14 -5
- package/src/workspace/records-amend.schema.json +167 -0
- package/src/workspace/records-cli.ts +308 -19
- package/src/workspace/records-contract.test.ts +57 -2
- package/src/workspace/records-formats.test.ts +640 -0
- package/src/workspace/records-new.schema.json +158 -0
- package/src/workspace/records-quorum.test.ts +196 -0
- package/src/workspace/records-review.schema.json +227 -0
- package/src/workspace/records-sessions.test.ts +108 -0
- package/src/workspace/records-since.schema.json +193 -0
- package/src/workspace/records-since.test.ts +174 -0
- package/src/workspace/records-since.ts +259 -0
- package/src/workspace/records-write-contract.test.ts +125 -0
- package/src/workspace/records-write.test.ts +373 -0
- package/src/workspace/records-write.ts +765 -0
- package/src/workspace/records.schema.json +202 -9
- package/src/workspace/records.ts +700 -41
- package/src/workspace/runtimes.ts +107 -0
- package/src/workspace/status-contract.test.ts +163 -0
- package/src/workspace/status-gates.ts +215 -0
- package/src/workspace/status.schema.json +69 -3
- package/src/workspace/status.ts +35 -2
- package/src/workspace/trust/seal.test.ts +232 -0
- package/src/workspace/trust/seal.ts +195 -0
- package/src/workspace/trust/ssh-commit.ts +2 -2
- package/src/workspace/work.test.ts +390 -0
- 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
|
-
|
|
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,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
|
+
};
|