@kungfu-tech/buildchain 4.0.1-alpha.3 → 4.0.1-alpha.30
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/actions/release-tail/README.md +8 -0
- package/architecture/ci-lane-change-budget.json +187 -0
- package/architecture/decisions/0002-next-development-transition.md +101 -0
- package/architecture/internal-capabilities.json +117 -8
- package/architecture/maintainability-policy.json +43 -25
- package/architecture/release-tail-contract-inventory.json +5 -2
- package/architecture/v3-core-mechanism-inventory.json +89 -0
- package/architecture/v4-adopter-delivery-parity.json +32 -5
- package/architecture/v4-capability-state-machine-manifest.json +73 -0
- package/architecture/v4-compatibility-facts-parity.json +77 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +15 -6
- package/architecture/v4-floating-consumer-policy.json +6 -0
- package/architecture/v4-next-development-parity.json +192 -0
- package/architecture/v4-publication-rehearsal-parity.json +84 -0
- package/architecture/v4-release-train-parity.json +1 -0
- package/architecture/v4-tail-reseal-parity.json +150 -0
- package/bin/buildchain.mjs +23 -2
- package/bin/internal/adopter-delivery-cli.mjs +70 -0
- package/bin/internal/command-registry.mjs +3 -0
- package/bin/internal/compatibility-facts-cli.mjs +101 -0
- package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
- package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
- package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
- package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
- package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +7 -1
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
- package/contracts/next-development-request-v1.schema.json +66 -0
- package/contracts/next-development-transition-v1.schema.json +292 -0
- package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
- package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
- package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
- package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/contracts/v4-tail-reseal-v1.schema.json +276 -0
- package/dist/site/agent-index.json +4 -0
- package/dist/site/artifact-schemas.json +8 -0
- package/dist/site/buildchain-contract.json +1393 -28
- package/dist/site/buildchain-site.json +477 -32
- package/dist/site/capability-registry.json +17 -14
- package/dist/site/cli-registry.json +159 -1
- package/dist/site/kfd-claims.json +455 -15
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +53 -7
- package/dist/site/node-api-registry.json +13921 -9565
- package/dist/site/page-registry.json +442 -21
- package/dist/site/public-surface-audit.json +440 -11
- package/dist/site/publication-authority-registry.json +44 -8
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +22 -0
- package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
- package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
- package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
- package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/dist/site/site-manifest.json +35 -11
- package/dist/site/workflow-registry.json +142 -8
- package/docs/MAP.md +7 -2
- package/docs/cli-reference.md +193 -0
- package/docs/cli.md +8 -0
- package/docs/dev-alpha-candidate-patrol.md +8 -0
- package/docs/dev-delivery-warrant.md +30 -5
- package/docs/next-development-transition.md +118 -0
- package/docs/node-api-reference.md +578 -285
- package/docs/release-candidate.md +7 -0
- package/docs/release-tail-provider-plane.md +30 -0
- package/docs/v4-adopter-delivery.md +133 -0
- package/docs/v4-compatibility-facts.md +91 -0
- package/docs/v4-publication-rehearsal.md +130 -0
- package/docs/v4-tail-reseal.md +73 -0
- package/docs/versioning.md +4 -3
- package/package.json +30 -6
- package/packages/core/adopter-delivery-passport.js +259 -0
- package/packages/core/adopter-delivery-vectors.js +158 -0
- package/packages/core/adopter-delivery-vectors.json +77 -0
- package/packages/core/buildchain-agent-manuals.js +3 -0
- package/packages/core/buildchain-compatibility-authority.js +336 -0
- package/packages/core/buildchain-compatibility-fact.js +394 -0
- package/packages/core/buildchain-compatibility-facts.json +217 -0
- package/packages/core/buildchain-compatibility-proof.js +566 -0
- package/packages/core/buildchain-config.js +84 -0
- package/packages/core/buildchain-contract.js +93 -141
- package/packages/core/buildchain-delivery-bootstrap.js +240 -0
- package/packages/core/buildchain-delivery-infrastructure.js +164 -0
- package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/channel-candidate.js +8 -0
- package/packages/core/channel-promotion-baseline.js +55 -0
- package/packages/core/ci-lane-change-budget.js +247 -0
- package/packages/core/dev-alpha-candidate-selection.js +10 -2
- package/packages/core/dev-delivery-execution-transfer.js +21 -1
- package/packages/core/dev-delivery-native-proof.js +43 -27
- package/packages/core/dev-delivery-process-boundary.js +19 -7
- package/packages/core/dev-delivery-provider-heartbeat.js +18 -6
- package/packages/core/dev-delivery-warrant-state.js +32 -10
- package/packages/core/dev-delivery-warrant.js +9 -3
- package/packages/core/github-governance-authority.js +1 -0
- package/packages/core/index.js +5 -0
- package/packages/core/kungfu-temporal-fact.js +557 -0
- package/packages/core/legacy-kfd-adopter-driver.js +167 -0
- package/packages/core/next-development-candidate-reservation.js +186 -0
- package/packages/core/next-development-controller.js +728 -0
- package/packages/core/next-development-projection.js +287 -0
- package/packages/core/next-development-transition.js +738 -0
- package/packages/core/paper-agent-entry.js +11 -5
- package/packages/core/paper.js +16 -5
- package/packages/core/published-delivery-authority.js +266 -0
- package/packages/core/release-candidate-recovery.js +70 -15
- package/packages/core/release-candidate.js +2 -2
- package/packages/core/release-passport.js +36 -9
- package/packages/core/v4-adopter-delivery-parity.js +3 -3
- package/packages/core/v4-adopter-delivery.js +359 -0
- package/packages/core/v4-canonical-contracts.js +5 -0
- package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
- package/packages/core/v4-floating-consumer-evidence.js +18 -4
- package/packages/core/v4-floating-consumer-policy.js +143 -20
- package/packages/core/v4-floating-consumer-release-passport.js +35 -8
- package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
- package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
- package/packages/core/v4-publication-rehearsal.js +481 -0
- package/packages/core/v4-tail-reseal-contract.js +26 -0
- package/packages/core/v4-tail-reseal-github.js +178 -0
- package/packages/core/v4-tail-reseal-receipt.js +225 -0
- package/packages/core/v4-tail-reseal.js +594 -0
- package/packages/core/workflow-yaml-contract.js +24 -2
- package/scripts/artifact-signing-controller-core.mjs +1 -1
- package/scripts/buildchain-cli-help.mjs +30 -0
- package/scripts/check-ci-lane-change-budget.mjs +100 -0
- package/scripts/check-inventory.mjs +14 -3
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +23 -4
- package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
- package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
- package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
- package/scripts/dev-delivery-warrant-store.mjs +10 -6
- package/scripts/dev-delivery-warrant.mjs +5 -1
- package/scripts/dev-pr-auto-merge.mjs +1 -1
- package/scripts/dev-pr-delivery-warrant.mjs +1 -0
- package/scripts/dev-pr-prequeue-guard.mjs +0 -18
- package/scripts/dev-qualification-patrol.mjs +9 -9
- package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
- package/scripts/generate-channel-promotion-workflow.mjs +2 -2
- package/scripts/generate-next-development-guidance.mjs +49 -0
- package/scripts/generate-release-candidate-passport.mjs +152 -0
- package/scripts/generate-site-bundle.mjs +46 -3
- package/scripts/init-repo.mjs +22 -4
- package/scripts/next-development-self-dogfood-harness.mjs +460 -0
- package/scripts/next-development-self-dogfood.mjs +597 -0
- package/scripts/next-development-transition.mjs +47 -0
- package/scripts/promotion-channel-router.mjs +4 -4
- package/scripts/release-candidate-anchor-provenance.mjs +99 -0
- package/scripts/release-tail.mjs +51 -3
- package/scripts/release-train-self-dogfood.mjs +94 -92
- package/scripts/resume-from-candidate-run.mjs +560 -30
- package/scripts/site-capability-metadata.mjs +29 -0
- package/scripts/stable-candidate-qualification.mjs +9 -10
- package/scripts/v4-consumer-policy.mjs +5 -5
- package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
- package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
- package/scripts/v4-tail-reseal.mjs +416 -0
- package/templates/native-dev-delivery.yml +2 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { parseWorkflowDocument } from "./workflow-yaml-contract.js";
|
|
3
|
+
|
|
4
|
+
export const CI_LANE_CHANGE_BUDGET_CONTRACT =
|
|
5
|
+
"buildchain.ci-lane-change-budget/v1";
|
|
6
|
+
|
|
7
|
+
const AUTHORITY_CLASSES = new Set([
|
|
8
|
+
"merge-critical-required",
|
|
9
|
+
"merge-critical-admission",
|
|
10
|
+
"post-merge-advisory",
|
|
11
|
+
"diagnostic",
|
|
12
|
+
"evidence",
|
|
13
|
+
"release",
|
|
14
|
+
]);
|
|
15
|
+
const TRIGGER_CLASSES = new Set([
|
|
16
|
+
"merge-group",
|
|
17
|
+
"pull-request",
|
|
18
|
+
"push",
|
|
19
|
+
"scheduled",
|
|
20
|
+
"manual",
|
|
21
|
+
"reusable",
|
|
22
|
+
"release",
|
|
23
|
+
"mixed",
|
|
24
|
+
]);
|
|
25
|
+
const CONCURRENCY_MODES = new Set([
|
|
26
|
+
"none",
|
|
27
|
+
"ref-scoped",
|
|
28
|
+
"workflow-scoped",
|
|
29
|
+
"serialized",
|
|
30
|
+
]);
|
|
31
|
+
const CANCELLATION_BEHAVIORS = new Set([
|
|
32
|
+
"cancel-stale",
|
|
33
|
+
"finish-started",
|
|
34
|
+
"provider-default",
|
|
35
|
+
"not-applicable",
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
function canonical(value) {
|
|
39
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
40
|
+
if (value && typeof value === "object") {
|
|
41
|
+
return Object.fromEntries(
|
|
42
|
+
Object.keys(value)
|
|
43
|
+
.sort()
|
|
44
|
+
.map((key) => [key, canonical(value[key])]),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function root(value) {
|
|
51
|
+
return `sha256:${crypto
|
|
52
|
+
.createHash("sha256")
|
|
53
|
+
.update(JSON.stringify(canonical(value)))
|
|
54
|
+
.digest("hex")}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function triggerClass(triggers) {
|
|
58
|
+
const classes = new Set();
|
|
59
|
+
for (const trigger of triggers) {
|
|
60
|
+
if (trigger === "merge_group") classes.add("merge-group");
|
|
61
|
+
else if (trigger.startsWith("pull_request")) classes.add("pull-request");
|
|
62
|
+
else if (trigger === "push") classes.add("push");
|
|
63
|
+
else if (trigger === "schedule") classes.add("scheduled");
|
|
64
|
+
else if (trigger === "workflow_dispatch") classes.add("manual");
|
|
65
|
+
else if (trigger === "workflow_call") classes.add("reusable");
|
|
66
|
+
else if (trigger === "release") classes.add("release");
|
|
67
|
+
else classes.add("mixed");
|
|
68
|
+
}
|
|
69
|
+
return classes.size === 1 ? [...classes][0] : "mixed";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function diagnostic(code, laneId, message) {
|
|
73
|
+
return { code, laneId, message };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function validateDeclaration(declaration, observed) {
|
|
77
|
+
const laneId = String(declaration?.laneId || "");
|
|
78
|
+
const diagnostics = [];
|
|
79
|
+
if (!AUTHORITY_CLASSES.has(declaration?.authorityClass)) {
|
|
80
|
+
diagnostics.push(
|
|
81
|
+
diagnostic(
|
|
82
|
+
"invalid-authority-class",
|
|
83
|
+
laneId,
|
|
84
|
+
"authorityClass must identify the lane's merge or non-merge authority",
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (!TRIGGER_CLASSES.has(declaration?.triggerClass)) {
|
|
89
|
+
diagnostics.push(
|
|
90
|
+
diagnostic(
|
|
91
|
+
"invalid-trigger-class",
|
|
92
|
+
laneId,
|
|
93
|
+
"triggerClass must identify the workflow trigger class",
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
} else if (observed && declaration.triggerClass !== observed.triggerClass) {
|
|
97
|
+
diagnostics.push(
|
|
98
|
+
diagnostic(
|
|
99
|
+
"trigger-class-mismatch",
|
|
100
|
+
laneId,
|
|
101
|
+
`declared ${declaration.triggerClass}, observed ${observed.triggerClass}`,
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const concurrency = declaration?.concurrencyPolicy;
|
|
106
|
+
if (
|
|
107
|
+
!concurrency ||
|
|
108
|
+
!CONCURRENCY_MODES.has(concurrency.mode) ||
|
|
109
|
+
typeof concurrency.cancelInProgress !== "boolean"
|
|
110
|
+
) {
|
|
111
|
+
diagnostics.push(
|
|
112
|
+
diagnostic(
|
|
113
|
+
"invalid-concurrency-policy",
|
|
114
|
+
laneId,
|
|
115
|
+
"concurrencyPolicy requires a known mode and boolean cancelInProgress",
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
if (
|
|
120
|
+
!Number.isFinite(declaration?.expectedRunnerMinutes) ||
|
|
121
|
+
declaration.expectedRunnerMinutes <= 0
|
|
122
|
+
) {
|
|
123
|
+
diagnostics.push(
|
|
124
|
+
diagnostic(
|
|
125
|
+
"invalid-runner-budget",
|
|
126
|
+
laneId,
|
|
127
|
+
"expectedRunnerMinutes must be greater than zero",
|
|
128
|
+
),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (!CANCELLATION_BEHAVIORS.has(declaration?.cancellationBehavior)) {
|
|
132
|
+
diagnostics.push(
|
|
133
|
+
diagnostic(
|
|
134
|
+
"invalid-cancellation-behavior",
|
|
135
|
+
laneId,
|
|
136
|
+
"cancellationBehavior must describe how stale or started work settles",
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
const impact = declaration?.sloImpact;
|
|
141
|
+
if (
|
|
142
|
+
!impact ||
|
|
143
|
+
typeof impact.mergeCritical !== "boolean" ||
|
|
144
|
+
!String(impact.metric || "").trim() ||
|
|
145
|
+
!Number.isFinite(impact.expectedContributionSeconds) ||
|
|
146
|
+
impact.expectedContributionSeconds < 0 ||
|
|
147
|
+
!String(impact.rationale || "").trim()
|
|
148
|
+
) {
|
|
149
|
+
diagnostics.push(
|
|
150
|
+
diagnostic(
|
|
151
|
+
"invalid-slo-impact",
|
|
152
|
+
laneId,
|
|
153
|
+
"sloImpact requires mergeCritical, metric, expectedContributionSeconds, and rationale",
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return diagnostics;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function collectLanes(workflows) {
|
|
161
|
+
const lanes = new Map();
|
|
162
|
+
for (const workflow of workflows) {
|
|
163
|
+
const workflowPath = String(workflow?.path || "").trim();
|
|
164
|
+
if (!workflowPath) throw new Error("workflow path is required");
|
|
165
|
+
const document = parseWorkflowDocument(workflow.text);
|
|
166
|
+
for (const job of document.jobs) {
|
|
167
|
+
const laneId = `${workflowPath}#${job.id}`;
|
|
168
|
+
if (lanes.has(laneId)) throw new Error(`duplicate CI lane: ${laneId}`);
|
|
169
|
+
lanes.set(laneId, {
|
|
170
|
+
laneId,
|
|
171
|
+
workflowPath,
|
|
172
|
+
jobId: job.id,
|
|
173
|
+
triggerClass: triggerClass(document.triggers),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return lanes;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function evaluateCiLaneChangeBudget({
|
|
181
|
+
policy,
|
|
182
|
+
workflows,
|
|
183
|
+
baselineWorkflows = null,
|
|
184
|
+
} = {}) {
|
|
185
|
+
if (policy?.schema !== CI_LANE_CHANGE_BUDGET_CONTRACT) {
|
|
186
|
+
throw new Error(`expected ${CI_LANE_CHANGE_BUDGET_CONTRACT}`);
|
|
187
|
+
}
|
|
188
|
+
if (!Array.isArray(workflows)) throw new Error("workflows must be an array");
|
|
189
|
+
const observed = collectLanes(workflows);
|
|
190
|
+
const baselineLaneIds = baselineWorkflows
|
|
191
|
+
? [...collectLanes(baselineWorkflows).keys()]
|
|
192
|
+
: policy.baseline?.lanes || [];
|
|
193
|
+
const baseline = new Set(baselineLaneIds);
|
|
194
|
+
if (baseline.size !== baselineLaneIds.length) {
|
|
195
|
+
throw new Error("baseline CI lanes must be unique");
|
|
196
|
+
}
|
|
197
|
+
const declarations = new Map();
|
|
198
|
+
const diagnostics = [];
|
|
199
|
+
for (const declaration of policy.declarations || []) {
|
|
200
|
+
const laneId = String(declaration?.laneId || "").trim();
|
|
201
|
+
if (!laneId) throw new Error("CI lane declaration laneId is required");
|
|
202
|
+
if (declarations.has(laneId))
|
|
203
|
+
throw new Error(`duplicate CI lane declaration: ${laneId}`);
|
|
204
|
+
declarations.set(laneId, declaration);
|
|
205
|
+
if (!observed.has(laneId)) {
|
|
206
|
+
diagnostics.push(
|
|
207
|
+
diagnostic(
|
|
208
|
+
"stale-declaration",
|
|
209
|
+
laneId,
|
|
210
|
+
"declared CI lane is absent from the current workflow cut",
|
|
211
|
+
),
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
diagnostics.push(
|
|
215
|
+
...validateDeclaration(declaration, observed.get(laneId) || null),
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
const newLanes = [...observed.keys()]
|
|
219
|
+
.filter((laneId) => !baseline.has(laneId))
|
|
220
|
+
.sort();
|
|
221
|
+
for (const laneId of newLanes) {
|
|
222
|
+
if (!declarations.has(laneId)) {
|
|
223
|
+
diagnostics.push(
|
|
224
|
+
diagnostic(
|
|
225
|
+
"undeclared-new-lane",
|
|
226
|
+
laneId,
|
|
227
|
+
"new CI lane must declare authority, trigger, concurrency, runner, cancellation, and SLO budgets",
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const result = {
|
|
233
|
+
schema: "buildchain.ci-lane-change-budget.evaluation/v1",
|
|
234
|
+
ok: diagnostics.length === 0,
|
|
235
|
+
observedLaneCount: observed.size,
|
|
236
|
+
baselineLaneCount: baseline.size,
|
|
237
|
+
newLaneCount: newLanes.length,
|
|
238
|
+
declaredLaneCount: declarations.size,
|
|
239
|
+
newLanes,
|
|
240
|
+
diagnostics: diagnostics.sort((left, right) =>
|
|
241
|
+
`${left.laneId}:${left.code}`.localeCompare(
|
|
242
|
+
`${right.laneId}:${right.code}`,
|
|
243
|
+
),
|
|
244
|
+
),
|
|
245
|
+
};
|
|
246
|
+
return { ...result, evaluationRoot: root(result) };
|
|
247
|
+
}
|
|
@@ -82,7 +82,9 @@ export function selectLatestQualifiedSource({
|
|
|
82
82
|
requiredWorkflowPaths,
|
|
83
83
|
now,
|
|
84
84
|
maxAgeSeconds,
|
|
85
|
+
ignoredSourceShas = [],
|
|
85
86
|
}) {
|
|
87
|
+
const ignored = new Set(ignoredSourceShas);
|
|
86
88
|
const latestByPath = new Map(
|
|
87
89
|
requiredWorkflowPaths.map((workflow) => [
|
|
88
90
|
workflow,
|
|
@@ -90,7 +92,11 @@ export function selectLatestQualifiedSource({
|
|
|
90
92
|
]),
|
|
91
93
|
);
|
|
92
94
|
for (let index = 0; index < sourceHistory.length; index += 1) {
|
|
93
|
-
const sourceSha =
|
|
95
|
+
const sourceSha =
|
|
96
|
+
typeof sourceHistory[index] === "string"
|
|
97
|
+
? sourceHistory[index]
|
|
98
|
+
: sourceHistory[index]?.sha;
|
|
99
|
+
if (ignored.has(sourceSha)) continue;
|
|
94
100
|
const rows = requiredWorkflowPaths.map((workflow) =>
|
|
95
101
|
latestByPath.get(workflow).get(sourceSha),
|
|
96
102
|
);
|
|
@@ -115,7 +121,9 @@ export function selectLatestQualifiedSource({
|
|
|
115
121
|
};
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
|
-
const staleSuccessfulPair = sourceHistory.some((
|
|
124
|
+
const staleSuccessfulPair = sourceHistory.some((entry) => {
|
|
125
|
+
const sourceSha = typeof entry === "string" ? entry : entry?.sha;
|
|
126
|
+
if (ignored.has(sourceSha)) return false;
|
|
119
127
|
const rows = requiredWorkflowPaths.map((workflow) =>
|
|
120
128
|
latestByPath.get(workflow).get(sourceSha),
|
|
121
129
|
);
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
devDeliveryText as text,
|
|
14
14
|
devDeliveryTimestamp as timestamp,
|
|
15
15
|
} from "./dev-delivery-common.js";
|
|
16
|
+
import { verifyNativeProofReuseDecision } from "./dev-delivery-native-proof.js";
|
|
16
17
|
import { verifyNativeQualificationProof } from "./dev-delivery-native-proof.js";
|
|
17
18
|
import { verifyNativeExecutionFailureOutcome } from "./dev-delivery-execution-failure.js";
|
|
18
19
|
|
|
@@ -408,8 +409,28 @@ function verifySuccessOutcome(transfer, directory, warrant) {
|
|
|
408
409
|
"native proof reuse decision root",
|
|
409
410
|
);
|
|
410
411
|
delete decisionBody.decisionRoot;
|
|
412
|
+
const reuseVerification = verifyNativeProofReuseDecision(decision, {
|
|
413
|
+
proof,
|
|
414
|
+
current: {
|
|
415
|
+
sourceHead: warrant.sourceHead,
|
|
416
|
+
sourceIdentityRoot: warrant.sourceIdentityRoot,
|
|
417
|
+
sourcePatchRoot: warrant.sourcePatchRoot,
|
|
418
|
+
planRoot: warrant.planRoot,
|
|
419
|
+
closureRoot: warrant.closureRoot,
|
|
420
|
+
dependencyRoot: warrant.dependencyRoot,
|
|
421
|
+
toolchainRoot: warrant.toolchainRoot,
|
|
422
|
+
environmentRoot: warrant.environmentRoot,
|
|
423
|
+
nativeCommandRoot: warrant.nativeCommandContract?.commandRoot,
|
|
424
|
+
currentBase: decision.currentBase,
|
|
425
|
+
graphKnown: decision.graphKnown,
|
|
426
|
+
attributionComplete: decision.attributionComplete,
|
|
427
|
+
changedPaths: decision.changedPaths,
|
|
428
|
+
renames: decision.renames,
|
|
429
|
+
},
|
|
430
|
+
});
|
|
411
431
|
if (
|
|
412
432
|
!proofVerification.ok ||
|
|
433
|
+
!reuseVerification.ok ||
|
|
413
434
|
result.nativeProofRoot !== transfer.nativeProofRoot ||
|
|
414
435
|
proof.proofRoot !== transfer.nativeProofRoot ||
|
|
415
436
|
result.nativeReuseDecisionRoot !== transfer.nativeReuseDecisionRoot ||
|
|
@@ -420,7 +441,6 @@ function verifySuccessOutcome(transfer, directory, warrant) {
|
|
|
420
441
|
proof.repository !== transfer.warrant.repository ||
|
|
421
442
|
proof.protectedBase !== transfer.warrant.protectedBase ||
|
|
422
443
|
proof.sourceHead !== transfer.warrant.sourceHead ||
|
|
423
|
-
proof.qualifiedBase !== warrant.qualifiedBase ||
|
|
424
444
|
proof.sourceIdentityRoot !== warrant.sourceIdentityRoot ||
|
|
425
445
|
proof.sourcePatchRoot !== warrant.sourcePatchRoot ||
|
|
426
446
|
proof.planRoot !== warrant.planRoot ||
|
|
@@ -43,7 +43,39 @@ export const DEV_DELIVERY_QUALIFICATION_RECEIPT_SCHEMA =
|
|
|
43
43
|
export const NATIVE_PROOF_BASE_DELTA_SCHEMA =
|
|
44
44
|
"kungfu.buildchain.native-proof-base-delta/v1";
|
|
45
45
|
const NATIVE_QUALIFICATION_ROOT_SEMANTICS = "semantic-native-identity-v1";
|
|
46
|
-
|
|
46
|
+
|
|
47
|
+
function normalizeQualifiedWarrant(warrant, candidate, allowLegacyV3Readback) {
|
|
48
|
+
const qualifiedV3Readback =
|
|
49
|
+
allowLegacyV3Readback &&
|
|
50
|
+
!warrant.nativeCommandContract &&
|
|
51
|
+
!candidate.nativeCommandContract &&
|
|
52
|
+
!warrant.nativeExecutionReceiptRoot &&
|
|
53
|
+
!warrant.qualificationReceiptRoot;
|
|
54
|
+
warrant.nativeProofRoot = exactRoot(
|
|
55
|
+
warrant.nativeProofRoot,
|
|
56
|
+
"Warrant nativeProofRoot",
|
|
57
|
+
);
|
|
58
|
+
warrant.nativeProofReuseRoot = exactRoot(
|
|
59
|
+
warrant.nativeProofReuseRoot,
|
|
60
|
+
"Warrant nativeProofReuseRoot",
|
|
61
|
+
);
|
|
62
|
+
if (!qualifiedV3Readback) {
|
|
63
|
+
warrant.nativeExecutionReceiptRoot = exactRoot(
|
|
64
|
+
warrant.nativeExecutionReceiptRoot,
|
|
65
|
+
"Warrant nativeExecutionReceiptRoot",
|
|
66
|
+
);
|
|
67
|
+
warrant.qualificationReceiptRoot = exactRoot(
|
|
68
|
+
warrant.qualificationReceiptRoot,
|
|
69
|
+
"Warrant qualificationReceiptRoot",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
warrant.qualifiedAt = timestamp(warrant.qualifiedAt, "Warrant qualifiedAt");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function validateActiveDevDeliveryWarrant(
|
|
76
|
+
queue,
|
|
77
|
+
{ allowLegacyV3Readback = false } = {},
|
|
78
|
+
) {
|
|
47
79
|
const warrant = queue.activeWarrant;
|
|
48
80
|
if (warrant.schema !== "kungfu.buildchain.dev-delivery-warrant/v1")
|
|
49
81
|
throw new Error("active Warrant schema is unsupported");
|
|
@@ -68,24 +100,17 @@ export function validateActiveDevDeliveryWarrant(queue) {
|
|
|
68
100
|
const phase = historicalPhaseLess ? "legacy-active" : warrant.phase;
|
|
69
101
|
if (!["legacy-active", "provisional", "qualified"].includes(phase))
|
|
70
102
|
throw new Error("active Warrant phase is unsupported");
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"Warrant
|
|
79
|
-
);
|
|
80
|
-
warrant.nativeExecutionReceiptRoot = exactRoot(
|
|
81
|
-
warrant.nativeExecutionReceiptRoot,
|
|
82
|
-
"Warrant nativeExecutionReceiptRoot",
|
|
83
|
-
);
|
|
84
|
-
warrant.qualificationReceiptRoot = exactRoot(
|
|
85
|
-
warrant.qualificationReceiptRoot,
|
|
86
|
-
"Warrant qualificationReceiptRoot",
|
|
103
|
+
const active = queue.candidates.filter((candidate) =>
|
|
104
|
+
["selected", "proving", "waiting", "blocked", "qualified"].includes(
|
|
105
|
+
candidate.status,
|
|
106
|
+
),
|
|
107
|
+
);
|
|
108
|
+
if (active.length !== 1 || active[0].candidateId !== warrant.candidateId)
|
|
109
|
+
throw new Error(
|
|
110
|
+
"exactly one active candidate must match the active Warrant",
|
|
87
111
|
);
|
|
88
|
-
|
|
112
|
+
if (warrant.phase === "qualified") {
|
|
113
|
+
normalizeQualifiedWarrant(warrant, active[0], allowLegacyV3Readback);
|
|
89
114
|
} else if (
|
|
90
115
|
warrant.phase === "provisional" &&
|
|
91
116
|
(warrant.nativeProofRoot ||
|
|
@@ -98,15 +123,6 @@ export function validateActiveDevDeliveryWarrant(queue) {
|
|
|
98
123
|
"provisional Warrant cannot carry qualified native evidence",
|
|
99
124
|
);
|
|
100
125
|
}
|
|
101
|
-
const active = queue.candidates.filter((candidate) =>
|
|
102
|
-
["selected", "proving", "waiting", "blocked", "qualified"].includes(
|
|
103
|
-
candidate.status,
|
|
104
|
-
),
|
|
105
|
-
);
|
|
106
|
-
if (active.length !== 1 || active[0].candidateId !== warrant.candidateId)
|
|
107
|
-
throw new Error(
|
|
108
|
-
"exactly one active candidate must match the active Warrant",
|
|
109
|
-
);
|
|
110
126
|
if (
|
|
111
127
|
historicalPhaseLess &&
|
|
112
128
|
(active[0].environmentRoot ||
|
|
@@ -30,7 +30,7 @@ const PROVIDER_CREDENTIAL_NAME =
|
|
|
30
30
|
/(?:^(?:ACTIONS_ID_TOKEN_REQUEST_TOKEN|ACTIONS_RUNTIME_TOKEN|AWS_SESSION_TOKEN|AWS_WEB_IDENTITY_TOKEN_FILE|AZURE_FEDERATED_TOKEN_FILE|GH_TOKEN|GITHUB_TOKEN|GOOGLE_GHA_CREDS_PATH|KUNGFU_GITHUB_TOKEN|NPM_TOKEN|SSH_AUTH_SOCK)$|(?:^|_)(?:(?:api|access|auth|publish|release|provider|promotion|governance|approval|mutation|write)_?(?:key|token)|authorization|credentials?|password|secret|private_key|client_secret|signing_key)(?:_|$))/iu;
|
|
31
31
|
const HOSTED_RUNNER_BOUNDARY = "github-actions-runner-worker/v1";
|
|
32
32
|
const GITHUB_HOSTED_LINUX_WORKER =
|
|
33
|
-
/^\/home\/runner\/runners\/(\d+\.\d+\.\d+)\/bin\/Runner\.Worker$/u;
|
|
33
|
+
/^\/home\/runner\/(?:runners|actions-runner\/cached)\/(\d+\.\d+\.\d+)\/bin\/Runner\.Worker$/u;
|
|
34
34
|
function text(value, label) {
|
|
35
35
|
const normalized = normalizedText(value);
|
|
36
36
|
if (!normalized) throw new Error(`${label} is required`);
|
|
@@ -61,10 +61,16 @@ function processCredentialNames(procRoot, pid, readFileSync) {
|
|
|
61
61
|
.split("\0")
|
|
62
62
|
.filter((entry) => {
|
|
63
63
|
const separator = entry.indexOf("=");
|
|
64
|
+
const name = entry.slice(0, separator);
|
|
65
|
+
const value = entry.slice(separator + 1);
|
|
64
66
|
return (
|
|
65
67
|
separator > 0 &&
|
|
66
|
-
Boolean(
|
|
67
|
-
|
|
68
|
+
Boolean(value) &&
|
|
69
|
+
!(
|
|
70
|
+
name === "BUILDCHAIN_CREDENTIAL_ANCESTRY_BOUNDARY" &&
|
|
71
|
+
value === HOSTED_RUNNER_BOUNDARY
|
|
72
|
+
) &&
|
|
73
|
+
isCredentialVariableName(name)
|
|
68
74
|
);
|
|
69
75
|
})
|
|
70
76
|
.map((entry) => entry.slice(0, entry.indexOf("=")));
|
|
@@ -98,7 +104,6 @@ function assertHostedLinuxController(environment) {
|
|
|
98
104
|
function hostedBoundary(procRoot, pid, command, readlinkSync) {
|
|
99
105
|
if (
|
|
100
106
|
command.length !== 4 ||
|
|
101
|
-
!GITHUB_HOSTED_LINUX_WORKER.test(command[0] || "") ||
|
|
102
107
|
command[1] !== "spawnclient" ||
|
|
103
108
|
!/^\d+$/u.test(command[2] || "") ||
|
|
104
109
|
!/^\d+$/u.test(command[3] || "")
|
|
@@ -109,9 +114,11 @@ function hostedBoundary(procRoot, pid, command, readlinkSync) {
|
|
|
109
114
|
const executable = ancestryRead(readlinkSync, link);
|
|
110
115
|
if (
|
|
111
116
|
!GITHUB_HOSTED_LINUX_WORKER.test(executable) ||
|
|
112
|
-
command[0] !== executable
|
|
117
|
+
path.posix.basename(command[0]) !== path.posix.basename(executable)
|
|
113
118
|
) {
|
|
114
|
-
throw new Error(
|
|
119
|
+
throw new Error(
|
|
120
|
+
`credential ancestry found an untrusted Runner.Worker: executable=${JSON.stringify(executable)} argv0=${JSON.stringify(command[0])}`,
|
|
121
|
+
);
|
|
115
122
|
}
|
|
116
123
|
return { pid, kind: HOSTED_RUNNER_BOUNDARY, executable };
|
|
117
124
|
}
|
|
@@ -164,7 +171,11 @@ export function assertCredentiallessProcessAncestry(options = {}) {
|
|
|
164
171
|
return inspection;
|
|
165
172
|
}
|
|
166
173
|
|
|
167
|
-
export function devDeliveryProviderJobByName(
|
|
174
|
+
export function devDeliveryProviderJobByName(
|
|
175
|
+
jobs,
|
|
176
|
+
expectedName,
|
|
177
|
+
{ allowMissing = false } = {},
|
|
178
|
+
) {
|
|
168
179
|
const exactName = text(expectedName, "provider job name");
|
|
169
180
|
const suffix = ` / ${exactName}`;
|
|
170
181
|
const matches = jobs.filter((job) => {
|
|
@@ -174,6 +185,7 @@ export function devDeliveryProviderJobByName(jobs, expectedName) {
|
|
|
174
185
|
(actualName.endsWith(suffix) && actualName.length > suffix.length)
|
|
175
186
|
);
|
|
176
187
|
});
|
|
188
|
+
if (matches.length === 0 && allowMissing) return null;
|
|
177
189
|
if (matches.length !== 1) {
|
|
178
190
|
throw new Error(`expected exactly one provider job named ${exactName}`);
|
|
179
191
|
}
|
|
@@ -13,7 +13,7 @@ export const DEV_DELIVERY_PROVIDER_HEARTBEAT_SCHEMA =
|
|
|
13
13
|
"kungfu.buildchain.provider-heartbeat-receipt/v1";
|
|
14
14
|
|
|
15
15
|
function selectedWarrant(result = {}) {
|
|
16
|
-
return result.
|
|
16
|
+
return result.warrant || result.observation?.activeWarrant || null;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function exactWarrant(input = {}) {
|
|
@@ -40,8 +40,9 @@ function exactWarrant(input = {}) {
|
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
function job(entries, name) {
|
|
44
|
-
const entry = devDeliveryProviderJobByName(entries, name);
|
|
43
|
+
function job(entries, name, options) {
|
|
44
|
+
const entry = devDeliveryProviderJobByName(entries, name, options);
|
|
45
|
+
if (!entry) return null;
|
|
45
46
|
const providerJob = {
|
|
46
47
|
id: positiveInteger(entry.id, `${name} job id`),
|
|
47
48
|
name,
|
|
@@ -66,13 +67,16 @@ function job(entries, name) {
|
|
|
66
67
|
return providerJob;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
function providerJobs(input, expected) {
|
|
70
|
+
function providerJobs(input, expected, { allowMissingSeal = false } = {}) {
|
|
70
71
|
const entries = Array.isArray(input) ? input : input?.jobs;
|
|
71
72
|
if (!Array.isArray(entries)) {
|
|
72
73
|
throw new Error("provider heartbeat jobs readback must contain jobs");
|
|
73
74
|
}
|
|
74
75
|
const native = job(entries, expected.nativeJobName);
|
|
75
|
-
const seal = job(entries, expected.sealJobName
|
|
76
|
+
const seal = job(entries, expected.sealJobName, {
|
|
77
|
+
allowMissing: allowMissingSeal,
|
|
78
|
+
});
|
|
79
|
+
if (!seal) return null;
|
|
76
80
|
if (native.id === seal.id) {
|
|
77
81
|
throw new Error("provider heartbeat native and seal jobs must be distinct");
|
|
78
82
|
}
|
|
@@ -206,7 +210,15 @@ export async function runDevDeliveryProviderHeartbeat(
|
|
|
206
210
|
const entry = heartbeatEntry(result, previousStateRoot);
|
|
207
211
|
heartbeats.push(entry);
|
|
208
212
|
previousStateRoot = entry.nextStateRoot;
|
|
209
|
-
jobs = providerJobs(
|
|
213
|
+
jobs = providerJobs(
|
|
214
|
+
await readJobs(),
|
|
215
|
+
{ nativeJobName, sealJobName },
|
|
216
|
+
{ allowMissingSeal: true },
|
|
217
|
+
);
|
|
218
|
+
if (!jobs) {
|
|
219
|
+
await wait(cadence * 1000);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
210
222
|
if (terminal(jobs.native) && terminal(jobs.seal)) break;
|
|
211
223
|
await wait(cadence * 1000);
|
|
212
224
|
}
|
|
@@ -51,8 +51,8 @@ export const TERMINAL_STATES = new Set([
|
|
|
51
51
|
"terminal-failure",
|
|
52
52
|
"dequeued",
|
|
53
53
|
"cancelled",
|
|
54
|
+
"superseded",
|
|
54
55
|
]);
|
|
55
|
-
|
|
56
56
|
export function issueDevDeliveryWarrant(
|
|
57
57
|
queue,
|
|
58
58
|
before,
|
|
@@ -158,6 +158,21 @@ function normalizePolicy(policy = {}) {
|
|
|
158
158
|
emergencyPolicy: text(policy.emergencyPolicy || "reviewed-explicit-only"),
|
|
159
159
|
};
|
|
160
160
|
}
|
|
161
|
+
|
|
162
|
+
function lacksLiveNativeProof(candidate, status, allowLegacyV3Readback) {
|
|
163
|
+
const { environmentRoot, nativeCommandContract } = candidate;
|
|
164
|
+
const native =
|
|
165
|
+
candidate.deliveryClass !== "non-native-fast" ||
|
|
166
|
+
environmentRoot ||
|
|
167
|
+
nativeCommandContract;
|
|
168
|
+
return (
|
|
169
|
+
native &&
|
|
170
|
+
(!environmentRoot ||
|
|
171
|
+
(!nativeCommandContract && allowLegacyV3Readback !== true)) &&
|
|
172
|
+
!TERMINAL_STATES.has(status)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
161
176
|
function normalizeCandidate(input, expected) {
|
|
162
177
|
const identity = createDevDeliveryCandidateIdentity(
|
|
163
178
|
input,
|
|
@@ -246,11 +261,8 @@ function normalizeCandidate(input, expected) {
|
|
|
246
261
|
"candidate shardEvidenceRoots",
|
|
247
262
|
);
|
|
248
263
|
}
|
|
249
|
-
if (candidate
|
|
250
|
-
throw new Error(
|
|
251
|
-
"native candidate requires an exact native command contract",
|
|
252
|
-
);
|
|
253
|
-
}
|
|
264
|
+
if (lacksLiveNativeProof(candidate, status, expected.allowLegacyV3Readback))
|
|
265
|
+
throw new Error("live native candidate requires exact native proof");
|
|
254
266
|
if (Object.hasOwn(input, "releaseBlockerPriority"))
|
|
255
267
|
candidate.releaseBlockerPriority = normalizeReleaseBlockerPriorityClaim(
|
|
256
268
|
input.releaseBlockerPriority,
|
|
@@ -319,13 +331,21 @@ export function normalizeDevDeliveryQueue(input, expected = {}) {
|
|
|
319
331
|
"queue fencingCounter",
|
|
320
332
|
);
|
|
321
333
|
queue.policy = normalizePolicy(queue.policy);
|
|
334
|
+
const allowLegacyV3Readback =
|
|
335
|
+
expected.allowLegacyV3Readback === true &&
|
|
336
|
+
!(queue.candidates || []).some(
|
|
337
|
+
(candidate) => candidate?.nativeCommandContract,
|
|
338
|
+
) &&
|
|
339
|
+
!queue.activeWarrant?.nativeCommandContract &&
|
|
340
|
+
!queue.activeWarrant?.nativeExecutionReceiptRoot &&
|
|
341
|
+
!queue.activeWarrant?.qualificationReceiptRoot;
|
|
322
342
|
queue.candidates = (queue.candidates || []).map((candidate) =>
|
|
323
|
-
normalizeCandidate(candidate, queue),
|
|
343
|
+
normalizeCandidate(candidate, { ...queue, allowLegacyV3Readback }),
|
|
324
344
|
);
|
|
325
345
|
validateDevDeliveryCandidateChain(queue.candidates, TERMINAL_STATES);
|
|
326
346
|
queue.updatedAt = timestamp(queue.updatedAt, "queue updatedAt");
|
|
327
347
|
if (queue.activeWarrant) {
|
|
328
|
-
validateActiveDevDeliveryWarrant(queue);
|
|
348
|
+
validateActiveDevDeliveryWarrant(queue, { allowLegacyV3Readback });
|
|
329
349
|
} else if (
|
|
330
350
|
queue.candidates.some((candidate) =>
|
|
331
351
|
["selected", "proving", "waiting", "blocked", "qualified"].includes(
|
|
@@ -545,9 +565,11 @@ function effectivePriority(candidate, policy, now) {
|
|
|
545
565
|
}
|
|
546
566
|
export function rankDevDeliveryCandidates(
|
|
547
567
|
queueInput,
|
|
548
|
-
{ now = new Date().toISOString() } = {},
|
|
568
|
+
{ now = new Date().toISOString(), allowLegacyV3Readback = false } = {},
|
|
549
569
|
) {
|
|
550
|
-
const queue = normalizeDevDeliveryQueue(queueInput
|
|
570
|
+
const queue = normalizeDevDeliveryQueue(queueInput, {
|
|
571
|
+
allowLegacyV3Readback,
|
|
572
|
+
});
|
|
551
573
|
const currentTime = timestamp(now, "now");
|
|
552
574
|
return queue.candidates
|
|
553
575
|
.filter((candidate) => candidate.status === "queued")
|
|
@@ -267,6 +267,7 @@ export function heartbeatDevDeliveryWarrant(
|
|
|
267
267
|
"leaseSeconds",
|
|
268
268
|
queue.policy.leaseSeconds,
|
|
269
269
|
);
|
|
270
|
+
queue.activeWarrant.heartbeatAt = currentTime;
|
|
270
271
|
queue.activeWarrant.expiresAt = new Date(
|
|
271
272
|
Date.parse(currentTime) + effectiveLeaseSeconds * 1000,
|
|
272
273
|
).toISOString();
|
|
@@ -515,14 +516,19 @@ export const settleDevDeliveryTerminalEvent = createDevDeliveryTerminalSettler({
|
|
|
515
516
|
});
|
|
516
517
|
export function observeDevDeliveryQueue(
|
|
517
518
|
queueInput,
|
|
518
|
-
{ now = new Date().toISOString() } = {},
|
|
519
|
+
{ now = new Date().toISOString(), allowLegacyV3Readback = false } = {},
|
|
519
520
|
) {
|
|
520
|
-
const queue = normalizeDevDeliveryQueue(queueInput
|
|
521
|
+
const queue = normalizeDevDeliveryQueue(queueInput, {
|
|
522
|
+
allowLegacyV3Readback,
|
|
523
|
+
});
|
|
521
524
|
const currentTime = timestamp(now, "now");
|
|
522
525
|
const states = {};
|
|
523
526
|
for (const candidate of queue.candidates)
|
|
524
527
|
states[candidate.status] = (states[candidate.status] || 0) + 1;
|
|
525
|
-
const queued = rankDevDeliveryCandidates(queue, {
|
|
528
|
+
const queued = rankDevDeliveryCandidates(queue, {
|
|
529
|
+
now: currentTime,
|
|
530
|
+
allowLegacyV3Readback,
|
|
531
|
+
});
|
|
526
532
|
return {
|
|
527
533
|
schema: "kungfu.buildchain.dev-delivery-queue-observation/v1",
|
|
528
534
|
repository: queue.repository,
|
|
@@ -116,6 +116,7 @@ const PROTECTED_AUTHORITY_PATHS = Object.freeze([
|
|
|
116
116
|
".github/CODEOWNERS",
|
|
117
117
|
".github/workflows/.publication-authority.yml",
|
|
118
118
|
".github/workflows/.release-candidate-promote.yml",
|
|
119
|
+
".github/workflows/buildchain-ref-promotion-recovery.yml",
|
|
119
120
|
".github/workflows/buildchain-ref-promotion.yml",
|
|
120
121
|
".github/workflows/github-governance-audit.yml",
|
|
121
122
|
".github/workflows/paper-release-sealed.yml",
|
package/packages/core/index.js
CHANGED
|
@@ -139,6 +139,11 @@ export {
|
|
|
139
139
|
normalizeCandidateTimelineEvent,
|
|
140
140
|
} from "./candidate-timeline.js";
|
|
141
141
|
|
|
142
|
+
export {
|
|
143
|
+
CI_LANE_CHANGE_BUDGET_CONTRACT,
|
|
144
|
+
evaluateCiLaneChangeBudget,
|
|
145
|
+
} from "./ci-lane-change-budget.js";
|
|
146
|
+
|
|
142
147
|
export {
|
|
143
148
|
CHANNEL_CANDIDATE_DECISION_SCHEMA,
|
|
144
149
|
channelCandidateSourceLockRef,
|