@intentius/chant 0.124.30 → 0.124.32
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/bin/chant +7 -2
- package/dist/cli/commands/check-lexicon-plugin.d.ts +7 -4
- package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-plugin.js +10 -7
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.js +18 -12
- package/dist/cli/commands/import.d.ts +10 -0
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/import.js +37 -0
- package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/project.js +4 -3
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/onboard.js +8 -4
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +2 -1
- package/dist/cli/dependencies.d.ts +18 -0
- package/dist/cli/dependencies.d.ts.map +1 -0
- package/dist/cli/dependencies.js +120 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +23 -4
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +12 -4
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +16 -6
- package/dist/cli/handlers/lint.d.ts.map +1 -1
- package/dist/cli/handlers/lint.js +6 -0
- package/dist/cli/handlers/operator.d.ts +2 -2
- package/dist/cli/handlers/operator.js +3 -3
- package/dist/cli/handlers/pr.d.ts +14 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +488 -32
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +3 -1
- package/dist/cli/handlers/run.d.ts +13 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +38 -15
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +74 -12
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +65 -0
- package/dist/cli/registry.d.ts +29 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/registry.js +6 -2
- package/dist/codegen/generate-post-synth-barrel.js +2 -2
- package/dist/codegen/lexicon-regen.d.ts +4 -0
- package/dist/codegen/lexicon-regen.d.ts.map +1 -1
- package/dist/codegen/lexicon-regen.js +1 -1
- package/dist/codegen/pinned-upgrade.js +2 -2
- package/dist/components/capability.d.ts +44 -3
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/capability.js +21 -0
- package/dist/components/changed-files.d.ts +7 -1
- package/dist/components/changed-files.d.ts.map +1 -1
- package/dist/components/changed-files.js +9 -1
- package/dist/components/driver.d.ts +38 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +47 -6
- package/dist/components/fan-out-record.d.ts +6 -1
- package/dist/components/fan-out-record.d.ts.map +1 -1
- package/dist/components/fan-out-record.js +24 -0
- package/dist/components/fan-out-run.d.ts +20 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +21 -1
- package/dist/components/pr-pipeline.d.ts +65 -2
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +107 -15
- package/dist/components/pr-run.d.ts +127 -29
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +290 -70
- package/dist/components/wave-plan.d.ts +5 -2
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +12 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-import.js +16 -6
- package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
- package/dist/discovery/sandbox/bundle.js +6 -0
- package/dist/discovery/specifier-resolve.d.ts.map +1 -1
- package/dist/discovery/specifier-resolve.js +8 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/fold.js +68 -1
- package/dist/fold/subset.d.ts +37 -0
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/fold/subset.js +88 -0
- package/dist/lexicon-module.d.ts +10 -0
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lexicon-module.js +53 -8
- package/dist/lexicon-plugin-helpers.d.ts.map +1 -1
- package/dist/lexicon-plugin-helpers.js +6 -4
- package/dist/lexicon.d.ts +2 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/affected.d.ts.map +1 -1
- package/dist/lifecycle/affected.js +7 -1
- package/dist/lifecycle/release-ledger.d.ts +21 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.js +24 -0
- package/dist/lifecycle/work-dir.d.ts +45 -0
- package/dist/lifecycle/work-dir.d.ts.map +1 -0
- package/dist/lifecycle/work-dir.js +78 -0
- package/dist/lint/discover.d.ts +17 -9
- package/dist/lint/discover.d.ts.map +1 -1
- package/dist/lint/discover.js +62 -54
- package/dist/module-source.d.ts +57 -0
- package/dist/module-source.d.ts.map +1 -0
- package/dist/module-source.js +104 -0
- package/dist/op/activities/factory.js +1 -1
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/discover.js +13 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +3 -0
- package/dist/op/local-executor.d.ts +12 -0
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +22 -3
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +20 -5
- package/dist/op/operator.d.ts +2 -2
- package/dist/op/operator.js +2 -2
- package/dist/op/redact.d.ts +34 -0
- package/dist/op/redact.d.ts.map +1 -0
- package/dist/op/redact.js +94 -0
- package/dist/op/step-console.d.ts +2 -0
- package/dist/op/step-console.d.ts.map +1 -1
- package/dist/op/step-console.js +5 -2
- package/dist/op/steward.d.ts +6 -4
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/steward.js +5 -3
- package/dist/op/work-lease-run.d.ts +20 -7
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/op/work-lease-run.js +56 -12
- package/dist/pr-forge.d.ts +27 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +51 -6
- package/dist/pr-loop.d.ts +141 -4
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +122 -6
- package/dist/pr-report-url.d.ts +10 -2
- package/dist/pr-report-url.d.ts.map +1 -1
- package/dist/pr-report-url.js +52 -6
- package/dist/ts-loader.d.ts +38 -4
- package/dist/ts-loader.d.ts.map +1 -1
- package/dist/ts-loader.js +190 -16
- package/dist/wave-runtime.mjs +208 -208
- package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts +34 -0
- package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/fountain-retired-kinds.js +176 -0
- package/dist/workspace/chant-migrations.d.ts.map +1 -1
- package/dist/workspace/chant-migrations.js +2 -0
- package/dist/workspace/checks/boxes.d.ts +6 -6
- package/dist/workspace/checks/boxes.js +9 -9
- package/dist/workspace/ci-green-forge.d.ts +6 -4
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green.d.ts +28 -12
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +49 -21
- package/dist/workspace/ci-list.schema.json +12 -6
- package/dist/workspace/ci-report-url.d.ts +18 -0
- package/dist/workspace/ci-report-url.d.ts.map +1 -0
- package/dist/workspace/ci-report-url.js +33 -0
- package/dist/workspace/ci-run-index.d.ts +108 -0
- package/dist/workspace/ci-run-index.d.ts.map +1 -0
- package/dist/workspace/ci-run-index.js +164 -0
- package/dist/workspace/ci-run-index.schema.json +52 -0
- package/dist/workspace/ci-run.d.ts +159 -9
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +419 -50
- package/dist/workspace/ci-runner-key.d.ts +133 -0
- package/dist/workspace/ci-runner-key.d.ts.map +1 -0
- package/dist/workspace/ci-runner-key.js +237 -0
- package/dist/workspace/ci-secrets.d.ts +65 -0
- package/dist/workspace/ci-secrets.d.ts.map +1 -0
- package/dist/workspace/ci-secrets.js +211 -0
- package/dist/workspace/ci-serve.d.ts +67 -4
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +240 -35
- package/dist/workspace/declaration.d.ts +8 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +11 -2
- package/dist/workspace/declaration.schema.json +7 -2
- package/dist/workspace/export.d.ts +1 -1
- package/dist/workspace/export.d.ts.map +1 -1
- package/dist/workspace/export.js +7 -3
- package/dist/workspace/ls.d.ts +6 -1
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +1 -0
- package/dist/workspace/ls.schema.json +7 -1
- package/dist/workspace/pin-integrity.d.ts.map +1 -1
- package/dist/workspace/pin-integrity.js +3 -2
- package/dist/workspace/pr-report.schema.json +74 -7
- package/dist/workspace/reason-codes.d.ts +7 -3
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +9 -4
- package/dist/workspace/run-event.schema.json +3 -1
- package/dist/workspace/status.schema.json +3 -1
- package/dist/workspace/trust/trust-write.d.ts +109 -0
- package/dist/workspace/trust/trust-write.d.ts.map +1 -0
- package/dist/workspace/trust/trust-write.js +320 -0
- package/dist/workspace/trust-write.schema.json +265 -0
- package/dist/workspace/write-scope.d.ts +9 -0
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/dist/workspace/write-scope.js +11 -1
- package/package.json +1 -1
- package/src/__fixtures__/pr-loop/pr-apply-wave-waiting.note.golden.md +48 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +3 -3
- package/src/cli/commands/audit.test.ts +2 -2
- package/src/cli/commands/check-lexicon-plugin.test.ts +9 -0
- package/src/cli/commands/check-lexicon-plugin.ts +11 -8
- package/src/cli/commands/check-lexicon.test.ts +1 -1
- package/src/cli/commands/check-lexicon.ts +16 -11
- package/src/cli/commands/import.ts +40 -0
- package/src/cli/commands/init-lexicon/templates/project.ts +4 -3
- package/src/cli/commands/init-lexicon.test.ts +8 -5
- package/src/cli/commands/onboard.test.ts +45 -0
- package/src/cli/commands/onboard.ts +8 -4
- package/src/cli/commands/update.test.ts +6 -0
- package/src/cli/commands/update.ts +4 -1
- package/src/cli/compiled-cli.e2e.test.ts +76 -11
- package/src/cli/dependencies.e2e.test.ts +101 -0
- package/src/cli/dependencies.test.ts +93 -0
- package/src/cli/dependencies.ts +122 -0
- package/src/cli/handlers/build.test.ts +69 -0
- package/src/cli/handlers/build.ts +20 -4
- package/src/cli/handlers/fan-out.test.ts +73 -0
- package/src/cli/handlers/fan-out.ts +11 -4
- package/src/cli/handlers/graph.test.ts +76 -0
- package/src/cli/handlers/graph.ts +19 -12
- package/src/cli/handlers/lint.test.ts +42 -0
- package/src/cli/handlers/lint.ts +6 -0
- package/src/cli/handlers/operator.ts +3 -3
- package/src/cli/handlers/pr-waves.test.ts +449 -0
- package/src/cli/handlers/pr.test.ts +134 -0
- package/src/cli/handlers/pr.ts +523 -31
- package/src/cli/handlers/promote.test.ts +26 -1
- package/src/cli/handlers/promote.ts +3 -2
- package/src/cli/handlers/run-components-exit.test.ts +99 -0
- package/src/cli/handlers/run-signer.test.ts +79 -0
- package/src/cli/handlers/run.ts +34 -16
- package/src/cli/main.test.ts +19 -0
- package/src/cli/main.ts +73 -12
- package/src/cli/parse-args.ts +48 -0
- package/src/cli/registry.ts +35 -4
- package/src/codegen/generate-post-synth-barrel.ts +2 -2
- package/src/codegen/lexicon-regen.ts +1 -1
- package/src/codegen/pinned-upgrade.ts +2 -2
- package/src/codegen/post-synth-barrel-guards.test.ts +6 -11
- package/src/components/capability.ts +51 -3
- package/src/components/changed-files.test.ts +16 -1
- package/src/components/changed-files.ts +9 -1
- package/src/components/driver.test.ts +36 -1
- package/src/components/driver.ts +76 -4
- package/src/components/fan-out-record.ts +29 -1
- package/src/components/fan-out-run.test.ts +35 -0
- package/src/components/fan-out-run.ts +40 -1
- package/src/components/pr-pipeline.test.ts +100 -0
- package/src/components/pr-pipeline.ts +132 -17
- package/src/components/pr-run.test.ts +323 -18
- package/src/components/pr-run.ts +354 -75
- package/src/components/wave-plan.ts +16 -4
- package/src/discovery/fold-host-method.test.ts +87 -0
- package/src/discovery/fold-import.ts +15 -5
- package/src/discovery/fold-package-composite.test.ts +27 -0
- package/src/discovery/fold-specifiers.test.ts +8 -0
- package/src/discovery/sandbox/bundle.ts +6 -0
- package/src/discovery/specifier-resolve.ts +8 -2
- package/src/fold/fold.test.ts +72 -0
- package/src/fold/fold.ts +71 -0
- package/src/fold/subset.test.ts +31 -0
- package/src/fold/subset.ts +99 -0
- package/src/lexicon-dist.e2e.test.ts +254 -0
- package/src/lexicon-module.test.ts +47 -0
- package/src/lexicon-module.ts +49 -7
- package/src/lexicon-plugin-helpers.ts +6 -4
- package/src/lexicon.ts +2 -0
- package/src/lifecycle/affected.test.ts +7 -0
- package/src/lifecycle/affected.ts +6 -1
- package/src/lifecycle/release-ledger.ts +34 -0
- package/src/lifecycle/work-dir.test.ts +77 -0
- package/src/lifecycle/work-dir.ts +82 -0
- package/src/lint/discover.test.ts +46 -2
- package/src/lint/discover.ts +55 -49
- package/src/module-source.test.ts +85 -0
- package/src/module-source.ts +112 -0
- package/src/op/activities/factory.ts +1 -1
- package/src/op/discover-root.test.ts +31 -2
- package/src/op/discover.ts +14 -4
- package/src/op/gate.test.ts +30 -0
- package/src/op/gate.ts +3 -0
- package/src/op/local-executor.ts +31 -3
- package/src/op/op-waves-run.ts +20 -5
- package/src/op/op-waves.test.ts +49 -0
- package/src/op/operator.ts +2 -2
- package/src/op/redact.ts +93 -0
- package/src/op/step-console.ts +7 -2
- package/src/op/steward.ts +6 -4
- package/src/op/work-lease-run.test.ts +88 -2
- package/src/op/work-lease-run.ts +52 -12
- package/src/pr-forge-bundle.test.ts +2 -2
- package/src/pr-forge.test.ts +71 -0
- package/src/pr-forge.ts +70 -6
- package/src/pr-loop-bundle.test.ts +2 -1
- package/src/pr-loop.test.ts +117 -1
- package/src/pr-loop.ts +252 -10
- package/src/pr-report-url.ts +58 -6
- package/src/terraform/__fixtures__/advise.test.ts +21 -2
- package/src/ts-loader.test.ts +89 -1
- package/src/ts-loader.ts +180 -13
- package/src/workspace/changes.test.ts +1 -1
- package/src/workspace/chant-migrations/fountain-retired-kinds.test.ts +33 -0
- package/src/workspace/chant-migrations/fountain-retired-kinds.ts +190 -0
- package/src/workspace/chant-migrations.e2e.test.ts +5 -0
- package/src/workspace/chant-migrations.ts +2 -0
- package/src/workspace/checks/boxes.ts +9 -9
- package/src/workspace/ci-green-forge.ts +6 -4
- package/src/workspace/ci-green-runs.test.ts +53 -11
- package/src/workspace/ci-green.test.ts +1 -1
- package/src/workspace/ci-green.ts +50 -22
- package/src/workspace/ci-list.schema.json +12 -6
- package/src/workspace/ci-report-url.test.ts +220 -0
- package/src/workspace/ci-report-url.ts +33 -0
- package/src/workspace/ci-run-index.schema.json +52 -0
- package/src/workspace/ci-run-index.test.ts +392 -0
- package/src/workspace/ci-run-index.ts +206 -0
- package/src/workspace/ci-run.test.ts +520 -42
- package/src/workspace/ci-run.ts +483 -51
- package/src/workspace/ci-runner-key.test.ts +204 -0
- package/src/workspace/ci-runner-key.ts +275 -0
- package/src/workspace/ci-secrets.test.ts +106 -0
- package/src/workspace/ci-secrets.ts +213 -0
- package/src/workspace/ci-serve.test.ts +282 -39
- package/src/workspace/ci-serve.ts +231 -34
- package/src/workspace/declaration.schema.json +7 -2
- package/src/workspace/declaration.test.ts +23 -0
- package/src/workspace/declaration.ts +19 -3
- package/src/workspace/export.ts +6 -3
- package/src/workspace/ls.schema.json +7 -1
- package/src/workspace/ls.ts +7 -2
- package/src/workspace/pin-integrity.test.ts +5 -0
- package/src/workspace/pin-integrity.ts +3 -2
- package/src/workspace/pr-report.schema.json +74 -7
- package/src/workspace/reason-codes.test.ts +2 -0
- package/src/workspace/reason-codes.ts +9 -4
- package/src/workspace/run-event.schema.json +3 -1
- package/src/workspace/status.schema.json +3 -1
- package/src/workspace/trust/trust-write.test.ts +179 -0
- package/src/workspace/trust/trust-write.ts +377 -0
- package/src/workspace/trust-write.schema.json +265 -0
- package/src/workspace/write-scope.test.ts +29 -1
- package/src/workspace/write-scope.ts +12 -1
|
@@ -24,6 +24,8 @@ const writtenTemplates: unknown[] = [];
|
|
|
24
24
|
let configEnvironments: unknown[] | undefined;
|
|
25
25
|
let publishedDigest = "sha256:api";
|
|
26
26
|
let failApplyFor: string | undefined;
|
|
27
|
+
/** What `failApplyFor`'s apply throws; a plain error when unset. */
|
|
28
|
+
let applyError: Error | undefined;
|
|
27
29
|
|
|
28
30
|
vi.mock("../../lifecycle/git", () => ({
|
|
29
31
|
getHeadCommit: async () => "abc123",
|
|
@@ -58,7 +60,7 @@ vi.mock("../../components/fan-out-support", () => ({
|
|
|
58
60
|
step("docker-build", () => ({ digest: "sha256:rebuilt" }));
|
|
59
61
|
step("publish-image", () => ({ digest: publishedDigest, uri: `reg/api@${publishedDigest}` }));
|
|
60
62
|
step("apply", (ctx) => {
|
|
61
|
-
if (ctx.component === failApplyFor) throw new Error("apply failed");
|
|
63
|
+
if (ctx.component === failApplyFor) throw applyError ?? new Error("apply failed");
|
|
62
64
|
return {};
|
|
63
65
|
});
|
|
64
66
|
// #4045: a planner whose member is provisional.
|
|
@@ -221,6 +223,7 @@ beforeEach(() => {
|
|
|
221
223
|
configEnvironments = undefined;
|
|
222
224
|
publishedDigest = "sha256:api";
|
|
223
225
|
failApplyFor = undefined;
|
|
226
|
+
applyError = undefined;
|
|
224
227
|
errors = [];
|
|
225
228
|
logs = [];
|
|
226
229
|
vi.spyOn(console, "error").mockImplementation((...m: unknown[]) => { errors.push(m.join(" ")); });
|
|
@@ -358,6 +361,21 @@ describe("chant components promote", () => {
|
|
|
358
361
|
expect(appendReleaseRecordMock.mock.calls[0][0]).toMatchObject({ component: "api", env: "prod" });
|
|
359
362
|
});
|
|
360
363
|
|
|
364
|
+
test.each([
|
|
365
|
+
["marked planMoved", 3, { planMoved: true }],
|
|
366
|
+
["with exitCode 3", 3, { exitCode: 3 }],
|
|
367
|
+
["with exitCode 7", 1, { exitCode: 7 }],
|
|
368
|
+
])("an apply that refuses its plan %s exits %i, and what deployed is still recorded (#3997)", async (_label, code, mark) => {
|
|
369
|
+
const web: DriverComponent = { ...api, name: "web" };
|
|
370
|
+
resolveTargetsMock.mockResolvedValue({ success: true, targets: [api, web] });
|
|
371
|
+
readReleaseLedgerMock.mockResolvedValue({ records: [staging, { ...staging, component: "web" }], malformed: 0 });
|
|
372
|
+
failApplyFor = "web";
|
|
373
|
+
applyError = Object.assign(new Error("web: refusing to apply, the plan moved"), mark);
|
|
374
|
+
expect(await runComponentsPromote(ctx({}))).toBe(code);
|
|
375
|
+
expect(appendReleaseRecordMock).toHaveBeenCalledTimes(1);
|
|
376
|
+
expect(errors.join("\n")).toMatch(/promote failed at "web"/);
|
|
377
|
+
});
|
|
378
|
+
|
|
361
379
|
test("--digest <component>=<digest> promotes the pinned release, not the latest one (#2602)", async () => {
|
|
362
380
|
const web: DriverComponent = { ...api, name: "web" };
|
|
363
381
|
resolveTargetsMock.mockResolvedValue({ success: true, targets: [api, web] });
|
|
@@ -795,6 +813,13 @@ describe("chant components redeploy", () => {
|
|
|
795
813
|
]);
|
|
796
814
|
expect(errors.join("\n")).toMatch(/redeploy failed at "api"/);
|
|
797
815
|
});
|
|
816
|
+
|
|
817
|
+
test("a redeploy whose apply refuses a moved plan exits 3 (#3997)", async () => {
|
|
818
|
+
failApplyFor = "api";
|
|
819
|
+
applyError = Object.assign(new Error("api: refusing to apply, the plan moved"), { planMoved: true });
|
|
820
|
+
expect(await runComponentsRedeploy(rd({}))).toBe(3);
|
|
821
|
+
expect(appendReleaseRecordMock).not.toHaveBeenCalled();
|
|
822
|
+
});
|
|
798
823
|
});
|
|
799
824
|
|
|
800
825
|
describe("--events-json on promote, rollback and redeploy (#4041)", () => {
|
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
type PromotionItem,
|
|
59
59
|
type PromotionPlan,
|
|
60
60
|
} from "../../components/promote";
|
|
61
|
-
import type
|
|
61
|
+
import { failedRunExitCode, type DriverComponent } from "../../components/driver";
|
|
62
62
|
import { summaryLedgerPrefix, writeGatedRunSummary } from "../../op/gate-summary";
|
|
63
63
|
import { approveCommand, describeGateMismatch, gateIsSealed, gitGateLedgerPort, type GateLedgerPort } from "../../op/gate";
|
|
64
64
|
import type { ReleaseGateBinding } from "../../components/driver";
|
|
@@ -522,7 +522,8 @@ async function deployPlan(
|
|
|
522
522
|
|
|
523
523
|
if (run.status !== "ok") {
|
|
524
524
|
console.error(formatError({ message: `${verb} failed at "${run.failedComponent ?? "unknown"}"` }));
|
|
525
|
-
|
|
525
|
+
// A step that refused a plan that moved since it was approved exits 3 (#3997).
|
|
526
|
+
return failedRunExitCode(run.results);
|
|
526
527
|
}
|
|
527
528
|
return 0;
|
|
528
529
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant run --components` exits 3 when a step refuses a plan that moved
|
|
3
|
+
* since it was approved (#3997), on the single-component path and on the
|
|
4
|
+
* driver's `all` and list paths. Discovery and the capability registry are
|
|
5
|
+
* fakes; the run is the real `runComponents` on the local runtime.
|
|
6
|
+
*/
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
8
|
+
import type { Component } from "../../components/component";
|
|
9
|
+
import type { ParsedArgs } from "../registry";
|
|
10
|
+
|
|
11
|
+
const discoverComponentsMock = vi.fn();
|
|
12
|
+
vi.mock("../../components/discover", () => ({
|
|
13
|
+
discoverComponents: (...args: unknown[]) => discoverComponentsMock(...args),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock("../../config", async () => {
|
|
17
|
+
const actual = await vi.importActual<typeof import("../../config")>("../../config");
|
|
18
|
+
return { ...actual, loadChantConfig: async () => ({ config: {} }) };
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/** The error a component's step throws, in place of applying. */
|
|
22
|
+
const refusal = new Map<string, Error>();
|
|
23
|
+
|
|
24
|
+
vi.mock("../../op/runtimes/local", async () => {
|
|
25
|
+
const { CapabilityRegistry } = await import("../../components/capability");
|
|
26
|
+
const { runComponents } = await import("../../components/cli-support");
|
|
27
|
+
const { memoryGateLedgerPort } = await import("../../op/gate");
|
|
28
|
+
const registry = new CapabilityRegistry();
|
|
29
|
+
registry.register({
|
|
30
|
+
kind: "fake-apply",
|
|
31
|
+
async run(ctx: { component: string }) {
|
|
32
|
+
const err = refusal.get(ctx.component);
|
|
33
|
+
if (err) throw err;
|
|
34
|
+
return {};
|
|
35
|
+
},
|
|
36
|
+
} as never);
|
|
37
|
+
return {
|
|
38
|
+
createLocalOpRuntime: () => ({
|
|
39
|
+
name: "local",
|
|
40
|
+
runComponents: (path: string, selector: string, options: object) =>
|
|
41
|
+
runComponents(path, selector, { ...options, registry, config: {}, revision: null, gates: memoryGateLedgerPort() }),
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const { runOpComponents } = await import("./run");
|
|
47
|
+
|
|
48
|
+
const component = (name: string, dependsOn: string[] = []): Component => ({
|
|
49
|
+
name,
|
|
50
|
+
dependsOn,
|
|
51
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "fake-apply" }] }],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const ctx = (selector: string) =>
|
|
55
|
+
({
|
|
56
|
+
args: { command: "run", path: selector, components: true, on: "local", format: "", fix: false, watch: false, verbose: false, help: false, live: false } as ParsedArgs,
|
|
57
|
+
plugins: [],
|
|
58
|
+
}) as never;
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
refusal.clear();
|
|
62
|
+
const estate = [component("net"), component("app", ["net"])];
|
|
63
|
+
discoverComponentsMock.mockResolvedValue({
|
|
64
|
+
components: new Map(estate.map((c) => [c.name, { component: c, exportName: c.name, filePath: `${c.name}.component.ts` }])),
|
|
65
|
+
sourceFiles: [],
|
|
66
|
+
errors: [],
|
|
67
|
+
});
|
|
68
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
69
|
+
vi.spyOn(console, "log").mockImplementation(() => {});
|
|
70
|
+
vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
vi.restoreAllMocks();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const marks: Array<[string, number, object]> = [
|
|
78
|
+
["marked planMoved", 3, { planMoved: true }],
|
|
79
|
+
["with exitCode 3", 3, { exitCode: 3 }],
|
|
80
|
+
["with exitCode 7", 1, { exitCode: 7 }],
|
|
81
|
+
["unmarked", 1, {}],
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
describe("chant run --components: a step that refuses a moved plan exits 3 (#3997)", () => {
|
|
85
|
+
test.each(marks)("one component, its step refusing %s: exit %i", async (_label, code, mark) => {
|
|
86
|
+
refusal.set("app", Object.assign(new Error("app: refusing to apply, the plan moved"), mark));
|
|
87
|
+
expect(await runOpComponents(ctx("app"))).toBe(code);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test.each(marks)("all, a step refusing %s: exit %i", async (_label, code, mark) => {
|
|
91
|
+
refusal.set("net", Object.assign(new Error("net: refusing to apply, the plan moved"), mark));
|
|
92
|
+
expect(await runOpComponents(ctx("all"))).toBe(code);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("a list that pulls in a dependency whose step refuses a moved plan exits 3", async () => {
|
|
96
|
+
refusal.set("net", Object.assign(new Error("net: refusing to apply, the plan moved"), { planMoved: true }));
|
|
97
|
+
expect(await runOpComponents(ctx("app,net"))).toBe(3);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #4156: `chant run`'s runner key is opt-in hardening. A `CHANT_RUNNER_KEY`
|
|
3
|
+
* that `.chant/trust.json` does not list leaves the run unsigned with no
|
|
4
|
+
* warning, unless the declaration sets `ci.green.signedSince`. A key that is
|
|
5
|
+
* not an Ed25519 PEM is an error either way.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createPublicKey, generateKeyPairSync } from "node:crypto";
|
|
9
|
+
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
10
|
+
import { cleanScratch, declaration, git, scratchDir, writeFiles } from "../../workspace/__fixtures__/contract-repo";
|
|
11
|
+
import { sshFromEd25519 } from "../../workspace/trust/dsse";
|
|
12
|
+
import type { CommandContext } from "../registry";
|
|
13
|
+
import { runSignerFor } from "./run";
|
|
14
|
+
|
|
15
|
+
const home = process.cwd();
|
|
16
|
+
const saved = process.env.CHANT_RUNNER_KEY;
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
process.chdir(home);
|
|
19
|
+
if (saved === undefined) delete process.env.CHANT_RUNNER_KEY;
|
|
20
|
+
else process.env.CHANT_RUNNER_KEY = saved;
|
|
21
|
+
vi.restoreAllMocks();
|
|
22
|
+
cleanScratch();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function key(): { pem: string; ssh: string } {
|
|
26
|
+
const { privateKey } = generateKeyPairSync("ed25519");
|
|
27
|
+
const jwk = createPublicKey(privateKey).export({ format: "jwk" }) as { x: string };
|
|
28
|
+
return { pem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(), ssh: sshFromEd25519(Buffer.from(jwk.x, "base64url")) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function repo(green?: Record<string, unknown>): string {
|
|
32
|
+
const root = scratchDir("chant-run-signer-");
|
|
33
|
+
git(root, "init", "-q");
|
|
34
|
+
for (const [k, v] of [["user.name", "t"], ["user.email", "t@example.com"], ["commit.gpgsign", "false"]]) git(root, "config", k!, v!);
|
|
35
|
+
writeFiles(root, {
|
|
36
|
+
"chant.workspace.json": declaration([{ name: "app", dir: "app", kind: "other", because: "the app" }], green ? { ci: { green } } : {}),
|
|
37
|
+
".chant/trust.json": JSON.stringify({ schema: 1, runners: [{ principal: "ci-bot", class: "runner", key: key().ssh }], roles: { runner: ["ci-bot"] } }),
|
|
38
|
+
"app/a.txt": "a\n",
|
|
39
|
+
});
|
|
40
|
+
git(root, "add", "-A");
|
|
41
|
+
git(root, "commit", "-q", "-m", "init");
|
|
42
|
+
return root;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function signer(root: string, pem: string, verbose = false) {
|
|
46
|
+
process.chdir(root);
|
|
47
|
+
process.env.CHANT_RUNNER_KEY = pem;
|
|
48
|
+
const err: string[] = [];
|
|
49
|
+
vi.spyOn(console, "error").mockImplementation((s: string) => void err.push(String(s)));
|
|
50
|
+
const result = await runSignerFor({ args: { verbose } } as unknown as CommandContext);
|
|
51
|
+
return { result, err: err.join("\n") };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
describe("chant run's runner key from the environment (#4156)", () => {
|
|
55
|
+
test("a key trust.json does not list leaves the run unsigned and quiet, with one line under --verbose", async () => {
|
|
56
|
+
const root = repo();
|
|
57
|
+
const quiet = await signer(root, key().pem);
|
|
58
|
+
expect(quiet.result).toBeUndefined();
|
|
59
|
+
expect(quiet.err).toBe("");
|
|
60
|
+
const verbose = await signer(root, key().pem, true);
|
|
61
|
+
expect(verbose.result).toBeUndefined();
|
|
62
|
+
expect(verbose.err).toContain("CHANT_RUNNER_KEY is set, and the run is not signed");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("with ci.green.signedSince the same key warns", async () => {
|
|
66
|
+
const root = repo();
|
|
67
|
+
const head = git(root, "rev-parse", "HEAD");
|
|
68
|
+
writeFiles(root, { "chant.workspace.json": declaration([{ name: "app", dir: "app", kind: "other", because: "the app" }], { ci: { green: { branch: "main", phases: { test: ["test"] }, require: ["test"], signedSince: head } } }) });
|
|
69
|
+
const r = await signer(root, key().pem);
|
|
70
|
+
expect(r.result).toBeUndefined();
|
|
71
|
+
expect(r.err).toContain(`ci.green.signedSince (${head}) will not count it`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("a key that is not an Ed25519 PEM is an error", async () => {
|
|
75
|
+
const r = await signer(repo(), "not a key");
|
|
76
|
+
expect(r.result).toBeNull();
|
|
77
|
+
expect(r.err).toContain("CHANT_RUNNER_KEY is not an Ed25519 private key in PEM");
|
|
78
|
+
});
|
|
79
|
+
});
|
package/src/cli/handlers/run.ts
CHANGED
|
@@ -31,7 +31,7 @@ import type { RunEvent } from "../../run-event";
|
|
|
31
31
|
import { openCliRunEvents, refusesBothStreams, stdoutLine } from "../run-events";
|
|
32
32
|
import { maybeRecordAutoRelease } from "../../components/auto-release";
|
|
33
33
|
import { maybePersistBuildManifest } from "../../components/manifest-persistence";
|
|
34
|
-
import type
|
|
34
|
+
import { failedRunExitCode, type DriverComponentResult } from "../../components/driver";
|
|
35
35
|
import { isComponentList, type ComponentRunPlan, type RunComponentsResult } from "../../components/cli-support";
|
|
36
36
|
import { resolveCiRun, type ReleaseRecord, type RunOrigin } from "../../lifecycle/release-ledger";
|
|
37
37
|
import { gateAdmission, gateApprover } from "../../components/promote";
|
|
@@ -730,9 +730,9 @@ type StewardTurnGate =
|
|
|
730
730
|
* `chant run <op>` for an Op a declared steward lists is one of that
|
|
731
731
|
* steward's turns, not a run beside it (#2750, follow-up to #2731).
|
|
732
732
|
*
|
|
733
|
-
* On fountain, the run belongs
|
|
733
|
+
* On fountain, the run belongs in the steward's conversation: without `--on
|
|
734
734
|
* fountain` this refuses outright, naming the command that posts it there
|
|
735
|
-
* instead (the fountain runtime's own turn already refuses a busy
|
|
735
|
+
* instead (the fountain runtime's own turn already refuses a busy steward,
|
|
736
736
|
* unretried — see chant-fountain-ops).
|
|
737
737
|
*
|
|
738
738
|
* In the local form, the run takes the steward's turn lease
|
|
@@ -771,15 +771,15 @@ async function stewardTurnGate(opName: string, ctx: CommandContext): Promise<Ste
|
|
|
771
771
|
const form = stewardFormFor(owner, env);
|
|
772
772
|
|
|
773
773
|
if (form === "fountain") {
|
|
774
|
-
// `run.on: "fountain"` asks for the steward's
|
|
774
|
+
// `run.on: "fountain"` asks for the steward's conversation as much as the flag
|
|
775
775
|
// does (#2523).
|
|
776
776
|
if ((await chooseOpRuntime(ctx, true))?.on === "fountain") return { ok: true };
|
|
777
777
|
return {
|
|
778
778
|
ok: false,
|
|
779
779
|
message: `Op "${opName}" is one of steward "${owner.name}"'s Ops, which runs on fountain in environment "${env}"`,
|
|
780
780
|
hint:
|
|
781
|
-
`Run \`chant run ${opName} --on fountain\` so it posts to "${owner.name}"'s
|
|
782
|
-
`A plain \`chant run ${opName}\` would run it outside that
|
|
781
|
+
`Run \`chant run ${opName} --on fountain\` so it posts to "${owner.name}"'s conversation. ` +
|
|
782
|
+
`A plain \`chant run ${opName}\` would run it outside that conversation, beside the steward instead of one of its turns.`,
|
|
783
783
|
};
|
|
784
784
|
}
|
|
785
785
|
|
|
@@ -1283,7 +1283,10 @@ async function runComponentSelection(ctx: CommandContext): Promise<number> {
|
|
|
1283
1283
|
}
|
|
1284
1284
|
}
|
|
1285
1285
|
|
|
1286
|
-
|
|
1286
|
+
// A step that refused a plan that moved since it was approved exits 3
|
|
1287
|
+
// (#3997). `--gated-exit` doesn't remap it: the run failed.
|
|
1288
|
+
if (result.success) return 0;
|
|
1289
|
+
return result.run ? failedRunExitCode(result.run.results) : 1;
|
|
1287
1290
|
}
|
|
1288
1291
|
|
|
1289
1292
|
/**
|
|
@@ -1359,9 +1362,10 @@ async function runOpOnResolvedRuntime(ctx: CommandContext): Promise<number> {
|
|
|
1359
1362
|
return 1;
|
|
1360
1363
|
}
|
|
1361
1364
|
|
|
1362
|
-
// #4044: a runner key signs the run's record as it settles, so
|
|
1363
|
-
// can
|
|
1364
|
-
// CHANT_RUNNER_KEY that
|
|
1365
|
+
// #4044: a runner key signs the run's record as it settles, so a reader
|
|
1366
|
+
// can see who ran it. `--key` must be a runner key; the PEM in
|
|
1367
|
+
// CHANT_RUNNER_KEY that trust.json does not list leaves the run unsigned,
|
|
1368
|
+
// with a warning only when ci.green.signedSince asks for signed runs (#4156).
|
|
1365
1369
|
const signRecord = await runSignerFor(ctx);
|
|
1366
1370
|
if (signRecord === null) {
|
|
1367
1371
|
await stewardGate?.release?.();
|
|
@@ -1564,10 +1568,13 @@ const RUNNER_KEY_ENV = "CHANT_RUNNER_KEY";
|
|
|
1564
1568
|
* What signs the run's record (#4044): the runner key `--key` names, or the
|
|
1565
1569
|
* PEM in `CHANT_RUNNER_KEY`, checked against `.chant/trust.json` at `HEAD`.
|
|
1566
1570
|
* Undefined when neither is given. Null, after printing why, when `--key`
|
|
1567
|
-
* is not a runner key
|
|
1568
|
-
*
|
|
1571
|
+
* is not a runner key, or either is not an Ed25519 PEM. A `CHANT_RUNNER_KEY`
|
|
1572
|
+
* that trust.json does not list leaves the run unsigned (#4156): quietly,
|
|
1573
|
+
* with a line under `--verbose`, unless `ci.green.signedSince` asks for
|
|
1574
|
+
* signed runs, which warns. The variable may be set for `ci tick` in a job
|
|
1575
|
+
* that also runs Ops.
|
|
1569
1576
|
*/
|
|
1570
|
-
async function runSignerFor(ctx: CommandContext): Promise<((record: OpRunRecord) => NonNullable<OpRunRecord["statement"]>) | undefined | null> {
|
|
1577
|
+
export async function runSignerFor(ctx: CommandContext): Promise<((record: OpRunRecord) => NonNullable<OpRunRecord["statement"]>) | undefined | null> {
|
|
1571
1578
|
const fromFlag = ctx.args.key !== undefined;
|
|
1572
1579
|
let pem: string | undefined;
|
|
1573
1580
|
try {
|
|
@@ -1577,9 +1584,17 @@ async function runSignerFor(ctx: CommandContext): Promise<((record: OpRunRecord)
|
|
|
1577
1584
|
return null;
|
|
1578
1585
|
}
|
|
1579
1586
|
if (pem === undefined) return undefined;
|
|
1587
|
+
// Loaded only when a key is given, so a run without one loads no workspace code (level 0, #2525).
|
|
1588
|
+
const { opRunSigner, signedRunsRequired } = await import("../../workspace/ci-green");
|
|
1589
|
+
const { loadRunnerKey } = await import("../../workspace/trust/dsse");
|
|
1590
|
+
try {
|
|
1591
|
+
loadRunnerKey(pem);
|
|
1592
|
+
} catch (err) {
|
|
1593
|
+
// A key that is there and is not one is an error either way: the caller asked to sign (#4156).
|
|
1594
|
+
console.error(formatError({ message: `${fromFlag ? `--key ${ctx.args.key}` : RUNNER_KEY_ENV} is not an Ed25519 private key in PEM: ${err instanceof Error ? err.message : String(err)}` }));
|
|
1595
|
+
return null;
|
|
1596
|
+
}
|
|
1580
1597
|
try {
|
|
1581
|
-
// Loaded only when a key is given, so a run without one loads no workspace code (level 0, #2525).
|
|
1582
|
-
const { opRunSigner } = await import("../../workspace/ci-green");
|
|
1583
1598
|
return opRunSigner(process.cwd(), pem);
|
|
1584
1599
|
} catch (err) {
|
|
1585
1600
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1587,7 +1602,10 @@ async function runSignerFor(ctx: CommandContext): Promise<((record: OpRunRecord)
|
|
|
1587
1602
|
console.error(formatError({ message: `the run would not be signed: ${message}` }));
|
|
1588
1603
|
return null;
|
|
1589
1604
|
}
|
|
1590
|
-
|
|
1605
|
+
// #4156: signing is opt-in, so a key trust.json does not list warns only when ci.green.signedSince asks for signed runs.
|
|
1606
|
+
const required = signedRunsRequired(process.cwd());
|
|
1607
|
+
if (required) console.error(formatWarning({ message: `${RUNNER_KEY_ENV} is set, and the run will not be signed, so ci.green.signedSince (${required}) will not count it: ${message}` }));
|
|
1608
|
+
else if (ctx.args.verbose) console.error(`${RUNNER_KEY_ENV} is set, and the run is not signed: ${message}`);
|
|
1591
1609
|
return undefined;
|
|
1592
1610
|
}
|
|
1593
1611
|
}
|
package/src/cli/main.test.ts
CHANGED
|
@@ -666,6 +666,18 @@ describe("resolveCommand", () => {
|
|
|
666
666
|
expect(result!.compound).toBe(false);
|
|
667
667
|
});
|
|
668
668
|
|
|
669
|
+
test("`sync` is an alias for `update` (#3988)", () => {
|
|
670
|
+
const result = resolveCommand(makeArgs({ command: "sync" }), [...testRegistry, { name: "update", handler: noop }]);
|
|
671
|
+
expect(result!.def.name).toBe("update");
|
|
672
|
+
expect(result!.compound).toBe(false);
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
test("`sync <path>` resolves to `update` with the path kept (#3988)", () => {
|
|
676
|
+
const result = resolveCommand(makeArgs({ command: "sync", path: "./proj" }), [...testRegistry, { name: "update", handler: noop }]);
|
|
677
|
+
expect(result!.def.name).toBe("update");
|
|
678
|
+
expect(result!.compound).toBe(false);
|
|
679
|
+
});
|
|
680
|
+
|
|
669
681
|
test("resolves components status as compound command", () => {
|
|
670
682
|
const result = resolveCommand(makeArgs({ command: "components", path: "status" }), testRegistry);
|
|
671
683
|
expect(result!.def.name).toBe("components status");
|
|
@@ -836,6 +848,7 @@ describe("workspace init and ls (#2534)", () => {
|
|
|
836
848
|
"workspace runs",
|
|
837
849
|
"workspace signers",
|
|
838
850
|
"workspace status",
|
|
851
|
+
"workspace trust",
|
|
839
852
|
"workspace upgrade",
|
|
840
853
|
"workspace verify",
|
|
841
854
|
"workspace versions",
|
|
@@ -1020,4 +1033,10 @@ describe("chant --help", () => {
|
|
|
1020
1033
|
const commands = new Set(commandRegistry.map((def) => def.name.split(" ")[0]));
|
|
1021
1034
|
expect([...commands].filter((name) => !listed.has(name))).toEqual([]);
|
|
1022
1035
|
});
|
|
1036
|
+
|
|
1037
|
+
test("names `sync` as the alias of `update` (#3988)", () => {
|
|
1038
|
+
const source = readFileSync(join(import.meta.dirname, "main.ts"), "utf-8");
|
|
1039
|
+
const help = source.slice(source.indexOf("function printHelp("), source.indexOf("Options:", source.indexOf("function printHelp(")));
|
|
1040
|
+
expect(help).toMatch(/^ {2}update \(alias: sync\)/m);
|
|
1041
|
+
});
|
|
1023
1042
|
});
|
package/src/cli/main.ts
CHANGED
|
@@ -238,34 +238,60 @@ CI:
|
|
|
238
238
|
its window, newest first, each green, revoked or
|
|
239
239
|
undecided with its required phases' results. Reads
|
|
240
240
|
local tags; --forge reads check runs for undecided
|
|
241
|
-
commits, and source runs reads the
|
|
242
|
-
|
|
241
|
+
commits, and source runs reads the Op runs on the
|
|
242
|
+
local chant/lifecycle, signed or not. --since replaces the window.
|
|
243
243
|
ci tick [--dry-run] [--forge github] [--source checks|runs] [--key <runner key.pem>]
|
|
244
244
|
Tag each commit within ci.green's window whose
|
|
245
245
|
required phases all passed ci/green/<sha>, and revoke
|
|
246
246
|
a green one that now fails ci/revoked/<sha>. Reads
|
|
247
247
|
check runs from the forge, or with source runs
|
|
248
248
|
(ci.green.source or --source) the Op runs on
|
|
249
|
-
chant/lifecycle
|
|
249
|
+
chant/lifecycle; with ci.green.signedSince, the ones a
|
|
250
|
+
runner key signed. Pushes the
|
|
250
251
|
tags. --key, or the PEM in CHANT_RUNNER_KEY, signs
|
|
251
252
|
each tag with a runner key.
|
|
252
253
|
ci run plan|apply --env <env> --base <sha> [--pr <n>] [--commit <sha>]
|
|
253
|
-
[--forge github|gitlab|forgejo] [--gate <name>] [--
|
|
254
|
-
[--events-json] [--report-url <url>] [--image <image>]
|
|
254
|
+
[--forge github|gitlab|forgejo] [--gate <name>] [--wave-gate <name>]
|
|
255
|
+
[--branch <name>] [--events-json] [--report-url <url>] [--image <image>]
|
|
255
256
|
[--before-script <line>]... [--pr-build]
|
|
256
257
|
[--pr-build-command <line>] [--chant <command>]
|
|
257
|
-
[--pass-env <NAME>]... [--key <runner key.pem>]
|
|
258
|
+
[--pass-env <NAME>]... [--secrets <file>] [--key <runner key.pem>]
|
|
259
|
+
[-o <dir>] [--wave <k>] [--no-require-review]
|
|
260
|
+
[--no-status] [--status-url <url template>]
|
|
261
|
+
[--in-place | --local] [--no-push] [--verbose]
|
|
258
262
|
Run the pull-request pipeline's plan or apply job as
|
|
259
263
|
a pipeline Op in a fresh worktree of --commit (default
|
|
260
264
|
HEAD) with a clean environment. --base and --pr stand
|
|
261
265
|
in for the forge's event values. --pr-build runs
|
|
262
266
|
the job's build and lint lines first. Records the run as
|
|
263
|
-
pr-plan or pr-apply on chant/lifecycle,
|
|
264
|
-
|
|
265
|
-
|
|
267
|
+
pr-plan or pr-apply on chant/lifecycle, which ci tick
|
|
268
|
+
from runs counts, signed when --key or CHANT_RUNNER_KEY
|
|
269
|
+
gives a runner key, and copies the report to -o
|
|
270
|
+
(default .chant/pr). --in-place runs in this checkout
|
|
271
|
+
at HEAD, as a generated forge job does, and names the
|
|
272
|
+
forge's run in the record; --no-push keeps the record
|
|
273
|
+
local. Exits with the command's code. With a forge
|
|
274
|
+
token it sets a chant/pr-plan or chant/pr-apply commit
|
|
275
|
+
status (pending, then success or failure); --no-status
|
|
276
|
+
turns that off, and --status-url links it.
|
|
277
|
+
--secrets (or CHANT_CI_SECRETS) names a dotenv or
|
|
278
|
+
JSON file only its owner can read whose values the
|
|
279
|
+
job gets, and which are shown as *** in the events,
|
|
280
|
+
the record, the report and the output.
|
|
281
|
+
With --report-url it also adds the run to
|
|
282
|
+
<url>/runs/index.json, newest first, cut to the
|
|
283
|
+
newest --run-index-limit entries (default 200).
|
|
284
|
+
Without --report-url, the workspace's ci.reportUrl.
|
|
285
|
+
--local runs the job on this machine: the record is
|
|
286
|
+
kept on the local chant/lifecycle with runOrigin
|
|
287
|
+
local and is never pushed, no status is set, nothing
|
|
288
|
+
goes under a report URL, and ci tick does not count it.
|
|
289
|
+
ci runs [--report-url <url>] [--json]
|
|
290
|
+
List the runs in the index at the report URL (default
|
|
291
|
+
the workspace's ci.reportUrl), newest first.
|
|
266
292
|
ci serve --forge forgejo|github --repo <owner/repo> --env <env>
|
|
267
293
|
[--interval <s>] [--max-jobs <n>] [--state <dir>]
|
|
268
|
-
[--author <login>]... [--once] [--rerun-failed]
|
|
294
|
+
[--author <login>]... [--once] [--rerun-failed] [--unsigned]
|
|
269
295
|
[ci run's job flags]
|
|
270
296
|
Poll the forge for open pull requests and the target
|
|
271
297
|
branch's head, and run ci run plan for each new pull
|
|
@@ -275,7 +301,16 @@ CI:
|
|
|
275
301
|
each apply's resume record there, and runs at most
|
|
276
302
|
--max-jobs (default 1) jobs at once. --once runs one
|
|
277
303
|
poll and waits for its jobs. --rerun-failed runs a
|
|
278
|
-
failed apply or plan again.
|
|
304
|
+
failed apply or plan again. Signs runs with a runner
|
|
305
|
+
key when .chant/trust.json at the target's head lists
|
|
306
|
+
it, and serves unsigned otherwise; with
|
|
307
|
+
ci.green.signedSince it refuses to start without one
|
|
308
|
+
ci serve|run --setup-key [--trust [--principal <name>]] [--state <dir>] [--branch <name>]
|
|
309
|
+
Make the host's runner key (Ed25519, mode 0600) in the
|
|
310
|
+
state directory when there is none and print its public
|
|
311
|
+
key. --trust commits its runner entry on a branch of its
|
|
312
|
+
own and pushes it for review; the target branch is never
|
|
313
|
+
written
|
|
279
314
|
ci workflow [--workflow <name>]... [--output <file>] Write
|
|
280
315
|
[--chant <command>] [--install <command>]
|
|
281
316
|
[--token-secret <NAME> | --app-id-var <VAR> --app-key-secret <NAME>]
|
|
@@ -701,6 +736,13 @@ Workspace (level 1, #2524):
|
|
|
701
736
|
threshold of the old one. A commit an agent run made
|
|
702
737
|
is attested by the run's signed statement; with
|
|
703
738
|
attested-runs, only those commits must be
|
|
739
|
+
workspace trust runner add <principal> --key <ssh-ed25519 line or file> [--class runner|service] [--by <principal>] [--expect <digest>] [--dry-run]
|
|
740
|
+
workspace trust runner remove <principal> [--by <principal>] [--expect <digest>] [--dry-run]
|
|
741
|
+
List a runner's key under runners in .chant/trust.json,
|
|
742
|
+
granting a runner the runner role, or take both away,
|
|
743
|
+
editing the file in place. --expect refuses the write
|
|
744
|
+
when the file's digest has changed. Prints the
|
|
745
|
+
trust-write document and the new digest; never commits
|
|
704
746
|
workspace signers [--json] [rotate [--threshold <n>] | sign --key <file>]
|
|
705
747
|
Show the signer history at base, or propose the next
|
|
706
748
|
signer set and sign it with a current signer's key
|
|
@@ -899,6 +941,10 @@ Component release ledger + status:
|
|
|
899
941
|
member's loop, gate op pr-<n>-<member>;
|
|
900
942
|
--resume <file> finishes an apply that failed
|
|
901
943
|
partway under the same approval;
|
|
944
|
+
--wave-gate <name>: apply a wave at a time, each
|
|
945
|
+
planned after the one before applied, a moved
|
|
946
|
+
wave waiting at <name>-wave-<k>, with --wave <k>
|
|
947
|
+
for one wave per CI job;
|
|
902
948
|
--events-json, both commands: the run-event
|
|
903
949
|
stream on stdout; --report-url <s3 or file URL>,
|
|
904
950
|
both commands: copy the report, the change set
|
|
@@ -968,7 +1014,8 @@ Servers:
|
|
|
968
1014
|
serve mcp Start the MCP server (stdio)
|
|
969
1015
|
|
|
970
1016
|
Project:
|
|
971
|
-
update
|
|
1017
|
+
update (alias: sync) Sync lexicon types into .chant/types/ (does not install
|
|
1018
|
+
packages: run npm install first)
|
|
972
1019
|
doctor Check project health and configuration
|
|
973
1020
|
|
|
974
1021
|
Options:
|
|
@@ -1187,6 +1234,9 @@ async function loadPluginsOrExit(path: string, lexicon?: string): Promise<import
|
|
|
1187
1234
|
plugins = await loadPlugins(lexiconNames);
|
|
1188
1235
|
} catch (error) {
|
|
1189
1236
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1237
|
+
// #4116: a lexicon package the project has not installed is named as such first.
|
|
1238
|
+
const { reportUninstalledDependencies } = await import("./dependencies");
|
|
1239
|
+
reportUninstalledDependencies([errorMessage], resolve(path));
|
|
1190
1240
|
console.error(formatError({ message: errorMessage }));
|
|
1191
1241
|
process.exit(1);
|
|
1192
1242
|
}
|
|
@@ -1335,6 +1385,8 @@ export const commandRegistry: CommandDef[] = [
|
|
|
1335
1385
|
{ name: "ci workflow", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/ci-green-cli")).runCiWorkflow(ctx) },
|
|
1336
1386
|
// #4094: one pull-request job as a pipeline Op in a fresh checkout, the chant runner's first slice.
|
|
1337
1387
|
{ name: "ci run", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/ci-run")).runCiRun(ctx) },
|
|
1388
|
+
// #4098: the run index at the report URL.
|
|
1389
|
+
{ name: "ci runs", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/ci-run-index")).runCiRuns(ctx) },
|
|
1338
1390
|
{ name: "ci serve", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/ci-serve")).runCiServe(ctx) },
|
|
1339
1391
|
// #4026: the release cut from a green commit (ws-104). Imported on first use.
|
|
1340
1392
|
{ name: "release cut", runsNoConfig: true, handler: async (ctx) => (await import("./handlers/release")).runReleaseCut(ctx) },
|
|
@@ -1439,6 +1491,8 @@ export const commandRegistry: CommandDef[] = [
|
|
|
1439
1491
|
// #2548 — an agent session's member, write scope and spec, for a session that resumes.
|
|
1440
1492
|
{ name: "workspace agent", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/agent-cli")).runWorkspaceAgent(ctx) },
|
|
1441
1493
|
{ name: "workspace verify", handler: async (ctx) => (await import("../workspace/trust/verify-cli")).runWorkspaceVerify(ctx) },
|
|
1494
|
+
// #3510 — a runner entry of .chant/trust.json, written through chant so a box repo never edits it by hand (ws-074).
|
|
1495
|
+
{ name: "workspace trust", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/trust/trust-write")).runWorkspaceTrust(ctx) },
|
|
1442
1496
|
{ name: "workspace signers", handler: async (ctx) => (await import("../workspace/trust/signers-cli")).runWorkspaceSigners(ctx) },
|
|
1443
1497
|
{ name: "workspace evidence", handler: async (ctx) => (await import("../workspace/trust/evidence-cli")).runWorkspaceEvidence(ctx) },
|
|
1444
1498
|
|
|
@@ -1691,6 +1745,13 @@ const isMain = isEntryPoint(process.argv[1], import.meta.url);
|
|
|
1691
1745
|
if (isMain) {
|
|
1692
1746
|
main().catch(async (err) => {
|
|
1693
1747
|
const verbose = process.argv.includes("--verbose") || process.argv.includes("-v");
|
|
1748
|
+
// #4116: a failure to import a package the project has not installed is named as such first.
|
|
1749
|
+
try {
|
|
1750
|
+
const { reportUninstalledDependencies } = await import("./dependencies");
|
|
1751
|
+
reportUninstalledDependencies([err instanceof Error ? err.message : String(err)], process.cwd());
|
|
1752
|
+
} catch {
|
|
1753
|
+
// The original error below is what matters.
|
|
1754
|
+
}
|
|
1694
1755
|
if (verbose && err instanceof Error && err.stack) {
|
|
1695
1756
|
console.error(err.stack);
|
|
1696
1757
|
} else {
|