@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
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import { createHash } from "node:crypto";
|
|
29
29
|
import { lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
30
30
|
import { join } from "node:path";
|
|
31
|
+
import { isWorkDir } from "../lifecycle/work-dir";
|
|
31
32
|
|
|
32
33
|
export type IntegrityAlgorithm = "sha256" | "sha384" | "sha512";
|
|
33
34
|
|
|
@@ -39,7 +40,7 @@ export function parseIntegrity(value: string): { algorithm: IntegrityAlgorithm;
|
|
|
39
40
|
return m ? { algorithm: m[1] as IntegrityAlgorithm, digest: m[2] } : undefined;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
/** Directories a plugin hash never looks inside. */
|
|
43
|
+
/** Directories a plugin hash never looks inside, with `.chant/work`, where work worktrees are (#2893). */
|
|
43
44
|
const SKIPPED_DIRS = new Set(["node_modules", ".git"]);
|
|
44
45
|
|
|
45
46
|
/** Thrown when a path can't be hashed: it is missing, or holds a symbolic link. */
|
|
@@ -50,7 +51,7 @@ function walk(dir: string, prefix: string, out: string[]): void {
|
|
|
50
51
|
const rel = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
51
52
|
if (entry.isSymbolicLink()) throw new PinHashError(`${rel} is a symbolic link, which a pinned plugin may not contain`);
|
|
52
53
|
if (entry.isDirectory()) {
|
|
53
|
-
if (!SKIPPED_DIRS.has(entry.name)) walk(join(dir, entry.name), rel, out);
|
|
54
|
+
if (!SKIPPED_DIRS.has(entry.name) && !isWorkDir(rel)) walk(join(dir, entry.name), rel, out);
|
|
54
55
|
} else if (entry.isFile()) {
|
|
55
56
|
out.push(rel);
|
|
56
57
|
}
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"description": "The change-set digest of what this stage planned: computePlanDigest(\"change-set\", ...) over every member's { member, planDigest }. What the gate binds."
|
|
34
34
|
},
|
|
35
35
|
"status": {
|
|
36
|
-
"enum": ["planned", "plan-failed", "nothing", "applied", "failed", "refused"],
|
|
37
|
-
"description": "planned: every member planned (plan stage). plan-failed: a member failed to plan, so nothing can apply. nothing: no component deploys what the change touches. applied: every member applied. failed: a member failed to apply and its dependents did not run. refused: nothing applied, see refusal."
|
|
36
|
+
"enum": ["planned", "plan-failed", "nothing", "applied", "failed", "wave-applied", "refused", "waiting"],
|
|
37
|
+
"description": "planned: every member planned (plan stage). plan-failed: a member failed to plan, so nothing can apply. nothing: no component deploys what the change touches. applied: every member applied. failed: a member failed to apply and its dependents did not run. wave-applied (#4003, chant 0.124.32 and newer): with --wave-gate and --wave, the wave applied and a later wave is still to run. refused: nothing applied, see refusal. waiting (#3999): the approved plans applied except members held back after an upstream applied, which wait on an approval of the plan the last of rounds made."
|
|
38
38
|
},
|
|
39
39
|
"refusal": {
|
|
40
|
-
"enum": ["not-approved", "plan-changed", "not-a-reviewer", "no-pull-request"],
|
|
41
|
-
"description": "Why an apply refused. plan-changed: an approval stands for another digest, so the plan changed after review."
|
|
40
|
+
"enum": ["not-approved", "plan-changed", "not-a-reviewer", "author-approved", "no-pull-request", "wave-not-approved", "wave-plan-changed", "earlier-wave"],
|
|
41
|
+
"description": "Why an apply refused. plan-changed: an approval stands for another digest, so the plan changed after review. author-approved (#4000): under --require-review, an approval names the pull request's author or an author or committer of one of its commits. With --wave-gate (#4003, chant 0.124.32 and newer): wave-not-approved, a wave's new plan is not covered by the approved one and no approval of its gate stands for it; wave-plan-changed, an approval of its gate stands for another digest; earlier-wave, an earlier wave has work left or no record says it applied. The waves that applied before a wave refused stay applied; waves says which."
|
|
42
42
|
},
|
|
43
43
|
"message": { "type": "string", "description": "The refusal or failure as printed." },
|
|
44
44
|
"resumed": {
|
|
@@ -54,6 +54,61 @@
|
|
|
54
54
|
"description": "Added within contract version 1 by #4104, written by chant 0.124.30 and newer. Per dependency a member was planned against, the names of the outputs read from the deployed estate through its capability's outputs, sorted. A dependency outside the change is listed here too, and one whose stack is not deployed yet lists []. Outputs given with --seed-outputs, or carried from an earlier attempt's record, are not listed. Absent when no dependency was read, and on a resumed apply.",
|
|
55
55
|
"additionalProperties": { "type": "array", "items": { "type": "string" } }
|
|
56
56
|
},
|
|
57
|
+
"waveGate": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"description": "Added within contract version 1 by #4003, written by chant 0.124.32 and newer. pr-apply --wave-gate <name>: the apply runs a wave at a time, each planned after the one before it applied, and wave k waits on <name>-wave-<k> under op when its plan moved. Absent without it."
|
|
60
|
+
},
|
|
61
|
+
"waves": {
|
|
62
|
+
"type": "array",
|
|
63
|
+
"description": "With waveGate: each wave reached so far, in order, with the approval it applied or waits under.",
|
|
64
|
+
"items": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"required": ["wave", "components", "status", "approval", "gate", "digest"],
|
|
67
|
+
"properties": {
|
|
68
|
+
"wave": { "type": "integer", "minimum": 1, "description": "1-based, from selection.waves." },
|
|
69
|
+
"components": { "type": "array", "items": { "type": "string" }, "description": "The components planned in it on the run that reached it." },
|
|
70
|
+
"status": { "enum": ["applied", "failed", "plan-failed", "waiting"] },
|
|
71
|
+
"approval": { "enum": ["pull-request", "wave"], "description": "pull-request: the approved change set covers the wave's plan, and it applies under the pull request's approval (gate and digest are the report's). wave: its plan moved, and it needs an approval of its own gate for its new plan." },
|
|
72
|
+
"gate": { "type": "string", "description": "The gate it applied or waits under: the report's gate, or <waveGate>-wave-<k>." },
|
|
73
|
+
"digest": { "type": "string", "pattern": "^(?:jcs1-)?sha256:[0-9a-f]{64}$", "description": "The digest that gate binds: the report's digest, or the change-set digest of the wave's new plan." },
|
|
74
|
+
"approvedBy": { "type": "array", "items": { "type": "string" } },
|
|
75
|
+
"approved": { "type": "string", "pattern": "^(?:jcs1-)?sha256:[0-9a-f]{64}$", "description": "Waiting: an approval of the wave's gate stands for this other digest." },
|
|
76
|
+
"moved": { "type": "array", "items": { "type": "string" }, "description": "Why the approved change set does not cover the wave's plan, one line per member." }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"plannedOutputs": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"description": "Added within contract version 1 by #3999, written by chant 0.124.32 and newer. Per dependency in the change that a member was planned against, the names of the outputs its plan knows, sorted. An output a member reads that is not listed is known only after apply, and the member lists it under unknownInputs. Absent when no member read one.",
|
|
83
|
+
"additionalProperties": { "type": "array", "items": { "type": "string" } }
|
|
84
|
+
},
|
|
85
|
+
"rounds": {
|
|
86
|
+
"type": "array",
|
|
87
|
+
"description": "Added within contract version 1 by #3999. Apply stage: the rounds planned after an upstream applied, in order. A member whose plan read an upstream output that applied as another value, or was known only after apply, is held back and planned again against the outputs the apply left; each round has its own gate and digest. Absent when nothing was held.",
|
|
88
|
+
"items": {
|
|
89
|
+
"type": "object",
|
|
90
|
+
"required": ["round", "gate", "digest", "components", "approval", "status", "changeSet"],
|
|
91
|
+
"properties": {
|
|
92
|
+
"round": { "type": "integer", "minimum": 2 },
|
|
93
|
+
"gate": { "type": "string", "description": "The gate the round waits on: <gate>-round-<round>." },
|
|
94
|
+
"digest": { "type": "string", "pattern": "^(?:jcs1-)?sha256:[0-9a-f]{64}$", "description": "The change-set digest of what the round planned. What its gate binds." },
|
|
95
|
+
"components": { "type": "array", "items": { "type": "string" }, "description": "The components planned again, in apply order." },
|
|
96
|
+
"approval": {
|
|
97
|
+
"type": "object",
|
|
98
|
+
"required": ["status"],
|
|
99
|
+
"description": "Where the approval stands for digest, as the top-level approval.",
|
|
100
|
+
"properties": {
|
|
101
|
+
"status": { "enum": ["approved", "changed", "pending"] },
|
|
102
|
+
"approvedBy": { "type": "array", "items": { "type": "string" } },
|
|
103
|
+
"approved": { "type": "string", "pattern": "^(?:jcs1-)?sha256:[0-9a-f]{64}$" }
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"status": { "enum": ["applied", "failed", "waiting", "plan-failed"] },
|
|
107
|
+
"message": { "type": "string", "description": "With waiting: why an approval that stands does not count under --require-review." },
|
|
108
|
+
"changeSet": { "type": "object", "description": "The change-set document the round planned. Its digest equals digest." }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
57
112
|
"approval": {
|
|
58
113
|
"type": "object",
|
|
59
114
|
"required": ["status"],
|
|
@@ -85,7 +140,7 @@
|
|
|
85
140
|
"member": { "type": "string", "description": "The member's name in the change set: the root, for a terraform-family step." },
|
|
86
141
|
"component": { "type": "string" },
|
|
87
142
|
"planDigest": { "type": ["string", "null"], "description": "What a gate on this member alone binds. null when it failed to plan." },
|
|
88
|
-
"status": { "enum": ["planned", "plan-failed", "applied", "failed", "blocked"] },
|
|
143
|
+
"status": { "enum": ["planned", "plan-failed", "applied", "failed", "blocked", "waiting"], "description": "waiting (#3999): held back because its plan read an upstream output that applied as another value, or was known only after apply; it waits on an approval of its plan in rounds." },
|
|
89
144
|
"counts": {
|
|
90
145
|
"type": "object",
|
|
91
146
|
"required": ["create", "update", "replace", "delete"],
|
|
@@ -100,8 +155,20 @@
|
|
|
100
155
|
"inputsMoved": {
|
|
101
156
|
"type": "array",
|
|
102
157
|
"items": { "type": "string" },
|
|
103
|
-
"description": "Apply stage: components this one depends on whose outputs changed when they applied. It applied the plan it was approved with; the next run plans it against the new values."
|
|
104
|
-
}
|
|
158
|
+
"description": "Apply stage: components this one depends on whose outputs changed when they applied. It applied the plan it was approved with; the next run plans it against the new values. Not written by chant 0.124.32 and newer, which holds such a member back instead (held, round)."
|
|
159
|
+
},
|
|
160
|
+
"unknownInputs": {
|
|
161
|
+
"type": "array",
|
|
162
|
+
"items": { "type": "string" },
|
|
163
|
+
"description": "#3999: the upstream outputs its plan read that the change's plans could not say, as <component>.<output>, sorted. Known only after that upstream applies; the approval does not cover what this member does with them."
|
|
164
|
+
},
|
|
165
|
+
"buildInputs": {
|
|
166
|
+
"type": "array",
|
|
167
|
+
"items": { "type": "string" },
|
|
168
|
+
"description": "#3999: the published artifact identities its plan read from a build in the same change, as <component>.publish.<field>, sorted. Known only after the build runs and never approved again: the build's provenance binds them, so the member applies without a later round."
|
|
169
|
+
},
|
|
170
|
+
"round": { "type": "integer", "minimum": 2, "description": "Apply stage (#3999): the round whose plan it applied or waits on, when not the first. planDigest and counts are that round's." },
|
|
171
|
+
"held": { "type": "string", "description": "Apply stage (#3999): why the first round's plan did not apply." }
|
|
105
172
|
}
|
|
106
173
|
}
|
|
107
174
|
},
|
|
@@ -44,6 +44,7 @@ import { PLANTABLE_REASON_CODES } from "./box-factory";
|
|
|
44
44
|
import { BOX_FACTORY_ERROR_CODES } from "./box-factory-write";
|
|
45
45
|
import { BOX_LISTING_ERROR_CODES } from "./box-listing";
|
|
46
46
|
import { DECLARATION_WRITE_ERROR_CODES } from "./declaration-write";
|
|
47
|
+
import { TRUST_WRITE_ERROR_CODES } from "./trust/trust-write";
|
|
47
48
|
import { BOX_PUBLISH_ERROR_CODES } from "./box-publish";
|
|
48
49
|
import { WRITE_LOCK_ERROR_CODES } from "./write-lock-cli";
|
|
49
50
|
import { WIP_ERROR_CODES, WIP_WRITE_ERROR_CODES } from "./wip";
|
|
@@ -115,6 +116,7 @@ const PER_COMMAND: Record<string, readonly string[]> = {
|
|
|
115
116
|
PLANTABLE_REASON_CODES,
|
|
116
117
|
BOX_LISTING_ERROR_CODES,
|
|
117
118
|
DECLARATION_WRITE_ERROR_CODES,
|
|
119
|
+
TRUST_WRITE_ERROR_CODES,
|
|
118
120
|
BOX_FACTORY_ERROR_CODES,
|
|
119
121
|
BOX_PUBLISH_ERROR_CODES,
|
|
120
122
|
WRITE_LOCK_ERROR_CODES,
|
|
@@ -232,8 +232,8 @@ export const REASONS = {
|
|
|
232
232
|
// A box that holds a credential (check, #2726). Each is also a WSP finding.
|
|
233
233
|
"box-credential-declared": "A file in a box member's directory carries a literal secret: a credential's shape, or a literal where a credential goes. A variable or secret-manager reference is not one.",
|
|
234
234
|
"box-capability-unbrokered": "A capability in a member's box block names no broker, so the box would hold its credential.",
|
|
235
|
-
// The credential fountain itself hands a
|
|
236
|
-
"box-fountain-callback-undeclared": "A box member builds a fountain Box, whose
|
|
235
|
+
// The credential fountain itself hands a box (check, #2780). Also a WSP finding.
|
|
236
|
+
"box-fountain-callback-undeclared": "A box member builds a fountain Box, whose sandbox fountain gives a callback token scoped to its owner, and the member's box block does not declare the fountain-callback capability brokered by fountain with scope owner.",
|
|
237
237
|
// A box's intent, the decision record its box block names (check, #2850). Each is also a WSP finding.
|
|
238
238
|
"box-intent-unknown": "A box block names an intent, and no record of a declared kind named decision has that id.",
|
|
239
239
|
"box-intent-unconstrained": "The decision record a box names as its intent constrains no member or path of this workspace: no member: entry for a declared member and no path: entry at, above or inside one's directory.",
|
|
@@ -271,6 +271,11 @@ export const REASONS = {
|
|
|
271
271
|
// A declaration's members and hosts written through chant (member add|remove, host set, #3596): why the write was refused. Nothing is written.
|
|
272
272
|
"member-exists": "member add names a member the declaration already has, with an entry other than the one given.",
|
|
273
273
|
"member-unknown": "member remove names a member the declaration does not declare.",
|
|
274
|
+
// A runner entry of .chant/trust.json written through chant (trust runner add|remove, #3510): why the write was refused. Nothing is written.
|
|
275
|
+
"trust-config-invalid": ".chant/trust.json in the working tree is not JSON or does not match its schema, or the write would make it so.",
|
|
276
|
+
"trust-conflict": "--expect named a digest .chant/trust.json no longer has: another write changed it after the caller read it. The refusal names the digest it has now.",
|
|
277
|
+
"runner-exists": "trust runner add names a principal .chant/trust.json already lists with another key or class, or gives a key it lists for another principal.",
|
|
278
|
+
"runner-unknown": "trust runner remove names a principal .chant/trust.json lists no runner for.",
|
|
274
279
|
// A box's factory written through chant (box factory set, #3600): why the write was refused. Nothing is written.
|
|
275
280
|
"factory-member-unknown": "box factory set names a member the declaration does not declare.",
|
|
276
281
|
"factory-box-missing": "box factory set names a member whose entry declares no box block, so it has no factory.",
|
|
@@ -314,8 +319,8 @@ export const REASONS = {
|
|
|
314
319
|
"evidence-payload-type": "The envelope's payload type is not application/vnd.in-toto+json.",
|
|
315
320
|
"evidence-statement-invalid": "The payload is not an in-toto Statement v1 with chant's runner-evidence predicate, or has a field the predicate does not define.",
|
|
316
321
|
"evidence-runner-mismatch": "The statement names a runner other than the one whose key signed it.",
|
|
317
|
-
"runner-key-invalid": "The key given to evidence sign or runs sign is not an Ed25519 private key in PEM.",
|
|
318
|
-
"runner-key-is-signer": "The key given to evidence sign
|
|
322
|
+
"runner-key-invalid": "The key given to evidence sign or runs sign is not an Ed25519 private key in PEM, or the key given to trust runner add is not an ssh-ed25519 public key or an Ed25519 key in PEM.",
|
|
323
|
+
"runner-key-is-signer": "The key given to evidence sign, runs sign or trust runner add is a person's key in the signers file, or trust runner add names a signer. Evidence and run statements are signed by a service or CI identity.",
|
|
319
324
|
"runner-key-unlisted": "The policy at base lists no runner with the key given to evidence sign or runs sign.",
|
|
320
325
|
// The lineage lock (check).
|
|
321
326
|
"lock-invalid": "The lineage lock can't be read.",
|
|
@@ -54,7 +54,9 @@
|
|
|
54
54
|
"description": "github: a GitHub Actions run, also the spelling Forgejo Actions uses. gitlab: a CI pipeline. op: a steward's run, named by its Op run id. local: a run on the machine that started it, outside CI. chant: a pull-request job the chant runner ran (chant ci run, #4094), named by its Op run id; added within contract version 1 and written by chant 0.124.30 and newer, so a reader that switches on forge keeps a case for a value it does not know."
|
|
55
55
|
},
|
|
56
56
|
"repo": { "type": "string", "description": "The repository or project slug on the forge, when the environment named one." },
|
|
57
|
-
"url": { "type": "string", "description": "A link to the run, when the environment gave enough to build one." }
|
|
57
|
+
"url": { "type": "string", "description": "A link to the run, when the environment gave enough to build one." },
|
|
58
|
+
"jobId": { "type": "string", "description": "The forge's id for the job within the run: GITHUB_JOB on GitHub and Forgejo, CI_JOB_ID on GitLab. Added within contract 1 by #4156 and written by chant 0.124.32 and newer, only by chant ci run in a forge job." },
|
|
59
|
+
"attempt": { "type": "integer", "minimum": 1, "description": "The run's attempt, GITHUB_RUN_ATTEMPT on GitHub and Forgejo. Added within contract 1 by #4156 and written by chant 0.124.32 and newer, only by chant ci run in a forge job." }
|
|
58
60
|
}
|
|
59
61
|
},
|
|
60
62
|
"settleStatus": {
|
|
@@ -451,7 +451,9 @@
|
|
|
451
451
|
"properties": {
|
|
452
452
|
"forge": { "enum": ["github", "gitlab", "op", "local", "chant"], "description": "chant: a pull-request job the chant runner ran (chant ci run, #4094), added within contract 1 and written by chant 0.124.30 and newer." },
|
|
453
453
|
"repo": { "type": "string" },
|
|
454
|
-
"url": { "type": "string" }
|
|
454
|
+
"url": { "type": "string" },
|
|
455
|
+
"jobId": { "type": "string", "description": "The forge's job id within the run. Added within contract 1 by #4156 and written by chant 0.124.32 and newer, only by chant ci run in a forge job." },
|
|
456
|
+
"attempt": { "type": "integer", "minimum": 1, "description": "The run's attempt on GitHub and Forgejo. Added within contract 1 by #4156 and written by chant 0.124.32 and newer, only by chant ci run in a forge job." }
|
|
455
457
|
}
|
|
456
458
|
},
|
|
457
459
|
"failure": {
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #3510: a runner entry of `.chant/trust.json` written through chant, with
|
|
3
|
+
* `--by` and `--expect` as the other working-tree writes take them, and read
|
|
4
|
+
* back by the readers that use runners: `readTrustPolicy`, `ciTagSigner` and
|
|
5
|
+
* `ci tick --source runs`.
|
|
6
|
+
*
|
|
7
|
+
* Each test works in its own temp repository (a bare origin where it pushes),
|
|
8
|
+
* and every key is a throwaway one. Nothing is written in process.cwd().
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execFileSync } from "node:child_process";
|
|
12
|
+
import { createPublicKey, generateKeyPairSync } from "node:crypto";
|
|
13
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
16
|
+
import { parseArgs } from "../../cli/parse-args";
|
|
17
|
+
import { cleanScratch, contract, declaration, git, scratchDir, writeFiles } from "../__fixtures__/contract-repo";
|
|
18
|
+
import { ciTagSigner, ciTick, opRunSigner } from "../ci-green";
|
|
19
|
+
import { workingTreeSource } from "../record-source";
|
|
20
|
+
import schema from "../trust-write.schema.json";
|
|
21
|
+
import { runOpLocally } from "../../op/local-executor";
|
|
22
|
+
import type { ActivityFn } from "../../op/activity-registry";
|
|
23
|
+
import { sshFromEd25519 } from "./dsse";
|
|
24
|
+
import { readTrustPolicy } from "./policy";
|
|
25
|
+
import { trustDigest, trustWrite, type TrustWriteDocument, type TrustWriteRequest } from "./trust-write";
|
|
26
|
+
|
|
27
|
+
afterAll(cleanScratch);
|
|
28
|
+
|
|
29
|
+
const { expectValid } = contract(schema);
|
|
30
|
+
|
|
31
|
+
function runnerKey(): { pem: string; ssh: string } {
|
|
32
|
+
const { privateKey } = generateKeyPairSync("ed25519");
|
|
33
|
+
const jwk = createPublicKey(privateKey).export({ format: "jwk" }) as { x: string };
|
|
34
|
+
return { pem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(), ssh: sshFromEd25519(Buffer.from(jwk.x, "base64url")) };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function emptyRepo(files: Record<string, string> = {}): string {
|
|
38
|
+
const root = scratchDir("chant-trust-write-");
|
|
39
|
+
git(root, "init", "-q");
|
|
40
|
+
writeFiles(root, files);
|
|
41
|
+
return root;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function run(root: string, req: Omit<TrustWriteRequest, "cwd">): TrustWriteDocument {
|
|
45
|
+
const doc = trustWrite({ ...req, cwd: root });
|
|
46
|
+
expectValid(doc);
|
|
47
|
+
return doc;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const code = (doc: TrustWriteDocument) => ("error" in doc ? doc.error.code : null);
|
|
51
|
+
const trustOf = (root: string) => JSON.parse(readFileSync(join(root, ".chant/trust.json"), "utf-8")) as Record<string, unknown>;
|
|
52
|
+
const ok = (doc: TrustWriteDocument) => {
|
|
53
|
+
if ("error" in doc) throw new Error(`${doc.error.code}: ${doc.error.message}`);
|
|
54
|
+
return doc;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
describe("trust runner add and remove (#3510)", () => {
|
|
58
|
+
test("add writes the entry and the runner role, remove takes both away, and a stale --expect is refused", () => {
|
|
59
|
+
const root = emptyRepo();
|
|
60
|
+
const bot = runnerKey();
|
|
61
|
+
writeFiles(root, { "keys/bot.pub": `${bot.ssh} bot@host\n` });
|
|
62
|
+
|
|
63
|
+
const added = ok(run(root, { action: "runner add", name: "ci:acme/app", key: "keys/bot.pub", by: "github:alex" }));
|
|
64
|
+
expect(added).toMatchObject({ action: "runner add", changed: true, previous: null, entry: { principal: "ci:acme/app", class: "runner", key: bot.ssh }, runnerRole: true, paths: [".chant/trust.json"] });
|
|
65
|
+
expect(added.previousDigest).toBe(trustDigest(null));
|
|
66
|
+
expect(added.digest).toBe(trustDigest(readFileSync(join(root, ".chant/trust.json"), "utf-8")));
|
|
67
|
+
expect(trustOf(root)).toEqual({ schema: 1, runners: [{ principal: "ci:acme/app", class: "runner", key: bot.ssh }], roles: { runner: ["ci:acme/app"] } });
|
|
68
|
+
|
|
69
|
+
// The same entry again changes nothing, and says so.
|
|
70
|
+
const again = ok(run(root, { action: "runner add", name: "ci:acme/app", key: bot.ssh, expect: added.digest }));
|
|
71
|
+
expect(again).toMatchObject({ changed: false, paths: [], digest: added.digest });
|
|
72
|
+
|
|
73
|
+
// A second runner, from a PEM private key, as a lobby holds it.
|
|
74
|
+
const lobby = runnerKey();
|
|
75
|
+
writeFileSync(join(root, "lobby.pem"), lobby.pem);
|
|
76
|
+
const second = ok(run(root, { action: "runner add", name: "studio:lobby", key: "lobby.pem", runnerClass: "service", expect: added.digest }));
|
|
77
|
+
expect(second).toMatchObject({ entry: { principal: "studio:lobby", class: "service", key: lobby.ssh }, runnerRole: false });
|
|
78
|
+
|
|
79
|
+
// The policy reads both, with the role for the runner only.
|
|
80
|
+
const policy = readTrustPolicy(workingTreeSource(root), null);
|
|
81
|
+
expect(policy.problems).toEqual([]);
|
|
82
|
+
expect(policy.runners.map((r) => r.principal)).toEqual(["ci:acme/app", "studio:lobby"]);
|
|
83
|
+
expect(policy.roles.runner).toEqual(["ci:acme/app"]);
|
|
84
|
+
|
|
85
|
+
const removed = ok(run(root, { action: "runner remove", name: "ci:acme/app", by: "github:alex", expect: second.digest }));
|
|
86
|
+
expect(removed).toMatchObject({ changed: true, previous: { principal: "ci:acme/app" }, entry: null, runnerRole: false });
|
|
87
|
+
expect(trustOf(root)).toEqual({ schema: 1, runners: [{ principal: "studio:lobby", class: "service", key: lobby.ssh }], roles: { runner: [] } });
|
|
88
|
+
|
|
89
|
+
// The digest the caller read is the one before the remove: refused, naming the last write.
|
|
90
|
+
const stale = run(root, { action: "runner remove", name: "studio:lobby", expect: second.digest });
|
|
91
|
+
expect(code(stale)).toBe("trust-conflict");
|
|
92
|
+
expect("conflict" in stale && stale.conflict).toMatchObject({ expected: second.digest, digest: removed.digest, lastWrite: { verb: "trust runner remove", by: "github:alex" } });
|
|
93
|
+
expect(trustOf(root).runners).toHaveLength(1);
|
|
94
|
+
|
|
95
|
+
expect(code(run(root, { action: "runner remove", name: "ci:acme/app" }))).toBe("runner-unknown");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("an existing file is edited in place, keeping its other keys and layout", () => {
|
|
99
|
+
const text = `{\n "schema": 1,\n "roles": {\n "admin": ["alex"]\n },\n "adopted": []\n}\n`;
|
|
100
|
+
const root = emptyRepo({ ".chant/trust.json": text });
|
|
101
|
+
const bot = runnerKey();
|
|
102
|
+
const doc = ok(run(root, { action: "runner add", name: "ci-bot", key: bot.ssh, expect: trustDigest(text) }));
|
|
103
|
+
const after = readFileSync(join(root, ".chant/trust.json"), "utf-8");
|
|
104
|
+
expect(after.startsWith(`{\n "schema": 1,\n "roles": {\n "admin": ["alex"],`)).toBe(true);
|
|
105
|
+
expect(JSON.parse(after)).toEqual({ schema: 1, roles: { admin: ["alex"], runner: ["ci-bot"] }, adopted: [], runners: [{ principal: "ci-bot", class: "runner", key: bot.ssh }] });
|
|
106
|
+
expect(doc.digest).toBe(trustDigest(after));
|
|
107
|
+
// --dry-run reports the same and writes nothing.
|
|
108
|
+
const dry = ok(run(root, { action: "runner remove", name: "ci-bot", dryRun: true }));
|
|
109
|
+
expect(dry.changed).toBe(true);
|
|
110
|
+
expect(readFileSync(join(root, ".chant/trust.json"), "utf-8")).toBe(after);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("refusals: a key that is not ssh-ed25519, a person's key, a taken key or principal, a broken file", () => {
|
|
114
|
+
const bot = runnerKey();
|
|
115
|
+
const person = runnerKey();
|
|
116
|
+
const root = emptyRepo({ ".chant/allowed_signers": `alex ${person.ssh}\n` });
|
|
117
|
+
expect(code(run(root, { action: "runner add", name: "x", key: "ssh-rsa AAAAB3NzaC1yc2E=" }))).toBe("runner-key-invalid");
|
|
118
|
+
expect(code(run(root, { action: "runner add", name: "x", key: "ssh-ed25519 AAAA" }))).toBe("runner-key-invalid");
|
|
119
|
+
expect(code(run(root, { action: "runner add", name: "x", key: "no-such-file.pub" }))).toBe("runner-key-invalid");
|
|
120
|
+
expect(code(run(root, { action: "runner add", name: "x", key: person.ssh }))).toBe("runner-key-is-signer");
|
|
121
|
+
expect(code(run(root, { action: "runner add", name: "alex", key: bot.ssh }))).toBe("runner-key-is-signer");
|
|
122
|
+
expect(code(run(root, { action: "runner add", name: "x" }))).toBe("write-usage-invalid");
|
|
123
|
+
expect(code(run(root, { action: "runner add", name: "x", key: bot.ssh, runnerClass: "person" }))).toBe("write-usage-invalid");
|
|
124
|
+
expect(code(run(root, { action: "runner add", name: "x", key: bot.ssh, expect: "abc" }))).toBe("write-usage-invalid");
|
|
125
|
+
expect(code(run(root, { action: "runner remove", name: "x", key: bot.ssh }))).toBe("write-usage-invalid");
|
|
126
|
+
|
|
127
|
+
ok(run(root, { action: "runner add", name: "ci-bot", key: bot.ssh }));
|
|
128
|
+
expect(code(run(root, { action: "runner add", name: "other", key: bot.ssh }))).toBe("runner-exists");
|
|
129
|
+
expect(code(run(root, { action: "runner add", name: "ci-bot", key: runnerKey().ssh }))).toBe("runner-exists");
|
|
130
|
+
expect(code(run(root, { action: "runner add", name: "ci-bot", key: bot.ssh, runnerClass: "service" }))).toBe("runner-exists");
|
|
131
|
+
|
|
132
|
+
writeFileSync(join(root, ".chant/trust.json"), `{ "schema": 2 }`);
|
|
133
|
+
expect(code(run(root, { action: "runner add", name: "y", key: runnerKey().ssh }))).toBe("trust-config-invalid");
|
|
134
|
+
expect(code(trustWrite({ cwd: scratchDir("chant-trust-nogit-"), action: "runner add", name: "y", key: bot.ssh }))).toBe("not-a-git-repository");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("the command line takes --class, --key, --by and --expect", () => {
|
|
138
|
+
const args = parseArgs(["workspace", "trust", "runner", "add", "ci-bot", "--key", "k.pub", "--class", "service", "--by", "github:alex", "--expect", "a".repeat(64)]);
|
|
139
|
+
expect(args).toMatchObject({ key: "k.pub", runnerClass: "service", by: "github:alex", expect: "a".repeat(64) });
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("a runner added through the write is trusted by the readers (#3510)", () => {
|
|
144
|
+
test("ci tick --source runs tags a commit from a run signed by a runner the write listed", async () => {
|
|
145
|
+
const bot = runnerKey();
|
|
146
|
+
const origin = scratchDir("chant-trust-write-origin-");
|
|
147
|
+
git(origin, "init", "-q", "--bare");
|
|
148
|
+
const root = scratchDir("chant-trust-write-ci-");
|
|
149
|
+
git(root, "init", "-q");
|
|
150
|
+
git(root, "symbolic-ref", "HEAD", "refs/heads/main");
|
|
151
|
+
git(root, "remote", "add", "origin", origin);
|
|
152
|
+
const green = { branch: "main", phases: { test: ["test"] }, require: ["test"], source: "runs" };
|
|
153
|
+
writeFiles(root, { "chant.workspace.json": declaration([{ name: "app", dir: "app", kind: "other", because: "the app" }], { ci: { green } }), "app/a.txt": "a\n", "runner.pem": bot.pem });
|
|
154
|
+
writeFiles(root, { ".gitignore": "runner.pem\n" });
|
|
155
|
+
|
|
156
|
+
// Before the write, the key is refused.
|
|
157
|
+
git(root, "add", "-A");
|
|
158
|
+
git(root, "commit", "-q", "-m", "init");
|
|
159
|
+
expect(() => ciTagSigner(root, git(root, "rev-parse", "HEAD"), bot.pem)).toThrow(/lists no runner with this key/);
|
|
160
|
+
|
|
161
|
+
ok(run(root, { action: "runner add", name: "ci-bot", key: "runner.pem", by: "t" }));
|
|
162
|
+
git(root, "add", "-A");
|
|
163
|
+
git(root, "commit", "-q", "-m", "trust the ci-bot runner");
|
|
164
|
+
const head = git(root, "rev-parse", "HEAD");
|
|
165
|
+
expect(ciTagSigner(root, head, bot.pem).principal).toBe("ci-bot");
|
|
166
|
+
|
|
167
|
+
const pass = new Map<string, ActivityFn>([["step", async () => ({})]]);
|
|
168
|
+
const op = { name: "test", overview: "run test", labels: { Env: "ci" }, phases: [{ name: "Run", steps: [{ kind: "activity" as const, fn: "step", args: {} }] }] };
|
|
169
|
+
const { record } = await runOpLocally(op, pass, {}, undefined, { cwd: root, ledger: { cwd: root }, signRecord: opRunSigner(root, bot.pem) });
|
|
170
|
+
expect(record.gitSha).toBe(head);
|
|
171
|
+
git(root, "push", "-q", "origin", "main");
|
|
172
|
+
git(root, "push", "-q", "origin", "chant/lifecycle");
|
|
173
|
+
|
|
174
|
+
const tick = await ciTick({ cwd: root, runnerKey: bot.pem, now: new Date() });
|
|
175
|
+
expect(tick.made).toEqual([`ci/green/${head}`]);
|
|
176
|
+
expect(tick.signer).toBe("ci-bot");
|
|
177
|
+
expect(execFileSync("git", ["ls-remote", "--tags", origin], { encoding: "utf-8" })).toContain(`refs/tags/ci/green/${head}`);
|
|
178
|
+
});
|
|
179
|
+
});
|