@intentius/chant 0.124.14 → 0.124.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build.d.ts +6 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +1 -0
- package/dist/change-set.d.ts +24 -2
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +50 -1
- package/dist/ci-jobs.d.ts +227 -0
- package/dist/ci-jobs.d.ts.map +1 -0
- package/dist/ci-jobs.js +448 -0
- package/dist/cli/flush.d.ts +16 -0
- package/dist/cli/flush.d.ts.map +1 -0
- package/dist/cli/flush.js +42 -0
- package/dist/cli/handlers/approve.d.ts +98 -0
- package/dist/cli/handlers/approve.d.ts.map +1 -0
- package/dist/cli/handlers/approve.js +495 -0
- package/dist/cli/handlers/operator.d.ts +1 -95
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/operator.js +4 -488
- package/dist/cli/handlers/override-approve.js +1 -1
- package/dist/cli/main.d.ts +4 -15
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +4 -1062
- package/dist/cli/parse-args.d.ts +6 -0
- package/dist/cli/parse-args.d.ts.map +1 -0
- package/dist/cli/parse-args.js +1028 -0
- package/dist/components/fan-out-record.d.ts +1 -1
- package/dist/components/fan-out-record.d.ts.map +1 -1
- package/dist/components/fan-out-record.js +3 -7
- package/dist/components/fan-out-run.d.ts +5 -3
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +7 -5
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +3 -2
- package/dist/composite.d.ts +10 -2
- package/dist/composite.d.ts.map +1 -1
- package/dist/composite.js +8 -0
- package/dist/discovery/collect.d.ts +2 -1
- package/dist/discovery/collect.d.ts.map +1 -1
- package/dist/discovery/collect.js +16 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-import.js +2 -107
- package/dist/discovery/host-composite.d.ts +35 -0
- package/dist/discovery/host-composite.d.ts.map +1 -0
- package/dist/discovery/host-composite.js +93 -0
- package/dist/discovery/index.d.ts +9 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/index.js +1 -1
- package/dist/gate-decision.d.ts +20 -9
- package/dist/gate-decision.d.ts.map +1 -1
- package/dist/gate-decision.js +72 -19
- package/dist/gated-waves.d.ts +25 -6
- package/dist/gated-waves.d.ts.map +1 -1
- package/dist/gated-waves.js +21 -6
- package/dist/lifecycle/gate-audit.d.ts +69 -2
- package/dist/lifecycle/gate-audit.d.ts.map +1 -1
- package/dist/lifecycle/gate-audit.js +76 -2
- package/dist/lifecycle/gate-ledger.d.ts +24 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.js +9 -1
- package/dist/op/activities/activity-contracts.d.ts +5 -5
- package/dist/op/activities/activity-contracts.js +1 -1
- package/dist/op/activities/change-set.d.ts +8 -3
- package/dist/op/activities/change-set.d.ts.map +1 -1
- package/dist/op/activities/change-set.js +5 -2
- package/dist/op/activities/reconcile.d.ts +28 -16
- package/dist/op/activities/reconcile.d.ts.map +1 -1
- package/dist/op/activities/reconcile.js +34 -20
- package/dist/op/builders.d.ts +1 -1
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +1 -1
- package/dist/op/op-waves-run.d.ts +2 -2
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +8 -9
- package/dist/op/op-waves.d.ts.map +1 -1
- package/dist/op/op-waves.js +4 -8
- package/dist/package-version.d.ts +2 -0
- package/dist/package-version.d.ts.map +1 -0
- package/dist/package-version.js +10 -0
- package/dist/wave-engine.d.ts +62 -0
- package/dist/wave-engine.d.ts.map +1 -0
- package/dist/wave-engine.js +59 -0
- package/dist/wave-layout.d.ts +29 -0
- package/dist/wave-layout.d.ts.map +1 -0
- package/dist/wave-layout.js +41 -0
- package/dist/wave-runtime.d.ts +29 -0
- package/dist/wave-runtime.d.ts.map +1 -0
- package/dist/wave-runtime.js +103 -0
- package/dist/wave-runtime.mjs +331 -0
- package/dist/workspace/__fixtures__/contract-repo.d.ts +7 -0
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.js +17 -1
- package/dist/workspace/__fixtures__/sessions.d.ts +1 -1
- package/dist/workspace/__fixtures__/sessions.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/sessions.js +3 -3
- package/dist/workspace/ajv2020.d.ts +5 -0
- package/dist/workspace/ajv2020.d.ts.map +1 -0
- package/dist/workspace/ajv2020.js +14 -0
- package/dist/workspace/change-set.schema.json +5 -3
- package/dist/workspace/ci-green-workflow.d.ts +3 -1
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-green-workflow.js +5 -2
- package/dist/workspace/class-data.d.ts +84 -0
- package/dist/workspace/class-data.d.ts.map +1 -0
- package/dist/workspace/class-data.js +238 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +4 -8
- package/dist/workspace/kinds.d.ts +3 -7
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/kinds.js +8 -45
- package/dist/workspace/points.d.ts.map +1 -1
- package/dist/workspace/points.js +2 -5
- package/dist/workspace/principal-classes.d.ts +4 -11
- package/dist/workspace/principal-classes.d.ts.map +1 -1
- package/dist/workspace/principal-classes.js +13 -99
- package/package.json +5 -3
- package/src/build.ts +8 -0
- package/src/change-set.test.ts +48 -0
- package/src/change-set.ts +52 -2
- package/src/ci-jobs.test.ts +49 -0
- package/src/ci-jobs.ts +515 -0
- package/src/cli/flush.ts +44 -0
- package/src/cli/handlers/approve.ts +618 -0
- package/src/cli/handlers/operator.ts +10 -608
- package/src/cli/handlers/override-approve.test.ts +1 -1
- package/src/cli/handlers/override-approve.ts +1 -1
- package/src/cli/main.ts +5 -816
- package/src/cli/parse-args.ts +781 -0
- package/src/components/fan-out-record.ts +3 -7
- package/src/components/fan-out-run.ts +7 -5
- package/src/components/pr-pipeline.ts +3 -2
- package/src/composite.ts +15 -3
- package/src/discovery/collect.ts +15 -1
- package/src/discovery/fold-build-parity.test.ts +71 -0
- package/src/discovery/fold-import.ts +2 -113
- package/src/discovery/fold-project-provenance.test.ts +21 -4
- package/src/discovery/host-composite.ts +101 -0
- package/src/discovery/index.ts +11 -1
- package/src/gate-decision-bundle.test.ts +6 -4
- package/src/gate-decision-identity.test.ts +59 -6
- package/src/gate-decision.ts +81 -21
- package/src/gated-waves.ts +28 -6
- package/src/lifecycle/gate-audit.test.ts +55 -1
- package/src/lifecycle/gate-audit.ts +116 -3
- package/src/lifecycle/gate-ledger.test.ts +20 -1
- package/src/lifecycle/gate-ledger.ts +33 -1
- package/src/op/activities/activity-contracts.ts +1 -1
- package/src/op/activities/change-set.ts +11 -3
- package/src/op/activities/lexicon-upgrade.test.ts +2 -0
- package/src/op/activities/reconcile.test.ts +62 -2
- package/src/op/activities/reconcile.ts +46 -22
- package/src/op/builders.ts +1 -1
- package/src/op/op-waves-run.ts +9 -9
- package/src/op/op-waves.test.ts +2 -0
- package/src/op/op-waves.ts +4 -8
- package/src/op/work-lease-run.test.ts +12 -4
- package/src/package-version.ts +11 -0
- package/src/wave-engine.test.ts +107 -0
- package/src/wave-engine.ts +98 -0
- package/src/wave-layout.ts +44 -0
- package/src/wave-runtime-bundle.test.ts +70 -0
- package/src/wave-runtime.e2e.test.ts +177 -0
- package/src/wave-runtime.ts +104 -0
- package/src/workspace/__fixtures__/contract-repo.ts +17 -1
- package/src/workspace/__fixtures__/sessions.ts +3 -3
- package/src/workspace/ajv2020.ts +17 -0
- package/src/workspace/change-set.schema.json +5 -3
- package/src/workspace/ci-green-workflow.test.ts +4 -4
- package/src/workspace/ci-green-workflow.ts +5 -2
- package/src/workspace/class-data.test.ts +64 -0
- package/src/workspace/class-data.ts +236 -0
- package/src/workspace/declaration.ts +4 -10
- package/src/workspace/kinds.ts +8 -40
- package/src/workspace/points.ts +2 -5
- package/src/workspace/principal-classes.ts +13 -99
- package/src/workspace/records-formats.test.ts +7 -2
- package/src/workspace/records-sessions-write.test.ts +3 -3
- package/src/workspace/status-contract.e2e.test.ts +37 -0
- package/src/workspace/status-contract.test.ts +23 -21
package/dist/ci-jobs.js
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The job primitives chant's three CI generators share (#3765): the scheduled
|
|
3
|
+
* Op pipeline (`generateOpsPipeline`), the Op waves pipeline
|
|
4
|
+
* (`generateOpWavesPipeline`) and the component pipeline (`chant build
|
|
5
|
+
* --components --generate`, with `--pr-loop`, `--wave-gate` and
|
|
6
|
+
* `--promote-to`). Each forge lexicon builds its documents from these, so a
|
|
7
|
+
* trigger, a token, a permission rule or an image default is decided once:
|
|
8
|
+
*
|
|
9
|
+
* - the image a generated job runs in, {@link CI_IMAGE};
|
|
10
|
+
* - the branches a trigger filters on, {@link triggerBranches};
|
|
11
|
+
* - the job token and secret expressions of GitHub Actions and Forgejo
|
|
12
|
+
* Actions, {@link GITHUB_JOB_TOKEN} and {@link githubSecret};
|
|
13
|
+
* - the `permissions:` a finding-mode needs and the rules for adding to it,
|
|
14
|
+
* {@link permissionsFor} and {@link mergePermissions};
|
|
15
|
+
* - the `setup` steps and `environment` a job may carry, checked once per
|
|
16
|
+
* forge ({@link assertSetupSteps}, {@link assertEnvironment},
|
|
17
|
+
* {@link gitlabSetupScript}, {@link assertGitlabEnvironment});
|
|
18
|
+
* - GitLab's `rules:` expressions and `id_tokens:` declaration.
|
|
19
|
+
*
|
|
20
|
+
* The Forgejo dialect (action refs, dropped `permissions:` and
|
|
21
|
+
* `environment:`, the header note naming what was dropped) is the forgejo
|
|
22
|
+
* lexicon's, in its `components/ci-jobs.ts`, since the transform it applies
|
|
23
|
+
* lives there.
|
|
24
|
+
*/
|
|
25
|
+
// ── Every forge ─────────────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* The image every generated job runs in unless the caller names another. It
|
|
28
|
+
* carries node and npm, which `chant` needs, and git, which a job that reads
|
|
29
|
+
* the base commit or pushes to `chant/lifecycle` needs (#3789).
|
|
30
|
+
*/
|
|
31
|
+
export const CI_IMAGE = "node:22";
|
|
32
|
+
/**
|
|
33
|
+
* The branch a `push` trigger filters on when it names none. A generator
|
|
34
|
+
* works from a spec, not a checkout, so it cannot read a project's real
|
|
35
|
+
* default branch; a project on another one names it.
|
|
36
|
+
*/
|
|
37
|
+
export const DEFAULT_BRANCH = "main";
|
|
38
|
+
/**
|
|
39
|
+
* The branches a push trigger filters on: the ones given, or
|
|
40
|
+
* {@link DEFAULT_BRANCH} when none are. An empty list is none, on every
|
|
41
|
+
* forge.
|
|
42
|
+
*/
|
|
43
|
+
export function pushBranches(branches) {
|
|
44
|
+
return branches && branches.length > 0 ? [...branches] : [DEFAULT_BRANCH];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The branches a trigger filters on: a `pull_request` trigger's list, or
|
|
48
|
+
* undefined for every branch; a `push` trigger's, with {@link pushBranches}'
|
|
49
|
+
* default; none for `cron`.
|
|
50
|
+
*/
|
|
51
|
+
export function triggerBranches(trigger) {
|
|
52
|
+
switch (trigger.kind) {
|
|
53
|
+
case "cron":
|
|
54
|
+
return undefined;
|
|
55
|
+
case "pull_request":
|
|
56
|
+
return trigger.branches && trigger.branches.length > 0 ? [...trigger.branches] : undefined;
|
|
57
|
+
case "push":
|
|
58
|
+
return pushBranches(trigger.branches);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** A job id every forge accepts, from a kebab- or camel-cased name. */
|
|
62
|
+
export function ciJobName(name) {
|
|
63
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
64
|
+
}
|
|
65
|
+
/** A command template with each `{name}` part filled in. */
|
|
66
|
+
export function fillCommand(template, name) {
|
|
67
|
+
return template.map((part) => part.replace("{name}", name));
|
|
68
|
+
}
|
|
69
|
+
/** `{ [key]: map }` when the map has an entry, else `{}`: for spreading an optional mapping into a document. */
|
|
70
|
+
export function nonEmpty(key, map) {
|
|
71
|
+
return map && Object.keys(map).length > 0 ? { [key]: map } : {};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A job's `environment:` mapping. Always the mapping form, never the string
|
|
75
|
+
* shorthand, so adding a `url` later is a new key rather than a reshaped
|
|
76
|
+
* value. GitHub and GitLab read the same two keys.
|
|
77
|
+
*/
|
|
78
|
+
export function environmentDoc(environment) {
|
|
79
|
+
return { name: environment.name, ...(environment.url === undefined ? {} : { url: environment.url }) };
|
|
80
|
+
}
|
|
81
|
+
/** The directory part of a relative path, `.` when there is none. */
|
|
82
|
+
export function dirnameOf(path) {
|
|
83
|
+
const i = path.lastIndexOf("/");
|
|
84
|
+
return i <= 0 ? "." : path.slice(0, i);
|
|
85
|
+
}
|
|
86
|
+
// ── GitHub Actions and Forgejo Actions ──────────────────────────────────────
|
|
87
|
+
/** The runner label a generated job asks for; the Forgejo dialect rewrites it to the project's label. */
|
|
88
|
+
export const GITHUB_RUNNER = "ubuntu-latest";
|
|
89
|
+
/** The job token, which GitHub and Forgejo both mint for every run. */
|
|
90
|
+
export const GITHUB_JOB_TOKEN = "${{ github.token }}";
|
|
91
|
+
/** A repository secret, by name. */
|
|
92
|
+
export function githubSecret(name) {
|
|
93
|
+
return `\${{ secrets.${name} }}`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* A repository secret, or the job token when the secret is unset (#3624). An
|
|
97
|
+
* unset secret reads as the empty string, so a workflow generated for a
|
|
98
|
+
* production token still runs in a fork or a fresh repository, with what the
|
|
99
|
+
* job token may do.
|
|
100
|
+
*/
|
|
101
|
+
export function githubSecretOrJobToken(name) {
|
|
102
|
+
return `\${{ secrets.${name} || github.token }}`;
|
|
103
|
+
}
|
|
104
|
+
/** Each name mapped from the repository secret of the same name. */
|
|
105
|
+
export function githubSecretEnv(names) {
|
|
106
|
+
return Object.fromEntries(names.map((n) => [n, githubSecret(n)]));
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Least-privilege `permissions:` for a finding-mode. `report` needs no write
|
|
110
|
+
* access; `issue` needs only `issues: write`; `pull-request` (and
|
|
111
|
+
* `merge-request`, generated the same way when a GitLab-authored spec is
|
|
112
|
+
* targeted at github) needs `contents: write` to push the reconcile branch
|
|
113
|
+
* plus `pull-requests: write` to open the PR. `comment` changes nothing in
|
|
114
|
+
* the repository, so its whole scope is `{ contents: read, pull-requests:
|
|
115
|
+
* write }`.
|
|
116
|
+
*/
|
|
117
|
+
export function permissionsForMode(mode) {
|
|
118
|
+
switch (mode) {
|
|
119
|
+
case "issue":
|
|
120
|
+
return { contents: "read", issues: "write" };
|
|
121
|
+
case "comment":
|
|
122
|
+
return { contents: "read", "pull-requests": "write" };
|
|
123
|
+
case "pull-request":
|
|
124
|
+
case "merge-request":
|
|
125
|
+
return { contents: "write", "pull-requests": "write" };
|
|
126
|
+
case "report":
|
|
127
|
+
return { contents: "read" };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* {@link permissionsForMode} on a trigger. A `pull_request` trigger reports
|
|
132
|
+
* its finding as a comment on the triggering PR itself (#2084): any mode but
|
|
133
|
+
* `report` posts something to act on a finding, so on that trigger every
|
|
134
|
+
* such mode also gets `pull-requests: write` for the comment.
|
|
135
|
+
*/
|
|
136
|
+
export function permissionsFor(mode, trigger) {
|
|
137
|
+
const base = permissionsForMode(mode);
|
|
138
|
+
if (trigger.kind === "pull_request" && mode !== "report")
|
|
139
|
+
return { ...base, "pull-requests": "write" };
|
|
140
|
+
return base;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Every scope `GITHUB_TOKEN` accepts in a workflow's `permissions:` mapping,
|
|
144
|
+
* kebab-cased as GitHub spells them. An additive scope outside this set is
|
|
145
|
+
* refused by name rather than emitted: GitHub ignores an unknown key, so
|
|
146
|
+
* `id_token` or `idToken` would generate a workflow that looks like it grants
|
|
147
|
+
* OIDC and hands the run no token at all.
|
|
148
|
+
*/
|
|
149
|
+
export const GITHUB_TOKEN_SCOPES = new Set([
|
|
150
|
+
"actions",
|
|
151
|
+
"attestations",
|
|
152
|
+
"checks",
|
|
153
|
+
"contents",
|
|
154
|
+
"deployments",
|
|
155
|
+
"discussions",
|
|
156
|
+
"id-token",
|
|
157
|
+
"issues",
|
|
158
|
+
"models",
|
|
159
|
+
"packages",
|
|
160
|
+
"pages",
|
|
161
|
+
"pull-requests",
|
|
162
|
+
"repository-projects",
|
|
163
|
+
"security-events",
|
|
164
|
+
"statuses",
|
|
165
|
+
]);
|
|
166
|
+
/**
|
|
167
|
+
* Merge a spec's additive `permissions` over the finding-mode's own set
|
|
168
|
+
* (#2242), refusing by name anything that is not strictly additive:
|
|
169
|
+
*
|
|
170
|
+
* - a blanket `write-all`/`read-all`, in either the key or the value
|
|
171
|
+
* position, which is the exact thing {@link permissionsForMode} exists to
|
|
172
|
+
* avoid;
|
|
173
|
+
* - a scope GitHub does not define ({@link GITHUB_TOKEN_SCOPES}), because
|
|
174
|
+
* GitHub ignores the key and the run silently gets nothing;
|
|
175
|
+
* - a scope the mode already grants, at any value — additive means additive,
|
|
176
|
+
* so this can neither downgrade `contents: write` to read nor restate it.
|
|
177
|
+
* A mode whose set is wrong is fixed by changing the mode, where the
|
|
178
|
+
* scope and the behavior that spends it stay together;
|
|
179
|
+
* - `pull-requests: write` on a trigger with no pull request. Pull-request
|
|
180
|
+
* access is what the finding-modes own: `pull-request` grants it together
|
|
181
|
+
* with the `contents: write` needed to push the branch first, and
|
|
182
|
+
* `comment` grants it on the one trigger that carries a pull request to
|
|
183
|
+
* comment on. Adding it beside a mode that posts nothing, on a cron or
|
|
184
|
+
* push run, grants write access no step in the generated job can spend.
|
|
185
|
+
*/
|
|
186
|
+
export function mergePermissions(name, base, additive, trigger) {
|
|
187
|
+
const merged = { ...base };
|
|
188
|
+
for (const [rawScope, value] of Object.entries(additive)) {
|
|
189
|
+
const scope = rawScope.trim();
|
|
190
|
+
const where = `Scheduled Op "${name}" adds permission "${scope}: ${value}"`;
|
|
191
|
+
if (scope === "write-all" || scope === "read-all" || String(value).endsWith("-all")) {
|
|
192
|
+
throw new Error(`${where}, a blanket grant. \`permissions\` on a scheduled Op is additive over the ` +
|
|
193
|
+
`least-privilege set its finding-mode needs, one named scope at a time. Name the scopes the ` +
|
|
194
|
+
`job actually spends (e.g. { "id-token": "write" }).`);
|
|
195
|
+
}
|
|
196
|
+
if (!GITHUB_TOKEN_SCOPES.has(scope)) {
|
|
197
|
+
throw new Error(`${where}, which is not a GITHUB_TOKEN permission scope. GitHub ignores an unrecognized key, so ` +
|
|
198
|
+
`this would emit a workflow that reads as granted and hands the run nothing. Known scopes: ` +
|
|
199
|
+
`${[...GITHUB_TOKEN_SCOPES].sort().join(", ")}.`);
|
|
200
|
+
}
|
|
201
|
+
if (scope in base) {
|
|
202
|
+
throw new Error(`${where}, but its finding-mode already grants "${scope}: ${base[scope]}". These permissions are ` +
|
|
203
|
+
`additive only — they never replace, widen or downgrade a scope the mode computed. Change the ` +
|
|
204
|
+
`Op's findingMode if that set is wrong, and add only scopes no mode grants (e.g. "id-token").`);
|
|
205
|
+
}
|
|
206
|
+
if (scope === "pull-requests" && trigger.kind !== "pull_request") {
|
|
207
|
+
throw new Error(`${where}, but this Op's trigger is "${trigger.kind}", which carries no pull request. Pull-request ` +
|
|
208
|
+
`write access belongs to a finding-mode: "pull-request" grants it with the contents: write its ` +
|
|
209
|
+
`branch push needs, and "comment" grants it on the pull_request trigger. Set findingMode instead ` +
|
|
210
|
+
`of adding the scope here.`);
|
|
211
|
+
}
|
|
212
|
+
merged[scope] = value;
|
|
213
|
+
}
|
|
214
|
+
return merged;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Refs that name an action repository's own default branch. A generated
|
|
218
|
+
* workflow is committed once and then re-run unattended, often over a cloud
|
|
219
|
+
* role, so "whatever was pushed to that repo last" is not a version — the
|
|
220
|
+
* code that assumes the role can change between the run somebody reviewed and
|
|
221
|
+
* the next one. A release channel the action's author cuts deliberately (`v6`,
|
|
222
|
+
* `v6.2.4`, `stable`) or a commit sha is a version, and both pass.
|
|
223
|
+
*/
|
|
224
|
+
const DEFAULT_BRANCH_REFS = new Set(["main", "master", "head", "default"]);
|
|
225
|
+
/** `owner/repo` or `owner/repo/subpath`, then `@ref`. */
|
|
226
|
+
const USES_PATTERN = /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)((?:\/[A-Za-z0-9_.-]+)*)@([^\s@]+)$/;
|
|
227
|
+
/**
|
|
228
|
+
* Validate a `setup` list for GitHub or Forgejo Actions (#2242). A `run`
|
|
229
|
+
* entry needs a non-empty line and nothing else. A `uses` entry has to be a
|
|
230
|
+
* pinned `owner/repo[/subpath]@ref`: no bare `owner/repo`, since an unpinned
|
|
231
|
+
* action resolves to its default branch, and no ref in
|
|
232
|
+
* {@link DEFAULT_BRANCH_REFS} for the same reason spelled out loud. Local
|
|
233
|
+
* (`./path`) and container (`docker://`) refs are refused too — they are
|
|
234
|
+
* legal GitHub Actions, but the generator emits a workflow into a repository
|
|
235
|
+
* it has never seen, so it cannot know a local path resolves there.
|
|
236
|
+
*/
|
|
237
|
+
export function assertSetupSteps(name, setup) {
|
|
238
|
+
setup.forEach((step, index) => {
|
|
239
|
+
const where = `Scheduled Op "${name}" setup step ${index + 1}`;
|
|
240
|
+
if ("uses" in step) {
|
|
241
|
+
const ref = step.uses.trim();
|
|
242
|
+
const match = USES_PATTERN.exec(ref);
|
|
243
|
+
if (!match) {
|
|
244
|
+
throw new Error(`${where} has \`uses: "${step.uses}"\`, which is not a pinned action reference. ` +
|
|
245
|
+
`Write it as owner/repo@ref (optionally owner/repo/subpath@ref), e.g. ` +
|
|
246
|
+
`"aws-actions/configure-aws-credentials@v6". A local "./path" or "docker://" ref is not ` +
|
|
247
|
+
`accepted here: this generator emits a workflow into a repository it cannot inspect, so it ` +
|
|
248
|
+
`has no way to tell whether such a ref resolves there.`);
|
|
249
|
+
}
|
|
250
|
+
const gitRef = match[4];
|
|
251
|
+
if (DEFAULT_BRANCH_REFS.has(gitRef.toLowerCase())) {
|
|
252
|
+
throw new Error(`${where} pins \`uses: "${step.uses}"\` to "${gitRef}", the action repository's own default ` +
|
|
253
|
+
`branch, which names whatever was pushed there last rather than a version. A generated ` +
|
|
254
|
+
`workflow is committed once and re-run unattended, often over a cloud role, so pin a release ` +
|
|
255
|
+
`tag or a commit sha instead (e.g. "${match[1]}/${match[2]}@v1" or "@<40-char sha>").`);
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (step.run.trim() === "") {
|
|
260
|
+
throw new Error(`${where} has an empty \`run\` line. Give it a command, or drop the entry.`);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
/** One setup entry as a GitHub Actions step: `with:` and `env:` only when they hold something. */
|
|
265
|
+
export function setupStepDoc(step) {
|
|
266
|
+
if ("uses" in step) {
|
|
267
|
+
return {
|
|
268
|
+
uses: step.uses,
|
|
269
|
+
...nonEmpty("with", step.with),
|
|
270
|
+
...nonEmpty("env", step.env),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return { run: step.run, ...nonEmpty("env", step.env) };
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Validate an `environment` for GitHub Actions (#2257). GitHub creates an
|
|
277
|
+
* environment it has never seen on first use rather than failing the run,
|
|
278
|
+
* and an environment created that way carries no protection rules at all —
|
|
279
|
+
* so a job can name one and read as gated while being gated by nothing.
|
|
280
|
+
* Neither the generator nor GitHub can tell those apart at build time, which
|
|
281
|
+
* is why what is refused here is only the shape that could never bind: a
|
|
282
|
+
* name that is blank, and a `url` that is neither absolute nor an expression
|
|
283
|
+
* the forge resolves.
|
|
284
|
+
*/
|
|
285
|
+
export function assertEnvironment(name, environment) {
|
|
286
|
+
const where = `Scheduled Op "${name}" environment`;
|
|
287
|
+
if (environment.name.trim() === "") {
|
|
288
|
+
throw new Error(`${where} has an empty \`name\`. An environment is named repository configuration — the ` +
|
|
289
|
+
`protection rules and reviewers live on the environment, not in this workflow — so there is ` +
|
|
290
|
+
`nothing for a blank name to resolve to. Give it the environment's name, or drop the option.`);
|
|
291
|
+
}
|
|
292
|
+
if (environment.url === undefined)
|
|
293
|
+
return;
|
|
294
|
+
const url = environment.url.trim();
|
|
295
|
+
if (url === "") {
|
|
296
|
+
throw new Error(`${where} "${environment.name}" has an empty \`url\`. Omit the field rather than setting it to "".`);
|
|
297
|
+
}
|
|
298
|
+
if (!/^https?:\/\//.test(url) && !url.includes("${{")) {
|
|
299
|
+
throw new Error(`${where} "${environment.name}" has \`url: "${environment.url}"\`, which is neither an absolute ` +
|
|
300
|
+
`http(s) URL nor a \${{ }} expression. GitHub renders this value as the deployment's own link, ` +
|
|
301
|
+
`so a relative path becomes a dead link on the environment page rather than an error anywhere. ` +
|
|
302
|
+
`Write the full URL, or an expression the run resolves to one.`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// ── GitLab CI ───────────────────────────────────────────────────────────────
|
|
306
|
+
/** The pipeline source GitLab reports for a merge-request pipeline. */
|
|
307
|
+
export const GITLAB_MERGE_REQUEST_SOURCE = '$CI_PIPELINE_SOURCE == "merge_request_event"';
|
|
308
|
+
/** The pipeline source GitLab reports for a pipeline schedule. */
|
|
309
|
+
export const GITLAB_SCHEDULE_SOURCE = '$CI_PIPELINE_SOURCE == "schedule"';
|
|
310
|
+
/** A job variable that makes the runner clone the whole history, which a job reading the base commit needs. */
|
|
311
|
+
export const GITLAB_FULL_HISTORY = { GIT_DEPTH: "0" };
|
|
312
|
+
/** The `rules:` if-expression for a push to `branch`, a quoted name or an expression such as `$CI_DEFAULT_BRANCH`. */
|
|
313
|
+
export function gitlabPushRule(branch) {
|
|
314
|
+
return `$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == ${branch}`;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Refuse a branch name that cannot be interpolated into a `rules:`
|
|
318
|
+
* if-expression. The expression is a string GitLab parses, so a `"` would end
|
|
319
|
+
* it early and a `$` would expand as a variable — either one silently changes
|
|
320
|
+
* which pipelines match the job, which is worse than not generating it.
|
|
321
|
+
* `subject` names what filters, such as `Scheduled Op "apply"`.
|
|
322
|
+
*/
|
|
323
|
+
export function assertGitlabBranch(subject, branch) {
|
|
324
|
+
if (branch.trim() === "") {
|
|
325
|
+
throw new Error(`${subject} has an empty branch filter. Name a branch, or drop the filter.`);
|
|
326
|
+
}
|
|
327
|
+
if (/["$\\]/.test(branch)) {
|
|
328
|
+
throw new Error(`${subject} filters on branch "${branch}", which carries a character this generator ` +
|
|
329
|
+
`cannot put in a GitLab \`rules:\` expression: a quote would end the expression early and a "$" ` +
|
|
330
|
+
`would expand as a CI/CD variable, either of which silently changes which pipelines run the job. ` +
|
|
331
|
+
`Name a branch without \`"\`, \`$\` or \`\\\`.`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* One `rules:` entry per branch a trigger filters on, each checked by
|
|
336
|
+
* {@link assertGitlabBranch}. Two or more branches are two or more entries:
|
|
337
|
+
* `rules:` is an OR over its list, which is how GitLab spells the alternation
|
|
338
|
+
* github expresses as a `branches:` array. A cron trigger is selected by
|
|
339
|
+
* `selector`, the CI/CD variable a pipeline schedule sets, and the Op's name.
|
|
340
|
+
*/
|
|
341
|
+
export function gitlabRules(subject, trigger, selector) {
|
|
342
|
+
switch (trigger.kind) {
|
|
343
|
+
case "cron":
|
|
344
|
+
return [{ if: `${GITLAB_SCHEDULE_SOURCE} && $${selector.variable} == "${selector.value}"` }];
|
|
345
|
+
case "pull_request": {
|
|
346
|
+
const branches = triggerBranches(trigger);
|
|
347
|
+
if (!branches)
|
|
348
|
+
return [{ if: GITLAB_MERGE_REQUEST_SOURCE }];
|
|
349
|
+
return branches.map((branch) => {
|
|
350
|
+
assertGitlabBranch(subject, branch);
|
|
351
|
+
return { if: `${GITLAB_MERGE_REQUEST_SOURCE} && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "${branch}"` };
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
case "push":
|
|
355
|
+
return pushBranches(trigger.branches).map((branch) => {
|
|
356
|
+
assertGitlabBranch(subject, branch);
|
|
357
|
+
return { if: gitlabPushRule(JSON.stringify(branch)) };
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* The `id_tokens:` entry an `id-token: write` spec gets. The name is the
|
|
363
|
+
* environment variable the JWT lands in, which the job's own setup line reads
|
|
364
|
+
* (`aws sts assume-role-with-web-identity --web-identity-token
|
|
365
|
+
* "$CHANT_ID_TOKEN"`, or the provider's equivalent).
|
|
366
|
+
*
|
|
367
|
+
* `$CI_SERVER_URL` as the audience is GitLab's own documented default: an
|
|
368
|
+
* identity provider federated to a GitLab instance is registered with that
|
|
369
|
+
* instance's URL as its audience, and the variable expands to exactly that on
|
|
370
|
+
* gitlab.com and on a self-managed instance alike.
|
|
371
|
+
*/
|
|
372
|
+
export const GITLAB_ID_TOKEN_NAME = "CHANT_ID_TOKEN";
|
|
373
|
+
export const GITLAB_ID_TOKEN_AUDIENCE = "$CI_SERVER_URL";
|
|
374
|
+
/**
|
|
375
|
+
* Turn additive `permissions` (#2242) into the one GitLab declaration that
|
|
376
|
+
* carries the same meaning, refusing everything else by name.
|
|
377
|
+
*
|
|
378
|
+
* `id-token: write` is that one: GitLab's OIDC surface is `id_tokens:`, a
|
|
379
|
+
* per-job declaration of a JWT the job exchanges for cloud credentials
|
|
380
|
+
* itself. `id-token: read` is refused rather than mapped, because GitLab
|
|
381
|
+
* either mints the token into the job or does not. Every other scope is
|
|
382
|
+
* refused: GitLab has no per-job token-scope mapping at all, and a scope
|
|
383
|
+
* quietly dropped would emit a job that reads as granted and runs with
|
|
384
|
+
* nothing.
|
|
385
|
+
*/
|
|
386
|
+
export function gitlabIdTokens(name, permissions) {
|
|
387
|
+
let wantsIdToken = false;
|
|
388
|
+
for (const [rawScope, value] of Object.entries(permissions ?? {})) {
|
|
389
|
+
const scope = rawScope.trim();
|
|
390
|
+
if (scope === "id-token") {
|
|
391
|
+
if (value !== "write") {
|
|
392
|
+
throw new Error(`Scheduled Op "${name}" adds permission "id-token: ${value}", but GitLab either mints an ` +
|
|
393
|
+
`OIDC token into a job or does not — there is no read-only half of an \`id_tokens:\` ` +
|
|
394
|
+
`declaration. Ask for { "id-token": "write" }, or drop the option.`);
|
|
395
|
+
}
|
|
396
|
+
wantsIdToken = true;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
throw new Error(`Scheduled Op "${name}" adds permission "${scope}: ${value}", but GitLab CI has no per-job ` +
|
|
400
|
+
`token-scope mapping — there is no \`permissions:\` key to add it to (#2242). The one scope that ` +
|
|
401
|
+
`does cross over is "id-token": "write", which becomes an \`id_tokens:\` declaration. Drop the ` +
|
|
402
|
+
`option here, or generate this Op for github.`);
|
|
403
|
+
}
|
|
404
|
+
return wantsIdToken ? { [GITLAB_ID_TOKEN_NAME]: { aud: GITLAB_ID_TOKEN_AUDIENCE } } : undefined;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* A `setup` list as GitLab `script:` lines. A `uses` entry is a GitHub
|
|
408
|
+
* Actions marketplace action, which GitLab has no shape for, so it is refused
|
|
409
|
+
* by name rather than dropped. `subject` names whose setup it is, such as
|
|
410
|
+
* `Scheduled Op "apply"`.
|
|
411
|
+
*/
|
|
412
|
+
export function gitlabSetupScript(subject, setup) {
|
|
413
|
+
return (setup ?? []).map((step, index) => {
|
|
414
|
+
if ("uses" in step) {
|
|
415
|
+
throw new Error(`${subject} setup step ${index + 1} is \`uses: "${step.uses}"\`, a GitHub Actions ` +
|
|
416
|
+
`marketplace action. GitLab CI jobs run \`script\` lines only, so there is nothing to translate it ` +
|
|
417
|
+
`into (#2242). Express the setup as a \`{ run }\` entry, or generate it for github/forgejo.`);
|
|
418
|
+
}
|
|
419
|
+
return step.run;
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Validate an `environment` on GitLab's own terms (#2257). A GitLab job
|
|
424
|
+
* naming an environment that does not exist creates an unprotected one on
|
|
425
|
+
* first deploy rather than failing, so the only shapes worth refusing at
|
|
426
|
+
* build time are the ones that could never bind: a blank name, and a `url`
|
|
427
|
+
* that is neither absolute nor a variable expression GitLab expands, which
|
|
428
|
+
* would render as a dead "View deployment" link.
|
|
429
|
+
*/
|
|
430
|
+
export function assertGitlabEnvironment(name, environment) {
|
|
431
|
+
const where = `Scheduled Op "${name}" environment`;
|
|
432
|
+
if (environment.name.trim() === "") {
|
|
433
|
+
throw new Error(`${where} has an empty \`name\`. A GitLab environment is a project object, and its approval ` +
|
|
434
|
+
`rules live on that object rather than in this file, so a blank name resolves to nothing. ` +
|
|
435
|
+
`Give it the environment's name, or drop the option.`);
|
|
436
|
+
}
|
|
437
|
+
if (environment.url === undefined)
|
|
438
|
+
return;
|
|
439
|
+
const url = environment.url.trim();
|
|
440
|
+
if (url === "") {
|
|
441
|
+
throw new Error(`${where} "${environment.name}" has an empty \`url\`. Omit the field rather than setting it to "".`);
|
|
442
|
+
}
|
|
443
|
+
if (!/^https?:\/\//.test(url) && !url.includes("$")) {
|
|
444
|
+
throw new Error(`${where} "${environment.name}" has \`url: "${environment.url}"\`, which is neither an absolute ` +
|
|
445
|
+
`http(s) URL nor a variable expression GitLab expands. It becomes the environment's own link, ` +
|
|
446
|
+
`so a relative path is a dead link on the environment page rather than an error anywhere.`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exiting without truncating piped output, shared by the full CLI (`./main.ts`)
|
|
3
|
+
* and the slim wave-job runtime (`../wave-runtime.ts`, #3896).
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Wait until a writable stream has flushed its buffer. `process.exit()` discards
|
|
7
|
+
* data still buffered for an async sink (a pipe or file), truncating large output
|
|
8
|
+
* at the ~64 KB pipe buffer — so `chant graph --format ir` piped into a consumer
|
|
9
|
+
* loses everything past 64 KB and its JSON won't parse. A TTY writes
|
|
10
|
+
* synchronously (`writableLength` stays 0), so this is a no-op there. Resolves on
|
|
11
|
+
* `error`/`close` too, so a reader that closes early (EPIPE) can't hang exit.
|
|
12
|
+
*/
|
|
13
|
+
export declare function waitForStreamDrain(stream: NodeJS.WriteStream): Promise<void>;
|
|
14
|
+
/** Flush stdout+stderr, then exit — so a large piped payload isn't truncated. */
|
|
15
|
+
export declare function flushAndExit(code: number): Promise<never>;
|
|
16
|
+
//# sourceMappingURL=flush.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flush.d.ts","sourceRoot":"","sources":["../../src/cli/flush.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB5E;AAED,iFAAiF;AACjF,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAI/D"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exiting without truncating piped output, shared by the full CLI (`./main.ts`)
|
|
3
|
+
* and the slim wave-job runtime (`../wave-runtime.ts`, #3896).
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Wait until a writable stream has flushed its buffer. `process.exit()` discards
|
|
7
|
+
* data still buffered for an async sink (a pipe or file), truncating large output
|
|
8
|
+
* at the ~64 KB pipe buffer — so `chant graph --format ir` piped into a consumer
|
|
9
|
+
* loses everything past 64 KB and its JSON won't parse. A TTY writes
|
|
10
|
+
* synchronously (`writableLength` stays 0), so this is a no-op there. Resolves on
|
|
11
|
+
* `error`/`close` too, so a reader that closes early (EPIPE) can't hang exit.
|
|
12
|
+
*/
|
|
13
|
+
export function waitForStreamDrain(stream) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
const tick = () => {
|
|
16
|
+
if (stream.writableLength === 0 || stream.writableEnded || stream.destroyed) {
|
|
17
|
+
cleanup();
|
|
18
|
+
resolve();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
stream.once("drain", tick);
|
|
22
|
+
};
|
|
23
|
+
const stop = () => {
|
|
24
|
+
cleanup();
|
|
25
|
+
resolve();
|
|
26
|
+
};
|
|
27
|
+
const cleanup = () => {
|
|
28
|
+
stream.off("drain", tick);
|
|
29
|
+
stream.off("error", stop);
|
|
30
|
+
stream.off("close", stop);
|
|
31
|
+
};
|
|
32
|
+
stream.once("error", stop);
|
|
33
|
+
stream.once("close", stop);
|
|
34
|
+
tick();
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** Flush stdout+stderr, then exit — so a large piped payload isn't truncated. */
|
|
38
|
+
export async function flushAndExit(code) {
|
|
39
|
+
await waitForStreamDrain(process.stdout);
|
|
40
|
+
await waitForStreamDrain(process.stderr);
|
|
41
|
+
process.exit(code);
|
|
42
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { GateResolutionRecord, PendingGateRecord } from "../../lifecycle/gate-ledger.js";
|
|
2
|
+
import { type GateOrigin } from "../../lifecycle/gate-origin.js";
|
|
3
|
+
import { type GatePolicyEvaluator, type ResolvedGateApproval } from "../../op/gate-approval.js";
|
|
4
|
+
import type { CommandContext } from "../registry.js";
|
|
5
|
+
export declare function runApprove(ctx: CommandContext): Promise<number>;
|
|
6
|
+
/** What a caller supplies alongside the op and gate names. */
|
|
7
|
+
export interface GateApprovalOptions {
|
|
8
|
+
/**
|
|
9
|
+
* The channel this resolution arrived on (chant#2384). Defaults to the
|
|
10
|
+
* process's own — `"cli"` unless an entry point declared otherwise — and is
|
|
11
|
+
* a parameter only so a caller that knows better can say so.
|
|
12
|
+
*/
|
|
13
|
+
origin?: GateOrigin;
|
|
14
|
+
/**
|
|
15
|
+
* Record a resolution that the same-origin rule would refuse (chant#2384).
|
|
16
|
+
*
|
|
17
|
+
* There is a legitimate case: a single operator driving an agent, who wants
|
|
18
|
+
* the model's run resolved from the same session and accepts what that
|
|
19
|
+
* means. The override exists so that is a deliberate act with a trace on the
|
|
20
|
+
* record, rather than the default.
|
|
21
|
+
*/
|
|
22
|
+
allowSameOrigin?: boolean;
|
|
23
|
+
/** `--actor`/`--approver`; falls back to the CI or shell identity. */
|
|
24
|
+
actor?: string;
|
|
25
|
+
/** `--note` — free-text prose. */
|
|
26
|
+
note?: string;
|
|
27
|
+
/**
|
|
28
|
+
* `--relayed-by <principal>` (#3402): who carried this approval to chant for
|
|
29
|
+
* the approver, such as a follower program relaying a person's approval.
|
|
30
|
+
* Recorded as `relayedBy` on the resolution line and covered by `--sign`.
|
|
31
|
+
* The approver is still `--actor`; the relay is not an approver.
|
|
32
|
+
*/
|
|
33
|
+
relayedBy?: string;
|
|
34
|
+
/** `--url` — the approval surface, validated as absolute http/https. */
|
|
35
|
+
url?: string;
|
|
36
|
+
/**
|
|
37
|
+
* `--plan <digest>` (#2300) — the plan this approval is for. Omitted, the
|
|
38
|
+
* digest is read off the gate's standing pending fact, so the common path
|
|
39
|
+
* stays one command: run, read what it planned, approve it.
|
|
40
|
+
*/
|
|
41
|
+
plan?: string;
|
|
42
|
+
/**
|
|
43
|
+
* `--env <env>` (#2574) — which environment's pending fact a component gate
|
|
44
|
+
* approval answers. A component's gate ledger holds every environment's
|
|
45
|
+
* facts, so without it the newest one is approved. Ignored for a gate whose
|
|
46
|
+
* pending facts record no environment (every Op gate).
|
|
47
|
+
*/
|
|
48
|
+
environment?: string;
|
|
49
|
+
/** `--role` (#2508) — roles the approver claims. */
|
|
50
|
+
roles?: string[];
|
|
51
|
+
/**
|
|
52
|
+
* `--agent` (#2508) — record this as an agent's approval. On a
|
|
53
|
+
* model-authored channel it is one whether or not this is set.
|
|
54
|
+
*/
|
|
55
|
+
agent?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* `--sign [<key file>]` (#3163, ws-080): seal the approval with an ssh key,
|
|
58
|
+
* a key file or true for git's `user.signingkey`. The seal attests the
|
|
59
|
+
* approver when the signers file at base lists the key for them, and a gate
|
|
60
|
+
* the declaration's `identity.gates` names counts only sealed approvals.
|
|
61
|
+
*/
|
|
62
|
+
sign?: string | true;
|
|
63
|
+
/** Where the workspace and the key file are looked up from. Defaults to the process's directory. */
|
|
64
|
+
cwd?: string;
|
|
65
|
+
/** Replaces the lexicon evaluator a gate policy is loaded from. For tests. */
|
|
66
|
+
evaluator?: GatePolicyEvaluator;
|
|
67
|
+
}
|
|
68
|
+
export type GateApprovalOutcome = {
|
|
69
|
+
ok: true;
|
|
70
|
+
record: GateResolutionRecord;
|
|
71
|
+
pushed: boolean;
|
|
72
|
+
} | {
|
|
73
|
+
ok: false;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* The ledger write behind `chant approve` — extracted so `chant run approve`
|
|
77
|
+
* (#2121) records the identical fact before waking the runtime that hosts the
|
|
78
|
+
* gated run, instead of growing a second, drifting writer.
|
|
79
|
+
*
|
|
80
|
+
* Prints the same warning for an undiscovered op and the same success line as
|
|
81
|
+
* `chant approve` always did; the caller adds whatever it does next.
|
|
82
|
+
*
|
|
83
|
+
* Since #2300 the resolution names the plan it approves. By default that is
|
|
84
|
+
* the standing pending fact's plan — the one the gated run produced — so the
|
|
85
|
+
* common path is still `chant run`, read, `chant approve`. With no pending
|
|
86
|
+
* fact and no `--plan`, this refuses: there is nothing on the ledger saying
|
|
87
|
+
* what would be approved, and writing a resolution anyway is what made an
|
|
88
|
+
* approval mean "the next run" instead of "this plan".
|
|
89
|
+
*/
|
|
90
|
+
export declare function recordGateApproval(opName: string, gate: string, opts: GateApprovalOptions): Promise<GateApprovalOutcome>;
|
|
91
|
+
/**
|
|
92
|
+
* What a just-recorded approval did to a gate with an `approval` block
|
|
93
|
+
* (#2508): the policy's decision and whether it binds, then the quorum count
|
|
94
|
+
* for this plan. The run decides the gate from the ledger; these lines only
|
|
95
|
+
* say what it will find.
|
|
96
|
+
*/
|
|
97
|
+
export declare function describeApprovalProgress(approval: ResolvedGateApproval, record: GateResolutionRecord, prior: GateResolutionRecord[], standing: PendingGateRecord | undefined, pending?: readonly PendingGateRecord[]): string[];
|
|
98
|
+
//# sourceMappingURL=approve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approve.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/approve.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAC3F,OAAO,EACuE,KAAK,UAAU,EAC5F,MAAM,6BAA6B,CAAC;AAOrC,OAAO,EAEuC,KAAK,mBAAmB,EAAE,KAAK,oBAAoB,EAChG,MAAM,wBAAwB,CAAC;AAOhC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAkDlD,wBAAsB,UAAU,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAuGrE;AAED,8DAA8D;AAC9D,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,oGAAoG;IACpG,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,SAAS,CAAC,EAAE,mBAAmB,CAAC;CACjC;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAC3D;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAAC;AAElB;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,mBAAmB,CAAC,CAiT9B;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,oBAAoB,EAC9B,MAAM,EAAE,oBAAoB,EAC5B,KAAK,EAAE,oBAAoB,EAAE,EAC7B,QAAQ,EAAE,iBAAiB,GAAG,SAAS,EACvC,OAAO,GAAE,SAAS,iBAAiB,EAA+B,GACjE,MAAM,EAAE,CA8BV"}
|