@intentius/chant 0.84.0 → 0.86.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/mcp/resource-handlers.d.ts +2 -1
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts +1 -0
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/tools/composites.d.ts +44 -0
- package/dist/cli/mcp/tools/composites.d.ts.map +1 -0
- package/dist/cli/mcp/tools/search.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +13 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +4 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/composite.d.ts +6 -0
- package/dist/composite.d.ts.map +1 -1
- package/dist/lexicon.d.ts +44 -0
- package/dist/lexicon.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__/contract-repo.d.ts +7 -0
- package/dist/workspace/__fixtures__/contract-repo.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 +152 -0
- package/dist/workspace/composites.d.ts.map +1 -0
- 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 +24 -2
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/intent-cli.d.ts +6 -1
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-joins.d.ts +74 -9
- package/dist/workspace/intent-joins.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts +90 -6
- 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/member-commands.d.ts +7 -2
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/reason-codes.d.ts +52 -2
- 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 +65 -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 +164 -0
- package/dist/workspace/records-write.d.ts.map +1 -0
- package/dist/workspace/records.d.ts +202 -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/work.d.ts +56 -0
- package/dist/workspace/work.d.ts.map +1 -0
- package/package.json +19 -1
- package/src/cli/handlers/graph.ts +4 -0
- package/src/cli/main.test.ts +9 -0
- package/src/cli/main.ts +56 -3
- package/src/cli/mcp/resource-handlers.ts +17 -0
- package/src/cli/mcp/server.test.ts +140 -4
- package/src/cli/mcp/server.ts +5 -1
- package/src/cli/mcp/tools/composites.ts +98 -0
- package/src/cli/mcp/tools/search.ts +47 -5
- package/src/cli/registry.ts +13 -1
- package/src/components/cli-support.test.ts +16 -0
- package/src/components/cli-support.ts +8 -2
- package/src/composite.ts +9 -0
- package/src/lexicon.ts +47 -0
- package/src/lifecycle/gate-ledger.ts +14 -0
- package/src/workspace/__fixtures__/contract-repo.ts +17 -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 +533 -0
- package/src/workspace/composites.test.ts +334 -0
- package/src/workspace/composites.ts +316 -0
- 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 +364 -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 +40 -4
- package/src/workspace/intent-cli.ts +54 -7
- package/src/workspace/intent-joins.test.ts +60 -0
- package/src/workspace/intent-joins.ts +117 -20
- package/src/workspace/intent.schema.json +357 -19
- package/src/workspace/intent.test.ts +235 -20
- package/src/workspace/intent.ts +396 -51
- package/src/workspace/ls.schema.json +34 -0
- package/src/workspace/ls.ts +69 -4
- package/src/workspace/member-commands.ts +11 -5
- package/src/workspace/read-contract.test.ts +52 -3
- package/src/workspace/reason-codes.test.ts +48 -4
- package/src/workspace/reason-codes.ts +67 -2
- 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 +246 -19
- package/src/workspace/records-contract.test.ts +77 -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 +202 -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 +736 -0
- package/src/workspace/records.schema.json +187 -9
- package/src/workspace/records.test.ts +93 -0
- package/src/workspace/records.ts +683 -49
- 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/work.test.ts +388 -0
- package/src/workspace/work.ts +163 -0
|
@@ -389,6 +389,22 @@ export const backup =
|
|
|
389
389
|
expect(result.composites?.["aws-plane"]).toEqual(["ArtifactBucket", "OperatorRole"]);
|
|
390
390
|
expect(result.composites?.plain).toBeUndefined();
|
|
391
391
|
});
|
|
392
|
+
|
|
393
|
+
test("carries each component's archetype, declared or inferred (#2662)", async () => {
|
|
394
|
+
await writeFile(
|
|
395
|
+
join(testDir, "lib.component.ts"),
|
|
396
|
+
`export const lib = { name: "lib", archetype: "service", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "shell", reason: "test" }] }] };`,
|
|
397
|
+
);
|
|
398
|
+
await writeFile(
|
|
399
|
+
join(testDir, "plain.component.ts"),
|
|
400
|
+
`export const plain = { name: "plain", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "shell", reason: "test" }] }] };`,
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
const result = await computeComponentGraph(testDir);
|
|
404
|
+
|
|
405
|
+
expect(result.success).toBe(true);
|
|
406
|
+
expect(result.archetypes).toEqual({ lib: "service", plain: "infra" });
|
|
407
|
+
});
|
|
392
408
|
});
|
|
393
409
|
|
|
394
410
|
// ── runComponents (#585) ─────────────────────────────────────────────────────
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import { lexiconModulePath, lexiconNames } from "../lexicon-module";
|
|
26
26
|
import { discoverComponents } from "./discover";
|
|
27
27
|
import type { BuildParamProvenance } from "../provenance";
|
|
28
|
-
import { projectToJson, type Archetype } from "./component";
|
|
28
|
+
import { inferArchetype, projectToJson, type Archetype } from "./component";
|
|
29
29
|
import {
|
|
30
30
|
resolveComponentGraph,
|
|
31
31
|
runInterpretDriver,
|
|
@@ -152,6 +152,10 @@ export interface ComponentGraphResult {
|
|
|
152
152
|
* entirely rather than defaulting to a guess; a consumer with no entry here
|
|
153
153
|
* keeps applying its own naming-convention default. */
|
|
154
154
|
composites?: Record<string, string[]>;
|
|
155
|
+
/** Component name → its archetype, declared or inferred from the composition
|
|
156
|
+
* (`inferArchetype`), so a reader can label a component without its source
|
|
157
|
+
* (#2662). */
|
|
158
|
+
archetypes?: Record<string, Archetype>;
|
|
155
159
|
error?: string;
|
|
156
160
|
}
|
|
157
161
|
|
|
@@ -187,7 +191,9 @@ export async function computeComponentGraph(
|
|
|
187
191
|
// component name → declared composite kind(s) (#1492), only for components
|
|
188
192
|
// that declared them — no identity fallback (see ComponentGraphResult doc).
|
|
189
193
|
const composites: Record<string, string[]> = {};
|
|
194
|
+
const archetypes: Record<string, Archetype> = {};
|
|
190
195
|
for (const [name, discovered] of result.components) {
|
|
196
|
+
archetypes[name] = discovered.component.archetype ?? inferArchetype(discovered.component);
|
|
191
197
|
files[name] = relative(path, discovered.filePath);
|
|
192
198
|
const declared = discovered.component.liveNames;
|
|
193
199
|
liveNames[name] = declared && declared.length > 0 ? [...declared] : [name];
|
|
@@ -201,7 +207,7 @@ export async function computeComponentGraph(
|
|
|
201
207
|
for (const c of driverComponents) {
|
|
202
208
|
for (const dep of c.dependsOn ?? []) edges.push({ from: c.name, to: dep });
|
|
203
209
|
}
|
|
204
|
-
return { success: true, order, waves, edges, files, liveNames, composites };
|
|
210
|
+
return { success: true, order, waves, edges, files, liveNames, composites, archetypes };
|
|
205
211
|
} catch (err) {
|
|
206
212
|
if (err instanceof UnknownDependencyError || err instanceof DependencyCycleError) {
|
|
207
213
|
return { success: false, order: [], waves: [], edges: [], error: err.message };
|
package/src/composite.ts
CHANGED
|
@@ -71,6 +71,15 @@ export function isCompositeInstance(value: unknown): value is CompositeInstance
|
|
|
71
71
|
);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Type guard: is this value a composite definition, what `Composite()` and
|
|
76
|
+
* `withDefaults()` return? A lexicon's composite catalog test uses it to find
|
|
77
|
+
* the composites a module exports (#2662).
|
|
78
|
+
*/
|
|
79
|
+
export function isCompositeDefinition(value: unknown): value is CompositeDefinition<unknown> {
|
|
80
|
+
return typeof value === "function" && typeof (value as { compositeName?: unknown }).compositeName === "string";
|
|
81
|
+
}
|
|
82
|
+
|
|
74
83
|
/**
|
|
75
84
|
* Global registry of composite definitions.
|
|
76
85
|
*/
|
package/src/lexicon.ts
CHANGED
|
@@ -28,6 +28,7 @@ import type { BehaviourKinds } from "./behaviour-kinds";
|
|
|
28
28
|
import type { DisruptionQuery, DisruptionVerdict } from "./lifecycle/disruption";
|
|
29
29
|
import type { OwnerChainVerdict } from "./owner-chain";
|
|
30
30
|
import type { CommandGroup } from "./cli/command-group";
|
|
31
|
+
import type { Archetype } from "./components/component";
|
|
31
32
|
|
|
32
33
|
// Re-exported so a lexicon that hosts Op runs (#2121) can type its
|
|
33
34
|
// `opRuntime` from the same entry it imports the plugin contract from.
|
|
@@ -929,6 +930,45 @@ export interface AuditEntitiesInput {
|
|
|
929
930
|
baseDir?: string;
|
|
930
931
|
}
|
|
931
932
|
|
|
933
|
+
/**
|
|
934
|
+
* One parameter of a composite, read from the composite's declared props type
|
|
935
|
+
* (#2662).
|
|
936
|
+
*/
|
|
937
|
+
export interface CompositeParam {
|
|
938
|
+
/** The prop name as a caller writes it. */
|
|
939
|
+
name: string;
|
|
940
|
+
/** The declared TypeScript type, as source text (long inline types are shortened). */
|
|
941
|
+
type: string;
|
|
942
|
+
/** False when the prop is optional. */
|
|
943
|
+
required: boolean;
|
|
944
|
+
/** The prop's JSDoc summary, when it has one. */
|
|
945
|
+
description?: string;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* A composite this lexicon exports, as static catalog data (#2662).
|
|
950
|
+
*
|
|
951
|
+
* The catalog is what `chant serve mcp` answers "what composites do you have
|
|
952
|
+
* for aws?" from: the `composites` tool, the `chant://composites` resource and
|
|
953
|
+
* `search` results of kind `composite` all read it, and none of them calls a
|
|
954
|
+
* provider. A lexicon writes it once, from source, and a test in the lexicon
|
|
955
|
+
* holds it to the composites the package actually exports.
|
|
956
|
+
*/
|
|
957
|
+
export interface CompositeEntry {
|
|
958
|
+
/** The exported name a caller imports (an alias gets its own entry). */
|
|
959
|
+
name: string;
|
|
960
|
+
/** The lexicon that exports it. */
|
|
961
|
+
lexicon: string;
|
|
962
|
+
/** One line saying what it builds. */
|
|
963
|
+
description: string;
|
|
964
|
+
/** Resource kinds (the lexicon's class names) its members are, nested composites flattened. */
|
|
965
|
+
bundles: string[];
|
|
966
|
+
/** Its props, from the declared props type. */
|
|
967
|
+
params: CompositeParam[];
|
|
968
|
+
/** The component archetype that ships it, when that is known. */
|
|
969
|
+
archetype?: Archetype;
|
|
970
|
+
}
|
|
971
|
+
|
|
932
972
|
export interface LexiconPlugin {
|
|
933
973
|
// ── Required ──────────────────────────────────────────────
|
|
934
974
|
/** Human-readable name (e.g. "aws", "gcp") */
|
|
@@ -1212,6 +1252,13 @@ export interface LexiconPlugin {
|
|
|
1212
1252
|
/** Generate documentation pages */
|
|
1213
1253
|
docs?(options?: { verbose?: boolean }): Promise<void>;
|
|
1214
1254
|
|
|
1255
|
+
/**
|
|
1256
|
+
* The composites this lexicon exports, as static data (#2662). Read by the
|
|
1257
|
+
* MCP `composites` tool, the `chant://composites` resource and `search`.
|
|
1258
|
+
* Omit for a lexicon that exports no composites.
|
|
1259
|
+
*/
|
|
1260
|
+
composites?(): CompositeEntry[];
|
|
1261
|
+
|
|
1215
1262
|
// MCP
|
|
1216
1263
|
/** Return MCP tool contributions */
|
|
1217
1264
|
mcpTools?(): McpToolContribution[];
|
|
@@ -47,6 +47,9 @@ import type { GateApprover, GatePolicyDecision, ResolvedGateApproval } from "../
|
|
|
47
47
|
import { readBlobFromPath, readPathSha, readBlobBySha, writeBlobToPath, RefCASConflictError } from "./git";
|
|
48
48
|
|
|
49
49
|
const DIR = "_gates";
|
|
50
|
+
|
|
51
|
+
/** The directory on the `chant/lifecycle` branch that holds gate ledgers, under a member's prefix when it has one. */
|
|
52
|
+
export const GATES_DIR = DIR;
|
|
50
53
|
const APPEND_RETRY_ATTEMPTS = 5;
|
|
51
54
|
|
|
52
55
|
/**
|
|
@@ -350,7 +353,18 @@ export async function readGateLedger(
|
|
|
350
353
|
): Promise<{ resolutions: GateResolutionRecord[]; pending: PendingGateRecord[]; malformed: number }> {
|
|
351
354
|
const content = await readBlobFromPath(DIR, filename(op), opts);
|
|
352
355
|
if (!content) return { resolutions: [], pending: [], malformed: 0 };
|
|
356
|
+
return parseGateLedger(content);
|
|
357
|
+
}
|
|
353
358
|
|
|
359
|
+
/**
|
|
360
|
+
* Split the text of one gate ledger file into its two kinds of line, oldest
|
|
361
|
+
* first, the way {@link readGateLedger} reads it. Exported for a reader that
|
|
362
|
+
* fetches the file itself, such as `chant workspace status` (#2674), which
|
|
363
|
+
* reads every member's ledger by its full path from the workspace root.
|
|
364
|
+
*/
|
|
365
|
+
export function parseGateLedger(
|
|
366
|
+
content: string,
|
|
367
|
+
): { resolutions: GateResolutionRecord[]; pending: PendingGateRecord[]; malformed: number } {
|
|
354
368
|
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
355
369
|
const resolutions: GateResolutionRecord[] = [];
|
|
356
370
|
const pending: PendingGateRecord[] = [];
|
|
@@ -98,3 +98,20 @@ case "$1" in
|
|
|
98
98
|
*) echo "Error: Unknown command: $1" >&2; exit 1 ;;
|
|
99
99
|
esac
|
|
100
100
|
`;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A chant older than `workspace member-run` that answers from files (#2662):
|
|
104
|
+
* `chant graph --components` prints the member's `components.json`, or an
|
|
105
|
+
* empty IR when there is none, and plain `chant graph` prints `ir.json`. A
|
|
106
|
+
* `components.json` holding `FAIL` makes the component graph exit 1.
|
|
107
|
+
*/
|
|
108
|
+
export const FAKE_FILE_GRAPH_CHANT = `#!/bin/sh
|
|
109
|
+
[ "$1" = graph ] || { echo "Error: Unknown command: $1" >&2; exit 1; }
|
|
110
|
+
case " $* " in
|
|
111
|
+
*" --components "*)
|
|
112
|
+
if [ ! -f components.json ]; then printf '{"version":1,"nodes":[],"edges":[],"groups":{}}\\n'
|
|
113
|
+
elif grep -q FAIL components.json; then echo "Error: component discovery failed" >&2; exit 1
|
|
114
|
+
else cat components.json; fi ;;
|
|
115
|
+
*) cat ir.json ;;
|
|
116
|
+
esac
|
|
117
|
+
`;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A repository with the reference workspace's decisions and design member,
|
|
3
|
+
* and helpers that write sessions and review entries into it, for the
|
|
4
|
+
* session and records --since tests (#2673).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { cpSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { sessionSeal } from "../record-sessions";
|
|
10
|
+
import { commitAll, git, REPO, scratchDir } from "./contract-repo";
|
|
11
|
+
|
|
12
|
+
export const SESSIONS_KIND = "design/sessions/session.kind.mjs";
|
|
13
|
+
export const DECISIONS_KIND = "decisions/decision.kind.mjs";
|
|
14
|
+
|
|
15
|
+
/** A git repository holding a copy of the reference workspace's decisions and design member. */
|
|
16
|
+
export function sessionsRepo(): string {
|
|
17
|
+
const root = scratchDir("chant-sessions-");
|
|
18
|
+
for (const d of ["decisions", "design"]) cpSync(join(REPO, "reference-workspace", d), join(root, d), { recursive: true });
|
|
19
|
+
git(root, "init", "-q");
|
|
20
|
+
commitAll(root, "reference");
|
|
21
|
+
return root;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A session file's text, sealed when it is closed. */
|
|
25
|
+
export function sessionText(s: { id: string; state: "open" | "closed"; verdicts?: { record: string; principal: string; verdict: string }[]; agenda?: string[] }): string {
|
|
26
|
+
const lines = [
|
|
27
|
+
"---",
|
|
28
|
+
"schema: 1",
|
|
29
|
+
`id: "${s.id}"`,
|
|
30
|
+
`title: "Session ${s.id}"`,
|
|
31
|
+
`state: "${s.state}"`,
|
|
32
|
+
"agenda:",
|
|
33
|
+
...(s.agenda ?? ["ref-001"]).map((r) => ` - record: "${r}"`),
|
|
34
|
+
"attendance:",
|
|
35
|
+
' - principal: "lex00"',
|
|
36
|
+
' class: "person"',
|
|
37
|
+
' - principal: "alice"',
|
|
38
|
+
' class: "person"',
|
|
39
|
+
' - principal: "helper"',
|
|
40
|
+
' class: "agent"',
|
|
41
|
+
'opened: "2026-09-24T18:00:00Z"',
|
|
42
|
+
`closed: ${s.state === "closed" ? '"2026-09-24T19:00:00Z"' : "null"}`,
|
|
43
|
+
...(s.verdicts?.length
|
|
44
|
+
? ["verdicts:", ...s.verdicts.flatMap((v) => [` - record: "${v.record}"`, ` principal: "${v.principal}"`, ` verdict: "${v.verdict}"`])]
|
|
45
|
+
: ["verdicts: []"]),
|
|
46
|
+
...(s.state === "closed" ? ['closed_digest: "SEAL"'] : []),
|
|
47
|
+
"---",
|
|
48
|
+
"",
|
|
49
|
+
`# Session ${s.id}`,
|
|
50
|
+
"",
|
|
51
|
+
];
|
|
52
|
+
const text = lines.join("\n");
|
|
53
|
+
return s.state === "closed" ? text.replace('"SEAL"', `"${sessionSeal(text, "closed_digest")}"`) : text;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Rewrite ref-001 with the given review entries, each naming `session`. */
|
|
57
|
+
export function reviewed(root: string, reviewers: string[], session: string, state = "decided"): void {
|
|
58
|
+
const file = join(root, "decisions", "ref-001-how-the-app-is-deployed.md");
|
|
59
|
+
const reviews = reviewers.map((r) => ` - reviewer: "${r}"\n verdict: "agree"\n on: "2026-09-24"\n session: "${session}"`).join("\n");
|
|
60
|
+
writeFileSync(
|
|
61
|
+
file,
|
|
62
|
+
readFileSync(file, "utf-8")
|
|
63
|
+
.replace(/^state: .*$/m, `state: "${state}"`)
|
|
64
|
+
.replace(/^reviews: .*$/m, reviewers.length ? `reviews:\n${reviews}` : "reviews: []"),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* | WSP112 | a pinned file that does not exist |
|
|
14
14
|
* | WSP113 | a pinned file that a superseding record pins at the old hash: the artifact did not follow the decision |
|
|
15
15
|
* | WSP114 | the records of `--kind` can't be read (fixed) |
|
|
16
|
+
* | WSP115 | a record kind the declaration names is missing or does not load as one (fixed, #2680) |
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
import type { WorkspaceCheck, WorkspaceDiagnostic } from "../checks";
|
|
@@ -80,4 +81,22 @@ export const RECORD_CHECKS: readonly WorkspaceCheck[] = [
|
|
|
80
81
|
return [{ checkId: this.id, severity: this.severity, message: `--kind ${facts.kind}: ${facts.error.code}: ${facts.error.message}`, pointer: "" }];
|
|
81
82
|
},
|
|
82
83
|
},
|
|
84
|
+
{
|
|
85
|
+
id: "WSP115",
|
|
86
|
+
name: "record-kind-unloadable",
|
|
87
|
+
description: "Every record kind the declaration names is a file that exports a valid recordKind, with the schema it names.",
|
|
88
|
+
severity: "error",
|
|
89
|
+
configurable: false,
|
|
90
|
+
check(ctx) {
|
|
91
|
+
return (ctx.facts?.declaredKinds ?? [])
|
|
92
|
+
.filter((k) => k.reason !== null)
|
|
93
|
+
.map((k) => ({
|
|
94
|
+
checkId: this.id,
|
|
95
|
+
severity: this.severity,
|
|
96
|
+
message: `${k.declared.member === null ? "the workspace" : `member ${k.declared.member}`} declares the record kind ${k.declared.kind}, which can't be loaded: ${k.reason!.code}: ${k.reason!.message}`,
|
|
97
|
+
...(k.declared.member !== null ? { entity: k.declared.member } : {}),
|
|
98
|
+
pointer: `${k.declared.pointer}/kind`,
|
|
99
|
+
}));
|
|
100
|
+
},
|
|
101
|
+
},
|
|
83
102
|
];
|
|
@@ -58,6 +58,8 @@ describe("the WSP catalog", () => {
|
|
|
58
58
|
"link-kind-unknown",
|
|
59
59
|
// --kind names a record kind, and records that can't be read check nothing (#2549).
|
|
60
60
|
"records-unreadable",
|
|
61
|
+
// A declared record kind that doesn't load breaks every reader's default (#2680).
|
|
62
|
+
"record-kind-unloadable",
|
|
61
63
|
]);
|
|
62
64
|
});
|
|
63
65
|
|
package/src/workspace/checks.ts
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* | WSP091 to WSP097 | member links (#2539) |
|
|
28
28
|
* | WSP101 to WSP106 | generated files |
|
|
29
29
|
* | WSP111 to WSP114 | records read with `--kind` (#2549) |
|
|
30
|
+
* | WSP115 | the record kinds the declaration names (#2680) |
|
|
30
31
|
*/
|
|
31
32
|
|
|
32
33
|
import { realpathSync } from "node:fs";
|
|
@@ -44,6 +45,7 @@ import { gatherGeneratedFacts, GENERATED_CHECKS, type GeneratedFileFacts } from
|
|
|
44
45
|
import { gatherLedgerFacts, LEDGER_CHECKS, type MemberLedgerFacts } from "./checks/ledgers";
|
|
45
46
|
import { gatherPipelineFacts, PIPELINE_CHECKS, type MemberPipelineFacts } from "./checks/pipelines";
|
|
46
47
|
import { RECORD_CHECKS, type RecordFacts } from "./checks/records";
|
|
48
|
+
import { loadDeclaredKinds, type DeclaredKind } from "./declared-kinds";
|
|
47
49
|
|
|
48
50
|
/**
|
|
49
51
|
* What the checks beyond the declaration read, gathered from the checkout
|
|
@@ -59,6 +61,8 @@ export interface WorkspaceFacts {
|
|
|
59
61
|
generated?: readonly GeneratedFileFacts[];
|
|
60
62
|
/** The records of the kind named with `--kind`, with their pins checked (#2549). */
|
|
61
63
|
records?: RecordFacts;
|
|
64
|
+
/** The record kinds the declaration names, each loaded, or only looked for under `--at` (#2680). */
|
|
65
|
+
declaredKinds?: readonly DeclaredKind[];
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
/** What every declaration check reads. */
|
|
@@ -444,7 +448,9 @@ export async function runDeclarationChecks(
|
|
|
444
448
|
}
|
|
445
449
|
const { registry, problems } = loadKindRegistry(declaration.pins, root);
|
|
446
450
|
const gathered = options.gather === false || options.tree ? {} : await gatherWorkspaceFacts(root, declaration, options);
|
|
447
|
-
|
|
451
|
+
// The declared record kinds load from the working tree; under --at only whether each exists at the revision is checked (#2680).
|
|
452
|
+
const declaredKinds = options.gather === false ? undefined : await loadDeclaredKinds(declaration, tree, root, { load: !options.tree });
|
|
453
|
+
const facts: WorkspaceFacts = { ...gathered, ...(options.records ? { records: options.records } : {}), ...(declaredKinds ? { declaredKinds } : {}) };
|
|
448
454
|
const ctx: WorkspaceCheckContext = { declaration, tree, groups, kinds: registry, kindProblems: problems, facts };
|
|
449
455
|
const findings = runWorkspaceChecks(ctx);
|
|
450
456
|
const { active, suppressed } = applyCheckSettings(declaration, findings);
|