@intentius/chant 0.124.39 → 0.124.41
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/component-endpoint.d.ts +20 -0
- package/dist/cli/component-endpoint.d.ts.map +1 -0
- package/dist/cli/component-endpoint.js +36 -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 +15 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +26 -4
- 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 +74 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +514 -58
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +16 -2
- 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 +4 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +17 -6
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +41 -2
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +31 -2
- package/dist/cli/registry.d.ts +12 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/auto-release.d.ts +35 -3
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/auto-release.js +29 -2
- package/dist/components/cli-support.d.ts +2 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +2 -0
- package/dist/components/deploy-units.d.ts.map +1 -1
- package/dist/components/deploy-units.js +5 -0
- package/dist/components/fan-out-run.d.ts +28 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +27 -3
- 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 +72 -2
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +96 -5
- 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.map +1 -1
- package/dist/components/pr-run.js +5 -3
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +39 -16
- 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/__fixtures__/publish-fakes.d.ts.map +1 -1
- package/dist/lifecycle/__fixtures__/publish-fakes.js +6 -1
- 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/publish-declaration.d.ts.map +1 -1
- package/dist/lifecycle/publish-declaration.js +4 -3
- package/dist/lifecycle/publish-npm.d.ts +2 -2
- package/dist/lifecycle/publish-npm.d.ts.map +1 -1
- package/dist/lifecycle/publish-npm.js +31 -8
- 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/release-ledger.d.ts +31 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.js +19 -1
- 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/status.d.ts +5 -1
- package/dist/lifecycle/status.d.ts.map +1 -1
- package/dist/lifecycle/status.js +10 -5
- 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/live-endpoint.d.ts +18 -0
- package/dist/live-endpoint.d.ts.map +1 -1
- package/dist/live-endpoint.js +27 -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/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 +62 -1
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +190 -11
- 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/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 +36 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +82 -0
- package/dist/pr-loop.d.ts +90 -1
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +77 -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/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 +239 -239
- 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/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-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 +24 -1
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +85 -6
- package/dist/workspace/declaration.d.ts +63 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +72 -1
- package/dist/workspace/declaration.schema.json +147 -0
- 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 +73 -2
- 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 +3 -1
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +7 -3
- package/dist/workspace/status.schema.json +1 -0
- 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/component-endpoint.ts +43 -0
- package/src/cli/handlers/approve.ts +18 -5
- package/src/cli/handlers/build.ts +15 -1
- package/src/cli/handlers/fan-out.test.ts +10 -0
- package/src/cli/handlers/fan-out.ts +29 -4
- 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-endpoint.test.ts +169 -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 +508 -64
- package/src/cli/handlers/promote.ts +25 -2
- package/src/cli/handlers/query.ts +78 -0
- package/src/cli/handlers/run.test.ts +2 -1
- package/src/cli/handlers/run.ts +16 -5
- package/src/cli/main.ts +41 -2
- package/src/cli/parse-args.ts +23 -2
- package/src/cli/registry.ts +12 -2
- package/src/components/auto-release-infra.test.ts +131 -0
- package/src/components/auto-release.test.ts +40 -1
- package/src/components/auto-release.ts +50 -4
- package/src/components/cli-support.ts +4 -0
- package/src/components/deploy-units.ts +5 -0
- package/src/components/fan-out-run.ts +39 -2
- package/src/components/fan-out-waves.test.ts +45 -1
- 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 +130 -5
- package/src/components/pr-policy.test.ts +145 -0
- package/src/components/pr-policy.ts +211 -0
- package/src/components/pr-run.ts +5 -2
- package/src/components/promote.test.ts +39 -0
- package/src/components/promote.ts +38 -14
- 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/__fixtures__/publish-fakes.ts +6 -1
- package/src/lifecycle/gate-checkpoint.ts +1 -1
- package/src/{workspace/trust → lifecycle}/gate-seal.ts +1 -1
- package/src/lifecycle/publish-declaration.ts +4 -3
- package/src/lifecycle/publish-npm.ts +33 -8
- package/src/lifecycle/publish-targets.test.ts +36 -3
- package/src/lifecycle/query.test.ts +91 -0
- package/src/lifecycle/query.ts +315 -0
- package/src/lifecycle/release-ledger.ts +42 -1
- package/src/lifecycle/run-ledger.ts +18 -0
- package/src/lifecycle/status.test.ts +19 -0
- package/src/lifecycle/status.ts +14 -6
- package/src/lifecycle/target-lock.test.ts +195 -0
- package/src/lifecycle/target-lock.ts +347 -0
- package/src/live-endpoint.ts +31 -0
- package/src/op/activities/activity-contracts.ts +31 -0
- package/src/op/activities/index.ts +6 -0
- 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 +233 -11
- 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/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 +114 -0
- package/src/pr-loop-bundle.test.ts +2 -2
- package/src/pr-loop.test.ts +58 -0
- package/src/pr-loop.ts +146 -4
- package/src/pr-post.test.ts +141 -0
- package/src/pr-post.ts +159 -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/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-locks.ts +16 -0
- package/src/workspace/ci-run-index.test.ts +2 -1
- package/src/workspace/ci-run.test.ts +133 -8
- package/src/workspace/ci-run.ts +92 -7
- package/src/workspace/declaration.schema.json +147 -0
- package/src/workspace/declaration.test.ts +76 -0
- package/src/workspace/declaration.ts +134 -2
- 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 +73 -2
- package/src/workspace/security.ts +4 -0
- package/src/workspace/status-gates.ts +9 -6
- package/src/workspace/status.schema.json +1 -0
- package/src/workspace/status.ts +11 -4
- 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
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate-time policy on the pull-request path (#4294): `ci.policy` in the
|
|
3
|
+
* workspace declaration, run by `chant components pr-plan` and `pr-apply`
|
|
4
|
+
* over the change-set document the gate binds (`CapabilityPlan.changeSet`,
|
|
5
|
+
* composed by `planPrSet`).
|
|
6
|
+
*
|
|
7
|
+
* The settings and the policy directory are both read at the base the job
|
|
8
|
+
* measures the change from, so a pull request cannot loosen, edit or delete
|
|
9
|
+
* the policy that judges it. The engine and its result shapes are
|
|
10
|
+
* `../gate-policy.ts`.
|
|
11
|
+
*
|
|
12
|
+
* - deny: pr-apply applies nothing unless an override counts
|
|
13
|
+
* (`../override-gate.ts`, subject the gate's op, such as `pr-12`). The
|
|
14
|
+
* first refusal records the denial on `_gates/policy-override.jsonl`, so
|
|
15
|
+
* `chant approve policy-override <op> --rule <id> --reason <text>` can
|
|
16
|
+
* answer it. The gate's own approval is still needed. The override is
|
|
17
|
+
* spent just before anything applies.
|
|
18
|
+
* - hold: the plan needs a person's approval of its own digest. pr-apply
|
|
19
|
+
* always asks for one, so a hold changes one path: a green commit's plan
|
|
20
|
+
* no longer applies under the approvals of the merges it carries.
|
|
21
|
+
* - error: the policy did not run or gave no readable answer. pr-apply
|
|
22
|
+
* applies nothing.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { readDeclaration, WorkspaceReadError } from "../workspace/declaration";
|
|
26
|
+
import { locateWorkspace } from "../workspace/which-chant";
|
|
27
|
+
import type { WorkspaceTree } from "../workspace/tree";
|
|
28
|
+
import {
|
|
29
|
+
checkGatePolicy,
|
|
30
|
+
DEFAULT_POLICY_PATH,
|
|
31
|
+
exportGatePolicy,
|
|
32
|
+
type GatePolicyExec,
|
|
33
|
+
type GatePolicySettings,
|
|
34
|
+
type GatePolicyVerdict,
|
|
35
|
+
} from "../gate-policy";
|
|
36
|
+
import type { ChangeSetDocument } from "../change-set";
|
|
37
|
+
import type { GateLedgerPort } from "../op/gate";
|
|
38
|
+
import type { PrPolicyReport } from "../pr-loop";
|
|
39
|
+
|
|
40
|
+
/** `ci.policy` as read at base. */
|
|
41
|
+
export interface PrLoopPolicy {
|
|
42
|
+
/** Absent when the declaration at base sets none. */
|
|
43
|
+
settings?: GatePolicySettings;
|
|
44
|
+
/** The commit it was read at. */
|
|
45
|
+
base: string;
|
|
46
|
+
/** The tree of the declaration's directory at base, where the policy directory is read. */
|
|
47
|
+
tree?: WorkspaceTree;
|
|
48
|
+
/** A line for the log when no policy runs because the base has no declaration, or could not be read. */
|
|
49
|
+
note?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Whether the working tree's declaration names a `ci.policy`. */
|
|
53
|
+
function checkoutNamesPolicy(cwd: string): boolean {
|
|
54
|
+
try {
|
|
55
|
+
return readDeclaration(locateWorkspace(cwd).tree).ci?.policy !== undefined;
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The policy a pull-request job in `cwd` runs, from the declaration at
|
|
63
|
+
* `base`. Throws when the declaration at base cannot be read and the
|
|
64
|
+
* working tree's names a policy: running none would let the change waive it.
|
|
65
|
+
*/
|
|
66
|
+
export function prLoopPolicyAt(cwd: string, base: string): PrLoopPolicy {
|
|
67
|
+
try {
|
|
68
|
+
const located = locateWorkspace(cwd, base);
|
|
69
|
+
const settings = readDeclaration(located.tree).ci?.policy;
|
|
70
|
+
return { base, ...(settings ? { settings, tree: located.tree } : {}) };
|
|
71
|
+
} catch (err) {
|
|
72
|
+
const missing = err instanceof WorkspaceReadError && (err.code === "declaration-missing" || err.code === "not-a-git-repository");
|
|
73
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
74
|
+
if (checkoutNamesPolicy(cwd)) {
|
|
75
|
+
if (missing) return { base, note: `ci.policy: the base ${base.slice(0, 12)} has no workspace declaration, so the policy this change declares runs once it merges` };
|
|
76
|
+
throw new Error(`ci.policy is read from the workspace declaration at ${base.slice(0, 12)}, and it could not be read (${why}), so the policy cannot be run`);
|
|
77
|
+
}
|
|
78
|
+
return missing ? { base } : { base, note: `ci.policy: the declaration at ${base.slice(0, 12)} could not be read (${why}); this checkout names no policy, so none runs` };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** What the engine reads as `input`. */
|
|
83
|
+
export interface PrPolicyInput {
|
|
84
|
+
version: 1;
|
|
85
|
+
stage: "plan" | "apply";
|
|
86
|
+
/** The gate's op, `pr-<n>`. */
|
|
87
|
+
op: string;
|
|
88
|
+
gate: string;
|
|
89
|
+
env: string;
|
|
90
|
+
pr: number | null;
|
|
91
|
+
base: string;
|
|
92
|
+
head: string;
|
|
93
|
+
/** The change-set document the gate binds. Its `entries` are every change the plan proposes. */
|
|
94
|
+
changeSet: ChangeSetDocument;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Run the policy over `input`. Undefined when the base sets no policy. */
|
|
98
|
+
export function runPrPolicy(read: PrLoopPolicy, input: PrPolicyInput, exec?: GatePolicyExec): GatePolicyVerdict | undefined {
|
|
99
|
+
if (!read.settings || !read.tree) return undefined;
|
|
100
|
+
const engine = read.settings.engine ?? "conftest";
|
|
101
|
+
const path = read.settings.path ?? DEFAULT_POLICY_PATH;
|
|
102
|
+
const exported = exportGatePolicy(read.tree, path);
|
|
103
|
+
if ("error" in exported) return { result: "error", engine, denials: [], rules: [], holds: [], warnings: [], error: exported.error };
|
|
104
|
+
try {
|
|
105
|
+
return checkGatePolicy({ settings: read.settings, input, policyDir: exported.dir, cwd: process.cwd(), ...(exec ? { exec } : {}) });
|
|
106
|
+
} finally {
|
|
107
|
+
exported.cleanup();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The verdict as the report keeps it. */
|
|
112
|
+
export function prPolicyReport(verdict: GatePolicyVerdict, base: string): PrPolicyReport {
|
|
113
|
+
return {
|
|
114
|
+
result: verdict.result,
|
|
115
|
+
engine: verdict.engine,
|
|
116
|
+
base,
|
|
117
|
+
denials: verdict.denials,
|
|
118
|
+
...(verdict.rules.length > 0 ? { rules: verdict.rules } : {}),
|
|
119
|
+
holds: verdict.holds,
|
|
120
|
+
warnings: verdict.warnings,
|
|
121
|
+
...(verdict.error !== undefined ? { error: verdict.error } : {}),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface PrPolicyOverrideInput {
|
|
126
|
+
port: GateLedgerPort;
|
|
127
|
+
/** The override's subject: the gate's op. */
|
|
128
|
+
subject: string;
|
|
129
|
+
/** The digest the gate binds: the change-set document's. */
|
|
130
|
+
digest: string;
|
|
131
|
+
rules: readonly string[];
|
|
132
|
+
/** The commit the overriders and signers are read at. */
|
|
133
|
+
base: string;
|
|
134
|
+
cwd: string;
|
|
135
|
+
/** Count only sealed overrides, as a sealed gate counts only sealed approvals. */
|
|
136
|
+
sealed: boolean;
|
|
137
|
+
/** Record the denial when no override counts and none stands. The plan stage records nothing. */
|
|
138
|
+
record: boolean;
|
|
139
|
+
description?: string;
|
|
140
|
+
now?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Where an override of a denial stands. */
|
|
144
|
+
export type PrPolicyOverride =
|
|
145
|
+
| { status: "overridden"; override: NonNullable<PrPolicyReport["override"]> }
|
|
146
|
+
| { status: "none"; refusals: string[]; command: string; overriders: string[]; recorded: boolean; moved?: { by: string } };
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Decide a denied plan against the override ledger, as an Op waves job
|
|
150
|
+
* decides a denied run (`settleDenials` in `../op/op-waves-run.ts`).
|
|
151
|
+
*/
|
|
152
|
+
export async function decidePrPolicyOverride(input: PrPolicyOverrideInput): Promise<PrPolicyOverride> {
|
|
153
|
+
const og = await import("../override-gate");
|
|
154
|
+
const overriders = og.overridersAt(input.cwd, input.base).principals;
|
|
155
|
+
const signers = input.sealed ? (await import("../gate-decision")).gateSignersAt(input.cwd, input.base) : undefined;
|
|
156
|
+
const ledger = await input.port.read(og.OVERRIDE_OP);
|
|
157
|
+
const applied = ((await input.port.readApplied?.(og.OVERRIDE_OP)) ?? []) as unknown as import("../override-gate").AppliedOverrideRecord[];
|
|
158
|
+
const now = input.now ?? new Date().toISOString();
|
|
159
|
+
const verdict = og.decideOverride({
|
|
160
|
+
ledger,
|
|
161
|
+
applied,
|
|
162
|
+
subject: input.subject,
|
|
163
|
+
planDigest: input.digest,
|
|
164
|
+
rules: input.rules,
|
|
165
|
+
overriders,
|
|
166
|
+
mode: input.sealed ? "sealed" : "ledger",
|
|
167
|
+
...(signers ? { signers } : {}),
|
|
168
|
+
now,
|
|
169
|
+
});
|
|
170
|
+
if (verdict.status === "overridden") {
|
|
171
|
+
const o = verdict.override;
|
|
172
|
+
return { status: "overridden", override: { by: o.by, at: o.at, reason: o.reason, digest: o.digest } };
|
|
173
|
+
}
|
|
174
|
+
let recorded = false;
|
|
175
|
+
if (input.record && !verdict.standing) {
|
|
176
|
+
const { version: _v, kind: _k, ...fact } = og.overridePending({
|
|
177
|
+
subject: input.subject,
|
|
178
|
+
planDigest: input.digest,
|
|
179
|
+
rules: input.rules,
|
|
180
|
+
timestamp: now,
|
|
181
|
+
expiresAt: new Date(new Date(now).getTime() + 48 * 3600 * 1000).toISOString(),
|
|
182
|
+
...(input.description ? { description: input.description } : {}),
|
|
183
|
+
});
|
|
184
|
+
await input.port.appendPending(fact);
|
|
185
|
+
recorded = true;
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
status: "none",
|
|
189
|
+
refusals: verdict.refusals,
|
|
190
|
+
command: og.overrideCommand(input.subject, input.rules, input.sealed),
|
|
191
|
+
overriders,
|
|
192
|
+
recorded,
|
|
193
|
+
...(verdict.status === "moved" ? { moved: { by: verdict.by } } : {}),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Spend the override the apply runs under: append its applied record before
|
|
199
|
+
* anything applies. Throws when it could not be written or did not reach the
|
|
200
|
+
* remote, and the caller applies nothing.
|
|
201
|
+
*/
|
|
202
|
+
export async function spendPrPolicyOverride(port: GateLedgerPort, subject: string, override: NonNullable<PrPolicyReport["override"]>, at: { now?: string; commit?: string } = {}): Promise<void> {
|
|
203
|
+
const og = await import("../override-gate");
|
|
204
|
+
if (!port.appendApplied) throw new Error("this gate ledger cannot record an applied override");
|
|
205
|
+
const record = og.appliedOverride({ subject, digest: override.digest, at: override.at, by: override.by }, { timestamp: at.now ?? new Date().toISOString(), ...(at.commit ? { commit: at.commit } : {}) });
|
|
206
|
+
const pushed = await port.appendApplied(record as unknown as Parameters<NonNullable<GateLedgerPort["appendApplied"]>>[0]);
|
|
207
|
+
const { isLifecycleLocalNote } = await import("../lifecycle/local-note");
|
|
208
|
+
if (!pushed.pushed && !isLifecycleLocalNote(pushed.pushWarning)) {
|
|
209
|
+
throw new Error(`the applied record did not reach the remote the next run reads: ${pushed.pushWarning ?? "the push did not land"}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
package/src/components/pr-run.ts
CHANGED
|
@@ -514,7 +514,7 @@ export async function applyPrSet(options: PrApplyOptions): Promise<PrApplyResult
|
|
|
514
514
|
|
|
515
515
|
const completed = new Set(run.completed);
|
|
516
516
|
const held = new Map((run.held ?? []).map((h) => [h.component, h]));
|
|
517
|
-
const blocked = new
|
|
517
|
+
const blocked = new Map(run.blocked.map((b) => [b.component, b.blockedBy]));
|
|
518
518
|
const errorOf = (component: string): string | undefined => {
|
|
519
519
|
const result = [...run.results].reverse().find((r) => r.component === component);
|
|
520
520
|
if (result?.status === "gated") return `stopped at its own gate ${result.gate?.gate ?? ""}`.trim();
|
|
@@ -529,7 +529,10 @@ export async function applyPrSet(options: PrApplyOptions): Promise<PrApplyResult
|
|
|
529
529
|
const h = held.get(m.component);
|
|
530
530
|
if (h) return { ...asPlanned, status: "waiting", held: h.heldBy ? `${h.heldBy} is held: ${h.reason}` : h.reason };
|
|
531
531
|
if (fresh?.status === "plan-failed") return { ...asPlanned, status: "plan-failed", ...(fresh.error ? { error: fresh.error } : {}) };
|
|
532
|
-
if (blocked.has(m.component))
|
|
532
|
+
if (blocked.has(m.component)) {
|
|
533
|
+
const by = blocked.get(m.component);
|
|
534
|
+
return { ...asPlanned, status: "blocked", ...(by ? { blockedBy: by } : {}) };
|
|
535
|
+
}
|
|
533
536
|
const error = errorOf(m.component);
|
|
534
537
|
return { ...asPlanned, status: "failed", ...(error ? { error } : {}) };
|
|
535
538
|
});
|
|
@@ -108,6 +108,45 @@ describe("planning", () => {
|
|
|
108
108
|
});
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
+
describe("records of an apply, not an artifact (#4274)", () => {
|
|
112
|
+
const apply = (component: string, timestamp: string, digestKind: "plan" | "deploy-input" = "plan"): ReleaseRecord => ({
|
|
113
|
+
...rec(component, "staging", `jcs1-sha256:${timestamp.slice(0, 10)}`, timestamp),
|
|
114
|
+
digestKind,
|
|
115
|
+
});
|
|
116
|
+
const records = [
|
|
117
|
+
rec("api", "staging", "sha256:api", "2026-01-02T00:00:00Z"),
|
|
118
|
+
apply("infra", "2026-01-02T00:00:00Z"),
|
|
119
|
+
apply("infra", "2026-01-03T00:00:00Z", "deploy-input"),
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
test("promoting every component leaves an infrastructure component out and says why", () => {
|
|
123
|
+
const plan = planPromotion({ from: "staging", to: "prod", sourceRecords: records, declared: ["api", "infra"] }) as PromotionPlan;
|
|
124
|
+
expect(plan.items.map((i) => i.component)).toEqual(["api"]);
|
|
125
|
+
expect(plan.notPromoted).toEqual([{ component: "infra", reason: expect.stringMatching(/only applies are recorded/) }]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("a newer apply never hides the artifact release before it", () => {
|
|
129
|
+
const mixed = [rec("api", "staging", "sha256:api", "2026-01-01T00:00:00Z"), apply("api", "2026-01-05T00:00:00Z")];
|
|
130
|
+
const picked = selectRelease(mixed, "api", "staging");
|
|
131
|
+
expect("record" in picked && picked.record.digest).toBe("sha256:api");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("promote, rollback and redeploy of a component with only applies are refused by name", () => {
|
|
135
|
+
const promote = planPromotion({ from: "staging", to: "prod", sourceRecords: records, declared: ["api", "infra"], component: "infra" });
|
|
136
|
+
expect("error" in promote && promote.error).toMatch(/"infra" has only applies recorded in "staging"/);
|
|
137
|
+
const rollback = planRollback({ env: "staging", records, declared: ["infra"], component: "infra" });
|
|
138
|
+
expect("error" in rollback && rollback.error).toMatch(/only applies recorded/);
|
|
139
|
+
const redeploy = planRedeploy({ env: "staging", records, declared: ["infra"], component: "infra" });
|
|
140
|
+
expect("error" in redeploy && redeploy.error).toMatch(/only applies recorded/);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("a rollback or redeploy carries a templates digest's kind forward", () => {
|
|
144
|
+
const source: ReleaseRecord = { ...rec("infra", "staging", "sha256:tpl", "2026-01-02T00:00:00Z"), digestKind: "templates" };
|
|
145
|
+
const run = { runId: "r2", actor: "ci", timestamp: "2026-01-04T00:00:00Z" };
|
|
146
|
+
expect(rollbackRecord({ component: "infra", digest: "sha256:tpl", source }, "staging", run)).toMatchObject({ digestKind: "templates" });
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
111
150
|
describe("pinning each component to a digest (#2602)", () => {
|
|
112
151
|
// api's pinned release is older than its latest one: another pipeline
|
|
113
152
|
// recorded sha256:newer while this one ran.
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
41
|
import { parseWiringRef, type WiringRef } from "../lint/rules/comp/support";
|
|
42
|
-
import { latestPerComponent, type ReleaseRecord, type ReleaseRecordInput, type ReleaseAdmission, type RunOrigin } from "../lifecycle/release-ledger";
|
|
42
|
+
import { isApplyRecord, latestPerComponent, type ReleaseRecord, type ReleaseRecordInput, type ReleaseAdmission, type RunOrigin } from "../lifecycle/release-ledger";
|
|
43
43
|
import { CapabilityRegistry, type Capability } from "./capability";
|
|
44
44
|
import {
|
|
45
45
|
runInterpretDriver,
|
|
@@ -109,19 +109,35 @@ export function selectRelease(
|
|
|
109
109
|
env: string,
|
|
110
110
|
digest?: string,
|
|
111
111
|
): { record: ReleaseRecord } | { error: string } {
|
|
112
|
-
const own = records
|
|
113
|
-
if (
|
|
114
|
-
if (digest === undefined) return { record: latestPerComponent(own).get(component)! };
|
|
115
|
-
const matching = own.filter((r) => r.digest === digest);
|
|
112
|
+
const own = deployableRecords(records, component, env);
|
|
113
|
+
if ("error" in own) return own;
|
|
114
|
+
if (digest === undefined) return { record: latestPerComponent(own.records).get(component)! };
|
|
115
|
+
const matching = own.records.filter((r) => r.digest === digest);
|
|
116
116
|
if (matching.length === 0) {
|
|
117
117
|
return {
|
|
118
118
|
error: `digest ${digest} is not recorded for "${component}" in "${env}"; ` +
|
|
119
|
-
`its recorded digests are ${[...new Set(own.map((r) => r.digest))].join(", ")}`,
|
|
119
|
+
`its recorded digests are ${[...new Set(own.records.map((r) => r.digest))].join(", ")}`,
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
122
|
return { record: latestPerComponent(matching).get(component)! };
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* The records of `component` a promote, rollback or redeploy can deploy from
|
|
127
|
+
* (#4274): every one but those that only say it applied ({@link isApplyRecord}),
|
|
128
|
+
* whose digest is a plan or deploy-input digest and names no artifact.
|
|
129
|
+
*/
|
|
130
|
+
function deployableRecords(records: ReleaseRecord[], component: string, env: string): { records: ReleaseRecord[] } | { error: string } {
|
|
131
|
+
const own = records.filter((r) => r.component === component);
|
|
132
|
+
if (own.length === 0) return { error: `no release of "${component}" is recorded in "${env}"` };
|
|
133
|
+
const deployable = own.filter((r) => !isApplyRecord(r));
|
|
134
|
+
if (deployable.length === 0) return { error: appliesOnly(component, env) };
|
|
135
|
+
return { records: deployable };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const appliesOnly = (component: string, env: string): string =>
|
|
139
|
+
`"${component}" has only applies recorded in "${env}", each by a plan or deploy-input digest, so no artifact of it can be deployed again`;
|
|
140
|
+
|
|
125
141
|
/**
|
|
126
142
|
* Read `--digest <component>=<digest>` values (#2602) into a component-to-
|
|
127
143
|
* digest map. A value with no `=` is left to the single-component form
|
|
@@ -198,13 +214,17 @@ export function planPromotion(input: {
|
|
|
198
214
|
return { from, to, items: [{ component, digest: picked.record.digest, source: picked.record }], notPromoted: [] };
|
|
199
215
|
}
|
|
200
216
|
|
|
201
|
-
const latest = latestPerComponent(sourceRecords);
|
|
217
|
+
const latest = latestPerComponent(sourceRecords.filter((r) => !isApplyRecord(r)));
|
|
202
218
|
const items: PromotionItem[] = [];
|
|
203
219
|
const notPromoted: PromotionPlan["notPromoted"] = [];
|
|
204
220
|
for (const [name, record] of [...latest].sort(([a], [b]) => a.localeCompare(b))) {
|
|
205
221
|
if (declared.includes(name)) items.push({ component: name, digest: record.digest, source: record });
|
|
206
222
|
else notPromoted.push({ component: name, reason: "recorded but not declared in this checkout" });
|
|
207
223
|
}
|
|
224
|
+
// #4274: a component that only applied (infrastructure, no artifact) has nothing to promote.
|
|
225
|
+
for (const name of [...new Set(sourceRecords.filter((r) => isApplyRecord(r) && !latest.has(r.component)).map((r) => r.component))].sort()) {
|
|
226
|
+
notPromoted.push({ component: name, reason: "only applies are recorded, by a plan or deploy-input digest; there is no artifact to promote" });
|
|
227
|
+
}
|
|
208
228
|
if (items.length === 0) {
|
|
209
229
|
return { error: `nothing to promote: no component declared here has a release recorded in "${from}"` };
|
|
210
230
|
}
|
|
@@ -228,9 +248,10 @@ export function planRollback(input: {
|
|
|
228
248
|
if (!declared.includes(component)) {
|
|
229
249
|
return { error: `component "${component}" is not declared in this checkout` };
|
|
230
250
|
}
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
251
|
+
const deployable = deployableRecords(records, component, env);
|
|
252
|
+
if ("error" in deployable) return deployable;
|
|
253
|
+
const own = deployable.records;
|
|
254
|
+
const current = latestPerComponent(own).get(component)!;
|
|
234
255
|
|
|
235
256
|
let target: ReleaseRecord;
|
|
236
257
|
if (digest !== undefined) {
|
|
@@ -284,7 +305,7 @@ export function planReleaseRollback(input: {
|
|
|
284
305
|
notPromoted.push({ component, reason: `in release ${name} but not declared in this checkout` });
|
|
285
306
|
continue;
|
|
286
307
|
}
|
|
287
|
-
const ran = records.filter((r) => r.component === component && r.releaseDigest === release.releaseDigest);
|
|
308
|
+
const ran = records.filter((r) => r.component === component && r.releaseDigest === release.releaseDigest && !isApplyRecord(r));
|
|
288
309
|
if (ran.length === 0) {
|
|
289
310
|
neverRan.push(component);
|
|
290
311
|
continue;
|
|
@@ -299,7 +320,7 @@ export function planReleaseRollback(input: {
|
|
|
299
320
|
};
|
|
300
321
|
}
|
|
301
322
|
if (items.length === 0) return { error: `release ${name} has no component declared in this checkout` };
|
|
302
|
-
const current = latestPerComponent(records);
|
|
323
|
+
const current = latestPerComponent(records.filter((r) => !isApplyRecord(r)));
|
|
303
324
|
const already = items.every((i) => {
|
|
304
325
|
const now = current.get(i.component);
|
|
305
326
|
return now !== undefined && now.releaseDigest === release.releaseDigest && now.digest === i.digest && !input.unsettled?.has(i.component);
|
|
@@ -329,8 +350,9 @@ export function planRedeploy(input: {
|
|
|
329
350
|
if (!declared.includes(component)) {
|
|
330
351
|
return { error: `component "${component}" is not declared in this checkout` };
|
|
331
352
|
}
|
|
332
|
-
const
|
|
333
|
-
if (
|
|
353
|
+
const deployable = deployableRecords(records, component, env);
|
|
354
|
+
if ("error" in deployable) return deployable;
|
|
355
|
+
const current = latestPerComponent(deployable.records).get(component)!;
|
|
334
356
|
if (digest !== undefined && digest !== current.digest) {
|
|
335
357
|
return {
|
|
336
358
|
error: `${digest} is not the current release of "${component}" in "${env}" (that is ${current.digest}); ` +
|
|
@@ -699,6 +721,8 @@ function sharedRecord(item: PromotionItem, env: string, run: DeployRunInfo): Rel
|
|
|
699
721
|
...(run.admission ? { admission: run.admission } : {}),
|
|
700
722
|
...(source.manifestDigest ? { manifestDigest: source.manifestDigest } : {}),
|
|
701
723
|
...(source.inputDigest ? { inputDigest: source.inputDigest } : {}),
|
|
724
|
+
// #4274: a release's templates digest stays one when it is deployed again.
|
|
725
|
+
...(source.digestKind ? { digestKind: source.digestKind } : {}),
|
|
702
726
|
};
|
|
703
727
|
}
|
|
704
728
|
|
|
@@ -41,20 +41,21 @@ describe("the gate-decision bundle", () => {
|
|
|
41
41
|
"packages/core/src/intrinsic.ts",
|
|
42
42
|
"packages/core/src/lifecycle/gate-applied.ts",
|
|
43
43
|
"packages/core/src/lifecycle/gate-ledger-select.ts",
|
|
44
|
+
"packages/core/src/lifecycle/gate-seal.ts",
|
|
44
45
|
"packages/core/src/lifecycle/plan-digest.ts",
|
|
45
46
|
"packages/core/src/notify-verify.ts",
|
|
46
47
|
"packages/core/src/notify.ts",
|
|
47
48
|
"packages/core/src/op/gate-point.ts",
|
|
49
|
+
"packages/core/src/op/gate-quorum.ts",
|
|
48
50
|
"packages/core/src/op/op-waves.ts",
|
|
49
51
|
"packages/core/src/wave-layout.ts",
|
|
50
52
|
"packages/core/src/workspace/class-data.ts",
|
|
51
53
|
"packages/core/src/workspace/identity-gate-rule.ts",
|
|
52
54
|
"packages/core/src/workspace/jsonc.ts",
|
|
53
|
-
"packages/core/src/workspace/trust/gate-seal.ts",
|
|
54
55
|
"packages/core/src/workspace/trust/trust-files.ts",
|
|
55
56
|
]);
|
|
56
|
-
// About
|
|
57
|
+
// About 49 KB unminified today: 7 KB of it the pinned classes read as data (#3888), 6 KB the approval rules (#4290).
|
|
57
58
|
const bytes = result.outputFiles.reduce((n, f) => n + f.contents.byteLength, 0);
|
|
58
|
-
expect(bytes).toBeLessThan(
|
|
59
|
+
expect(bytes).toBeLessThan(54 * 1024);
|
|
59
60
|
});
|
|
60
61
|
});
|
package/src/gate-decision.ts
CHANGED
|
@@ -27,6 +27,11 @@
|
|
|
27
27
|
* counts for nothing, as terragucci's wave gate has it: the caller hands in
|
|
28
28
|
* the lines of `_gates/<op>/applied.jsonl` ({@link parseAppliedGateLines}).
|
|
29
29
|
*
|
|
30
|
+
* Approval rules (#4290), a quorum as a list of `{ targets, count, roles,
|
|
31
|
+
* notAuthor }`, are judged with `./op/gate-quorum.ts`, re-exported here:
|
|
32
|
+
* {@link trustRolesAt} and {@link gateSignersAt} read what
|
|
33
|
+
* `trustQuorumJudge` judges by, at base.
|
|
34
|
+
*
|
|
30
35
|
* The module reads the ledger lines and the review it is handed; reading
|
|
31
36
|
* them is the caller's. It imports no TypeScript, esbuild, zod, ajv, lint,
|
|
32
37
|
* fold or codegen module, no package's code, and none of chant's git ledger
|
|
@@ -46,7 +51,7 @@ import {
|
|
|
46
51
|
} from "./lifecycle/gate-ledger-select";
|
|
47
52
|
import { samePlanDigest } from "./lifecycle/plan-digest";
|
|
48
53
|
import { isApprovalSpent, type AppliedGateRecord } from "./lifecycle/gate-applied";
|
|
49
|
-
import { gateSealRefusal, type GateSigners, type SealedGateApproval } from "./
|
|
54
|
+
import { gateSealRefusal, type GateSigners, type SealedGateApproval } from "./lifecycle/gate-seal";
|
|
50
55
|
import {
|
|
51
56
|
DEFAULT_SIGNERS_PATH,
|
|
52
57
|
TRUST_CONFIG_PATH,
|
|
@@ -79,7 +84,7 @@ export {
|
|
|
79
84
|
type GateSigners,
|
|
80
85
|
type SealedGateApproval,
|
|
81
86
|
type SshSignatureCheck,
|
|
82
|
-
} from "./
|
|
87
|
+
} from "./lifecycle/gate-seal";
|
|
83
88
|
export {
|
|
84
89
|
DEFAULT_SIGNERS_PATH,
|
|
85
90
|
TRUST_CONFIG_PATH,
|
|
@@ -105,6 +110,28 @@ export {
|
|
|
105
110
|
type AppliedGateRecord,
|
|
106
111
|
} from "./lifecycle/gate-applied";
|
|
107
112
|
export type { MergedReview } from "./op/gate-review";
|
|
113
|
+
export {
|
|
114
|
+
quorumNeed,
|
|
115
|
+
quorumProblems,
|
|
116
|
+
quorumRuleLine,
|
|
117
|
+
quorumRuleProgress,
|
|
118
|
+
quorumRules,
|
|
119
|
+
quorumRulesFor,
|
|
120
|
+
quorumWaiting,
|
|
121
|
+
rolesOf,
|
|
122
|
+
ruleNeedsProof,
|
|
123
|
+
samePrincipal,
|
|
124
|
+
targetGlobMatch,
|
|
125
|
+
trustQuorumJudge,
|
|
126
|
+
UNPROVEN_JUDGE,
|
|
127
|
+
type GateQuorumJudge,
|
|
128
|
+
type GateQuorumRule,
|
|
129
|
+
type GateQuorumRuleProgress,
|
|
130
|
+
type GateQuorumSpec,
|
|
131
|
+
type QuorumApprover,
|
|
132
|
+
type QuorumJudgement,
|
|
133
|
+
type TrustQuorumInput,
|
|
134
|
+
} from "./op/gate-quorum";
|
|
108
135
|
|
|
109
136
|
// ── reading at base ──────────────────────────────────────────────────────
|
|
110
137
|
|
|
@@ -172,6 +199,16 @@ export function gateSignersAt(repo: string, base: string): GateSigners {
|
|
|
172
199
|
return trustAt(repo, base).signers;
|
|
173
200
|
}
|
|
174
201
|
|
|
202
|
+
/**
|
|
203
|
+
* The role grants of `.chant/trust.json` at `base` (#4290): role name to the
|
|
204
|
+
* principals that hold it, as chant reads them. A missing file gives none,
|
|
205
|
+
* and so does one that is not valid, which turns chant's trust off.
|
|
206
|
+
* {@link trustQuorumJudge} takes them with {@link gateSignersAt}'s signers.
|
|
207
|
+
*/
|
|
208
|
+
export function trustRolesAt(repo: string, base: string): Record<string, string[]> {
|
|
209
|
+
return trustAt(repo, base).roles;
|
|
210
|
+
}
|
|
211
|
+
|
|
175
212
|
/** A gate the declaration at base names under `identity.gates`, and the principal class its signer must be in, if any. */
|
|
176
213
|
export interface IdentityGate {
|
|
177
214
|
gate: string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gate policy bundles alone (#4294), as the override gate does: a
|
|
3
|
+
* product runs the engine and reads its answer from one bundled file with
|
|
4
|
+
* no TypeScript toolchain. This bundles `@intentius/chant/gate-policy` and
|
|
5
|
+
* reads esbuild's list of what went in.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { build } from "esbuild";
|
|
10
|
+
import { describe, expect, test } from "vitest";
|
|
11
|
+
import { REPO } from "./workspace/__fixtures__/contract-repo";
|
|
12
|
+
|
|
13
|
+
describe("the gate-policy bundle", () => {
|
|
14
|
+
test("holds the policy module and nothing else", async () => {
|
|
15
|
+
const result = await build({
|
|
16
|
+
entryPoints: [join(REPO, "packages", "core", "src", "gate-policy.ts")],
|
|
17
|
+
bundle: true,
|
|
18
|
+
platform: "node",
|
|
19
|
+
format: "esm",
|
|
20
|
+
write: false,
|
|
21
|
+
metafile: true,
|
|
22
|
+
outdir: "out",
|
|
23
|
+
logLevel: "silent",
|
|
24
|
+
});
|
|
25
|
+
expect(Object.keys(result.metafile.inputs).map((i) => i.replace(/^.*?packages\//, "packages/"))).toEqual(["packages/core/src/gate-policy.ts"]);
|
|
26
|
+
expect(result.outputFiles.reduce((n, f) => n + f.contents.byteLength, 0)).toBeLessThan(16 * 1024);
|
|
27
|
+
});
|
|
28
|
+
});
|