@intentius/chant 0.100.0 → 0.101.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/build.d.ts +6 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/build-options.d.ts +2 -0
- package/dist/cli/build-options.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +8 -0
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +22 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/lexicon.d.ts +24 -1
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/plan-digest.d.ts +26 -5
- package/dist/lifecycle/plan-digest.d.ts.map +1 -1
- package/dist/lint/config.d.ts +4 -4
- package/dist/op/activities/activity-contracts.d.ts +1 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/propose-upgrade.d.ts +2 -0
- package/dist/op/activities/propose-upgrade.d.ts.map +1 -1
- package/dist/op/index.d.ts +1 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/serializer.d.ts +8 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/telemetry-attribution.d.ts +77 -0
- package/dist/telemetry-attribution.d.ts.map +1 -0
- package/dist/workspace/agent-cli.d.ts +83 -0
- package/dist/workspace/agent-cli.d.ts.map +1 -0
- package/dist/workspace/changes-cli.d.ts.map +1 -1
- package/dist/workspace/changes.d.ts +8 -1
- package/dist/workspace/changes.d.ts.map +1 -1
- package/dist/workspace/checks/links.d.ts +1 -0
- package/dist/workspace/checks/links.d.ts.map +1 -1
- package/dist/workspace/checks/live.d.ts +40 -0
- package/dist/workspace/checks/live.d.ts.map +1 -0
- package/dist/workspace/checks.d.ts +21 -2
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/compose-graph.d.ts +63 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/decide.d.ts +1 -1
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/declaration.d.ts +32 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +138 -3
- package/dist/workspace/export-cli.d.ts +12 -0
- package/dist/workspace/export-cli.d.ts.map +1 -0
- package/dist/workspace/export.d.ts +145 -0
- package/dist/workspace/export.d.ts.map +1 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/import.d.ts +73 -0
- package/dist/workspace/import.d.ts.map +1 -0
- package/dist/workspace/kinds.d.ts +6 -2
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/lineage-adopt-cli.d.ts +15 -0
- package/dist/workspace/lineage-adopt-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-adopt.d.ts +106 -0
- package/dist/workspace/lineage-adopt.d.ts.map +1 -0
- package/dist/workspace/lineage-check.d.ts +9 -2
- package/dist/workspace/lineage-check.d.ts.map +1 -1
- package/dist/workspace/lineage-cli.d.ts +6 -1
- package/dist/workspace/lineage-cli.d.ts.map +1 -1
- package/dist/workspace/lineage-hash-index.d.ts +110 -0
- package/dist/workspace/lineage-hash-index.d.ts.map +1 -0
- package/dist/workspace/lineage-init.d.ts +10 -0
- package/dist/workspace/lineage-init.d.ts.map +1 -1
- package/dist/workspace/lineage-lock.d.ts +147 -0
- package/dist/workspace/lineage-lock.d.ts.map +1 -1
- package/dist/workspace/lineage-migrations.d.ts +15 -3
- package/dist/workspace/lineage-migrations.d.ts.map +1 -1
- package/dist/workspace/lineage-provenance.d.ts +18 -0
- package/dist/workspace/lineage-provenance.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts +2 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.d.ts +30 -1
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/lineage-versions.d.ts +116 -0
- package/dist/workspace/lineage-versions.d.ts.map +1 -0
- package/dist/workspace/links.d.ts +31 -5
- package/dist/workspace/links.d.ts.map +1 -1
- package/dist/workspace/ls-generated.d.ts +37 -0
- package/dist/workspace/ls-generated.d.ts.map +1 -0
- package/dist/workspace/ls.d.ts +4 -0
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/nested-graph.d.ts +56 -0
- package/dist/workspace/nested-graph.d.ts.map +1 -0
- package/dist/workspace/nesting.d.ts +21 -0
- package/dist/workspace/nesting.d.ts.map +1 -0
- package/dist/workspace/pin-cli.d.ts +10 -0
- package/dist/workspace/pin-cli.d.ts.map +1 -0
- package/dist/workspace/pin-integrity.d.ts +51 -0
- package/dist/workspace/pin-integrity.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +26 -2
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-sessions.d.ts +7 -11
- package/dist/workspace/record-sessions.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +30 -1
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +5 -2
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +22 -4
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +43 -5
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/returns.d.ts +129 -0
- package/dist/workspace/returns.d.ts.map +1 -0
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/template-manifest.d.ts +11 -3
- package/dist/workspace/template-manifest.d.ts.map +1 -1
- package/dist/workspace/trust/attestor.d.ts +8 -0
- package/dist/workspace/trust/attestor.d.ts.map +1 -1
- package/dist/workspace/trust/dsse.d.ts +58 -0
- package/dist/workspace/trust/dsse.d.ts.map +1 -0
- package/dist/workspace/trust/evidence-cli.d.ts +66 -0
- package/dist/workspace/trust/evidence-cli.d.ts.map +1 -0
- package/dist/workspace/trust/evidence.d.ts +93 -0
- package/dist/workspace/trust/evidence.d.ts.map +1 -0
- package/dist/workspace/trust/policy.d.ts +54 -1
- package/dist/workspace/trust/policy.d.ts.map +1 -1
- package/dist/workspace/trust/provenance.d.ts +21 -1
- package/dist/workspace/trust/provenance.d.ts.map +1 -1
- package/dist/workspace/trust/rotation.d.ts +132 -0
- package/dist/workspace/trust/rotation.d.ts.map +1 -0
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/signers-cli.d.ts +49 -0
- package/dist/workspace/trust/signers-cli.d.ts.map +1 -0
- package/dist/workspace/trust/ssh-commit.d.ts +12 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
- package/dist/workspace/trust/test-repo.d.ts +13 -0
- package/dist/workspace/trust/test-repo.d.ts.map +1 -1
- package/dist/workspace/trust/verify.d.ts +15 -0
- package/dist/workspace/trust/verify.d.ts.map +1 -1
- package/dist/workspace/work-evidence.d.ts +1 -1
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/write-scope.d.ts +199 -0
- package/dist/workspace/write-scope.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/build.ts +8 -0
- package/src/cli/build-options.ts +4 -1
- package/src/cli/commands/build.ts +2 -0
- package/src/cli/commands/import-live.test.ts +69 -1
- package/src/cli/commands/import.ts +48 -22
- package/src/cli/handlers/fan-out.test.ts +6 -6
- package/src/cli/handlers/fan-out.ts +2 -1
- package/src/cli/handlers/graph.test.ts +42 -0
- package/src/cli/handlers/graph.ts +22 -0
- package/src/cli/handlers/operator.ts +1 -1
- package/src/cli/main.test.ts +32 -0
- package/src/cli/main.ts +103 -6
- package/src/cli/mcp/workspace-tools.test.ts +1 -1
- package/src/cli/mcp/workspace-tools.ts +33 -2
- package/src/cli/registry.ts +22 -0
- package/src/cli/serve-mcp-workspace.test.ts +1 -1
- package/src/codegen/release-wiring.test.ts +5 -1
- package/src/components/fan-out-output.test.ts +1 -1
- package/src/components/fan-out.test.ts +1 -1
- package/src/components/promote.test.ts +1 -1
- package/src/config.ts +12 -0
- package/src/content-digest.test.ts +2 -2
- package/src/lexicon.ts +25 -1
- package/src/lifecycle/gate-ledger.test.ts +14 -0
- package/src/lifecycle/gate-ledger.ts +2 -1
- package/src/lifecycle/plan-digest.test.ts +54 -3
- package/src/lifecycle/plan-digest.ts +38 -8
- package/src/op/activities/activity-contracts.ts +1 -0
- package/src/op/activities/propose-upgrade.ts +8 -5
- package/src/op/gate-approval.test.ts +17 -0
- package/src/op/gate.ts +3 -3
- package/src/op/index.ts +1 -1
- package/src/serializer.ts +9 -0
- package/src/telemetry-attribution.test.ts +91 -0
- package/src/telemetry-attribution.ts +145 -0
- package/src/workspace/agent-cli.ts +134 -0
- package/src/workspace/agent.schema.json +356 -0
- package/src/workspace/behold-kinds.test.ts +1 -1
- package/src/workspace/changes-cli.ts +5 -0
- package/src/workspace/changes.schema.json +179 -1
- package/src/workspace/changes.ts +58 -4
- package/src/workspace/check-live.test.ts +192 -0
- package/src/workspace/check.schema.json +64 -0
- package/src/workspace/checks/links.ts +23 -2
- package/src/workspace/checks/live.ts +113 -0
- package/src/workspace/checks.test.ts +2 -2
- package/src/workspace/checks.ts +20 -3
- package/src/workspace/compose-graph.test.ts +47 -0
- package/src/workspace/compose-graph.ts +120 -3
- package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +7 -1
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +5 -0
- package/src/workspace/declaration.schema.json +138 -3
- package/src/workspace/declaration.ts +106 -1
- package/src/workspace/declared-kinds.test.ts +33 -0
- package/src/workspace/evidence.schema.json +279 -0
- package/src/workspace/export-cli.ts +180 -0
- package/src/workspace/export-import.test.ts +282 -0
- package/src/workspace/export.ts +486 -0
- package/src/workspace/graph-cli.ts +47 -1
- package/src/workspace/graph-contract.test.ts +89 -4
- package/src/workspace/graph.schema.json +206 -1
- package/src/workspace/import.ts +325 -0
- package/src/workspace/kinds.test.ts +5 -5
- package/src/workspace/kinds.ts +25 -3
- package/src/workspace/lineage-adopt-cli.ts +103 -0
- package/src/workspace/lineage-adopt.test.ts +552 -0
- package/src/workspace/lineage-adopt.ts +452 -0
- package/src/workspace/lineage-check.ts +26 -5
- package/src/workspace/lineage-cli.ts +12 -2
- package/src/workspace/lineage-hash-index.ts +305 -0
- package/src/workspace/lineage-init.test.ts +9 -0
- package/src/workspace/lineage-init.ts +29 -9
- package/src/workspace/lineage-lock.ts +54 -0
- package/src/workspace/lineage-migrations.test.ts +27 -0
- package/src/workspace/lineage-migrations.ts +44 -14
- package/src/workspace/lineage-provenance.ts +40 -0
- package/src/workspace/lineage-upgrade-cli.ts +9 -5
- package/src/workspace/lineage-upgrade.test.ts +92 -1
- package/src/workspace/lineage-upgrade.ts +111 -16
- package/src/workspace/lineage-versions.ts +348 -0
- package/src/workspace/links.test.ts +121 -2
- package/src/workspace/links.ts +97 -6
- package/src/workspace/ls-contract.test.ts +84 -1
- package/src/workspace/ls-generated.ts +111 -0
- package/src/workspace/ls.schema.json +19 -0
- package/src/workspace/ls.ts +8 -1
- package/src/workspace/member-commands.ts +3 -1
- package/src/workspace/nested-graph.test.ts +176 -0
- package/src/workspace/nested-graph.ts +169 -0
- package/src/workspace/nesting.ts +37 -0
- package/src/workspace/pin-cli.test.ts +71 -0
- package/src/workspace/pin-cli.ts +57 -0
- package/src/workspace/pin-integrity.test.ts +121 -0
- package/src/workspace/pin-integrity.ts +104 -0
- package/src/workspace/points-write.schema.json +1 -0
- package/src/workspace/read-contract.test.ts +24 -0
- package/src/workspace/reason-codes.test.ts +10 -0
- package/src/workspace/reason-codes.ts +29 -2
- package/src/workspace/record-sessions.ts +12 -14
- package/src/workspace/records-amend.schema.json +4 -0
- package/src/workspace/records-cli.ts +93 -12
- package/src/workspace/records-close.schema.json +6 -2
- package/src/workspace/records-close.ts +10 -2
- package/src/workspace/records-formats.test.ts +13 -5
- package/src/workspace/records-new.schema.json +4 -0
- package/src/workspace/records-review.schema.json +4 -0
- package/src/workspace/records-sessions.test.ts +23 -6
- package/src/workspace/records-write.test.ts +65 -8
- package/src/workspace/records-write.ts +70 -6
- package/src/workspace/records.schema.json +55 -3
- package/src/workspace/records.ts +96 -5
- package/src/workspace/returns.ts +328 -0
- package/src/workspace/signers.schema.json +206 -0
- package/src/workspace/status-gates.ts +2 -1
- package/src/workspace/template-manifest.ts +22 -4
- package/src/workspace/trust/attestor.ts +15 -0
- package/src/workspace/trust/dsse.ts +134 -0
- package/src/workspace/trust/evidence-cli.ts +195 -0
- package/src/workspace/trust/evidence.test.ts +241 -0
- package/src/workspace/trust/evidence.ts +207 -0
- package/src/workspace/trust/policy.ts +110 -3
- package/src/workspace/trust/provenance.ts +41 -4
- package/src/workspace/trust/record-seal.test.ts +1 -1
- package/src/workspace/trust/rotation.test.ts +258 -0
- package/src/workspace/trust/rotation.ts +336 -0
- package/src/workspace/trust/seal.ts +11 -0
- package/src/workspace/trust/signers-cli.ts +178 -0
- package/src/workspace/trust/ssh-commit.ts +53 -4
- package/src/workspace/trust/test-repo.ts +18 -0
- package/src/workspace/trust/trust.test.ts +8 -2
- package/src/workspace/trust/verify-cli.ts +1 -0
- package/src/workspace/trust/verify.ts +22 -0
- package/src/workspace/work-evidence.schema.json +4 -0
- package/src/workspace/write-scope.test.ts +340 -0
- package/src/workspace/write-scope.ts +448 -0
|
@@ -204,8 +204,8 @@ describe("proof: a plan-only invocation prints the derivation and dispatches not
|
|
|
204
204
|
expect(out).toContain("wave 1: cluster-a");
|
|
205
205
|
expect(out).toContain("seeded from an earlier run: net");
|
|
206
206
|
expect(out).toMatch(/billing +unaffected/);
|
|
207
|
-
expect(out).toMatch(/plan: sha256:[0-9a-f]{64}/);
|
|
208
|
-
expect(out).toMatch(/approve: chant approve fan-out release --plan sha256:[0-9a-f]{64}/);
|
|
207
|
+
expect(out).toMatch(/plan: jcs1-sha256:[0-9a-f]{64}/);
|
|
208
|
+
expect(out).toMatch(/approve: chant approve fan-out release --plan jcs1-sha256:[0-9a-f]{64}/);
|
|
209
209
|
});
|
|
210
210
|
|
|
211
211
|
test("--dry-run with --resume shows what is left, at the digest that approves it", async () => {
|
|
@@ -214,7 +214,7 @@ describe("proof: a plan-only invocation prints the derivation and dispatches not
|
|
|
214
214
|
const resume = join(dir, "attempt.json");
|
|
215
215
|
failing = ["cluster-a"];
|
|
216
216
|
await runComponentsFanOut(ctx({ base: "main", env: "test", resume }));
|
|
217
|
-
const digest = /plan: (sha256:[0-9a-f]{64})/.exec(stderr())?.[1];
|
|
217
|
+
const digest = /plan: (jcs1-sha256:[0-9a-f]{64})/.exec(stderr())?.[1];
|
|
218
218
|
|
|
219
219
|
ran = [];
|
|
220
220
|
stderrLines = [];
|
|
@@ -236,7 +236,7 @@ describe("proof: a plan-only invocation prints the derivation and dispatches not
|
|
|
236
236
|
expect(exit).toBe(0);
|
|
237
237
|
const parsed = JSON.parse(stdout());
|
|
238
238
|
expect(parsed.order).toEqual(["net", "cluster-a", "cluster-b", "app-one", "app-three", "app-two"]);
|
|
239
|
-
expect(parsed.digest).toMatch(/^sha256:[0-9a-f]{64}$/);
|
|
239
|
+
expect(parsed.digest).toMatch(/^jcs1-sha256:[0-9a-f]{64}$/);
|
|
240
240
|
});
|
|
241
241
|
});
|
|
242
242
|
|
|
@@ -334,10 +334,10 @@ describe("proof: one approval covers the set, and the printed digest is what app
|
|
|
334
334
|
expect(ran).toEqual([]);
|
|
335
335
|
const out = stderr();
|
|
336
336
|
expect(out).toContain("gated: nothing ran.");
|
|
337
|
-
expect(out).toMatch(/approve : chant approve fan-out release --plan sha256:[0-9a-f]{64}/);
|
|
337
|
+
expect(out).toMatch(/approve : chant approve fan-out release --plan jcs1-sha256:[0-9a-f]{64}/);
|
|
338
338
|
// One pending fact for the whole set, not one per component.
|
|
339
339
|
expect(gateLedger.appended).toHaveLength(1);
|
|
340
|
-
expect(gateLedger.appended[0].planDigest).toBe(/plan: (sha256:[0-9a-f]{64})/.exec(out)?.[1]);
|
|
340
|
+
expect(gateLedger.appended[0].planDigest).toBe(/plan: (jcs1-sha256:[0-9a-f]{64})/.exec(out)?.[1]);
|
|
341
341
|
});
|
|
342
342
|
|
|
343
343
|
test("the resolution for that digest lets the whole set through", async () => {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
import { resolve, dirname } from "node:path";
|
|
39
39
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
40
40
|
import { loadChantConfig, resolveAutoReleaseDisabled, type ChantConfig } from "../../config";
|
|
41
|
+
import { samePlanDigest } from "../../lifecycle/plan-digest";
|
|
41
42
|
import { affectedStacks } from "../../lifecycle/affected";
|
|
42
43
|
import { deriveFanOut, fanOutRegistry } from "../../components/fan-out-support";
|
|
43
44
|
import { runFanOut } from "../../components/fan-out-run";
|
|
@@ -242,7 +243,7 @@ export async function runComponentsFanOut(ctx: CommandContext): Promise<number>
|
|
|
242
243
|
console.error(formatError({ message: `--resume: could not read "${args.resume}": ${err instanceof Error ? err.message : String(err)}` }));
|
|
243
244
|
return 1;
|
|
244
245
|
}
|
|
245
|
-
if (attempt && attempt.digest
|
|
246
|
+
if (attempt && !samePlanDigest(attempt.digest, derived.plan.digest)) {
|
|
246
247
|
console.error(formatWarning({
|
|
247
248
|
message: `--resume: "${args.resume}" records a different fan-out (${attempt.digest || "no digest"}), so its progress does not apply here`,
|
|
248
249
|
hint: "The derivation changed, which makes this a different change. Everything selected will run.",
|
|
@@ -161,6 +161,7 @@ describe("runGraph", () => {
|
|
|
161
161
|
observeMock.mockReset();
|
|
162
162
|
buildMock.mockReset();
|
|
163
163
|
loadPluginsMock.mockReset();
|
|
164
|
+
loadPluginsMock.mockResolvedValue([]);
|
|
164
165
|
resolveLexMock.mockReset();
|
|
165
166
|
loadChantConfigMock.mockReset();
|
|
166
167
|
loadChantConfigMock.mockResolvedValue({ config: {} });
|
|
@@ -228,6 +229,47 @@ describe("runGraph", () => {
|
|
|
228
229
|
expect(ir.edges).toContainEqual({ from: "subnet", to: "vpc", kind: "ref", viaAttr: "network" });
|
|
229
230
|
});
|
|
230
231
|
|
|
232
|
+
// #2559 — a lexicon's graphMeta answer lands in the IR meta, so the
|
|
233
|
+
// workspace graph can list collector pipelines without core importing otel.
|
|
234
|
+
test("--format ir carries what a lexicon's graphMeta reports under meta", async () => {
|
|
235
|
+
lintClean(); discovered();
|
|
236
|
+
const seen: string[][] = [];
|
|
237
|
+
loadPluginsMock.mockResolvedValue([{
|
|
238
|
+
name: "gcp",
|
|
239
|
+
serializer: {},
|
|
240
|
+
graphMeta: (entities: Map<string, unknown>) => {
|
|
241
|
+
seen.push([...entities.keys()].sort());
|
|
242
|
+
return { collector: { pipelines: [] } };
|
|
243
|
+
},
|
|
244
|
+
}]);
|
|
245
|
+
resolveLexMock.mockResolvedValue(["gcp"]);
|
|
246
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir" }), plugins: [], serializers: [] });
|
|
247
|
+
expect(exit).toBe(0);
|
|
248
|
+
expect(seen).toEqual([["pod", "subnet", "vpc"]]);
|
|
249
|
+
expect(JSON.parse(stdoutBuf.join("\n")).meta).toEqual({ collector: { pipelines: [] } });
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("--format ir has no meta when no lexicon reports anything", async () => {
|
|
253
|
+
lintClean(); discovered();
|
|
254
|
+
loadPluginsMock.mockResolvedValue([{ name: "gcp", serializer: {}, graphMeta: () => undefined }, { name: "k8s", serializer: {} }]);
|
|
255
|
+
resolveLexMock.mockResolvedValue(["gcp", "k8s"]);
|
|
256
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir" }), plugins: [], serializers: [] });
|
|
257
|
+
expect(exit).toBe(0);
|
|
258
|
+
expect("meta" in JSON.parse(stdoutBuf.join("\n"))).toBe(false);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("--format ir keeps the first answer when two lexicons report one key", async () => {
|
|
262
|
+
lintClean(); discovered();
|
|
263
|
+
loadPluginsMock.mockResolvedValue([
|
|
264
|
+
{ name: "a", serializer: {}, graphMeta: () => ({ collector: 1 }) },
|
|
265
|
+
{ name: "b", serializer: {}, graphMeta: () => ({ collector: 2 }) },
|
|
266
|
+
]);
|
|
267
|
+
resolveLexMock.mockResolvedValue(["a", "b"]);
|
|
268
|
+
const exit = await runGraph({ args: makeArgs({ format: "ir" }), plugins: [], serializers: [] });
|
|
269
|
+
expect(exit).toBe(0);
|
|
270
|
+
expect(JSON.parse(stdoutBuf.join("\n")).meta).toEqual({ collector: 1 });
|
|
271
|
+
});
|
|
272
|
+
|
|
231
273
|
// #2529 — the IR carries its version, as the first key, so a reader can
|
|
232
274
|
// decide how to read the rest before it reads anything else.
|
|
233
275
|
test("--format ir leads with the IR version", async () => {
|
|
@@ -702,6 +702,27 @@ async function buildPipelineProjection(
|
|
|
702
702
|
* Lint-gated: the IR represents valid infra, so we refuse to emit for source that
|
|
703
703
|
* does not pass lint. Non-zero on discovery errors or a layout-engine failure.
|
|
704
704
|
*/
|
|
705
|
+
/**
|
|
706
|
+
* Add what the lexicons' `graphMeta` hooks report to `ir.meta` (#2559). A
|
|
707
|
+
* lexicon with nothing to report leaves the IR as it was, so a project that
|
|
708
|
+
* declares none of their entities prints what it did before. Two lexicons
|
|
709
|
+
* that answer the same key keep the first and say so on stderr.
|
|
710
|
+
*/
|
|
711
|
+
function withGraphMeta(ir: GraphIR, plugins: LexiconPlugin[], entities: Map<string, import("../../declarable").Declarable>): GraphIR {
|
|
712
|
+
const added: Record<string, unknown> = {};
|
|
713
|
+
for (const p of plugins) {
|
|
714
|
+
if (!p.graphMeta) continue;
|
|
715
|
+
for (const [key, value] of Object.entries(p.graphMeta(entities) ?? {})) {
|
|
716
|
+
if (key in added || (ir.meta && key in ir.meta)) {
|
|
717
|
+
console.error(formatWarning({ message: `the ${p.name} lexicon reports graph meta "${key}", which another source already set; its value is dropped` }));
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
added[key] = value;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return Object.keys(added).length === 0 ? ir : { ...ir, meta: { ...(ir.meta ?? {}), ...added } };
|
|
724
|
+
}
|
|
725
|
+
|
|
705
726
|
/** Returned instead of a graph when the prediction cannot be attempted honestly. */
|
|
706
727
|
const REFUSED = Symbol("behaviour-refused");
|
|
707
728
|
|
|
@@ -855,6 +876,7 @@ async function runGraphView(
|
|
|
855
876
|
});
|
|
856
877
|
if (predicted === REFUSED) return 1;
|
|
857
878
|
ir = predicted;
|
|
879
|
+
ir = withGraphMeta(ir, plugins as LexiconPlugin[], result.entities);
|
|
858
880
|
}
|
|
859
881
|
|
|
860
882
|
if (ctx.args.lens) {
|
|
@@ -1047,7 +1047,7 @@ export async function recordGateApproval(
|
|
|
1047
1047
|
if (opts.plan !== undefined) {
|
|
1048
1048
|
if (!isPlanDigest(opts.plan)) {
|
|
1049
1049
|
console.error(formatError({
|
|
1050
|
-
message: `--plan must be a plan digest ("sha256:"
|
|
1050
|
+
message: `--plan must be a plan digest ("jcs1-sha256:" or "sha256:", then 64 hex characters), got "${opts.plan}"`,
|
|
1051
1051
|
hint: "Copy it from the gated run's `plan :` line, or from the pending-gate summary. It is not a plan file path.",
|
|
1052
1052
|
}));
|
|
1053
1053
|
return { ok: false };
|
package/src/cli/main.test.ts
CHANGED
|
@@ -747,25 +747,57 @@ describe("workspace init and ls (#2534)", () => {
|
|
|
747
747
|
expect(resolveCommand(init, commandRegistry)?.def.name).toBe("workspace init");
|
|
748
748
|
expect(commandRegistry.filter((c) => c.name === "workspace" || c.name.startsWith("workspace ")).map((c) => c.name).sort()).toEqual([
|
|
749
749
|
"workspace",
|
|
750
|
+
"workspace admit",
|
|
751
|
+
"workspace adopt-lineage",
|
|
752
|
+
"workspace agent",
|
|
750
753
|
"workspace audit",
|
|
751
754
|
"workspace build",
|
|
752
755
|
"workspace check",
|
|
756
|
+
"workspace evidence",
|
|
757
|
+
"workspace export",
|
|
753
758
|
"workspace graph",
|
|
759
|
+
"workspace hash-index",
|
|
760
|
+
"workspace import",
|
|
754
761
|
"workspace init",
|
|
755
762
|
"workspace lineage",
|
|
756
763
|
"workspace lint",
|
|
757
764
|
"workspace ls",
|
|
758
765
|
"workspace member-run",
|
|
759
766
|
"workspace patch",
|
|
767
|
+
"workspace pin",
|
|
760
768
|
"workspace points",
|
|
761
769
|
"workspace records",
|
|
770
|
+
"workspace signers",
|
|
762
771
|
"workspace status",
|
|
763
772
|
"workspace upgrade",
|
|
764
773
|
"workspace verify",
|
|
774
|
+
"workspace versions",
|
|
765
775
|
"workspace work",
|
|
766
776
|
]);
|
|
767
777
|
});
|
|
768
778
|
|
|
779
|
+
test("adopt-lineage, hash-index, versions and upgrade --source take their flags (#2551)", () => {
|
|
780
|
+
const adopt = parseArgs(["workspace", "adopt-lineage", "app", "--from", "acme/starter#svc", "--tags", "v*", "--index", "i.json", "--param", "name=x", "--dry-run"]);
|
|
781
|
+
expect(adopt).toMatchObject({ command: "workspace", path: "adopt-lineage", extraPositional: "app", migrateFrom: "acme/starter#svc", tags: "v*", index: "i.json", param: ["name=x"], dryRun: true });
|
|
782
|
+
expect(resolveCommand(adopt, commandRegistry)?.def.name).toBe("workspace adopt-lineage");
|
|
783
|
+
expect(parseArgs(["workspace", "versions", "--available", "--template", "x"])).toMatchObject({ path: "versions", available: true, template: "x" });
|
|
784
|
+
expect(parseArgs(["workspace", "upgrade", "--source", "acme/upstream", "--to", "v2.0.0"])).toMatchObject({ source: "acme/upstream", migrateTo: "v2.0.0" });
|
|
785
|
+
expect(() => parseArgs(["workspace", "adopt-lineage", "--tags"])).toThrow(/--tags needs a tag glob/);
|
|
786
|
+
expect(() => parseArgs(["workspace", "upgrade", "--source", "--to"])).toThrow(/--source needs a template/);
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
test("export, import and admit take their arguments (#2552)", () => {
|
|
790
|
+
const exp = parseArgs(["workspace", "export", "app,design", "--to", "out", "--param", "domain=example.com", "--dry-run", "--json"]);
|
|
791
|
+
expect(exp).toMatchObject({ command: "workspace", path: "export", extraPositional: "app,design", migrateTo: "out", param: ["domain=example.com"], dryRun: true, json: true });
|
|
792
|
+
expect(resolveCommand(exp, commandRegistry)?.def.name).toBe("workspace export");
|
|
793
|
+
const imp = parseArgs(["workspace", "import", "../copy", "--remove"]);
|
|
794
|
+
expect(imp).toMatchObject({ path: "import", extraPositional: "../copy", remove: true });
|
|
795
|
+
expect(resolveCommand(imp, commandRegistry)?.def.name).toBe("workspace import");
|
|
796
|
+
const admit = parseArgs(["workspace", "admit", "ret-0123456789ab", "--note", "returned from the studio"]);
|
|
797
|
+
expect(admit).toMatchObject({ path: "admit", extraPositional: "ret-0123456789ab", note: "returned from the studio" });
|
|
798
|
+
expect(resolveCommand(admit, commandRegistry)?.def.name).toBe("workspace admit");
|
|
799
|
+
});
|
|
800
|
+
|
|
769
801
|
test("workspace work takes its verb, id and lease flags (#2732)", () => {
|
|
770
802
|
const claim = parseArgs(["workspace", "work", "claim", "W-001", "--holder", "a", "--ttl", "60", "--json"]);
|
|
771
803
|
expect(claim).toMatchObject({ command: "workspace", path: "work", extraPositional: "claim", extraPositional2: "W-001", holder: "a", ttl: "60", json: true });
|
package/src/cli/main.ts
CHANGED
|
@@ -456,6 +456,12 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
456
456
|
// `chant workspace records new <kind> --prefix <prefix>` (#2670): the id prefix to allocate under.
|
|
457
457
|
result.prefix = args[++i];
|
|
458
458
|
if (!result.prefix || result.prefix.startsWith("-")) throw new Error("--prefix needs an id prefix: --prefix <prefix>");
|
|
459
|
+
} else if (arg === "--key" || arg === "--check-id" || arg === "--claim" || arg === "--environment" || arg === "--envelope" || arg === "--threshold") {
|
|
460
|
+
// `chant workspace evidence sign|verify` and `chant workspace signers` (#2553)
|
|
461
|
+
const value = args[++i];
|
|
462
|
+
if (value === undefined || value.startsWith("-")) throw new Error(`${arg} needs a value`);
|
|
463
|
+
const field = ({ "--key": "key", "--check-id": "checkId", "--claim": "claim", "--environment": "environment", "--envelope": "envelope", "--threshold": "threshold" } as const)[arg];
|
|
464
|
+
result[field] = value;
|
|
459
465
|
} else if (arg === "--require") {
|
|
460
466
|
// `chant workspace records|verify --require attested` (#2547)
|
|
461
467
|
result.require = args[++i];
|
|
@@ -576,6 +582,24 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
576
582
|
// #2550 — `chant workspace upgrade` runs a template's code migrations
|
|
577
583
|
// only when asked to.
|
|
578
584
|
result.allowCode = true;
|
|
585
|
+
} else if (arg === "--source") {
|
|
586
|
+
// #2551 — `chant workspace upgrade --source <repo>[#<member>]`.
|
|
587
|
+
result.source = args[++i];
|
|
588
|
+
if (!result.source || result.source.startsWith("-")) throw new Error("--source needs a template: --source <repo>[#<member>]");
|
|
589
|
+
} else if (arg === "--tags") {
|
|
590
|
+
// #2551 — `chant workspace adopt-lineage --tags <glob>`, also hash-index and versions.
|
|
591
|
+
result.tags = args[++i];
|
|
592
|
+
if (!result.tags || result.tags.startsWith("-")) throw new Error("--tags needs a tag glob: --tags 'v*'");
|
|
593
|
+
} else if (arg === "--index") {
|
|
594
|
+
// #2551 — `chant workspace adopt-lineage --index <file>`.
|
|
595
|
+
result.index = args[++i];
|
|
596
|
+
if (!result.index || result.index.startsWith("-")) throw new Error("--index needs a hash index file: --index <file>");
|
|
597
|
+
} else if (arg === "--remove") {
|
|
598
|
+
// #2552 — `chant workspace import --remove` removes the export member instead of writing it again.
|
|
599
|
+
result.remove = true;
|
|
600
|
+
} else if (arg === "--available") {
|
|
601
|
+
// #2551 — `chant workspace versions --available` lists the template's version tags.
|
|
602
|
+
result.available = true;
|
|
579
603
|
} else if (arg === "--root-only") {
|
|
580
604
|
// #2537 — `chant build` and `chant lint` at a declared workspace root
|
|
581
605
|
// run on the root project alone instead of refusing with WSP000.
|
|
@@ -909,24 +933,77 @@ Workspace (level 1, #2524):
|
|
|
909
933
|
workspace points answer <id> --answer <value> --by <name>... [--kind <kind file>] [--dry-run]
|
|
910
934
|
Record people's answer to an open question, or confirm
|
|
911
935
|
a model's proposal, once the point's quorum is met
|
|
936
|
+
workspace pin <path> [--json]
|
|
937
|
+
Print the integrity value that pins the plugin at
|
|
938
|
+
<path>, to put in a path pin of chant.workspace.json.
|
|
939
|
+
chant checks it before it reads the plugin
|
|
940
|
+
workspace agent [<name>] [--json]
|
|
941
|
+
Print an agent session from the declaration: the one
|
|
942
|
+
member it is bound to, the record kinds and verbs its
|
|
943
|
+
write scope allows, and the spec records --current
|
|
944
|
+
prints. Without a name, the session CHANT_AGENT names
|
|
912
945
|
workspace verify [--base <rev>] [--head <rev>] [--require attested]
|
|
913
946
|
Check the commits in base..head against the signers
|
|
914
947
|
and roles read from base. A change to the signers file
|
|
915
948
|
or .chant/trust.json needs a signature by a signer
|
|
916
|
-
trusted at base
|
|
949
|
+
trusted at base, and a new signer set needs a
|
|
950
|
+
threshold of the old one. Does nothing without a
|
|
951
|
+
signers file
|
|
952
|
+
workspace signers [--json] [rotate [--threshold <n>] | sign --key <file>]
|
|
953
|
+
Show the signer history at base, or propose the next
|
|
954
|
+
signer set and sign it with a current signer's key
|
|
955
|
+
workspace evidence sign --kind <kind file> --key <runner.pem> --check-id <id>
|
|
956
|
+
Sign runner evidence over the records' hashes in a
|
|
957
|
+
DSSE envelope, with a runner key .chant/trust.json
|
|
958
|
+
lists. For CI and services, never a person's key
|
|
959
|
+
workspace evidence verify --envelope <file> [--at <rev>] [--json]
|
|
960
|
+
Verify runner evidence offline, and whether the
|
|
961
|
+
records it covers are unchanged
|
|
917
962
|
workspace lineage [--json]
|
|
918
963
|
Show each scope in .chant/workspace.lock.json: its
|
|
919
964
|
template and pin, locally edited files and open
|
|
920
965
|
manual steps. Needs no workspace file
|
|
921
966
|
workspace lineage resolve <path>
|
|
922
967
|
Close a manual step once the file is merged by hand
|
|
923
|
-
workspace upgrade [<scope>] [--to <ref|dir>] [--allow-code] [--dry-run] [--output <file>]
|
|
968
|
+
workspace upgrade [<scope>] [--to <ref|dir>] [--source <repo>[#<member>]] [--allow-code] [--dry-run] [--output <file>]
|
|
924
969
|
Bring a lineage scope to a newer template version: fetch
|
|
925
970
|
it, migrate and merge per file in a worktree, run build,
|
|
926
971
|
lint and workspace check there, then gate on the digest
|
|
927
972
|
of the patch (chant approve workspace-upgrade <scope>).
|
|
928
|
-
A second run with the approval applies the patch
|
|
929
|
-
|
|
973
|
+
A second run with the approval applies the patch.
|
|
974
|
+
--source <repo>[#<member>] moves the scope to another
|
|
975
|
+
template through that template's bridge migration
|
|
976
|
+
workspace adopt-lineage [<scope>] --from <repo>[@<ref>][#<member>] [--tags <glob>]
|
|
977
|
+
[--index <file>] [--param name=value] [--dry-run] [--json]
|
|
978
|
+
Give a scope a git lineage: match a scope with none
|
|
979
|
+
against the template's versions and record the best
|
|
980
|
+
one, with the adopted commit range in .chant/trust.json,
|
|
981
|
+
or move a directory lineage onto the version that
|
|
982
|
+
reproduces its recorded files
|
|
983
|
+
workspace hash-index --from <repo>[#<member>] [--tags <glob>] [--output <file>]
|
|
984
|
+
Compute the per-version file hashes adopt-lineage
|
|
985
|
+
matches against, for a template's CI to publish
|
|
986
|
+
workspace versions [<dir>] [--template <id>] [--available [--tags <glob>]] [--json]
|
|
987
|
+
Report the template, chant and lexicon versions of
|
|
988
|
+
every lineage lock under <dir>, grouped into families
|
|
989
|
+
by template; --available also lists each git
|
|
990
|
+
template's version tags and where each lock sits
|
|
991
|
+
workspace export [<member>[,<member>...]] [--to <member>] [--param name=value] [--dry-run] [--json]
|
|
992
|
+
Write members that set travel, with their lineage and
|
|
993
|
+
records, into the export member (role export) as a
|
|
994
|
+
workspace of their own; with no member named, every
|
|
995
|
+
member that travels and the workspace's own records.
|
|
996
|
+
--param gives a host-bound parameter its value there
|
|
997
|
+
workspace import [<dir>] [--remove] [--dry-run] [--json]
|
|
998
|
+
Bring an export back: merge per file against the
|
|
999
|
+
export's hashes, switch host values back, record the
|
|
1000
|
+
return in .chant/returns/<id>.json with the commits the
|
|
1001
|
+
files were made in, then write the export member again
|
|
1002
|
+
or, with --remove, remove it
|
|
1003
|
+
workspace admit <return id> [--note <text>] [--dry-run] [--json]
|
|
1004
|
+
Admit the signers of a return in .chant/trust.json, so
|
|
1005
|
+
its signed work reads as attested once merged
|
|
1006
|
+
workspace check [--at <rev>] [--json] [--format stylish|json|sarif] [--generated] [--kind <kind file>] [--live --env <env>]
|
|
930
1007
|
Fail on an unreadable lineage lock or an open manual
|
|
931
1008
|
step, and, in a declared workspace, on a WSP check of
|
|
932
1009
|
the declaration, member ledgers, pipelines or
|
|
@@ -934,14 +1011,21 @@ Workspace (level 1, #2524):
|
|
|
934
1011
|
and compares their output. --kind warns on records
|
|
935
1012
|
whose pinned files changed. Needs no workspace file.
|
|
936
1013
|
--at reads a commit's git objects; --format json
|
|
937
|
-
prints the read-contract document
|
|
1014
|
+
prints the read-contract document. --live --env
|
|
1015
|
+
resolves each declared member link against the live
|
|
1016
|
+
graph of <env> (each chant member runs chant graph
|
|
1017
|
+
--live, which reaches the account) and reports
|
|
1018
|
+
WSP141 and WSP142
|
|
938
1019
|
workspace check --changes <base>..<head> [--work <id>] [--severity off|warn|fail] [--kind <kind file>...] [--json]
|
|
939
1020
|
Map each path the diff changes to the current records
|
|
940
1021
|
whose constrains cover it: change-uncovered when none
|
|
941
1022
|
does, change-out-of-scope when a record in hand (the
|
|
942
1023
|
--work item and its decisions) lists it in
|
|
943
1024
|
out_of_scope. The declaration's changes block sets
|
|
944
|
-
the severity (warn by default) and ignore globs
|
|
1025
|
+
the severity (warn by default) and ignore globs. When
|
|
1026
|
+
the declaration at base has writeScope or agents, each
|
|
1027
|
+
commit that writes outside its writer's scope fails
|
|
1028
|
+
the check (write-scope-member, write-scope-kind)
|
|
945
1029
|
workspace patch <base>..<head>|<base>...<head>|<commit> [--path <p>...] [--max-bytes <n>] [--json]
|
|
946
1030
|
workspace patch [<commit>] --worktree [--path <p>...] [--max-bytes <n>] [--json]
|
|
947
1031
|
The hunks of a diff, file by file: a range, a work
|
|
@@ -1471,6 +1555,14 @@ export const commandRegistry: CommandDef[] = [
|
|
|
1471
1555
|
{ name: "workspace status", handler: async (ctx) => (await import("../workspace/status")).runWorkspaceStatus(ctx) },
|
|
1472
1556
|
{ name: "workspace lineage", handler: async (ctx) => (await import("../workspace/lineage-cli")).runWorkspaceLineage(ctx) },
|
|
1473
1557
|
{ name: "workspace upgrade", handler: async (ctx) => (await import("../workspace/lineage-upgrade-cli")).runWorkspaceUpgrade(ctx) },
|
|
1558
|
+
// #2551 — adopt a lineage, the hash index it matches against, and the versions of a family of workspaces.
|
|
1559
|
+
{ name: "workspace adopt-lineage", handler: async (ctx) => (await import("../workspace/lineage-adopt-cli")).runWorkspaceAdoptLineage(ctx) },
|
|
1560
|
+
{ name: "workspace hash-index", handler: async (ctx) => (await import("../workspace/lineage-adopt-cli")).runWorkspaceHashIndex(ctx) },
|
|
1561
|
+
// #2552 — export a workspace or members, bring an export back, and admit a return's signers.
|
|
1562
|
+
{ name: "workspace export", handler: async (ctx) => (await import("../workspace/export-cli")).runWorkspaceExport(ctx) },
|
|
1563
|
+
{ name: "workspace import", handler: async (ctx) => (await import("../workspace/export-cli")).runWorkspaceImport(ctx) },
|
|
1564
|
+
{ name: "workspace admit", handler: async (ctx) => (await import("../workspace/export-cli")).runWorkspaceAdmit(ctx) },
|
|
1565
|
+
{ name: "workspace versions", handler: async (ctx) => (await import("../workspace/lineage-versions")).runWorkspaceVersions(ctx) },
|
|
1474
1566
|
// #2641 — workspace check reads member configs statically and never runs one.
|
|
1475
1567
|
{ name: "workspace check", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/lineage-check")).runWorkspaceCheck(ctx) },
|
|
1476
1568
|
// The hunks of a diff, for a reader that runs no git of its own.
|
|
@@ -1484,7 +1576,12 @@ export const commandRegistry: CommandDef[] = [
|
|
|
1484
1576
|
{ name: "workspace graph", handler: async (ctx) => (await import("../workspace/member-commands")).runWorkspaceMembers(ctx, "graph") },
|
|
1485
1577
|
// Each unit decides for itself whether its config is loaded (runCommandInProcess).
|
|
1486
1578
|
{ name: "workspace member-run", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/member-run")).runWorkspaceMemberRun(ctx, runCommandInProcess) },
|
|
1579
|
+
{ name: "workspace pin", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/pin-cli")).runWorkspacePin(ctx) },
|
|
1580
|
+
// #2548 — an agent session's member, write scope and spec, for a session that resumes.
|
|
1581
|
+
{ name: "workspace agent", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/agent-cli")).runWorkspaceAgent(ctx) },
|
|
1487
1582
|
{ name: "workspace verify", handler: async (ctx) => (await import("../workspace/trust/verify-cli")).runWorkspaceVerify(ctx) },
|
|
1583
|
+
{ name: "workspace signers", handler: async (ctx) => (await import("../workspace/trust/signers-cli")).runWorkspaceSigners(ctx) },
|
|
1584
|
+
{ name: "workspace evidence", handler: async (ctx) => (await import("../workspace/trust/evidence-cli")).runWorkspaceEvidence(ctx) },
|
|
1488
1585
|
|
|
1489
1586
|
// State subcommands
|
|
1490
1587
|
{ name: "lifecycle snapshot", requiresPlugins: true, handler: runLifecycleSnapshot },
|
|
@@ -85,7 +85,7 @@ describe("which servers have the workspace tools", () => {
|
|
|
85
85
|
const { tools } = (await rpc(server(cwd), "tools/list")) as { tools: { name: string }[] };
|
|
86
86
|
expect(tools.map((t) => t.name)).toEqual(expect.arrayContaining(names));
|
|
87
87
|
}
|
|
88
|
-
expect(names).toEqual(["workspace-ls", "workspace-status", "workspace-graph", "workspace-changes", "workspace-records", "workspace-points", "workspace-work-history", "records-new", "records-amend", "records-review", "records-close", "points-answer"]);
|
|
88
|
+
expect(names).toEqual(["workspace-ls", "workspace-status", "workspace-graph", "workspace-changes", "workspace-records", "workspace-points", "workspace-agent", "workspace-work-history", "records-new", "records-amend", "records-review", "records-close", "points-answer"]);
|
|
89
89
|
});
|
|
90
90
|
|
|
91
91
|
test("a server outside any workspace, or given none, does not", async () => {
|
|
@@ -28,11 +28,20 @@
|
|
|
28
28
|
*
|
|
29
29
|
* Nothing here listens on a port or authenticates anyone (ws-052): the server
|
|
30
30
|
* speaks over stdio, and `by` is recorded as given, as `--by` is.
|
|
31
|
+
*
|
|
32
|
+
* An agent session (#2548): a server started with `CHANT_AGENT` set writes in
|
|
33
|
+
* that session, bound to its one member, and its writes are refused outside
|
|
34
|
+
* the session's write scope with the CLI's codes (`write-scope-member`,
|
|
35
|
+
* `write-scope-kind`, `agent-unknown`). `workspace-agent` returns what the
|
|
36
|
+
* session reloads from.
|
|
31
37
|
*/
|
|
32
38
|
|
|
33
39
|
import { spawn } from "node:child_process";
|
|
34
40
|
import type { ToolContext, ToolDefinition, ToolHandler } from "./types";
|
|
35
41
|
|
|
42
|
+
/** `AGENT_ENV` of `workspace/write-scope.ts`, kept here so the server loads nothing under workspace/ until a tool is called. */
|
|
43
|
+
const AGENT_ENV = "CHANT_AGENT";
|
|
44
|
+
|
|
36
45
|
/** How the reads run chant: a command and its leading arguments. */
|
|
37
46
|
export type ChantCommand = string[];
|
|
38
47
|
|
|
@@ -49,6 +58,8 @@ export interface WorkspaceToolsOptions {
|
|
|
49
58
|
cwd: string;
|
|
50
59
|
/** The chant the reads run. Defaults to {@link ownChantCommand}. */
|
|
51
60
|
chantCommand?: ChantCommand;
|
|
61
|
+
/** The agent session the writes are made in (#2548). Defaults to `CHANT_AGENT`. */
|
|
62
|
+
agent?: string;
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
const PROTOCOL =
|
|
@@ -154,6 +165,17 @@ export const workspaceReadTools: ToolDefinition[] = [
|
|
|
154
165
|
},
|
|
155
166
|
},
|
|
156
167
|
},
|
|
168
|
+
{
|
|
169
|
+
name: "workspace-agent",
|
|
170
|
+
description:
|
|
171
|
+
"An agent session and what it reloads from (#2548): chant workspace agent <name> --json (agent.schema.json). The one member it is bound to, the record kinds and verbs its write scope allows, and the spec records --current prints, read from the repository alone. Without name, this server's session (CHANT_AGENT). Returns the document unchanged.",
|
|
172
|
+
inputSchema: {
|
|
173
|
+
type: "object",
|
|
174
|
+
properties: {
|
|
175
|
+
name: { type: "string", description: "The session's name, as the declaration's agents list gives it." },
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
157
179
|
{
|
|
158
180
|
name: "workspace-work-history",
|
|
159
181
|
description:
|
|
@@ -334,6 +356,10 @@ export function readArgv(tool: string, params: Record<string, unknown>): string[
|
|
|
334
356
|
"--json",
|
|
335
357
|
];
|
|
336
358
|
}
|
|
359
|
+
case "workspace-agent": {
|
|
360
|
+
const name = str(params, "name", true)!;
|
|
361
|
+
return ["workspace", "agent", name, "--json"];
|
|
362
|
+
}
|
|
337
363
|
case "workspace-work-history": {
|
|
338
364
|
const item = str(params, "item", true)!;
|
|
339
365
|
const kind = str(params, "kind");
|
|
@@ -413,10 +439,12 @@ export interface WorkspaceTool {
|
|
|
413
439
|
export function createWorkspaceTools(options: WorkspaceToolsOptions): WorkspaceTool[] {
|
|
414
440
|
const { cwd } = options;
|
|
415
441
|
const chant = options.chantCommand ?? ownChantCommand();
|
|
442
|
+
const agent = options.agent ?? (process.env[AGENT_ENV] || undefined);
|
|
416
443
|
const reads: WorkspaceTool[] = workspaceReadTools.map((definition) => ({
|
|
417
444
|
definition,
|
|
418
445
|
handler: async (params) => {
|
|
419
|
-
const
|
|
446
|
+
const given = definition.name === "workspace-agent" && params.name === undefined && agent !== undefined ? { ...params, name: agent } : params;
|
|
447
|
+
const argv = readArgv(definition.name, given);
|
|
420
448
|
const id = definition.name === "workspace-records" ? str(params, "id") : undefined;
|
|
421
449
|
const doc = await runRead(chant, argv, cwd);
|
|
422
450
|
return id !== undefined ? onlyRecord(doc, id) : doc;
|
|
@@ -446,6 +474,7 @@ export function createWorkspaceTools(options: WorkspaceToolsOptions): WorkspaceT
|
|
|
446
474
|
sign: sign(params),
|
|
447
475
|
dryRun: bool(params, "dryRun"),
|
|
448
476
|
cwd,
|
|
477
|
+
agent,
|
|
449
478
|
through: { source: mcpSource(context), opensInitial: true },
|
|
450
479
|
});
|
|
451
480
|
},
|
|
@@ -463,6 +492,7 @@ export function createWorkspaceTools(options: WorkspaceToolsOptions): WorkspaceT
|
|
|
463
492
|
sign: sign(params),
|
|
464
493
|
dryRun: bool(params, "dryRun"),
|
|
465
494
|
cwd,
|
|
495
|
+
agent,
|
|
466
496
|
through: { source: mcpSource(context) },
|
|
467
497
|
});
|
|
468
498
|
},
|
|
@@ -481,6 +511,7 @@ export function createWorkspaceTools(options: WorkspaceToolsOptions): WorkspaceT
|
|
|
481
511
|
sign: sign(params),
|
|
482
512
|
dryRun: bool(params, "dryRun"),
|
|
483
513
|
cwd,
|
|
514
|
+
agent,
|
|
484
515
|
});
|
|
485
516
|
},
|
|
486
517
|
"points-answer": async (params) => {
|
|
@@ -498,7 +529,7 @@ export function createWorkspaceTools(options: WorkspaceToolsOptions): WorkspaceT
|
|
|
498
529
|
const named = str(params, "kind");
|
|
499
530
|
const kind = named !== undefined ? w.resolveWriteKind(named, cwd) : await w.declaredSessionKind(RECORDS_CLOSE_SCHEMA_ID, cwd);
|
|
500
531
|
if (typeof kind !== "string") return kind;
|
|
501
|
-
return closeRecord({ kind, id, dryRun: bool(params, "dryRun"), cwd });
|
|
532
|
+
return closeRecord({ kind, id, dryRun: bool(params, "dryRun"), cwd, agent });
|
|
502
533
|
},
|
|
503
534
|
};
|
|
504
535
|
|
package/src/cli/registry.ts
CHANGED
|
@@ -133,6 +133,16 @@ export interface ParsedArgs {
|
|
|
133
133
|
namespace?: string;
|
|
134
134
|
/** `chant workspace upgrade --allow-code` (#2550) — run migrations whose body is the template's own code. */
|
|
135
135
|
allowCode?: boolean;
|
|
136
|
+
/** `chant workspace upgrade --source <repo>[#<member>]` (#2551): move the scope to another template. */
|
|
137
|
+
source?: string;
|
|
138
|
+
/** `chant workspace adopt-lineage --tags <glob>` and `hash-index --tags` (#2551): the tags to compare. */
|
|
139
|
+
tags?: string;
|
|
140
|
+
/** `chant workspace adopt-lineage --index <file>` (#2551): a cached hash index. */
|
|
141
|
+
index?: string;
|
|
142
|
+
/** `chant workspace versions --available` (#2551): list each git template's version tags. */
|
|
143
|
+
available?: boolean;
|
|
144
|
+
/** `chant workspace import --remove` (#2552): remove the export member instead of writing it again. */
|
|
145
|
+
remove?: boolean;
|
|
136
146
|
/** `chant lifecycle rollback --dry-run` — compute the rollback delta and print it; open no PR, push nothing, leave no branch. */
|
|
137
147
|
dryRun?: boolean;
|
|
138
148
|
/** `chant lifecycle teardown <env> --yes` — execute the planned deletion
|
|
@@ -342,6 +352,18 @@ export interface ParsedArgs {
|
|
|
342
352
|
prefix?: string;
|
|
343
353
|
/** `chant workspace records|verify --require attested` (#2547): the provenance level a gate requires. */
|
|
344
354
|
require?: string;
|
|
355
|
+
/** `chant workspace evidence sign --key` (a runner's Ed25519 PEM) and `signers sign --key` (an ssh private key) (#2553). */
|
|
356
|
+
key?: string;
|
|
357
|
+
/** `chant workspace evidence sign --check-id <id>` (#2553): the check the evidence is for. */
|
|
358
|
+
checkId?: string;
|
|
359
|
+
/** `chant workspace evidence sign --claim <file>` (#2553): hashed into the evidence. */
|
|
360
|
+
claim?: string;
|
|
361
|
+
/** `chant workspace evidence sign --environment <file>` (#2553): the runner environment, hashed into the evidence. */
|
|
362
|
+
environment?: string;
|
|
363
|
+
/** `chant workspace evidence verify --envelope <file>` (#2553). */
|
|
364
|
+
envelope?: string;
|
|
365
|
+
/** `chant workspace signers rotate --threshold <n>` (#2553). */
|
|
366
|
+
threshold?: string;
|
|
345
367
|
/**
|
|
346
368
|
* `chant build --root-only` and `chant lint --root-only` (#2537): run on the
|
|
347
369
|
* root project of a declared workspace, members left out, instead of
|
|
@@ -70,7 +70,7 @@ describe("chant serve mcp at a workspace root with no lexicon of its own (#2700)
|
|
|
70
70
|
expect(init.serverInfo.name).toBe("chant");
|
|
71
71
|
for (const name of CORE_TOOLS) expect(tools).toContain(name);
|
|
72
72
|
expect(tools).toContain("docker:diff");
|
|
73
|
-
expect(init.instructions).toContain("Member delivery (delivery/) declares docker.");
|
|
73
|
+
expect(init.instructions).toContain("Member delivery (delivery/) declares docker, otel.");
|
|
74
74
|
}, TIMEOUT);
|
|
75
75
|
|
|
76
76
|
test("a lexiconless root, lexicons only in a member, serves core and the lexicons that load", () => {
|
|
@@ -36,6 +36,9 @@ function releaseTagShapes(): Array<{ recipe: string; example: string }> {
|
|
|
36
36
|
// `git tag "chant-v$next"` / `git tag "lexicon-{{name}}-v$next"`
|
|
37
37
|
for (const m of justfile.matchAll(/git tag "([^"]+)"/g)) {
|
|
38
38
|
const raw = m[1];
|
|
39
|
+
// The reference workspace's tag (`release_reference_tag`, #2543) rides
|
|
40
|
+
// along with a chant release and publishes nothing by design.
|
|
41
|
+
if (raw === "$rtag") continue;
|
|
39
42
|
const example = raw
|
|
40
43
|
.replace(/\{\{name\}\}/g, "docker")
|
|
41
44
|
.replace(/\$\{?next\}?/g, "9.9.9");
|
|
@@ -196,7 +199,8 @@ describe("release wiring: release the newest green commit (#2816)", () => {
|
|
|
196
199
|
}
|
|
197
200
|
const lib = readFileSync(join(repoRoot, "scripts", "release-lib.sh"), "utf-8");
|
|
198
201
|
// main and the tag land together or not at all, and nothing is forced.
|
|
199
|
-
expect(lib).toMatch(/
|
|
202
|
+
expect(lib).toMatch(/push_refs=\("refs\/tags\/\$tag"\)/);
|
|
203
|
+
expect(lib).toMatch(/git push --quiet --atomic origin "HEAD:refs\/heads\/main" "\$\{push_refs\[@\]\}"/);
|
|
200
204
|
expect(lib).not.toMatch(/git push[^\n]*(--force|\s-f\b)/);
|
|
201
205
|
});
|
|
202
206
|
|
|
@@ -93,7 +93,7 @@ describe("the derivation", () => {
|
|
|
93
93
|
expect(out).toContain(`plan: ${plan.digest}`);
|
|
94
94
|
expect(out).toContain(`chant approve fan-out release --plan ${plan.digest}`);
|
|
95
95
|
// Nothing abbreviates it: the whole 64-hex string is on the line.
|
|
96
|
-
expect(plan.digest).toMatch(/^sha256:[0-9a-f]{64}$/);
|
|
96
|
+
expect(plan.digest).toMatch(/^jcs1-sha256:[0-9a-f]{64}$/);
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
test("says so plainly when a change propagates to nothing", () => {
|
|
@@ -157,7 +157,7 @@ describe("the binding digest", () => {
|
|
|
157
157
|
});
|
|
158
158
|
|
|
159
159
|
test("it looks like every other plan digest, so `chant approve --plan` accepts it", () => {
|
|
160
|
-
expect(planFanOut({ components: ESTATE, changed: ["net"] }).digest).toMatch(/^sha256:[0-9a-f]{64}$/);
|
|
160
|
+
expect(planFanOut({ components: ESTATE, changed: ["net"] }).digest).toMatch(/^jcs1-sha256:[0-9a-f]{64}$/);
|
|
161
161
|
});
|
|
162
162
|
});
|
|
163
163
|
|
|
@@ -492,7 +492,7 @@ describe("a redeploy's gate approval is bound like a rollback's (#2574, #2604)",
|
|
|
492
492
|
const first = await runPromotion({ ...opts, now: "2026-01-05T00:00:00Z" });
|
|
493
493
|
expect(first.status).toBe("gated");
|
|
494
494
|
expect(first.gate?.environment).toBe("prod");
|
|
495
|
-
expect(first.gate?.planDigest).toMatch(/^sha256:[0-9a-f]{64}$/);
|
|
495
|
+
expect(first.gate?.planDigest).toMatch(/^jcs1-sha256:[0-9a-f]{64}$/);
|
|
496
496
|
expect(ran).toEqual([]);
|
|
497
497
|
|
|
498
498
|
resolutions.push(approval({ planDigest: first.gate!.planDigest!, environment: "prod" }, "2026-01-05T00:01:00Z"));
|
package/src/config.ts
CHANGED
|
@@ -283,6 +283,18 @@ export interface ChantConfig {
|
|
|
283
283
|
/** Lint configuration (rules, extends, overrides, plugins) */
|
|
284
284
|
lint?: LintConfig;
|
|
285
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Telemetry attribution (#2558, D22): lexicons that declare workloads stamp
|
|
288
|
+
* resource attributes (`chant.workspace`, `chant.member`, `chant.decl`,
|
|
289
|
+
* `service.name`, `deployment.environment.name`) as `OTEL_*` environment
|
|
290
|
+
* variables. Inside a workspace this is on; `attribution: false` turns it
|
|
291
|
+
* off. Outside a workspace it is off, and `attribution: true` turns it on
|
|
292
|
+
* with the attributes that need no workspace. See `./telemetry-attribution.ts`.
|
|
293
|
+
*/
|
|
294
|
+
telemetry?: {
|
|
295
|
+
attribution?: boolean;
|
|
296
|
+
};
|
|
297
|
+
|
|
286
298
|
/**
|
|
287
299
|
* Opt-in cloud-side ownership marking. When `stack` is set (and `enabled`
|
|
288
300
|
* is not false), the serializer stamps a chant ownership marker carrying
|