@intentius/chant 0.124.14 → 0.124.15

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 (110) hide show
  1. package/dist/build.d.ts +6 -0
  2. package/dist/build.d.ts.map +1 -1
  3. package/dist/build.js +1 -0
  4. package/dist/change-set.d.ts +24 -2
  5. package/dist/change-set.d.ts.map +1 -1
  6. package/dist/change-set.js +50 -1
  7. package/dist/ci-jobs.d.ts +227 -0
  8. package/dist/ci-jobs.d.ts.map +1 -0
  9. package/dist/ci-jobs.js +448 -0
  10. package/dist/components/pr-pipeline.d.ts.map +1 -1
  11. package/dist/components/pr-pipeline.js +3 -2
  12. package/dist/composite.d.ts +10 -2
  13. package/dist/composite.d.ts.map +1 -1
  14. package/dist/composite.js +8 -0
  15. package/dist/discovery/collect.d.ts +2 -1
  16. package/dist/discovery/collect.d.ts.map +1 -1
  17. package/dist/discovery/collect.js +16 -1
  18. package/dist/discovery/fold-import.d.ts.map +1 -1
  19. package/dist/discovery/fold-import.js +2 -107
  20. package/dist/discovery/host-composite.d.ts +35 -0
  21. package/dist/discovery/host-composite.d.ts.map +1 -0
  22. package/dist/discovery/host-composite.js +93 -0
  23. package/dist/discovery/index.d.ts +9 -0
  24. package/dist/discovery/index.d.ts.map +1 -1
  25. package/dist/discovery/index.js +1 -1
  26. package/dist/gate-decision.d.ts +20 -9
  27. package/dist/gate-decision.d.ts.map +1 -1
  28. package/dist/gate-decision.js +72 -19
  29. package/dist/lifecycle/gate-audit.d.ts +69 -2
  30. package/dist/lifecycle/gate-audit.d.ts.map +1 -1
  31. package/dist/lifecycle/gate-audit.js +76 -2
  32. package/dist/lifecycle/gate-ledger.d.ts +24 -0
  33. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  34. package/dist/lifecycle/gate-ledger.js +9 -1
  35. package/dist/op/activities/activity-contracts.d.ts +1 -1
  36. package/dist/op/activities/activity-contracts.js +1 -1
  37. package/dist/op/activities/change-set.d.ts +8 -3
  38. package/dist/op/activities/change-set.d.ts.map +1 -1
  39. package/dist/op/activities/change-set.js +5 -2
  40. package/dist/op/activities/reconcile.d.ts +28 -16
  41. package/dist/op/activities/reconcile.d.ts.map +1 -1
  42. package/dist/op/activities/reconcile.js +34 -20
  43. package/dist/op/builders.d.ts +1 -1
  44. package/dist/op/builders.d.ts.map +1 -1
  45. package/dist/op/builders.js +1 -1
  46. package/dist/op/op-waves-run.d.ts +1 -1
  47. package/dist/op/op-waves-run.d.ts.map +1 -1
  48. package/dist/op/op-waves-run.js +3 -3
  49. package/dist/workspace/__fixtures__/contract-repo.d.ts +7 -0
  50. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  51. package/dist/workspace/__fixtures__/contract-repo.js +17 -1
  52. package/dist/workspace/__fixtures__/sessions.d.ts +1 -1
  53. package/dist/workspace/__fixtures__/sessions.d.ts.map +1 -1
  54. package/dist/workspace/__fixtures__/sessions.js +3 -3
  55. package/dist/workspace/change-set.schema.json +5 -3
  56. package/dist/workspace/ci-green-workflow.d.ts +3 -1
  57. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  58. package/dist/workspace/ci-green-workflow.js +5 -2
  59. package/dist/workspace/class-data.d.ts +84 -0
  60. package/dist/workspace/class-data.d.ts.map +1 -0
  61. package/dist/workspace/class-data.js +238 -0
  62. package/dist/workspace/kinds.d.ts +3 -7
  63. package/dist/workspace/kinds.d.ts.map +1 -1
  64. package/dist/workspace/kinds.js +6 -40
  65. package/dist/workspace/principal-classes.d.ts +4 -11
  66. package/dist/workspace/principal-classes.d.ts.map +1 -1
  67. package/dist/workspace/principal-classes.js +11 -94
  68. package/package.json +1 -1
  69. package/src/build.ts +8 -0
  70. package/src/change-set.test.ts +48 -0
  71. package/src/change-set.ts +52 -2
  72. package/src/ci-jobs.test.ts +49 -0
  73. package/src/ci-jobs.ts +515 -0
  74. package/src/components/pr-pipeline.ts +3 -2
  75. package/src/composite.ts +15 -3
  76. package/src/discovery/collect.ts +15 -1
  77. package/src/discovery/fold-build-parity.test.ts +71 -0
  78. package/src/discovery/fold-import.ts +2 -113
  79. package/src/discovery/fold-project-provenance.test.ts +21 -4
  80. package/src/discovery/host-composite.ts +101 -0
  81. package/src/discovery/index.ts +11 -1
  82. package/src/gate-decision-bundle.test.ts +5 -4
  83. package/src/gate-decision-identity.test.ts +59 -6
  84. package/src/gate-decision.ts +81 -21
  85. package/src/lifecycle/gate-audit.test.ts +55 -1
  86. package/src/lifecycle/gate-audit.ts +116 -3
  87. package/src/lifecycle/gate-ledger.test.ts +20 -1
  88. package/src/lifecycle/gate-ledger.ts +33 -1
  89. package/src/op/activities/activity-contracts.ts +1 -1
  90. package/src/op/activities/change-set.ts +11 -3
  91. package/src/op/activities/lexicon-upgrade.test.ts +2 -0
  92. package/src/op/activities/reconcile.test.ts +62 -2
  93. package/src/op/activities/reconcile.ts +46 -22
  94. package/src/op/builders.ts +1 -1
  95. package/src/op/op-waves-run.ts +4 -3
  96. package/src/op/op-waves.test.ts +2 -0
  97. package/src/op/work-lease-run.test.ts +12 -4
  98. package/src/workspace/__fixtures__/contract-repo.ts +17 -1
  99. package/src/workspace/__fixtures__/sessions.ts +3 -3
  100. package/src/workspace/change-set.schema.json +5 -3
  101. package/src/workspace/ci-green-workflow.test.ts +4 -4
  102. package/src/workspace/ci-green-workflow.ts +5 -2
  103. package/src/workspace/class-data.test.ts +64 -0
  104. package/src/workspace/class-data.ts +236 -0
  105. package/src/workspace/kinds.ts +6 -35
  106. package/src/workspace/principal-classes.ts +11 -94
  107. package/src/workspace/records-formats.test.ts +7 -2
  108. package/src/workspace/records-sessions-write.test.ts +3 -3
  109. package/src/workspace/status-contract.e2e.test.ts +37 -0
  110. package/src/workspace/status-contract.test.ts +23 -21
