@kungfu-tech/buildchain 4.0.1-alpha.2 → 4.0.1-alpha.21
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/decisions/0003-two-phase-delivery-warrant.md +152 -0
- package/architecture/internal-capabilities.json +211 -8
- package/architecture/maintainability-policy.json +113 -28
- package/architecture/release-tail-contract-inventory.json +5 -2
- package/architecture/v3-core-mechanism-inventory.json +91 -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-authority-parity.json +250 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +41 -9
- 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 +32 -3
- 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/dev-delivery-authority-v2.schema.json +662 -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 +5 -0
- package/dist/site/artifact-schemas.json +10 -0
- package/dist/site/buildchain-contract.json +1475 -27
- package/dist/site/buildchain-site.json +627 -31
- package/dist/site/capability-registry.json +17 -14
- package/dist/site/cli-registry.json +205 -3
- package/dist/site/kfd-claims.json +490 -16
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +52 -6
- package/dist/site/node-api-registry.json +14736 -8851
- package/dist/site/page-registry.json +593 -21
- package/dist/site/public-surface-audit.json +837 -48
- package/dist/site/publication-authority-registry.json +44 -8
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +24 -0
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +662 -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 +34 -10
- package/dist/site/workflow-registry.json +160 -10
- package/docs/MAP.md +8 -2
- package/docs/cli-reference.md +404 -13
- package/docs/cli.md +8 -0
- package/docs/dev-alpha-candidate-patrol.md +8 -0
- package/docs/dev-delivery-qualification-landing-adr.md +251 -0
- package/docs/dev-delivery-warrant.md +351 -38
- package/docs/next-development-transition.md +118 -0
- package/docs/node-api-reference.md +646 -293
- 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 +34 -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 +96 -143
- 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-authority-candidate.js +270 -0
- package/packages/core/dev-delivery-authority-evidence.js +146 -0
- package/packages/core/dev-delivery-authority-landing.js +461 -0
- package/packages/core/dev-delivery-authority-observation.js +48 -0
- package/packages/core/dev-delivery-authority-qualification.js +591 -0
- package/packages/core/dev-delivery-authority-settlement.js +213 -0
- package/packages/core/dev-delivery-authority-state.js +583 -0
- package/packages/core/dev-delivery-candidate-identity.js +13 -0
- package/packages/core/dev-delivery-contract-surface.js +76 -0
- package/packages/core/dev-delivery-execution-failure.js +133 -0
- package/packages/core/dev-delivery-execution-transfer.js +592 -0
- package/packages/core/dev-delivery-landing-admission-core.js +119 -0
- package/packages/core/dev-delivery-landing-readback.js +598 -0
- package/packages/core/dev-delivery-landing-terminal-evidence.js +271 -0
- package/packages/core/dev-delivery-landing-testing-port.js +6 -0
- package/packages/core/dev-delivery-native-execution.js +110 -0
- package/packages/core/dev-delivery-native-proof.js +562 -0
- package/packages/core/dev-delivery-process-boundary.js +563 -0
- package/packages/core/dev-delivery-provider-attempt.js +127 -0
- package/packages/core/dev-delivery-provider-heartbeat.js +382 -0
- package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
- package/packages/core/dev-delivery-warrant-qualification.js +145 -0
- package/packages/core/dev-delivery-warrant-settlement.js +237 -36
- package/packages/core/dev-delivery-warrant-state.js +587 -0
- package/packages/core/dev-delivery-warrant.js +328 -370
- 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 +32 -5
- 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/buildchain-cli-help.mjs +41 -2
- 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-authority-command-adapters.mjs +206 -0
- package/scripts/dev-delivery-authority-provider.mjs +28 -0
- package/scripts/dev-delivery-authority.mjs +490 -0
- package/scripts/dev-delivery-native-run.mjs +177 -0
- package/scripts/dev-delivery-process-boundary.mjs +260 -0
- package/scripts/dev-delivery-proof.mjs +67 -2
- package/scripts/dev-delivery-provider-heartbeat.mjs +215 -0
- package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
- package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
- package/scripts/dev-delivery-two-phase-resume.mjs +345 -0
- package/scripts/dev-delivery-two-phase.mjs +573 -0
- package/scripts/dev-delivery-warrant-options.mjs +266 -0
- package/scripts/dev-delivery-warrant-store.mjs +231 -0
- package/scripts/dev-delivery-warrant.mjs +232 -194
- package/scripts/dev-pr-auto-merge.mjs +1 -1
- package/scripts/dev-pr-delivery-warrant.mjs +11 -0
- 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 +69 -4
- 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/npm-publish-transaction.mjs +2 -2
- 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 +557 -30
- package/scripts/site-capability-metadata.mjs +41 -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 +143 -0
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
cancelQueuedDevDeliveryCandidate,
|
|
6
|
+
closeDevDeliveryWarrant,
|
|
7
|
+
createDevDeliveryQueue,
|
|
8
|
+
createNativeCommandContract,
|
|
9
|
+
heartbeatDevDeliveryWarrant,
|
|
10
|
+
observeDevDeliveryQueue,
|
|
11
|
+
qualifyDevDeliveryWarrant,
|
|
12
|
+
recoverExpiredDevDeliveryWarrant,
|
|
13
|
+
selectDevDeliveryWarrant,
|
|
14
|
+
settleDevDeliveryTerminalEvent,
|
|
15
|
+
submitDevDeliveryCandidate,
|
|
16
|
+
} from "../packages/core/dev-delivery-warrant.js";
|
|
5
17
|
import { runV4DeliveryWarrantReadCandidate } from "../packages/core/v4-delivery-warrant-read-candidate.js";
|
|
6
18
|
|
|
7
|
-
|
|
19
|
+
import { GitHubDevDeliveryStore } from "./dev-delivery-warrant-store.mjs";
|
|
20
|
+
import { devDeliveryCliOptions } from "./dev-delivery-warrant-options.mjs";
|
|
21
|
+
export { GitHubDevDeliveryStore, devDeliveryCliOptions };
|
|
22
|
+
|
|
8
23
|
const STATE_REF_PREFIX = "buildchain/dev-delivery-warrant/";
|
|
9
24
|
|
|
10
25
|
function text(value = "") {
|
|
11
26
|
return String(value ?? "").trim();
|
|
12
27
|
}
|
|
13
|
-
|
|
14
28
|
function bool(value, fallback = false) {
|
|
15
29
|
if (value === undefined || value === null || value === "") return fallback;
|
|
16
30
|
if (typeof value === "boolean") return value;
|
|
@@ -19,33 +33,41 @@ function bool(value, fallback = false) {
|
|
|
19
33
|
|
|
20
34
|
function positiveInteger(value, label, fallback = 0) {
|
|
21
35
|
const parsed = Number(value ?? fallback);
|
|
22
|
-
if (!Number.isInteger(parsed) || parsed < 1)
|
|
36
|
+
if (!Number.isInteger(parsed) || parsed < 1)
|
|
37
|
+
throw new Error(`${label} must be a positive integer`);
|
|
23
38
|
return parsed;
|
|
24
39
|
}
|
|
25
40
|
|
|
26
41
|
function exactRoot(value, label) {
|
|
27
42
|
const normalized = text(value).toLowerCase();
|
|
28
|
-
if (!/^sha256:[0-9a-f]{64}$/.test(normalized))
|
|
43
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(normalized))
|
|
44
|
+
throw new Error(`${label} must be a sha256 content root`);
|
|
29
45
|
return normalized;
|
|
30
46
|
}
|
|
31
47
|
|
|
32
48
|
function exactSha(value, label) {
|
|
33
49
|
const normalized = text(value).toLowerCase();
|
|
34
|
-
if (!/^[0-9a-f]{40}$/.test(normalized))
|
|
50
|
+
if (!/^[0-9a-f]{40}$/.test(normalized))
|
|
51
|
+
throw new Error(`${label} must be a 40-character Git SHA`);
|
|
35
52
|
return normalized;
|
|
36
53
|
}
|
|
37
54
|
|
|
38
55
|
function normalizeRepository(value) {
|
|
39
56
|
const normalized = text(value);
|
|
40
57
|
const match = normalized.match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
41
|
-
if (!match)
|
|
58
|
+
if (!match)
|
|
59
|
+
throw new Error(
|
|
60
|
+
`repository must be owner/repo, got ${normalized || "<empty>"}`,
|
|
61
|
+
);
|
|
42
62
|
return { owner: match[1], repo: match[2], fullName: normalized };
|
|
43
63
|
}
|
|
44
64
|
|
|
45
65
|
function normalizeBranch(value) {
|
|
46
66
|
const normalized = text(value).replace(/^refs\/heads\//, "");
|
|
47
67
|
if (!/^dev\/v\d+\/v\d+\.\d+$/.test(normalized)) {
|
|
48
|
-
throw new Error(
|
|
68
|
+
throw new Error(
|
|
69
|
+
`branch must be dev/vN/vN.M, got ${normalized || "<empty>"}`,
|
|
70
|
+
);
|
|
49
71
|
}
|
|
50
72
|
return normalized;
|
|
51
73
|
}
|
|
@@ -56,142 +78,122 @@ export function defaultDevDeliveryStateRef(branch) {
|
|
|
56
78
|
}
|
|
57
79
|
|
|
58
80
|
function normalizeStateRef(value, branch) {
|
|
59
|
-
const normalized = text(value || defaultDevDeliveryStateRef(branch)).replace(
|
|
60
|
-
|
|
81
|
+
const normalized = text(value || defaultDevDeliveryStateRef(branch)).replace(
|
|
82
|
+
/^refs\/heads\//,
|
|
83
|
+
"",
|
|
84
|
+
);
|
|
85
|
+
if (
|
|
86
|
+
!normalized.startsWith(STATE_REF_PREFIX) ||
|
|
87
|
+
normalized.includes("..") ||
|
|
88
|
+
normalized.endsWith("/")
|
|
89
|
+
) {
|
|
61
90
|
throw new Error(`state ref must remain under ${STATE_REF_PREFIX}`);
|
|
62
91
|
}
|
|
63
92
|
return normalized;
|
|
64
93
|
}
|
|
65
94
|
|
|
66
|
-
function
|
|
67
|
-
|
|
95
|
+
function warrantIdentity(queue, options) {
|
|
96
|
+
const active = queue.activeWarrant;
|
|
97
|
+
if (!active) throw new Error("no active Delivery Warrant");
|
|
98
|
+
const fencingToken = exactRoot(options.fencingToken, "fencingToken");
|
|
99
|
+
const generation = positiveInteger(
|
|
100
|
+
options.leaseGeneration,
|
|
101
|
+
"leaseGeneration",
|
|
102
|
+
);
|
|
103
|
+
return { candidateId: active.candidateId, fencingToken, generation };
|
|
68
104
|
}
|
|
69
105
|
|
|
70
|
-
function
|
|
71
|
-
if (
|
|
72
|
-
return
|
|
106
|
+
function jsonFile(file, label) {
|
|
107
|
+
if (!file) throw new Error(`${label} is required`);
|
|
108
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
73
109
|
}
|
|
74
110
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.apiUrl = apiUrl.replace(/\/+$/, "");
|
|
82
|
-
this.fetch = fetchImpl;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async request(method, requestPath, body) {
|
|
86
|
-
const response = await this.fetch(`${this.apiUrl}${requestPath}`, {
|
|
87
|
-
method,
|
|
88
|
-
headers: {
|
|
89
|
-
accept: "application/vnd.github+json",
|
|
90
|
-
authorization: `Bearer ${this.token}`,
|
|
91
|
-
"content-type": "application/json",
|
|
92
|
-
"x-github-api-version": "2022-11-28",
|
|
93
|
-
},
|
|
94
|
-
body: body === undefined ? undefined : JSON.stringify(body),
|
|
95
|
-
});
|
|
96
|
-
const raw = await response.text();
|
|
97
|
-
const data = raw ? JSON.parse(raw) : null;
|
|
98
|
-
if (!response.ok) {
|
|
99
|
-
const error = new Error(data?.message || raw || `${method} ${requestPath} failed`);
|
|
100
|
-
error.status = response.status;
|
|
101
|
-
error.data = data;
|
|
102
|
-
throw error;
|
|
103
|
-
}
|
|
104
|
-
return data;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async read({ stateRef, protectedBase, now }) {
|
|
108
|
-
let ref;
|
|
109
|
-
try {
|
|
110
|
-
ref = await this.request("GET", `/repos/${this.repository.fullName}/git/ref/heads/${encodeRef(stateRef)}`);
|
|
111
|
-
} catch (error) {
|
|
112
|
-
if (error.status !== 404) throw error;
|
|
113
|
-
return {
|
|
114
|
-
exists: false,
|
|
115
|
-
commitSha: "",
|
|
116
|
-
queue: createDevDeliveryQueue({
|
|
117
|
-
repository: this.repository.fullName,
|
|
118
|
-
protectedBase,
|
|
119
|
-
now,
|
|
120
|
-
}),
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
const commitSha = exactSha(ref?.object?.sha, "state ref commit");
|
|
124
|
-
const readback = await this.readCommit(commitSha);
|
|
125
|
-
return { exists: true, ...readback };
|
|
111
|
+
function jsonList(value, label) {
|
|
112
|
+
let parsed;
|
|
113
|
+
try {
|
|
114
|
+
parsed = JSON.parse(value || "[]");
|
|
115
|
+
} catch (cause) {
|
|
116
|
+
throw new Error(`${label} must be a JSON array: ${cause.message}`);
|
|
126
117
|
}
|
|
118
|
+
if (!Array.isArray(parsed)) throw new Error(`${label} must be a JSON array`);
|
|
119
|
+
return parsed;
|
|
120
|
+
}
|
|
127
121
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const queue = JSON.parse(decodeBlob(blob));
|
|
136
|
-
return { commitSha, queue };
|
|
122
|
+
function jsonObject(value, label) {
|
|
123
|
+
if (!value) return null;
|
|
124
|
+
let parsed;
|
|
125
|
+
try {
|
|
126
|
+
parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
127
|
+
} catch (cause) {
|
|
128
|
+
throw new Error(`${label} must be a JSON object: ${cause.message}`);
|
|
137
129
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (queue.stateRoot === expectedStateRoot) throw new Error("state transition did not advance the queue root");
|
|
141
|
-
const blob = await this.request("POST", `/repos/${this.repository.fullName}/git/blobs`, {
|
|
142
|
-
content: `${JSON.stringify(queue, null, 2)}\n`,
|
|
143
|
-
encoding: "utf-8",
|
|
144
|
-
});
|
|
145
|
-
const tree = await this.request("POST", `/repos/${this.repository.fullName}/git/trees`, {
|
|
146
|
-
tree: [{ path: STATE_PATH, mode: "100644", type: "blob", sha: blob.sha }],
|
|
147
|
-
});
|
|
148
|
-
const commit = await this.request("POST", `/repos/${this.repository.fullName}/git/commits`, {
|
|
149
|
-
message: `chore(dev-delivery): advance Warrant queue ${receiptRoot.slice(0, 20)}`,
|
|
150
|
-
tree: tree.sha,
|
|
151
|
-
parents: expectedCommitSha ? [expectedCommitSha] : [],
|
|
152
|
-
});
|
|
153
|
-
if (expectedCommitSha) {
|
|
154
|
-
await this.request("PATCH", `/repos/${this.repository.fullName}/git/refs/heads/${encodeRef(stateRef)}`, { sha: commit.sha, force: false });
|
|
155
|
-
} else {
|
|
156
|
-
await this.request("POST", `/repos/${this.repository.fullName}/git/refs`, {
|
|
157
|
-
ref: `refs/heads/${stateRef}`,
|
|
158
|
-
sha: commit.sha,
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
const readback = await this.readCommit(commit.sha);
|
|
162
|
-
if (readback.commitSha !== commit.sha || readback.queue.stateRoot !== queue.stateRoot) {
|
|
163
|
-
throw new Error("dev delivery state commit readback mismatch after expected-old update");
|
|
164
|
-
}
|
|
165
|
-
return { commitSha: commit.sha, stateRoot: readback.queue.stateRoot };
|
|
130
|
+
if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
131
|
+
throw new Error(`${label} must be a JSON object`);
|
|
166
132
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
function warrantIdentity(queue, options) {
|
|
170
|
-
const active = queue.activeWarrant;
|
|
171
|
-
if (!active) throw new Error("no active Delivery Warrant");
|
|
172
|
-
const fencingToken = exactRoot(options.fencingToken, "fencingToken");
|
|
173
|
-
const generation = positiveInteger(options.leaseGeneration, "leaseGeneration");
|
|
174
|
-
return { candidateId: active.candidateId, fencingToken, generation };
|
|
133
|
+
return parsed;
|
|
175
134
|
}
|
|
176
135
|
|
|
177
136
|
function transitionFor(command, queue, options) {
|
|
178
137
|
if (command === "submit") {
|
|
138
|
+
const nativeCommandContract = options.environmentRoot
|
|
139
|
+
? createNativeCommandContract(options.nativeCommand)
|
|
140
|
+
: null;
|
|
141
|
+
if (
|
|
142
|
+
options.nativeCommandRoot &&
|
|
143
|
+
nativeCommandContract?.commandRoot !==
|
|
144
|
+
exactRoot(options.nativeCommandRoot, "nativeCommandRoot")
|
|
145
|
+
) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
"native command contract root does not match native-command",
|
|
148
|
+
);
|
|
149
|
+
}
|
|
179
150
|
return submitDevDeliveryCandidate(
|
|
180
151
|
queue,
|
|
181
152
|
{
|
|
182
|
-
pullRequestNumber: positiveInteger(
|
|
153
|
+
pullRequestNumber: positiveInteger(
|
|
154
|
+
options.pullRequestNumber,
|
|
155
|
+
"pullRequestNumber",
|
|
156
|
+
),
|
|
183
157
|
sourceHead: exactSha(options.sourceHead, "sourceHead"),
|
|
184
158
|
assignmentRoot: exactRoot(options.assignmentRoot, "assignmentRoot"),
|
|
185
159
|
initiativeRoot: exactRoot(options.initiativeRoot, "initiativeRoot"),
|
|
186
|
-
sourceIdentityRoot: exactRoot(
|
|
160
|
+
sourceIdentityRoot: exactRoot(
|
|
161
|
+
options.sourceIdentityRoot,
|
|
162
|
+
"sourceIdentityRoot",
|
|
163
|
+
),
|
|
187
164
|
sourcePatchRoot: exactRoot(options.sourcePatchRoot, "sourcePatchRoot"),
|
|
188
165
|
sourceProofRoot: exactRoot(options.sourceProofRoot, "sourceProofRoot"),
|
|
189
166
|
planRoot: exactRoot(options.planRoot, "planRoot"),
|
|
190
167
|
closureRoot: exactRoot(options.closureRoot, "closureRoot"),
|
|
191
168
|
dependencyRoot: exactRoot(options.dependencyRoot, "dependencyRoot"),
|
|
192
169
|
toolchainRoot: exactRoot(options.toolchainRoot, "toolchainRoot"),
|
|
170
|
+
...(options.environmentRoot
|
|
171
|
+
? {
|
|
172
|
+
environmentRoot: exactRoot(
|
|
173
|
+
options.environmentRoot,
|
|
174
|
+
"environmentRoot",
|
|
175
|
+
),
|
|
176
|
+
}
|
|
177
|
+
: {}),
|
|
178
|
+
...(nativeCommandContract ? { nativeCommandContract } : {}),
|
|
179
|
+
affectedPaths: jsonList(options.affectedPaths, "affected paths"),
|
|
180
|
+
shardEvidenceRoots: jsonList(
|
|
181
|
+
options.shardEvidenceRoots,
|
|
182
|
+
"shard evidence roots",
|
|
183
|
+
),
|
|
184
|
+
sourceWorkflowRunId: options.sourceWorkflowRunId
|
|
185
|
+
? positiveInteger(options.sourceWorkflowRunId, "sourceWorkflowRunId")
|
|
186
|
+
: 0,
|
|
193
187
|
deliveryClass: options.deliveryClass,
|
|
194
188
|
priority: options.priority || "ordinary",
|
|
189
|
+
...(options.releaseBlockerPriority
|
|
190
|
+
? {
|
|
191
|
+
releaseBlockerPriority: jsonObject(
|
|
192
|
+
options.releaseBlockerPriority,
|
|
193
|
+
"release blocker priority",
|
|
194
|
+
),
|
|
195
|
+
}
|
|
196
|
+
: {}),
|
|
195
197
|
},
|
|
196
198
|
{ now: options.now },
|
|
197
199
|
);
|
|
@@ -208,7 +210,25 @@ function transitionFor(command, queue, options) {
|
|
|
208
210
|
leaseSeconds: options.leaseSeconds,
|
|
209
211
|
});
|
|
210
212
|
}
|
|
211
|
-
if (command === "
|
|
213
|
+
if (command === "qualify") {
|
|
214
|
+
return qualifyDevDeliveryWarrant(queue, warrantIdentity(queue, options), {
|
|
215
|
+
nativeProof: jsonFile(options.nativeProofPath, "native proof"),
|
|
216
|
+
reuseDecision: jsonFile(
|
|
217
|
+
options.nativeReuseDecisionPath,
|
|
218
|
+
"native reuse decision",
|
|
219
|
+
),
|
|
220
|
+
current: {
|
|
221
|
+
currentBase: options.currentBase,
|
|
222
|
+
graphKnown: options.graphKnown,
|
|
223
|
+
attributionComplete: options.attributionComplete,
|
|
224
|
+
changedPaths: jsonList(options.changedPaths, "changed paths"),
|
|
225
|
+
renames: jsonList(options.renames, "renames"),
|
|
226
|
+
},
|
|
227
|
+
now: options.now,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
if (command === "recover")
|
|
231
|
+
return recoverExpiredDevDeliveryWarrant(queue, { now: options.now });
|
|
212
232
|
if (command === "close") {
|
|
213
233
|
return closeDevDeliveryWarrant(queue, warrantIdentity(queue, options), {
|
|
214
234
|
outcome: options.outcome,
|
|
@@ -221,14 +241,24 @@ function transitionFor(command, queue, options) {
|
|
|
221
241
|
return settleDevDeliveryTerminalEvent(
|
|
222
242
|
queue,
|
|
223
243
|
{
|
|
224
|
-
pullRequestNumber: positiveInteger(
|
|
225
|
-
|
|
244
|
+
pullRequestNumber: positiveInteger(
|
|
245
|
+
options.pullRequestNumber,
|
|
246
|
+
"pullRequestNumber",
|
|
247
|
+
),
|
|
248
|
+
sourceHead: exactSha(
|
|
249
|
+
options.expectedSourceHead || options.sourceHead,
|
|
250
|
+
"sourceHead",
|
|
251
|
+
),
|
|
226
252
|
fencingToken: options.fencingToken,
|
|
227
253
|
leaseGeneration: options.leaseGeneration,
|
|
228
254
|
outcome: options.outcome,
|
|
229
255
|
eventAction: options.eventAction,
|
|
230
256
|
evidenceRoot: options.evidenceRoot,
|
|
231
257
|
reason: options.reason,
|
|
258
|
+
transferRoot: options.transferRoot,
|
|
259
|
+
finalizerBoundaryRoot: options.finalizerBoundaryRoot,
|
|
260
|
+
nativeJobId: options.nativeJobId,
|
|
261
|
+
sealJobId: options.sealJobId,
|
|
232
262
|
},
|
|
233
263
|
{ now: options.now },
|
|
234
264
|
);
|
|
@@ -238,9 +268,18 @@ function transitionFor(command, queue, options) {
|
|
|
238
268
|
queue,
|
|
239
269
|
{
|
|
240
270
|
candidateId: exactRoot(options.candidateId, "candidateId"),
|
|
241
|
-
pullRequestNumber: positiveInteger(
|
|
242
|
-
|
|
243
|
-
|
|
271
|
+
pullRequestNumber: positiveInteger(
|
|
272
|
+
options.pullRequestNumber,
|
|
273
|
+
"pullRequestNumber",
|
|
274
|
+
),
|
|
275
|
+
expectedSourceHead: exactSha(
|
|
276
|
+
options.expectedSourceHead,
|
|
277
|
+
"expectedSourceHead",
|
|
278
|
+
),
|
|
279
|
+
observedSourceHead: exactSha(
|
|
280
|
+
options.observedSourceHead,
|
|
281
|
+
"observedSourceHead",
|
|
282
|
+
),
|
|
244
283
|
eventAction: options.eventAction,
|
|
245
284
|
outcome: options.outcome,
|
|
246
285
|
evidenceRoot: exactRoot(options.evidenceRoot, "evidenceRoot"),
|
|
@@ -254,17 +293,29 @@ function transitionFor(command, queue, options) {
|
|
|
254
293
|
|
|
255
294
|
async function observeQueue(loaded, options) {
|
|
256
295
|
const readMode = text(options.readMode || "v3").toLowerCase();
|
|
257
|
-
if (!["v3", "v4"].includes(readMode))
|
|
258
|
-
|
|
296
|
+
if (!["v3", "v4"].includes(readMode))
|
|
297
|
+
throw new Error("readMode must be v3 or v4");
|
|
298
|
+
let observation = observeDevDeliveryQueue(loaded.queue, {
|
|
299
|
+
now: options.now,
|
|
300
|
+
allowLegacyV3Readback: true,
|
|
301
|
+
});
|
|
259
302
|
let readCandidate;
|
|
260
303
|
if (readMode === "v4") {
|
|
261
|
-
const qualification =
|
|
304
|
+
const qualification =
|
|
305
|
+
options.readQualification ||
|
|
306
|
+
JSON.parse(fs.readFileSync(options.readQualificationPath, "utf8"));
|
|
262
307
|
const retain =
|
|
263
308
|
options.retainReadEvidence ||
|
|
264
309
|
(async (evidence) => {
|
|
265
|
-
if (!options.readEvidenceOutput)
|
|
266
|
-
|
|
267
|
-
fs.
|
|
310
|
+
if (!options.readEvidenceOutput)
|
|
311
|
+
throw new Error("readEvidenceOutput is required");
|
|
312
|
+
fs.mkdirSync(path.dirname(options.readEvidenceOutput), {
|
|
313
|
+
recursive: true,
|
|
314
|
+
});
|
|
315
|
+
fs.writeFileSync(
|
|
316
|
+
options.readEvidenceOutput,
|
|
317
|
+
`${JSON.stringify(evidence, null, 2)}\n`,
|
|
318
|
+
);
|
|
268
319
|
return { receiptRoot: evidence.evidenceRoot };
|
|
269
320
|
});
|
|
270
321
|
readCandidate = await runV4DeliveryWarrantReadCandidate(loaded.queue, {
|
|
@@ -305,20 +356,36 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
|
|
|
305
356
|
now: new Date(optionsInput.now || Date.now()).toISOString(),
|
|
306
357
|
execute: bool(optionsInput.execute, false),
|
|
307
358
|
};
|
|
359
|
+
if (
|
|
360
|
+
options.command === "submit" &&
|
|
361
|
+
options.execute &&
|
|
362
|
+
options.deliveryClass !== "non-native-fast"
|
|
363
|
+
) {
|
|
364
|
+
exactRoot(options.environmentRoot, "environmentRoot");
|
|
365
|
+
}
|
|
308
366
|
const store =
|
|
309
367
|
clientInput ||
|
|
310
368
|
new GitHubDevDeliveryStore({
|
|
311
369
|
repository: options.repository,
|
|
312
370
|
token: options.token || process.env.GITHUB_TOKEN,
|
|
313
|
-
apiUrl:
|
|
371
|
+
apiUrl:
|
|
372
|
+
options.apiUrl ||
|
|
373
|
+
process.env.GITHUB_API_URL ||
|
|
374
|
+
"https://api.github.com",
|
|
314
375
|
});
|
|
315
376
|
let loaded = await store.read({
|
|
316
377
|
stateRef: options.stateRef,
|
|
317
378
|
protectedBase: options.branch,
|
|
318
379
|
now: options.now,
|
|
380
|
+
allowLegacyV3Readback: options.command === "observe",
|
|
319
381
|
});
|
|
320
|
-
if (
|
|
321
|
-
|
|
382
|
+
if (
|
|
383
|
+
options.expectedOldStateRoot &&
|
|
384
|
+
loaded.queue.stateRoot !== options.expectedOldStateRoot
|
|
385
|
+
) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
`expected-old state drift: ${loaded.queue.stateRoot} != ${options.expectedOldStateRoot}`,
|
|
388
|
+
);
|
|
322
389
|
}
|
|
323
390
|
if (options.command === "observe") return observeQueue(loaded, options);
|
|
324
391
|
const initialLoaded = loaded;
|
|
@@ -328,7 +395,9 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
|
|
|
328
395
|
let concurrencyRecovery = null;
|
|
329
396
|
if (options.execute && mutates) {
|
|
330
397
|
if (changed.receipt.expectedOldStateRoot !== loaded.queue.stateRoot) {
|
|
331
|
-
throw new Error(
|
|
398
|
+
throw new Error(
|
|
399
|
+
"transition receipt expected-old root does not match the loaded authority",
|
|
400
|
+
);
|
|
332
401
|
}
|
|
333
402
|
try {
|
|
334
403
|
write = await store.write({
|
|
@@ -339,15 +408,21 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
|
|
|
339
408
|
receiptRoot: changed.receiptRoot,
|
|
340
409
|
});
|
|
341
410
|
} catch (error) {
|
|
342
|
-
if (options.command !== "settle" || options.expectedOldStateRoot)
|
|
411
|
+
if (options.command !== "settle" || options.expectedOldStateRoot)
|
|
412
|
+
throw error;
|
|
343
413
|
const latest = await store.read({
|
|
344
414
|
stateRef: options.stateRef,
|
|
345
415
|
protectedBase: options.branch,
|
|
346
416
|
now: options.now,
|
|
347
417
|
});
|
|
348
418
|
const reconciled = transitionFor(options.command, latest.queue, options);
|
|
349
|
-
const reconciledMutates =
|
|
350
|
-
|
|
419
|
+
const reconciledMutates =
|
|
420
|
+
reconciled.queue.stateRoot !== latest.queue.stateRoot;
|
|
421
|
+
if (
|
|
422
|
+
reconciledMutates ||
|
|
423
|
+
reconciled.receipt.action !== "duplicate-terminal-event-noop"
|
|
424
|
+
)
|
|
425
|
+
throw error;
|
|
351
426
|
loaded = latest;
|
|
352
427
|
changed = reconciled;
|
|
353
428
|
mutates = false;
|
|
@@ -381,83 +456,46 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
|
|
|
381
456
|
};
|
|
382
457
|
}
|
|
383
458
|
|
|
384
|
-
function flag(args, name, fallback = "") {
|
|
385
|
-
const index = args.indexOf(`--${name}`);
|
|
386
|
-
return index === -1 ? fallback : args[index + 1] || "";
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
function hasFlag(args, name) {
|
|
390
|
-
return args.includes(`--${name}`);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
export function devDeliveryCliOptions(args = [], environment = process.env) {
|
|
394
|
-
const [command = "", ...rest] = args;
|
|
395
|
-
return {
|
|
396
|
-
command,
|
|
397
|
-
repository: flag(rest, "repository", environment.GITHUB_REPOSITORY),
|
|
398
|
-
branch: flag(rest, "branch", environment.BUILDCHAIN_DEV_DELIVERY_BRANCH || environment.GITHUB_BASE_REF),
|
|
399
|
-
stateRef: flag(rest, "state-ref", environment.BUILDCHAIN_DEV_DELIVERY_STATE_REF),
|
|
400
|
-
expectedOldStateRoot: flag(rest, "expected-old", environment.BUILDCHAIN_DEV_DELIVERY_EXPECTED_OLD),
|
|
401
|
-
pullRequestNumber: flag(rest, "pull-request", environment.BUILDCHAIN_DEV_DELIVERY_PR_NUMBER),
|
|
402
|
-
candidateId: flag(rest, "candidate-id", environment.BUILDCHAIN_DEV_DELIVERY_CANDIDATE_ID),
|
|
403
|
-
sourceHead: flag(rest, "source-head", environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_HEAD),
|
|
404
|
-
expectedSourceHead: flag(rest, "expected-source-head", environment.BUILDCHAIN_DEV_DELIVERY_EXPECTED_SOURCE_HEAD),
|
|
405
|
-
observedSourceHead: flag(rest, "observed-source-head", environment.BUILDCHAIN_DEV_DELIVERY_OBSERVED_SOURCE_HEAD),
|
|
406
|
-
assignmentRoot: flag(rest, "assignment-root", environment.BUILDCHAIN_DEV_DELIVERY_ASSIGNMENT_ROOT),
|
|
407
|
-
initiativeRoot: flag(rest, "initiative-root", environment.BUILDCHAIN_DEV_DELIVERY_INITIATIVE_ROOT),
|
|
408
|
-
sourceIdentityRoot: flag(rest, "source-identity-root", environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_IDENTITY_ROOT),
|
|
409
|
-
sourcePatchRoot: flag(rest, "source-patch-root", environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_PATCH_ROOT),
|
|
410
|
-
sourceProofRoot: flag(rest, "source-proof-root", environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_PROOF_ROOT),
|
|
411
|
-
planRoot: flag(rest, "plan-root", environment.BUILDCHAIN_DEV_DELIVERY_PLAN_ROOT),
|
|
412
|
-
closureRoot: flag(rest, "closure-root", environment.BUILDCHAIN_DEV_DELIVERY_CLOSURE_ROOT),
|
|
413
|
-
dependencyRoot: flag(rest, "dependency-root", environment.BUILDCHAIN_DEV_DELIVERY_DEPENDENCY_ROOT),
|
|
414
|
-
toolchainRoot: flag(rest, "toolchain-root", environment.BUILDCHAIN_DEV_DELIVERY_TOOLCHAIN_ROOT),
|
|
415
|
-
deliveryClass: flag(rest, "delivery-class", environment.BUILDCHAIN_DEV_DELIVERY_CLASS),
|
|
416
|
-
priority: flag(rest, "priority", environment.BUILDCHAIN_DEV_DELIVERY_PRIORITY || "ordinary"),
|
|
417
|
-
fencingToken: flag(rest, "fencing-token", environment.BUILDCHAIN_DEV_DELIVERY_FENCING_TOKEN),
|
|
418
|
-
leaseGeneration: flag(rest, "lease-generation", environment.BUILDCHAIN_DEV_DELIVERY_LEASE_GENERATION),
|
|
419
|
-
leaseSeconds: flag(rest, "lease-seconds", environment.BUILDCHAIN_DEV_DELIVERY_LEASE_SECONDS),
|
|
420
|
-
outcome: flag(rest, "outcome", environment.BUILDCHAIN_DEV_DELIVERY_OUTCOME),
|
|
421
|
-
eventAction: flag(rest, "event-action", environment.BUILDCHAIN_DEV_DELIVERY_EVENT_ACTION),
|
|
422
|
-
evidenceRoot: flag(rest, "evidence-root", environment.BUILDCHAIN_DEV_DELIVERY_EVIDENCE_ROOT),
|
|
423
|
-
reason: flag(rest, "reason", environment.BUILDCHAIN_DEV_DELIVERY_REASON),
|
|
424
|
-
readMode: flag(rest, "read-mode", environment.BUILDCHAIN_V4_WARRANT_READ_MODE || "v3"),
|
|
425
|
-
readQualificationPath: flag(rest, "read-qualification", environment.BUILDCHAIN_V4_WARRANT_READ_QUALIFICATION),
|
|
426
|
-
readQualificationRoot: flag(rest, "read-qualification-root", environment.BUILDCHAIN_V4_WARRANT_READ_QUALIFICATION_ROOT),
|
|
427
|
-
readTypescriptRevision: flag(rest, "read-typescript-revision", environment.BUILDCHAIN_V4_WARRANT_TYPESCRIPT_REVISION),
|
|
428
|
-
readRustRevision: flag(rest, "read-rust-revision", environment.BUILDCHAIN_V4_WARRANT_RUST_REVISION),
|
|
429
|
-
readValidatorVersion: flag(rest, "read-validator-version", environment.BUILDCHAIN_V4_WARRANT_VALIDATOR_VERSION),
|
|
430
|
-
readTimeoutMs: flag(rest, "read-timeout-ms", environment.BUILDCHAIN_V4_WARRANT_READ_TIMEOUT_MS || "5000"),
|
|
431
|
-
readEvidenceOutput: flag(rest, "read-evidence-output", environment.BUILDCHAIN_V4_WARRANT_READ_EVIDENCE || ".buildchain/dev-delivery/v4-read-evidence.json"),
|
|
432
|
-
now: flag(rest, "now", environment.BUILDCHAIN_DEV_DELIVERY_NOW),
|
|
433
|
-
outputPath: flag(rest, "output", environment.BUILDCHAIN_DEV_DELIVERY_OUTPUT || ".buildchain/dev-delivery/result.json"),
|
|
434
|
-
execute: hasFlag(rest, "execute"),
|
|
435
|
-
json: hasFlag(rest, "json"),
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
|
|
439
459
|
function usage() {
|
|
440
|
-
return "Usage:\n buildchain dev warrant <submit|select|heartbeat|recover|close|settle|cancel-queued|observe> --repository owner/repo --branch dev/vN/vN.M [--execute] [--output FILE] [--json]\n\nRead candidate:\n observe --read-mode v4 --read-qualification FILE --read-qualification-root sha256:... --read-typescript-revision SHA --read-rust-revision SHA --read-validator-version TOKEN [--read-evidence-output FILE]\n";
|
|
460
|
+
return "Usage:\n buildchain dev warrant <submit|select|heartbeat|qualify|recover|close|settle|cancel-queued|observe> --repository owner/repo --branch dev/vN/vN.M [--execute] [--output FILE] [--json]\n\nRead candidate:\n observe --read-mode v4 --read-qualification FILE --read-qualification-root sha256:... --read-typescript-revision SHA --read-rust-revision SHA --read-validator-version TOKEN [--read-evidence-output FILE]\n";
|
|
441
461
|
}
|
|
442
462
|
|
|
443
463
|
async function main() {
|
|
444
464
|
const args = process.argv.slice(2);
|
|
445
|
-
if (args.length === 0 ||
|
|
465
|
+
if (args.length === 0 || args.includes("--help")) {
|
|
446
466
|
process.stdout.write(usage());
|
|
447
467
|
return;
|
|
448
468
|
}
|
|
449
469
|
const options = devDeliveryCliOptions(args);
|
|
450
|
-
if (
|
|
470
|
+
if (
|
|
471
|
+
![
|
|
472
|
+
"submit",
|
|
473
|
+
"select",
|
|
474
|
+
"heartbeat",
|
|
475
|
+
"qualify",
|
|
476
|
+
"recover",
|
|
477
|
+
"close",
|
|
478
|
+
"settle",
|
|
479
|
+
"cancel-queued",
|
|
480
|
+
"observe",
|
|
481
|
+
].includes(options.command)
|
|
482
|
+
) {
|
|
451
483
|
throw new Error(usage().trim());
|
|
452
484
|
}
|
|
453
485
|
const result = await runDevDeliveryCommand(options);
|
|
454
486
|
fs.mkdirSync(path.dirname(options.outputPath), { recursive: true });
|
|
455
487
|
fs.writeFileSync(options.outputPath, `${JSON.stringify(result, null, 2)}\n`);
|
|
456
|
-
if (options.json)
|
|
488
|
+
if (options.json)
|
|
489
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
457
490
|
else {
|
|
458
|
-
process.stdout.write(
|
|
459
|
-
|
|
460
|
-
|
|
491
|
+
process.stdout.write(
|
|
492
|
+
`Buildchain dev delivery ${options.command}: ${result.receipt?.reason || result.mode}\n`,
|
|
493
|
+
);
|
|
494
|
+
process.stdout.write(
|
|
495
|
+
`State root: ${result.after?.stateRoot || result.observation.stateRoot}\n`,
|
|
496
|
+
);
|
|
497
|
+
if (result.receiptRoot)
|
|
498
|
+
process.stdout.write(`Receipt root: ${result.receiptRoot}\n`);
|
|
461
499
|
process.stdout.write(`Result: ${options.outputPath}\n`);
|
|
462
500
|
}
|
|
463
501
|
}
|
|
@@ -70,7 +70,7 @@ function normalizeOptions(options = {}) {
|
|
|
70
70
|
allowedHeadPrefixes: splitList(options.allowedHeadPrefixes, DEFAULT_ALLOWED_HEAD_PREFIXES),
|
|
71
71
|
requiredChecks: splitList(options.requiredChecks, DEFAULT_REQUIRED_CHECKS),
|
|
72
72
|
queueAdmissionContext: String(options.queueAdmissionContext || "").trim(),
|
|
73
|
-
activeLeaseContext: String(options.activeLeaseContext || "").trim(),
|
|
73
|
+
activeLeaseContext: String(options.activeLeaseContext || (choiceOption(options.warrantMode, VALID_WARRANT_MODES, "off", "delivery Warrant mode") === "required" ? "Queue family lease/exact" : "")).trim(),
|
|
74
74
|
requireApproval: boolOption(options.requireApproval, true),
|
|
75
75
|
sameRepositoryOnly: boolOption(options.sameRepositoryOnly, true),
|
|
76
76
|
maxMerges: intOption(options.maxMerges, 1),
|
|
@@ -52,6 +52,11 @@ function exactWarrantBinding({ result, warrant, candidate, options, pullRequest
|
|
|
52
52
|
requireMatch(Number.isInteger(Number(warrant.generation)) && Number(warrant.generation) >= 1, "delivery-warrant-generation-invalid");
|
|
53
53
|
requireMatch(Number.isFinite(Date.parse(warrant.expiresAt)) && Date.parse(warrant.expiresAt) > Date.now(), "delivery-warrant-expired");
|
|
54
54
|
requireMatch(ROOT_PATTERN.test(String(result.receiptRoot || "")), "delivery-warrant-receipt-root-missing");
|
|
55
|
+
requireMatch((warrant.phase || "qualified") === "qualified", "delivery-warrant-not-qualified");
|
|
56
|
+
if (warrant.phase === "qualified") {
|
|
57
|
+
requireMatch(ROOT_PATTERN.test(String(warrant.nativeProofRoot || "")), "delivery-warrant-native-proof-missing");
|
|
58
|
+
requireMatch(ROOT_PATTERN.test(String(warrant.nativeProofReuseRoot || "")), "delivery-warrant-native-reuse-missing");
|
|
59
|
+
}
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
export function createDevPrAdmissionReceipt({ options, pr = {}, state, reason, readiness, decision = {}, queue = null, warrant = null, labels = [], nextAction }) {
|
|
@@ -120,6 +125,8 @@ export function readDeliveryWarrantResult(options, pullRequest) {
|
|
|
120
125
|
pullRequestNumber: warrant.pullRequestNumber,
|
|
121
126
|
sourceHead: warrant.sourceHead,
|
|
122
127
|
sourceProofRoot: warrant.sourceProofRoot,
|
|
128
|
+
nativeProofRoot: warrant.nativeProofRoot || "",
|
|
129
|
+
nativeProofReuseRoot: warrant.nativeProofReuseRoot || "",
|
|
123
130
|
};
|
|
124
131
|
}
|
|
125
132
|
|
|
@@ -136,6 +143,7 @@ export async function readCurrentDeliveryQueueState(client, repository, targetBr
|
|
|
136
143
|
const { queue } = await store.read({
|
|
137
144
|
stateRef: defaultDevDeliveryStateRef(targetBranch),
|
|
138
145
|
protectedBase: targetBranch,
|
|
146
|
+
allowLegacyV3Readback: true,
|
|
139
147
|
});
|
|
140
148
|
return queue;
|
|
141
149
|
}
|
|
@@ -162,6 +170,9 @@ export async function verifyCurrentDeliveryWarrant(client, options, pullRequest,
|
|
|
162
170
|
requireMatch(Number(active?.pullRequestNumber) === Number(pullRequest.number), "delivery-warrant-current-pr-mismatch");
|
|
163
171
|
requireMatch(String(active?.sourceHead || "").toLowerCase() === options.expectedHeadSha, "delivery-warrant-current-head-mismatch");
|
|
164
172
|
requireMatch(active?.sourceProofRoot === warrant.sourceProofRoot, "delivery-warrant-current-source-proof-mismatch");
|
|
173
|
+
requireMatch((active?.phase || "qualified") === "qualified", "delivery-warrant-current-not-qualified");
|
|
174
|
+
requireMatch((active?.nativeProofRoot || "") === (warrant.nativeProofRoot || ""), "delivery-warrant-current-native-proof-mismatch");
|
|
175
|
+
requireMatch((active?.nativeProofReuseRoot || "") === (warrant.nativeProofReuseRoot || ""), "delivery-warrant-current-native-reuse-mismatch");
|
|
165
176
|
requireMatch(Number.isFinite(Date.parse(active?.expiresAt)) && Date.parse(active.expiresAt) > Date.now(), "delivery-warrant-current-expired");
|
|
166
177
|
requireMatch(["selected", "proving", "waiting", "blocked", "qualified"].includes(candidate?.status), "delivery-warrant-current-candidate-not-selected");
|
|
167
178
|
requireMatch(candidate?.sourceHead === active.sourceHead, "delivery-warrant-current-candidate-head-mismatch");
|