@intentius/chant 0.124.40 → 0.124.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__fixtures__/oidc-emulator.d.ts +49 -0
- package/dist/__fixtures__/oidc-emulator.d.ts.map +1 -0
- package/dist/__fixtures__/oidc-emulator.js +89 -0
- package/dist/ci-handover.d.ts +117 -0
- package/dist/ci-handover.d.ts.map +1 -0
- package/dist/ci-handover.js +217 -0
- package/dist/ci-identity.d.ts +190 -0
- package/dist/ci-identity.d.ts.map +1 -0
- package/dist/ci-identity.js +219 -0
- package/dist/cli/handlers/approve.d.ts +2 -1
- package/dist/cli/handlers/approve.d.ts.map +1 -1
- package/dist/cli/handlers/approve.js +20 -6
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +18 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +42 -5
- package/dist/cli/handlers/gate-resume.d.ts +3 -0
- package/dist/cli/handlers/gate-resume.d.ts.map +1 -1
- package/dist/cli/handlers/gate-resume.js +15 -2
- package/dist/cli/handlers/lock.d.ts +8 -0
- package/dist/cli/handlers/lock.d.ts.map +1 -0
- package/dist/cli/handlers/lock.js +197 -0
- package/dist/cli/handlers/pr-env.d.ts +34 -0
- package/dist/cli/handlers/pr-env.d.ts.map +1 -0
- package/dist/cli/handlers/pr-env.js +436 -0
- package/dist/cli/handlers/pr.d.ts +76 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +615 -55
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +9 -0
- package/dist/cli/handlers/query.d.ts +6 -0
- package/dist/cli/handlers/query.d.ts.map +1 -0
- package/dist/cli/handlers/query.js +80 -0
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +11 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +49 -4
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +46 -2
- package/dist/cli/registry.d.ts +31 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/capability.d.ts +47 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/capability.js +44 -0
- package/dist/components/cli-support.d.ts +6 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +8 -1
- package/dist/components/driver.d.ts +10 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +52 -2
- package/dist/components/fan-out-run.d.ts +37 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +44 -4
- package/dist/components/gate-plan.d.ts +2 -0
- package/dist/components/gate-plan.d.ts.map +1 -1
- package/dist/components/gate-plan.js +1 -0
- package/dist/components/pr-environment.d.ts +298 -0
- package/dist/components/pr-environment.d.ts.map +1 -0
- package/dist/components/pr-environment.js +406 -0
- package/dist/components/pr-hooks.d.ts +66 -0
- package/dist/components/pr-hooks.d.ts.map +1 -0
- package/dist/components/pr-hooks.js +119 -0
- package/dist/components/pr-pipeline.d.ts +92 -2
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +150 -21
- package/dist/components/pr-policy.d.ts +109 -0
- package/dist/components/pr-policy.d.ts.map +1 -0
- package/dist/components/pr-policy.js +150 -0
- package/dist/components/pr-run.d.ts +6 -0
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +8 -3
- package/dist/components/wave-plan.d.ts +14 -1
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +34 -2
- package/dist/gate-decision.d.ts +15 -2
- package/dist/gate-decision.d.ts.map +1 -1
- package/dist/gate-decision.js +17 -2
- package/dist/gate-policy.d.ts +140 -0
- package/dist/gate-policy.d.ts.map +1 -0
- package/dist/gate-policy.js +242 -0
- package/dist/lifecycle/gate-checkpoint.d.ts +1 -1
- package/dist/lifecycle/gate-checkpoint.js +1 -1
- package/dist/{workspace/trust → lifecycle}/gate-seal.d.ts +1 -1
- package/dist/lifecycle/gate-seal.d.ts.map +1 -0
- package/dist/lifecycle/query.d.ts +68 -0
- package/dist/lifecycle/query.d.ts.map +1 -0
- package/dist/lifecycle/query.js +246 -0
- package/dist/lifecycle/run-ledger.d.ts +13 -0
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +16 -0
- package/dist/lifecycle/target-lock.d.ts +97 -0
- package/dist/lifecycle/target-lock.d.ts.map +1 -0
- package/dist/lifecycle/target-lock.js +286 -0
- package/dist/op/activities/activity-contracts.d.ts +30 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.js +26 -0
- package/dist/op/activities/index.d.ts +2 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/index.js +4 -0
- package/dist/op/activities/pr-refs.d.ts +10 -0
- package/dist/op/activities/pr-refs.d.ts.map +1 -0
- package/dist/op/activities/pr-refs.js +26 -0
- package/dist/op/activities/predict-behaviour.d.ts +2 -4
- package/dist/op/activities/predict-behaviour.d.ts.map +1 -1
- package/dist/op/activities/predict-behaviour.js +2 -21
- package/dist/op/activities/review-agent.d.ts +137 -0
- package/dist/op/activities/review-agent.d.ts.map +1 -0
- package/dist/op/activities/review-agent.js +266 -0
- package/dist/op/builders.d.ts +9 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +11 -0
- package/dist/op/cron.d.ts +8 -0
- package/dist/op/cron.d.ts.map +1 -1
- package/dist/op/cron.js +51 -0
- package/dist/op/gate-approval.d.ts +20 -13
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-approval.js +13 -18
- package/dist/op/gate-quorum.d.ts +131 -0
- package/dist/op/gate-quorum.d.ts.map +1 -0
- package/dist/op/gate-quorum.js +183 -0
- package/dist/op/gate-resume.d.ts +4 -2
- package/dist/op/gate-resume.d.ts.map +1 -1
- package/dist/op/gate-resume.js +5 -4
- package/dist/op/gate-review.js +1 -1
- package/dist/op/gate.d.ts +32 -4
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +40 -24
- package/dist/op/index.d.ts +6 -4
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +4 -3
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +9 -3
- package/dist/op/op-waves-run.d.ts +28 -43
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +170 -120
- package/dist/op/op-waves.d.ts +55 -3
- package/dist/op/op-waves.d.ts.map +1 -1
- package/dist/op/op-waves.js +63 -8
- package/dist/op/schedule-overdue.d.ts +57 -0
- package/dist/op/schedule-overdue.d.ts.map +1 -0
- package/dist/op/schedule-overdue.js +138 -0
- package/dist/op/stage-hooks.d.ts +132 -0
- package/dist/op/stage-hooks.d.ts.map +1 -0
- package/dist/op/stage-hooks.js +142 -0
- package/dist/override-gate.d.ts +2 -2
- package/dist/override-gate.d.ts.map +1 -1
- package/dist/override-gate.js +1 -1
- package/dist/pr-forge.d.ts +64 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +331 -44
- package/dist/pr-loop.d.ts +107 -1
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +84 -3
- package/dist/pr-post.d.ts +72 -0
- package/dist/pr-post.d.ts.map +1 -0
- package/dist/pr-post.js +134 -0
- package/dist/pr-requires.d.ts +38 -0
- package/dist/pr-requires.d.ts.map +1 -0
- package/dist/pr-requires.js +48 -0
- package/dist/wave-blocked.d.ts +79 -0
- package/dist/wave-blocked.d.ts.map +1 -0
- package/dist/wave-blocked.js +245 -0
- package/dist/wave-engine.d.ts +6 -0
- package/dist/wave-engine.d.ts.map +1 -1
- package/dist/wave-engine.js +2 -0
- package/dist/wave-runtime.mjs +243 -243
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts +1 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/two-member-workspace.js +1 -1
- package/dist/workspace/checks/ci-workflow.d.ts.map +1 -1
- package/dist/workspace/checks/ci-workflow.js +19 -4
- package/dist/workspace/ci-branches.d.ts +51 -0
- package/dist/workspace/ci-branches.d.ts.map +1 -0
- package/dist/workspace/ci-branches.js +69 -0
- package/dist/workspace/ci-cloud-identity.d.ts +11 -0
- package/dist/workspace/ci-cloud-identity.d.ts.map +1 -0
- package/dist/workspace/ci-cloud-identity.js +24 -0
- package/dist/workspace/ci-init.d.ts.map +1 -1
- package/dist/workspace/ci-init.js +5 -4
- package/dist/workspace/ci-locks.d.ts +12 -0
- package/dist/workspace/ci-locks.d.ts.map +1 -0
- package/dist/workspace/ci-locks.js +6 -0
- package/dist/workspace/ci-run.d.ts +17 -1
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +66 -4
- package/dist/workspace/ci-workflow-at.d.ts +14 -0
- package/dist/workspace/ci-workflow-at.d.ts.map +1 -0
- package/dist/workspace/ci-workflow-at.js +17 -0
- package/dist/workspace/ci-workflow.d.ts +11 -3
- package/dist/workspace/ci-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-workflow.js +10 -2
- package/dist/workspace/declaration.d.ts +69 -3
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +72 -1
- package/dist/workspace/declaration.schema.json +149 -2
- package/dist/workspace/identity-gate-rule.d.ts +1 -1
- package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
- package/dist/workspace/identity-gate-rule.js +1 -1
- package/dist/workspace/identity.d.ts +1 -1
- package/dist/workspace/identity.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.js +8 -5
- package/dist/workspace/member-pipeline.d.ts +6 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -1
- package/dist/workspace/member-pipeline.js +23 -0
- package/dist/workspace/pr-report.schema.json +94 -3
- package/dist/workspace/reason-codes.d.ts +1 -1
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +1 -1
- package/dist/workspace/security.d.ts.map +1 -1
- package/dist/workspace/security.js +4 -0
- package/dist/workspace/status-gates.d.ts +3 -2
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status-gates.js +8 -7
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +4 -1
- package/dist/workspace/trust/seal.d.ts +2 -2
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/seal.js +2 -2
- package/package.json +1 -1
- package/src/__fixtures__/oidc-emulator.ts +118 -0
- package/src/ci-handover.test.ts +209 -0
- package/src/ci-handover.ts +237 -0
- package/src/ci-identity.test.ts +201 -0
- package/src/ci-identity.ts +310 -0
- package/src/cli/handlers/approve.ts +18 -5
- package/src/cli/handlers/build.ts +18 -1
- package/src/cli/handlers/fan-out.ts +43 -6
- package/src/cli/handlers/gate-resume.ts +16 -2
- package/src/cli/handlers/lock.test.ts +98 -0
- package/src/cli/handlers/lock.ts +202 -0
- package/src/cli/handlers/operator.test.ts +3 -1
- package/src/cli/handlers/pr-before-merge.test.ts +276 -0
- package/src/cli/handlers/pr-env.ts +462 -0
- package/src/cli/handlers/pr-waves-blocked.test.ts +247 -0
- package/src/cli/handlers/pr.test.ts +184 -0
- package/src/cli/handlers/pr.ts +605 -61
- package/src/cli/handlers/promote.ts +9 -0
- package/src/cli/handlers/query.ts +78 -0
- package/src/cli/handlers/run.ts +10 -0
- package/src/cli/main.ts +49 -4
- package/src/cli/parse-args.ts +34 -2
- package/src/cli/registry.ts +31 -2
- package/src/components/capability-targets.test.ts +162 -0
- package/src/components/capability.ts +71 -0
- package/src/components/cli-support.ts +14 -1
- package/src/components/driver.ts +54 -3
- package/src/components/fan-out-run.ts +58 -3
- package/src/components/fan-out-waves.test.ts +45 -1
- package/src/components/gate-plan.ts +3 -0
- package/src/components/pr-environment.test.ts +249 -0
- package/src/components/pr-environment.ts +538 -0
- package/src/components/pr-hooks.test.ts +118 -0
- package/src/components/pr-hooks.ts +157 -0
- package/src/components/pr-pipeline.ts +194 -21
- package/src/components/pr-policy.test.ts +145 -0
- package/src/components/pr-policy.ts +211 -0
- package/src/components/pr-run.ts +14 -2
- package/src/components/wave-plan.ts +38 -3
- package/src/gate-decision-bundle.test.ts +4 -3
- package/src/gate-decision.ts +39 -2
- package/src/gate-policy-bundle.test.ts +28 -0
- package/src/gate-policy.test.ts +162 -0
- package/src/gate-policy.ts +298 -0
- package/src/lifecycle/gate-checkpoint.ts +1 -1
- package/src/{workspace/trust → lifecycle}/gate-seal.ts +1 -1
- package/src/lifecycle/query.test.ts +91 -0
- package/src/lifecycle/query.ts +315 -0
- package/src/lifecycle/run-ledger.ts +18 -0
- package/src/lifecycle/target-lock.test.ts +195 -0
- package/src/lifecycle/target-lock.ts +347 -0
- package/src/op/activities/activity-contracts.ts +31 -0
- package/src/op/activities/index.ts +6 -0
- package/src/op/activities/pr-refs.ts +28 -0
- package/src/op/activities/predict-behaviour.ts +3 -23
- package/src/op/activities/review-agent.test.ts +240 -0
- package/src/op/activities/review-agent.ts +355 -0
- package/src/op/builders.ts +13 -0
- package/src/op/cron.ts +44 -0
- package/src/op/gate-alias.test.ts +2 -2
- package/src/op/gate-approval.test.ts +30 -9
- package/src/op/gate-approval.ts +23 -26
- package/src/op/gate-quorum.test.ts +139 -0
- package/src/op/gate-quorum.ts +248 -0
- package/src/op/gate-resume.ts +6 -3
- package/src/op/gate-review.ts +1 -1
- package/src/op/gate.ts +64 -23
- package/src/op/index.ts +13 -1
- package/src/op/local-executor.ts +9 -2
- package/src/op/op-waves-hook-targets.test.ts +126 -0
- package/src/op/op-waves-policy-hold.test.ts +66 -0
- package/src/op/op-waves-quorum.test.ts +101 -0
- package/src/op/op-waves-run.ts +197 -183
- package/src/op/op-waves.ts +83 -10
- package/src/op/schedule-overdue.test.ts +220 -0
- package/src/op/schedule-overdue.ts +161 -0
- package/src/op/stage-hooks.ts +251 -0
- package/src/override-gate-bundle.test.ts +1 -1
- package/src/override-gate.ts +2 -2
- package/src/pr-forge-bundle.test.ts +2 -2
- package/src/pr-forge.test.ts +33 -0
- package/src/pr-forge.ts +347 -35
- package/src/pr-loop-bundle.test.ts +2 -2
- package/src/pr-loop.test.ts +58 -0
- package/src/pr-loop.ts +170 -4
- package/src/pr-post.test.ts +141 -0
- package/src/pr-post.ts +159 -0
- package/src/pr-requires.test.ts +184 -0
- package/src/pr-requires.ts +79 -0
- package/src/review-agent-bundle.test.ts +35 -0
- package/src/stage-hooks-bundle.test.ts +38 -0
- package/src/wave-blocked-bundle.test.ts +25 -0
- package/src/wave-blocked.test.ts +146 -0
- package/src/wave-blocked.ts +271 -0
- package/src/wave-engine.ts +8 -0
- package/src/workspace/__fixtures__/two-member-workspace.ts +2 -2
- package/src/workspace/checks/ci-workflow.test.ts +9 -0
- package/src/workspace/checks/ci-workflow.ts +18 -4
- package/src/workspace/ci-branches.test.ts +62 -0
- package/src/workspace/ci-branches.ts +79 -0
- package/src/workspace/ci-cloud-identity.test.ts +53 -0
- package/src/workspace/ci-cloud-identity.ts +23 -0
- package/src/workspace/ci-init.ts +5 -4
- package/src/workspace/ci-locks.ts +16 -0
- package/src/workspace/ci-run-index.test.ts +2 -1
- package/src/workspace/ci-run.test.ts +107 -8
- package/src/workspace/ci-run.ts +74 -5
- package/src/workspace/ci-workflow-at.ts +23 -0
- package/src/workspace/ci-workflow.ts +13 -2
- package/src/workspace/declaration.schema.json +149 -2
- package/src/workspace/declaration.test.ts +76 -0
- package/src/workspace/declaration.ts +140 -5
- package/src/workspace/identity-gate-rule.ts +1 -1
- package/src/workspace/identity.ts +1 -1
- package/src/workspace/lineage-upgrade.ts +11 -8
- package/src/workspace/member-pipeline.test.ts +29 -0
- package/src/workspace/member-pipeline.ts +30 -0
- package/src/workspace/pr-report.schema.json +94 -3
- package/src/workspace/reason-codes.ts +1 -1
- package/src/workspace/security.ts +4 -0
- package/src/workspace/status-gates.ts +9 -6
- package/src/workspace/status.ts +4 -1
- package/src/workspace/trust/gate-seal.test.ts +1 -1
- package/src/workspace/trust/seal.ts +2 -2
- package/dist/workspace/trust/gate-seal.d.ts.map +0 -1
- /package/dist/{workspace/trust → lifecycle}/gate-seal.js +0 -0
|
@@ -26,6 +26,7 @@ import { dirname, join, resolve } from "node:path";
|
|
|
26
26
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
27
27
|
import { loadChantConfig, type ChantConfig } from "../../config";
|
|
28
28
|
import { promotionOrder } from "../../workspace/promotion-order";
|
|
29
|
+
import { releaseTargetRefusal } from "../../components/pr-environment";
|
|
29
30
|
import { resolvePromotionSource } from "../../components/promote-order";
|
|
30
31
|
import { fetchLifecycle, getHeadCommit, pushLifecycle } from "../../lifecycle/git";
|
|
31
32
|
import {
|
|
@@ -897,6 +898,14 @@ export async function runComponentsPromote(ctx: CommandContext): Promise<number>
|
|
|
897
898
|
console.error(formatError({ message: "--to <env> is required", hint: PROMOTE_USAGE }));
|
|
898
899
|
return 1;
|
|
899
900
|
}
|
|
901
|
+
// #4298: a pull request's environment is neither a release's target nor its source.
|
|
902
|
+
for (const env of [to, args.migrateFrom]) {
|
|
903
|
+
const refusal = env ? releaseTargetRefusal(env) : undefined;
|
|
904
|
+
if (refusal) {
|
|
905
|
+
console.error(formatError({ message: refusal }));
|
|
906
|
+
return 1;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
900
909
|
const who = actorOrRefuse(ctx);
|
|
901
910
|
if (typeof who === "number") return who;
|
|
902
911
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant query "<sql>" [dir] [--member <name>] [--json]` (#4297): one
|
|
3
|
+
* read-only SQL statement over the snapshots, Op runs, gates, deploys and
|
|
4
|
+
* releases on the local `chant/lifecycle` branch. The tables and the engine
|
|
5
|
+
* live in `../../lifecycle/query.ts`; this reads the ledgers and prints.
|
|
6
|
+
*
|
|
7
|
+
* Inside a workspace every member's ledger is read, each row naming its
|
|
8
|
+
* member; `--member` keeps the members it names. Outside one the project's
|
|
9
|
+
* own ledger is read and `member` is NULL. It reads the local branch and
|
|
10
|
+
* never fetches.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { resolve } from "node:path";
|
|
14
|
+
import { formatError, formatWarning } from "../format";
|
|
15
|
+
import type { CommandContext } from "../registry";
|
|
16
|
+
import type { QueryLedger } from "../../lifecycle/query";
|
|
17
|
+
import { findWorkspaceRoot } from "../../project-root";
|
|
18
|
+
|
|
19
|
+
const USAGE = 'chant query "<sql>" [dir] [--member <name>] [--json]';
|
|
20
|
+
|
|
21
|
+
/** The ledgers `chant query` reads from `cwd`: every member's in a workspace, else the project's own. */
|
|
22
|
+
export async function queryLedgers(cwd: string, members?: readonly string[]): Promise<QueryLedger[]> {
|
|
23
|
+
const { gitLedgerReadPort } = await import("../../lifecycle/git");
|
|
24
|
+
const found = findWorkspaceRoot(cwd);
|
|
25
|
+
if (!found) return [{ member: null, port: gitLedgerReadPort({ cwd }) }];
|
|
26
|
+
// Workspace code loads only when there is a workspace, so a level-0 project never pays for it.
|
|
27
|
+
const { readDeclaration, WorkspaceReadError } = await import("../../workspace/declaration");
|
|
28
|
+
const { workingTree } = await import("../../workspace/tree");
|
|
29
|
+
const { QueryError } = await import("../../lifecycle/query");
|
|
30
|
+
let declaration;
|
|
31
|
+
try {
|
|
32
|
+
declaration = readDeclaration(workingTree(found.dir), "");
|
|
33
|
+
} catch (err) {
|
|
34
|
+
if (err instanceof WorkspaceReadError) throw new QueryError(`${err.code}: ${err.message}`);
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
const wanted = members && members.length > 0 ? new Set(members) : null;
|
|
38
|
+
if (wanted) {
|
|
39
|
+
const unknown = [...wanted].filter((n) => !declaration.members.some((m) => m.name === n));
|
|
40
|
+
if (unknown.length > 0) throw new QueryError(`--member ${unknown.join(", ")} names no member of the workspace`);
|
|
41
|
+
}
|
|
42
|
+
return declaration.members
|
|
43
|
+
.filter((m) => !wanted || wanted.has(m.name))
|
|
44
|
+
.map((m) => ({ member: m.name, port: gitLedgerReadPort({ cwd: found.dir, member: m.dir === "." ? "." : m.name }) }));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function runQueryCommand(ctx: CommandContext): Promise<number> {
|
|
48
|
+
const { args } = ctx;
|
|
49
|
+
const sql = args.positionals?.[0];
|
|
50
|
+
if (sql === undefined || sql.trim() === "") {
|
|
51
|
+
console.error(formatError({ message: "chant query needs a statement", hint: `${USAGE}, such as chant query "SELECT env, count(*) FROM resources GROUP BY env"` }));
|
|
52
|
+
return 1;
|
|
53
|
+
}
|
|
54
|
+
const cwd = resolve(args.positionals?.[1] ?? ".");
|
|
55
|
+
if (!existsSync(cwd)) {
|
|
56
|
+
console.error(formatError({ message: `${cwd} does not exist`, hint: USAGE }));
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
const { loadQueryData, runQuery, formatQueryResult, QueryError } = await import("../../lifecycle/query");
|
|
60
|
+
try {
|
|
61
|
+
const load = await loadQueryData(await queryLedgers(cwd, args.members));
|
|
62
|
+
const result = await runQuery(sql, load);
|
|
63
|
+
if (args.json) {
|
|
64
|
+
console.log(JSON.stringify(result, null, 2));
|
|
65
|
+
} else {
|
|
66
|
+
console.log(formatQueryResult(result));
|
|
67
|
+
const bad = Object.entries(result.malformed).filter(([, n]) => n > 0);
|
|
68
|
+
if (bad.length > 0) console.error(formatWarning({ message: `left out what could not be read: ${bad.map(([t, n]) => `${n} in ${t}`).join(", ")}` }));
|
|
69
|
+
}
|
|
70
|
+
return 0;
|
|
71
|
+
} catch (err) {
|
|
72
|
+
if (err instanceof QueryError) {
|
|
73
|
+
console.error(formatError({ message: err.message, hint: USAGE }));
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/cli/handlers/run.ts
CHANGED
|
@@ -34,6 +34,7 @@ import { appliedDigestOf, extractRunDigest, maybeRecordAutoRelease, type Applied
|
|
|
34
34
|
import { maybePersistBuildManifest } from "../../components/manifest-persistence";
|
|
35
35
|
import { failedRunExitCode, type DriverComponentResult } from "../../components/driver";
|
|
36
36
|
import { isComponentList, type ComponentRunPlan, type RunComponentsResult } from "../../components/cli-support";
|
|
37
|
+
import { parseComponentTargets } from "../../components/capability";
|
|
37
38
|
import { isApplyRecord, resolveCiRun, type ReleaseRecord, type RunOrigin } from "../../lifecycle/release-ledger";
|
|
38
39
|
import { gateAdmission, gateApprover } from "../../components/promote";
|
|
39
40
|
import { runOpGenerate } from "./run-generate";
|
|
@@ -1202,10 +1203,19 @@ async function runComponentSelection(ctx: CommandContext): Promise<number> {
|
|
|
1202
1203
|
if (plan.added.length > 0) console.error(formatInfo(`added as dependencies: ${plan.added.join(", ")}`));
|
|
1203
1204
|
}
|
|
1204
1205
|
: undefined;
|
|
1206
|
+
// #4300: `--target <component>:<address>`, the addresses a component's steps narrow to.
|
|
1207
|
+
let targets: Record<string, string[]> | undefined;
|
|
1208
|
+
try {
|
|
1209
|
+
targets = ctx.args.targets && ctx.args.targets.length > 0 ? parseComponentTargets(ctx.args.targets) : undefined;
|
|
1210
|
+
} catch (err) {
|
|
1211
|
+
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
1212
|
+
return 1;
|
|
1213
|
+
}
|
|
1205
1214
|
let result: RunComponentsResult;
|
|
1206
1215
|
try {
|
|
1207
1216
|
result = await runtime.runComponents(projectPath, selector, {
|
|
1208
1217
|
env: ctx.args.env,
|
|
1218
|
+
...(targets ? { targets } : {}),
|
|
1209
1219
|
componentOutputs: seededOutputs,
|
|
1210
1220
|
onProgress,
|
|
1211
1221
|
...(events ? { onRunEvent: events.emit } : {}),
|
package/src/cli/main.ts
CHANGED
|
@@ -173,6 +173,11 @@ Commands:
|
|
|
173
173
|
terms; --live or --at <ref> with --env <name> reads the
|
|
174
174
|
estate or a recorded snapshot; --show <attr>,
|
|
175
175
|
--check-live, --check-snapshot, --fail-on-drift)
|
|
176
|
+
query "<sql>" [dir] Run one read-only SQL statement over chant/lifecycle:
|
|
177
|
+
the resources and snapshots each lexicon observed, Op
|
|
178
|
+
runs, gates, deploys, releases and cuts, loaded into
|
|
179
|
+
node:sqlite (schema v1, listed in chant_schema).
|
|
180
|
+
--member <name> keeps members; --json prints rows
|
|
176
181
|
generate Generate typed code from sources chant.config declares
|
|
177
182
|
(k8s.crds, helm.charts) into src/generated/<lexicon>/
|
|
178
183
|
(codegen.outDir); --check exits 1 when the committed
|
|
@@ -342,7 +347,7 @@ CI:
|
|
|
342
347
|
key. --trust commits its runner entry on a branch of its
|
|
343
348
|
own and pushes it for review; the target branch is never
|
|
344
349
|
written
|
|
345
|
-
ci init --workflow trunk-curated|trunk-curated+apply-on-merge
|
|
350
|
+
ci init --workflow trunk-curated|trunk-curated+apply-on-merge|apply-before-merge
|
|
346
351
|
[--forge github|gitlab|forgejo] [--security <group>]... [--yes]
|
|
347
352
|
Set the workspace up for a prescribed delivery workflow:
|
|
348
353
|
print what it would write as a diff, then write it on
|
|
@@ -558,6 +563,15 @@ Ops:
|
|
|
558
563
|
applies it once, if .chant/overriders at base lists
|
|
559
564
|
--actor
|
|
560
565
|
|
|
566
|
+
lock <target>... Lock targets for a pull request on chant/lifecycle
|
|
567
|
+
(--pr <n>, --stage plan|apply|lock, --head <sha>,
|
|
568
|
+
--by <who>, --gate <name>, --json). --from-report
|
|
569
|
+
<pr-plan.json> locks the components a plan selected.
|
|
570
|
+
--forge <kind> reads which holders are still open and
|
|
571
|
+
posts chant/lock on --head. Exits 3 when another open
|
|
572
|
+
pull request holds a target. --list prints the locks
|
|
573
|
+
unlock [<target>...] Release the locks a pull request holds (--pr <n>,
|
|
574
|
+
--gate <name>, --json)
|
|
561
575
|
graph Show Op dependency graph (--stacks for cross-stack order,
|
|
562
576
|
--format ir|mermaid|dot|layout for the lint-gated graph IR,
|
|
563
577
|
a Mermaid flowchart, Graphviz DOT, or node positions;
|
|
@@ -997,17 +1011,33 @@ Component release ledger + status:
|
|
|
997
1011
|
when the wave is reached, with --canary <name>
|
|
998
1012
|
for wave 1 and --wave <n> for one wave per CI
|
|
999
1013
|
job; --resume <file> finishes an attempt that
|
|
1000
|
-
stopped
|
|
1014
|
+
stopped; --target <component>:<address> plans
|
|
1015
|
+
and applies only that address)
|
|
1001
1016
|
components pr-plan Plan the components a pull request reaches and
|
|
1002
1017
|
the ones that depend on them, and write the
|
|
1003
1018
|
report and note (--base <ref> --pr <n>
|
|
1004
1019
|
[--forge github|gitlab|forgejo] posts them);
|
|
1005
1020
|
applies nothing
|
|
1021
|
+
components pr-post Post the plan note and the chant/plan status
|
|
1022
|
+
from the report pr-plan --no-status wrote
|
|
1023
|
+
(--pr <n> --head <sha> --env <env> --forge
|
|
1024
|
+
<kind>), in a job that runs none of the
|
|
1025
|
+
change's code (#4295)
|
|
1006
1026
|
components pr-record On merge (#4168): plan the same members again,
|
|
1007
1027
|
store the change set and write the merge record
|
|
1008
1028
|
_merges/<commit>.json; applies and refuses
|
|
1009
1029
|
nothing, and a plan that differs from the
|
|
1010
1030
|
approved one sets a failing chant/record status
|
|
1031
|
+
components pr-env An environment per open pull request (#4298): up
|
|
1032
|
+
--base <ref> --pr <n> applies the change to pr-<n>,
|
|
1033
|
+
derived from ci.prEnvironment.template, once an
|
|
1034
|
+
approval of its plan stands (chant approve pr-<n>
|
|
1035
|
+
pr-env --plan <digest>); post --pr <n> --head
|
|
1036
|
+
<sha> keeps the record up --no-push handed on and
|
|
1037
|
+
posts the note; down --pr <n> tears down
|
|
1038
|
+
only what carries pr-<n>'s ownership marker; sweep
|
|
1039
|
+
tears down each one whose TTL passed or whose pull
|
|
1040
|
+
request closed. Never a release target
|
|
1011
1041
|
components pr-apply With --apply-on-merge: plan the same members again and apply
|
|
1012
1042
|
them only if an approval stands for that digest
|
|
1013
1043
|
(chant approve pr-<n> pr-apply --plan <digest>);
|
|
@@ -1026,6 +1056,11 @@ Component release ledger + status:
|
|
|
1026
1056
|
from the one last applied to --env, under the
|
|
1027
1057
|
approved plans of the merges since, else at gate
|
|
1028
1058
|
op green-<sha>;
|
|
1059
|
+
--before-merge --pr <n>: apply the open pull
|
|
1060
|
+
request's head under its gate, once the
|
|
1061
|
+
declaration at base sets ci.workflow to
|
|
1062
|
+
apply-before-merge and the forge says it is
|
|
1063
|
+
approved, mergeable, undiverged and green;
|
|
1029
1064
|
--events-json, both commands: the run-event
|
|
1030
1065
|
stream on stdout; --report-url <s3 or file URL>,
|
|
1031
1066
|
both commands: copy the report, the change set
|
|
@@ -1201,7 +1236,8 @@ Options:
|
|
|
1201
1236
|
chant.config.ts's build.sandbox: true; #1045)
|
|
1202
1237
|
--param <name=value> (build, graph, run --components, components fan-out,
|
|
1203
1238
|
components promote, components rollback,
|
|
1204
|
-
components redeploy,
|
|
1239
|
+
components redeploy, components pr-env up,
|
|
1240
|
+
release cut) Bind a
|
|
1205
1241
|
declared build-time parameter (chant.config.ts's buildParams)
|
|
1206
1242
|
to a value, for source to read as params.<name>
|
|
1207
1243
|
(#1064) instead of process.env — repeatable.
|
|
@@ -1213,7 +1249,8 @@ Options:
|
|
|
1213
1249
|
chant.template.json declares instead (#2627).
|
|
1214
1250
|
--params-file <path> (build, graph, run --components, components fan-out,
|
|
1215
1251
|
components promote, components rollback,
|
|
1216
|
-
components redeploy,
|
|
1252
|
+
components redeploy, components pr-env up,
|
|
1253
|
+
release cut) JSON file
|
|
1217
1254
|
of { "name": value } build-time parameter values
|
|
1218
1255
|
(#1064). Second precedence, after --param.
|
|
1219
1256
|
init --from refuses it: pass --param instead.
|
|
@@ -1441,6 +1478,8 @@ export const commandRegistry: CommandDef[] = [
|
|
|
1441
1478
|
{ name: "describe", handler: runDescribe },
|
|
1442
1479
|
{ name: "explain", handler: runExplain },
|
|
1443
1480
|
{ name: "search", handler: runSearch },
|
|
1481
|
+
// #4297 — SQL over the snapshots, runs, gates, deploys and releases on chant/lifecycle. Imported on first use.
|
|
1482
|
+
{ name: "query", runsNoConfig: true, handler: async (ctx) => (await import("./handlers/query")).runQueryCommand(ctx) },
|
|
1444
1483
|
// chant#2591 — `import --agents` and `audit` read chant.config statically
|
|
1445
1484
|
// and never evaluate it (chant#2589 for audit, which audits code it must not run).
|
|
1446
1485
|
{ name: "import", handler: runImport, runsNoConfig: (args) => args.agents === true },
|
|
@@ -1524,6 +1563,9 @@ export const commandRegistry: CommandDef[] = [
|
|
|
1524
1563
|
{ name: "operator log", handler: runOperatorLog },
|
|
1525
1564
|
{ name: "operator", handler: runOperator },
|
|
1526
1565
|
{ name: "approve", handler: runApprove },
|
|
1566
|
+
// #4293 — target locks a pull request holds on chant/lifecycle. Imported on first use.
|
|
1567
|
+
{ name: "lock", runsNoConfig: true, handler: async (ctx) => (await import("./handlers/lock")).runLock(ctx) },
|
|
1568
|
+
{ name: "unlock", runsNoConfig: true, handler: async (ctx) => (await import("./handlers/lock")).runUnlock(ctx) },
|
|
1527
1569
|
|
|
1528
1570
|
{ name: "graph", handler: runGraph },
|
|
1529
1571
|
{ name: "vendor", handler: runVendor },
|
|
@@ -1613,7 +1655,10 @@ export const commandRegistry: CommandDef[] = [
|
|
|
1613
1655
|
// #3183 — the pull-request loop the generated CI runs. Imported on first use.
|
|
1614
1656
|
{ name: "components pr-plan", requiresPlugins: true, handler: async (ctx) => (await import("./handlers/pr")).runComponentsPrPlan(ctx) },
|
|
1615
1657
|
{ name: "components pr-apply", requiresPlugins: true, handler: async (ctx) => (await import("./handlers/pr")).runComponentsPrApply(ctx) },
|
|
1658
|
+
// #4295: posts from the plan report in a job that runs none of the change's code, so it loads no plugins.
|
|
1659
|
+
{ name: "components pr-post", handler: async (ctx) => (await import("./handlers/pr")).runComponentsPrPost(ctx) },
|
|
1616
1660
|
{ name: "components pr-record", requiresPlugins: true, handler: async (ctx) => (await import("./handlers/pr")).runComponentsPrRecord(ctx) },
|
|
1661
|
+
{ name: "components pr-env", requiresPlugins: true, handler: async (ctx) => (await import("./handlers/pr-env")).runComponentsPrEnv(ctx) },
|
|
1617
1662
|
{ name: "components status", requiresPlugins: true, handler: runComponentsStatus },
|
|
1618
1663
|
{ name: "components release", handler: runComponentsReleaseRecord },
|
|
1619
1664
|
{ name: "components export", handler: runComponentsExport },
|
package/src/cli/parse-args.ts
CHANGED
|
@@ -43,6 +43,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
43
43
|
"--stacks",
|
|
44
44
|
"--components",
|
|
45
45
|
"--pr-build",
|
|
46
|
+
"--pr-env",
|
|
46
47
|
"--up",
|
|
47
48
|
"--down",
|
|
48
49
|
"--include-dependents",
|
|
@@ -72,6 +73,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
72
73
|
"--in-place",
|
|
73
74
|
"--require-runner-start",
|
|
74
75
|
"--no-push",
|
|
76
|
+
"--list",
|
|
75
77
|
"--no-require-review",
|
|
76
78
|
"--expire",
|
|
77
79
|
"--check-live",
|
|
@@ -376,6 +378,11 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
376
378
|
const share = Number(raw);
|
|
377
379
|
if (!Number.isInteger(share) || share < 1) throw new Error(`--share needs a share number from 1: got "${raw ?? ""}"`);
|
|
378
380
|
result.share = share;
|
|
381
|
+
} else if (arg === "--target") {
|
|
382
|
+
// `chant components fan-out|run --components --target <component>:<address>` (#4300), repeatable.
|
|
383
|
+
const raw = args[++i];
|
|
384
|
+
if (!raw || raw.startsWith("-") || !raw.includes(":")) throw new Error("--target needs a component and an address: --target <component>:<address>");
|
|
385
|
+
(result.targets ??= []).push(raw);
|
|
379
386
|
} else if (arg === "--canary") {
|
|
380
387
|
const raw = args[++i];
|
|
381
388
|
if (!raw || raw.startsWith("-")) throw new Error("--canary needs a component name: --canary <name>[,<name>...]");
|
|
@@ -396,11 +403,26 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
396
403
|
if (!result.forge || result.forge.startsWith("-")) throw new Error("--forge needs a forge: --forge github|gitlab|forgejo");
|
|
397
404
|
} else if (arg === "--require-review") {
|
|
398
405
|
result.requireReview = true;
|
|
406
|
+
} else if (arg === "--list") {
|
|
407
|
+
// `chant lock --list` (#4293): print the target locks instead of taking any.
|
|
408
|
+
result.list = true;
|
|
409
|
+
} else if (arg === "--stage") {
|
|
410
|
+
// `chant lock --stage plan|apply|lock` (#4293): how the lock was taken.
|
|
411
|
+
result.lockStage = args[++i];
|
|
412
|
+
if (!result.lockStage || result.lockStage.startsWith("-")) throw new Error("--stage needs plan, apply or lock: --stage <stage>");
|
|
413
|
+
} else if (arg === "--from-report") {
|
|
414
|
+
// `chant lock --from-report <pr-plan.json>` (#4293): lock the components a pull-request report lists.
|
|
415
|
+
result.fromReport = args[++i];
|
|
416
|
+
if (!result.fromReport || result.fromReport.startsWith("-")) throw new Error("--from-report needs a report file: --from-report <pr-plan.json>");
|
|
399
417
|
} else if (arg === "--report-url") {
|
|
400
418
|
// `chant components pr-plan|pr-apply --report-url <url>` (#4007).
|
|
401
419
|
const url = args[++i];
|
|
402
420
|
if (!url || url.startsWith("-")) throw new Error("--report-url needs a URL: --report-url s3://<bucket>/<prefix> or file://<directory>");
|
|
403
421
|
result.reportUrl = url;
|
|
422
|
+
} else if (arg === "--result") {
|
|
423
|
+
// `chant components pr-post --result <result>` (#4295): the plan job's own result, which fails the status when it failed.
|
|
424
|
+
result.planResult = args[++i];
|
|
425
|
+
if (!result.planResult || result.planResult.startsWith("-")) throw new Error("--result needs the plan job's result: --result success|failure|cancelled");
|
|
404
426
|
} else if (arg === "--no-status") {
|
|
405
427
|
// `chant ci run|serve --no-status` (#4097): post no commit status for the job.
|
|
406
428
|
result.noStatus = true;
|
|
@@ -419,9 +441,18 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
419
441
|
} else if (arg === "--green") {
|
|
420
442
|
// `chant components pr-apply --green` (#4168 step 9): apply the green commit at HEAD.
|
|
421
443
|
result.green = true;
|
|
444
|
+
} else if (arg === "--before-merge") {
|
|
445
|
+
// `chant components pr-apply --before-merge` (#4301, ws-106): apply an open pull request's plan from its head.
|
|
446
|
+
result.beforeMerge = true;
|
|
447
|
+
} else if (arg === "--apply-before-merge") {
|
|
448
|
+
// `chant build --pr-loop --apply-before-merge` and `chant ci run apply --apply-before-merge` (#4301): the job that applies before merge.
|
|
449
|
+
result.applyBeforeMerge = true;
|
|
422
450
|
} else if (arg === "--apply-on-merge") {
|
|
423
451
|
// `chant build --pr-loop --apply-on-merge` and `chant ci serve --apply-on-merge` (#4168): opt into applying on merge.
|
|
424
452
|
result.applyOnMerge = true;
|
|
453
|
+
} else if (arg === "--pr-env") {
|
|
454
|
+
// `chant build --pr-loop --pr-env` (#4298): add the pull request environment's jobs.
|
|
455
|
+
result.prEnv = true;
|
|
425
456
|
} else if (arg === "--pr-build") {
|
|
426
457
|
// `chant build --pr-loop --pr-build` (#4088), and `chant ci run --pr-build` (#4094).
|
|
427
458
|
result.prBuild = true;
|
|
@@ -864,8 +895,9 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
864
895
|
} else if (arg === "--expire") {
|
|
865
896
|
result.expire = true;
|
|
866
897
|
} else if (arg === "--role") {
|
|
867
|
-
// #2508 — a role the approver
|
|
868
|
-
// roles
|
|
898
|
+
// #2508 — a role the approver claims, which a gate's policy sees. A
|
|
899
|
+
// quorum reads roles from .chant/trust.json at base instead (#4290).
|
|
900
|
+
// Repeatable, and a comma list works too.
|
|
869
901
|
const value = args[++i] ?? "";
|
|
870
902
|
result.roles = [...(result.roles ?? []), ...value.split(",").map((r) => r.trim()).filter(Boolean)];
|
|
871
903
|
} else if (arg === "--agent") {
|
package/src/cli/registry.ts
CHANGED
|
@@ -294,6 +294,12 @@ export interface ParsedArgs {
|
|
|
294
294
|
share?: number;
|
|
295
295
|
/** `chant run wave --group <name>` (#3914) — apply one job group's runs of a decided wave. */
|
|
296
296
|
group?: string;
|
|
297
|
+
/**
|
|
298
|
+
* `chant components fan-out --target <component>:<address>` and `chant run
|
|
299
|
+
* --components --target ...` (#4300), repeatable: the addresses a
|
|
300
|
+
* component's steps narrow to. Raw values; `parseComponentTargets` groups them.
|
|
301
|
+
*/
|
|
302
|
+
targets?: string[];
|
|
297
303
|
/** `chant components fan-out --canary <component>` (#3049), repeatable — components that form wave 1. */
|
|
298
304
|
canary?: string[];
|
|
299
305
|
/**
|
|
@@ -302,6 +308,12 @@ export interface ParsedArgs {
|
|
|
302
308
|
* pr-apply finds it from the commit through `--forge` when omitted.
|
|
303
309
|
*/
|
|
304
310
|
pr?: number;
|
|
311
|
+
/** `chant lock --list` (#4293): print the target locks instead of taking any. */
|
|
312
|
+
list?: boolean;
|
|
313
|
+
/** `chant lock --stage plan|apply|lock` (#4293): how the lock is taken. Default `lock`. */
|
|
314
|
+
lockStage?: string;
|
|
315
|
+
/** `chant lock --from-report <file>` (#4293): lock the components a `pr-plan.json` lists. */
|
|
316
|
+
fromReport?: string;
|
|
305
317
|
/** Every `--pr <n>`, in order: `chant release compose` (#4168 4.3) takes several. */
|
|
306
318
|
prs?: number[];
|
|
307
319
|
/** `chant release cut --channel <tag>` (#4168 4.3): the tag a publish of the release moves in place of `latest`. */
|
|
@@ -345,6 +357,21 @@ export interface ParsedArgs {
|
|
|
345
357
|
* approved plans of the merges between or a gate of its own.
|
|
346
358
|
*/
|
|
347
359
|
green?: boolean;
|
|
360
|
+
/** `chant build --pr-loop --pr-env` (#4298) — add the env-up, env-down and env-sweep jobs of a pull request's environment. */
|
|
361
|
+
prEnv?: boolean;
|
|
362
|
+
/**
|
|
363
|
+
* `chant components pr-apply --before-merge` (#4301, ws-106): apply an open
|
|
364
|
+
* pull request's plan from its head, once the declaration at base sets
|
|
365
|
+
* `ci.workflow: "apply-before-merge"` and the pull request is approved,
|
|
366
|
+
* mergeable, undiverged and green.
|
|
367
|
+
*/
|
|
368
|
+
beforeMerge?: boolean;
|
|
369
|
+
/**
|
|
370
|
+
* `chant build --pr-loop --apply-before-merge` and `chant ci run apply
|
|
371
|
+
* --apply-before-merge` (#4301): the pipeline's apply job applies an open
|
|
372
|
+
* pull request before it merges, and a merge records.
|
|
373
|
+
*/
|
|
374
|
+
applyBeforeMerge?: boolean;
|
|
348
375
|
/** `chant build --pr-loop --pr-build` (#4088) — build and lint before pr-plan and pr-apply. */
|
|
349
376
|
prBuild?: boolean;
|
|
350
377
|
/** `chant ci serve --build-command <line>` (#4169) — the line that builds a commit a `ci/build/<sha>` tag asks for, in a worktree of it. */
|
|
@@ -477,7 +504,7 @@ export interface ParsedArgs {
|
|
|
477
504
|
records?: boolean;
|
|
478
505
|
/** `chant workspace wip save --label <text>` (#3172): what the caller calls the snapshot, such as a turn id. */
|
|
479
506
|
label?: string;
|
|
480
|
-
/** `chant workspace wip --branch <branch>` (#3172): list one branch's snapshots. */
|
|
507
|
+
/** `chant workspace wip --branch <branch>` (#3172): list one branch's snapshots. `chant components pr-apply|pr-record --branch <branch>` (#4292): the branch pushed, which `ci.branches` routes members by; default the forge's GITHUB_REF_NAME or CI_COMMIT_BRANCH. */
|
|
481
508
|
branch?: string;
|
|
482
509
|
/** `chant ci workflow --workflow <name>`, repeatable (#3573): a workflow whose completion triggers a tick, beside those the scan finds. Also `chant ci init --workflow <name>` (#4168 step 6), the prescribed workflow to set up. */
|
|
483
510
|
ciWorkflows?: string[];
|
|
@@ -491,6 +518,8 @@ export interface ParsedArgs {
|
|
|
491
518
|
ciSecrets?: string;
|
|
492
519
|
/** `chant ci workflow --chant <command>` (#3573): the command the tick step runs chant with, in place of the npx pin. Also `chant ci run --chant` (#4094): what runs chant in the job. */
|
|
493
520
|
ciChant?: string;
|
|
521
|
+
/** `chant components pr-post --result <result>` (#4295): the plan job's own result. `failure` or `cancelled` fails the `chant/plan` status. */
|
|
522
|
+
planResult?: string;
|
|
494
523
|
/** `chant ci run|serve --no-status` (#4097): post no commit status for the job. `chant components pr-plan|pr-apply --no-status` (#4161): post neither the status nor the note. */
|
|
495
524
|
noStatus?: boolean;
|
|
496
525
|
/** `chant ci run|serve --status-url <template>` (#4097): the link a job's status carries. `{key}` is `<pr>/<commit>[/<member>]`, as `--report-url` keys it. */
|
|
@@ -737,7 +766,7 @@ export interface ParsedArgs {
|
|
|
737
766
|
* on a model-authored channel.
|
|
738
767
|
*/
|
|
739
768
|
allowSameOrigin?: boolean;
|
|
740
|
-
/** `chant approve --role <name>` (#2508) — roles the approver claims,
|
|
769
|
+
/** `chant approve --role <name>` (#2508) — roles the approver claims, which a gate's policy sees. A quorum never counts them (#4290): its roles come from `.chant/trust.json` at base. Repeatable; a comma list also works. */
|
|
741
770
|
roles?: string[];
|
|
742
771
|
/** `chant approve --agent` (#2508) — record the approval as an agent's. An agent never counts toward a quorum; it passes a gate only through a policy permit in `enforce` mode. */
|
|
743
772
|
agent?: boolean;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource targeting (#4300): `--target <component>:<address>` narrows a
|
|
3
|
+
* component's steps, each capability accepts or refuses by name, and the
|
|
4
|
+
* targets join the plan digest an approval binds.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test } from "vitest";
|
|
8
|
+
import { CapabilityRegistry, parseComponentTargets, targetRefusal, type CapabilityPlan, type DeployContext } from "./capability";
|
|
9
|
+
import { deployContext, runComponentDeploy, type DriverComponent } from "./driver";
|
|
10
|
+
import { bindTargets, planWaveComponent, targetedPlanDigest } from "./wave-plan";
|
|
11
|
+
import { fanOutGateDigest, runFanOut } from "./fan-out-run";
|
|
12
|
+
import { planFanOut } from "./fan-out";
|
|
13
|
+
import { memoryGateLedgerPort } from "../op/gate";
|
|
14
|
+
|
|
15
|
+
const component = (name: string, ...kinds: string[]): DriverComponent => ({
|
|
16
|
+
name,
|
|
17
|
+
deploy: [{ phase: "Deploy", steps: kinds.map((kind) => ({ kind })) }],
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/** A registry with a verb that accepts targets, one that refuses, and one that says nothing. */
|
|
21
|
+
function registry(seen: DeployContext[] = []): CapabilityRegistry {
|
|
22
|
+
const r = new CapabilityRegistry();
|
|
23
|
+
r.register({
|
|
24
|
+
kind: "narrow",
|
|
25
|
+
targets: "accept",
|
|
26
|
+
async plan(ctx: DeployContext): Promise<CapabilityPlan> {
|
|
27
|
+
seen.push(ctx);
|
|
28
|
+
return { member: ctx.component, planDigest: "sha256:base", ...(ctx.targets ? { targets: ctx.targets } : {}) };
|
|
29
|
+
},
|
|
30
|
+
async run(ctx: DeployContext) {
|
|
31
|
+
seen.push(ctx);
|
|
32
|
+
return { ok: true };
|
|
33
|
+
},
|
|
34
|
+
} as never);
|
|
35
|
+
r.register({
|
|
36
|
+
kind: "whole",
|
|
37
|
+
targets: { refuse: "whole applies everything" },
|
|
38
|
+
async plan(ctx: DeployContext): Promise<CapabilityPlan> {
|
|
39
|
+
return { member: ctx.component, planDigest: "sha256:whole" };
|
|
40
|
+
},
|
|
41
|
+
async run() {
|
|
42
|
+
return { ok: true };
|
|
43
|
+
},
|
|
44
|
+
} as never);
|
|
45
|
+
r.register({
|
|
46
|
+
kind: "silent",
|
|
47
|
+
async run() {
|
|
48
|
+
return { ok: true };
|
|
49
|
+
},
|
|
50
|
+
} as never);
|
|
51
|
+
return r;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
describe("parseComponentTargets", () => {
|
|
55
|
+
test("groups by component, sorted and unique, splitting at the first colon", () => {
|
|
56
|
+
expect(parseComponentTargets(["net:aws_vpc.main", "net:aws_subnet.a", "net:aws_vpc.main", "app:Deployment/prod/web", "net:x[\"a:b\"]"])).toEqual({
|
|
57
|
+
app: ["Deployment/prod/web"],
|
|
58
|
+
net: ["aws_subnet.a", "aws_vpc.main", 'x["a:b"]'],
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("refuses a value with no component or no address, and an unknown component", () => {
|
|
63
|
+
expect(() => parseComponentTargets([":aws_vpc.main"])).toThrow(/--target <component>:<address>/);
|
|
64
|
+
expect(() => parseComponentTargets(["net:"])).toThrow(/--target <component>:<address>/);
|
|
65
|
+
expect(() => parseComponentTargets(["nope:a"], new Set(["net"]))).toThrow(/no component is named nope/);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("targetRefusal", () => {
|
|
70
|
+
test("accept, refuse with its reason, and unset refusing by name", () => {
|
|
71
|
+
expect(targetRefusal({ kind: "a", targets: "accept" })).toBeUndefined();
|
|
72
|
+
expect(targetRefusal({ kind: "b", targets: { refuse: "no" } })).toBe("no");
|
|
73
|
+
expect(targetRefusal({ kind: "c" })).toMatch(/^c does not say/);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("deployContext", () => {
|
|
78
|
+
test("gives a component its own targets, sorted, and none to another", () => {
|
|
79
|
+
const options = { env: "dev", targets: { net: ["b", "a", "a"] } };
|
|
80
|
+
expect(deployContext(options, "net").targets).toEqual(["a", "b"]);
|
|
81
|
+
expect(deployContext(options, "app").targets).toBeUndefined();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("planning a targeted component", () => {
|
|
86
|
+
test("binds the targets into the member's digest and its change-set part", async () => {
|
|
87
|
+
const seen: DeployContext[] = [];
|
|
88
|
+
const ctx = deployContext({ env: "dev", targets: { net: ["aws_vpc.main"] } }, "net");
|
|
89
|
+
const planned = await planWaveComponent(component("net", "narrow"), registry(seen), ctx, {});
|
|
90
|
+
expect("error" in planned).toBe(false);
|
|
91
|
+
if ("error" in planned) return;
|
|
92
|
+
expect(seen[0]!.targets).toEqual(["aws_vpc.main"]);
|
|
93
|
+
const digest = targetedPlanDigest("sha256:base", ["aws_vpc.main"]);
|
|
94
|
+
expect(planned.members).toEqual([{ member: "net", planDigest: digest }]);
|
|
95
|
+
expect(planned.parts[0]!.member.planDigest).toBe(digest);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("another set of targets is another digest, and none is the plan's own", async () => {
|
|
99
|
+
const a = targetedPlanDigest("sha256:base", ["x"]);
|
|
100
|
+
const b = targetedPlanDigest("sha256:base", ["y"]);
|
|
101
|
+
expect(a).not.toBe(b);
|
|
102
|
+
expect(targetedPlanDigest("sha256:base", ["y", "x"])).toBe(targetedPlanDigest("sha256:base", ["x", "y"]));
|
|
103
|
+
const untargeted = await planWaveComponent(component("net", "narrow"), registry(), deployContext({ env: "dev" }, "net"), {});
|
|
104
|
+
expect("error" in untargeted ? undefined : untargeted.members[0]!.planDigest).toBe("sha256:base");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("a verb that refuses names itself, and so does one that says nothing", async () => {
|
|
108
|
+
const ctx = deployContext({ env: "dev", targets: { net: ["a"] } }, "net");
|
|
109
|
+
const refused = await planWaveComponent(component("net", "narrow", "whole"), registry(), ctx, {});
|
|
110
|
+
expect(refused).toEqual({ component: "net", error: 'component "net": whole refuses targets: whole applies everything' });
|
|
111
|
+
const silent = await planWaveComponent(component("net", "silent"), registry(), ctx, {});
|
|
112
|
+
expect("error" in silent && silent.error).toMatch(/silent refuses targets: silent does not say/);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("a plan that does not give the targets back refuses", () => {
|
|
116
|
+
const ctx: DeployContext = { env: "dev", component: "net", targets: ["a", "b"] };
|
|
117
|
+
expect(() => bindTargets("narrow", ctx, { member: "net", planDigest: "d" })).toThrow(/names no targets/);
|
|
118
|
+
expect(() => bindTargets("narrow", ctx, { member: "net", planDigest: "d", targets: ["a"] })).toThrow(/names a$/);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("running a targeted component", () => {
|
|
123
|
+
test("refuses before any step runs when one verb can't narrow", async () => {
|
|
124
|
+
const seen: DeployContext[] = [];
|
|
125
|
+
const ctx = deployContext({ env: "dev", targets: { net: ["a"] } }, "net");
|
|
126
|
+
const result = await runComponentDeploy(component("net", "narrow", "whole"), ctx, registry(seen), {}, undefined, { port: memoryGateLedgerPort() });
|
|
127
|
+
expect(result.status).toBe("fail");
|
|
128
|
+
expect(result.records[0]).toMatchObject({ phase: "Targets", kind: "whole", status: "fail" });
|
|
129
|
+
expect(seen).toEqual([]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("hands the targets to a verb that accepts them", async () => {
|
|
133
|
+
const seen: DeployContext[] = [];
|
|
134
|
+
const ctx = deployContext({ env: "dev", targets: { net: ["a"] } }, "net");
|
|
135
|
+
const result = await runComponentDeploy(component("net", "narrow"), ctx, registry(seen), {}, undefined, { port: memoryGateLedgerPort() });
|
|
136
|
+
expect(result.status).toBe("ok");
|
|
137
|
+
expect(seen[0]!.targets).toEqual(["a"]);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("a gate over the whole set", () => {
|
|
142
|
+
test("binds the targets, so the untargeted approval does not cover a targeted run", async () => {
|
|
143
|
+
expect(fanOutGateDigest("sha256:set", undefined)).toBe("sha256:set");
|
|
144
|
+
expect(fanOutGateDigest("sha256:set", { net: [] })).toBe("sha256:set");
|
|
145
|
+
const narrowed = fanOutGateDigest("sha256:set", { net: ["a"] });
|
|
146
|
+
expect(narrowed).not.toBe("sha256:set");
|
|
147
|
+
expect(fanOutGateDigest("sha256:set", { net: ["b"] })).not.toBe(narrowed);
|
|
148
|
+
|
|
149
|
+
const estate = [component("net", "narrow")];
|
|
150
|
+
const plan = planFanOut({ components: estate, changed: ["net"] });
|
|
151
|
+
const gates = memoryGateLedgerPort();
|
|
152
|
+
const result = await runFanOut(plan, estate, registry(), {
|
|
153
|
+
env: "dev",
|
|
154
|
+
gates,
|
|
155
|
+
now: "2026-01-01T00:00:00Z",
|
|
156
|
+
gate: { op: "fan-out", gate: "set" },
|
|
157
|
+
targets: { net: ["a"] },
|
|
158
|
+
});
|
|
159
|
+
expect(result.status).toBe("gated");
|
|
160
|
+
expect(result.gate?.planDigest).toBe(fanOutGateDigest(plan.digest, { net: ["a"] }));
|
|
161
|
+
});
|
|
162
|
+
});
|