@@ -22,10 +22,9 @@
22
22
  * installed package, which must be at the version the base pins.
23
23
  */
24
24
 
25
- import { readFileSync } from "node:fs";
26
25
  import { createRequire } from "node:module";
27
- import { join, posix } from "node:path";
28
- import { kindsExportTarget, pinnedPackageDir, type KindLoadProblem, type PinLike } from "./kinds";
26
+ import { checkClassData, diskFiles, mergePinClasses, readClassFiles, type ClassData, type PackageFiles, type PinClassRead } from "./class-data";
27
+ import { pinnedPackageDir, type KindLoadProblem, type PinLike } from "./kinds";
29
28
  import schema from "./workspace-principals.schema.json" with { type: "json" };
30
29
  import type { TrustPolicy } from "./trust/policy";
31
30
  import { joinPath, type WorkspaceTree } from "./tree";
@@ -33,8 +32,7 @@ import { CORE_CLASSES, CORE_PRINCIPAL_CLASSES, grantedClasses, type PrincipalCla
33
32
 
34
33
  export const PRINCIPALS_SCHEMA_ID = schema.$id;
35
34
 
36
- /** The subpath a package publishes its principal classes at. */
37
- export const PRINCIPALS_SUBPATH = "./workspace-principals";
35
+ export { PRINCIPALS_SUBPATH } from "./class-data";
38
36
 
39
37
  export { CORE_CLASSES, CORE_PRINCIPAL_CLASSES, type CorePrincipalClass, type PrincipalClass } from "./identity-gate-rule";
40
38
 
@@ -44,8 +42,6 @@ export type PrincipalClassName = string;
44
42
  /** The grammar of a class name, as `workspace-principals.schema.json` and the declaration's writeScope keys say it. */
45
43
  export const CLASS_NAME = /^(?!x-)[a-z][a-z0-9-]{0,39}$/;
46
44
 
47
- const CORE_ROLES = new Set(CORE_CLASSES.map((c) => c.role).filter((r): r is string => r !== null));
48
-
49
45
  export interface ClassRegistry {
50
46
  get(name: string): PrincipalClass | undefined;
51
47
  /** Every class name, core first, for "unknown class" messages. */
@@ -111,11 +107,8 @@ function validator(): Validate {
111
107
  return compiled;
112
108
  }
113
109
 
114
- export interface ClassData {
115
- classes: PrincipalClass[];
116
- /** Why some of the data can't be used, one line each. Empty when all of it can. */
117
- problems: string[];
118
- }
110
+ export type { ClassData, PackageFiles } from "./class-data";
111
+ export { diskFiles } from "./class-data";
119
112
 
120
113
  /**
121
114
  * Check the text of a principals file against
@@ -135,46 +128,7 @@ export function parseClassData(text: string, source: string): ClassData {
135
128
  const problems = (validate.errors ?? []).map((e) => `${source}: ${e.instancePath || "the principals file"} ${e.message ?? "is invalid"}`);
136
129
  return { classes: [], problems: [...new Set(problems)] };
137
130
  }
138
- const problems: string[] = [];
139
- const classes: PrincipalClass[] = [];
140
- const names = new Set<string>();
141
- const roles = new Set<string>();
142
- for (const c of (raw as { classes: { name: string; description: string; role: string }[] }).classes) {
143
- if ((CORE_PRINCIPAL_CLASSES as readonly string[]).includes(c.name)) {
144
- problems.push(`${source}: class ${c.name} is a core class and can't be supplied by a package`);
145
- continue;
146
- }
147
- if (CORE_ROLES.has(c.role)) {
148
- problems.push(`${source}: class ${c.name} names the role ${c.role}, which puts a principal in the core ${c.role} class`);
149
- continue;
150
- }
151
- if (names.has(c.name)) {
152
- problems.push(`${source}: class ${c.name} is listed twice`);
153
- continue;
154
- }
155
- if (roles.has(c.role)) {
156
- problems.push(`${source}: class ${c.name} names the role ${c.role}, which another class in the file names; a role puts a principal in one class`);
157
- continue;
158
- }
159
- names.add(c.name);
160
- roles.add(c.role);
161
- classes.push({ name: c.name, description: c.description, role: c.role, source });
162
- }
163
- return { classes, problems };
164
- }
165
-
166
- /** A package's files, by a `/`-separated path inside it, or undefined when one is not there. */
167
- export type PackageFiles = (path: string) => string | undefined;
168
-
169
- /** A package directory on disk. */
170
- export function diskFiles(dir: string): PackageFiles {
171
- return (path) => {
172
- try {
173
- return readFileSync(join(dir, ...path.split("/")), "utf-8");
174
- } catch {
175
- return undefined;
176
- }
177
- };
131
+ return checkClassData(raw as Parameters<typeof checkClassData>[0], source);
178
132
  }
179
133
 
180
134
  /** A package directory at `dir` in `tree`, such as the workspace at the base revision. */
@@ -192,22 +146,7 @@ export function treeFiles(tree: WorkspaceTree, dir: string): PackageFiles {
192
146
 
193
147
  /** Read the classes a package publishes at `./workspace-principals`. Reads files only; imports nothing. A package that publishes none supplies none. */
194
148
  export function readPackageClasses(files: PackageFiles, source: string): ClassData {
195
- const manifest = files("package.json");
196
- if (manifest === undefined) return { classes: [], problems: [`${source}: no readable package.json`] };
197
- let pkg: Record<string, unknown>;
198
- try {
199
- pkg = JSON.parse(manifest) as Record<string, unknown>;
200
- } catch {
201
- return { classes: [], problems: [`${source}: package.json is not JSON`] };
202
- }
203
- const target = kindsExportTarget(pkg, PRINCIPALS_SUBPATH);
204
- if (target === undefined) return { classes: [], problems: [] };
205
- if (typeof target !== "string") return { classes: [], problems: [`${source}: ${target.problem}`] };
206
- const inside = posix.normalize(target.slice(2));
207
- if (inside.startsWith("../") || inside === "..") return { classes: [], problems: [`${source}: exports["${PRINCIPALS_SUBPATH}"] points outside the package`] };
208
- const text = files(inside);
209
- if (text === undefined) return { classes: [], problems: [`${source}: exports["${PRINCIPALS_SUBPATH}"] names ${target}, which does not exist`] };
210
- return parseClassData(text, source);
149
+ return readClassFiles(files, source, parseClassData);
211
150
  }
212
151
 
213
152
  export interface LoadedClasses {
@@ -227,10 +166,7 @@ export interface LoadedClasses {
227
166
  */
228
167
  export function loadClassRegistry(pins: readonly PinLike[], workspaceRoot: string, opts: { tree?: WorkspaceTree } = {}): LoadedClasses {
229
168
  const problems: KindLoadProblem[] = [];
230
- const extra: PrincipalClass[] = [];
231
- const byName = new Map<string, string>();
232
- const byRole = new Map<string, string>();
233
- const clashing = new Set<string>();
169
+ const reads: PinClassRead[] = [];
234
170
  pins.forEach((pin, i) => {
235
171
  let files: PackageFiles;
236
172
  let source: string;
@@ -247,29 +183,10 @@ export function loadClassRegistry(pins: readonly PinLike[], workspaceRoot: strin
247
183
  files = diskFiles(found.dir);
248
184
  source = found.source;
249
185
  }
250
- const read = readPackageClasses(files, source);
251
- for (const p of read.problems) problems.push({ pin: i, message: p });
252
- for (const c of read.classes) {
253
- const named = byName.get(c.name);
254
- const roled = byRole.get(c.role!);
255
- if (named !== undefined || roled !== undefined) {
256
- clashing.add(c.name);
257
- if (named !== undefined) {
258
- problems.push({ pin: i, message: `class ${c.name} is supplied by both ${named} and ${source}; a class name has one source` });
259
- } else {
260
- const other = extra.find((e) => e.role === c.role)!;
261
- clashing.add(other.name);
262
- problems.push({ pin: i, message: `class ${c.name} (${source}) names the role ${c.role}, which class ${other.name} (${roled}) names; a role puts a principal in one class` });
263
- }
264
- continue;
265
- }
266
- byName.set(c.name, source);
267
- byRole.set(c.role!, source);
268
- extra.push(c);
269
- }
186
+ reads.push({ pin: i, source, read: readPackageClasses(files, source) });
270
187
  });
271
- // A name or role two pins both supply is left out altogether: neither reading is safe.
272
- return { registry: createClassRegistry(extra.filter((c) => !clashing.has(c.name))), problems };
188
+ const merged = mergePinClasses(reads);
189
+ return { registry: createClassRegistry(merged.classes), problems: [...problems, ...merged.problems].sort((a, b) => a.pin - b.pin) };
273
190
  }
274
191
 
275
192
  /**
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { execFileSync } from "node:child_process";
14
14
  import { createHash } from "node:crypto";
15
- import { mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
15
+ import { cpSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
16
16
  import { tmpdir } from "node:os";
17
17
  import { dirname, join } from "node:path";
18
18
  import Ajv2020 from "ajv/dist/2020";
@@ -472,7 +472,12 @@ describe("the write commands through a declared kind (#2680)", () => {
472
472
 
473
473
  describe("the decision kind", () => {
474
474
  test("still reads as markdown front matter, and says so", async () => {
475
- const doc = await queryRecords({ kind: "docs/design/decisions/decision.kind.mjs", cwd: REPO });
475
+ // The repository's own decision records, copied into a one-commit repository:
476
+ // a read in the real checkout asks git for the history of every pinned path
477
+ // across thousands of commits, which took 12 s alone and 20+ s under load.
478
+ cpSync(join(REPO, "docs", "design", "decisions"), join(root, "docs", "design", "decisions"), { recursive: true });
479
+ commit();
480
+ const doc = await queryRecords({ kind: "docs/design/decisions/decision.kind.mjs", cwd: root });
476
481
  expect(validateOutput(doc)).toBe(true);
477
482
  if ("error" in doc) throw new Error(doc.error.message);
478
483
  expect(doc.kind.format).toBe("markdown-front-matter");
@@ -12,7 +12,7 @@
12
12
  import { cpSync, readFileSync, writeFileSync } from "node:fs";
13
13
  import { join } from "node:path";
14
14
  import { afterAll, describe, expect, test } from "vitest";
15
- import { cleanScratch, commitAll, contract, git, REPO, scratchDir } from "./__fixtures__/contract-repo";
15
+ import { cleanScratch, commitAll, contract, git, REPO, repoFixture, scratchDir } from "./__fixtures__/contract-repo";
16
16
  import { declareSessions, DECISIONS_KIND, newSessionFields, sessionsRepo, SESSIONS_KIND } from "./__fixtures__/sessions";
17
17
  import { sessionSeal } from "./record-sessions";
18
18
  import { parseFrontMatter, recordTextDigest } from "./records";
@@ -36,12 +36,12 @@ const code = (doc: object) => ("error" in doc ? (doc as { error: { code: string
36
36
  const HEX = /^[0-9a-f]{40}$/;
37
37
 
38
38
  /** The reference fixture with a declaration, committed. */
39
- function declared(): string {
39
+ const declared = repoFixture(() => {
40
40
  const root = sessionsRepo();
41
41
  declareSessions(root);
42
42
  commitAll(root, "declare");
43
43
  return root;
44
- }
44
+ });
45
45
 
46
46
  function front(root: string, path: string): Record<string, unknown> {
47
47
  const fm = parseFrontMatter(readFileSync(join(root, path), "utf-8"));
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The chant repo's own workspace declaration reads through
3
+ * `chant workspace status` and validates against the output schema (#2557).
4
+ * It reads the real checkout, 28 members and a git call or more for each,
5
+ * which is 5 to 9 s: split from status-contract.test.ts so the unit budget
6
+ * keeps to fixtures built here (#3880).
7
+ */
8
+
9
+ import { join } from "node:path";
10
+ import Ajv2020 from "ajv/dist/2020";
11
+ import { describe, expect, test } from "vitest";
12
+ import { listWorkspace } from "./ls";
13
+ import { workspaceStatus, type StatusDocument } from "./status";
14
+ import schema from "./status.schema.json";
15
+
16
+ const REPO = join(import.meta.dirname, "..", "..", "..", "..");
17
+ const validate = new Ajv2020({ strict: true, allErrors: true }).compile(schema);
18
+
19
+ function expectValid(doc: StatusDocument): void {
20
+ expect(validate(doc), JSON.stringify(validate.errors, null, 2)).toBe(true);
21
+ }
22
+
23
+ function result(doc: StatusDocument): Extract<StatusDocument, { members: unknown }> {
24
+ if ("error" in doc) throw new Error(`${doc.error.code}: ${doc.error.message}`);
25
+ return doc;
26
+ }
27
+
28
+ describe("the chant repo's own declaration", () => {
29
+ test("validates and lists every declared member", async () => {
30
+ const doc = result(await workspaceStatus({ cwd: join(REPO, "packages", "core"), env: "prod", compareTo: "staging" }));
31
+ expectValid(doc);
32
+ expect(doc.workspace).toMatchObject({ name: "chant", root: ".", file: "chant.workspace.json" });
33
+ const ls = listWorkspace({ cwd: REPO });
34
+ if ("error" in ls) throw new Error(ls.error.message);
35
+ expect(doc.members.map((m) => m.name)).toEqual(ls.members.map((m) => m.name));
36
+ });
37
+ });
@@ -7,13 +7,12 @@
7
7
  */
8
8
 
9
9
  import { execFileSync } from "node:child_process";
10
- import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
10
+ import { cpSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
11
11
  import { tmpdir } from "node:os";
12
12
  import { dirname, join } from "node:path";
13
13
  import Ajv2020 from "ajv/dist/2020";
14
14
  import { afterAll, describe, expect, test, vi } from "vitest";
15
15
  import type { CommandContext } from "../cli/registry";
16
- import { listWorkspace } from "./ls";
17
16
  import {
18
17
  formatStatus,
19
18
  runWorkspaceStatus,
@@ -35,8 +34,6 @@ import { runOpLocally } from "../op/local-executor";
35
34
  import { shellCmd } from "../op/activities/shell";
36
35
  import type { OpConfig } from "../op/types";
37
36
 
38
- const REPO = join(import.meta.dirname, "..", "..", "..", "..");
39
-
40
37
  const ajv = new Ajv2020({ strict: true, allErrors: true });
41
38
  const validate = ajv.compile(schema);
42
39
 
@@ -98,6 +95,22 @@ function lifecycle(root: string, files: Record<string, string>): string {
98
95
  return commit;
99
96
  }
100
97
 
98
+ /**
99
+ * Build a fixture repository once and hand each caller its own copy. A repo
100
+ * costs a dozen git processes; a file copy costs none, and a test may write
101
+ * to its copy without touching the next test's.
102
+ */
103
+ function fixture(build: () => string): () => string {
104
+ let template: string | undefined;
105
+ return () => {
106
+ template ??= build();
107
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-status-")));
108
+ scratch.push(root);
109
+ cpSync(template, root, { recursive: true });
110
+ return root;
111
+ };
112
+ }
113
+
101
114
  let n = 0;
102
115
  /** One ledger line. */
103
116
  function release(component: string, env: string, digest: string, gitSha: string, extra: Record<string, string> = {}): string {
@@ -121,7 +134,7 @@ const D = (c: string) => `sha256:${c.repeat(64)}`;
121
134
  const declaration = (members: unknown[]) => JSON.stringify({ name: "acme", schema: 1, members }, null, 2);
122
135
 
123
136
  /** web has moved to _members/ (#2538); api and the root member still read the flat ledger. */
124
- function twoLayouts(): string {
137
+ const twoLayouts = fixture(() => {
125
138
  const root = repo({
126
139
  "chant.workspace.json": declaration([
127
140
  { name: "site", dir: ".", kind: "other", because: "the root project" },
@@ -142,7 +155,7 @@ function twoLayouts(): string {
142
155
  "prod/releases.jsonl": jsonl(release("api", "prod", D("c"), "c".repeat(40))),
143
156
  });
144
157
  return root;
145
- }
158
+ });
146
159
 
147
160
  describe("status output schema", () => {
148
161
  test("is a valid draft 2020-12 document with the published $id", () => {
@@ -412,7 +425,7 @@ const NOW = "2026-09-24T00:00:00.000Z";
412
425
  * Each gate is in one state: approved, pending with part of a quorum,
413
426
  * expired, superseded, and an Op gate that records no environment.
414
427
  */
415
- function gated(): string {
428
+ const gated = fixture(() => {
416
429
  const root = twoLayouts();
417
430
  lifecycle(root, {
418
431
  "_members/web/staging/releases.jsonl": jsonl(release("web", "staging", D("a"), "a".repeat(40))),
@@ -437,7 +450,7 @@ function gated(): string {
437
450
  "_gates/nightly.jsonl": jsonl(pending("nightly", "confirm", "2026-09-23T00:00:00.000Z"), approval("nightly", "confirm", "dave", "2026-09-23T01:00:00.000Z")),
438
451
  });
439
452
  return root;
440
- }
453
+ });
441
454
 
442
455
  describe("gate state in chant workspace status --json (#2674)", () => {
443
456
  test("each member lists its gates with state, approvals, quorum and the approve line", async () => {
@@ -570,7 +583,7 @@ describe("stewards in chant workspace status --json (#2731)", () => {
570
583
  });
571
584
 
572
585
  /** A box member declaring one steward, local by default and fountain on fountain-k3d. */
573
- function stewarded(): string {
586
+ const stewarded = fixture(() => {
574
587
  const steward = {
575
588
  kind: "Chant::Steward",
576
589
  name: "box-steward",
@@ -594,7 +607,7 @@ describe("stewards in chant workspace status --json (#2731)", () => {
594
607
  });
595
608
  lifecycle(root, { "README": "ledger\n" });
596
609
  return root;
597
- }
610
+ });
598
611
 
599
612
  test("names the steward, its form in the environment asked for, its Ops, schedules, last runs and lease", async () => {
600
613
  const root = stewarded();
@@ -754,14 +767,3 @@ describe("stewards in chant workspace status --json (#2731)", () => {
754
767
  expect(doc.members[0].stewardReasons.map((r) => r.code)).toEqual(["stewards-unreadable"]);
755
768
  });
756
769
  });
757
-
758
- describe("chant workspace status on the chant repo (#2557)", () => {
759
- test("validates and lists every declared member", async () => {
760
- const doc = result(await workspaceStatus({ cwd: join(REPO, "packages", "core"), env: "prod", compareTo: "staging" }));
761
- expectValid(doc);
762
- expect(doc.workspace).toMatchObject({ name: "chant", root: ".", file: "chant.workspace.json" });
763
- const ls = listWorkspace({ cwd: REPO });
764
- if ("error" in ls) throw new Error(ls.error.message);
765
- expect(doc.members.map((m) => m.name)).toEqual(ls.members.map((m) => m.name));
766
- });
767
- });