@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
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
devDeliveryClone as clone,
|
|
7
|
+
devDeliveryContentRoot,
|
|
8
|
+
devDeliveryExactRoot as exactRoot,
|
|
9
|
+
devDeliveryExactSha as exactSha,
|
|
10
|
+
devDeliveryPositiveInteger as positiveInteger,
|
|
11
|
+
devDeliveryProtectedBase as protectedBase,
|
|
12
|
+
devDeliveryRepository as repository,
|
|
13
|
+
devDeliveryText as text,
|
|
14
|
+
devDeliveryTimestamp as timestamp,
|
|
15
|
+
} from "./dev-delivery-common.js";
|
|
16
|
+
import { verifyNativeProofReuseDecision } from "./dev-delivery-native-proof.js";
|
|
17
|
+
import { verifyNativeQualificationProof } from "./dev-delivery-native-proof.js";
|
|
18
|
+
import { verifyNativeExecutionFailureOutcome } from "./dev-delivery-execution-failure.js";
|
|
19
|
+
|
|
20
|
+
export const NATIVE_EXECUTION_TRANSFER_SCHEMA =
|
|
21
|
+
"kungfu.buildchain.native-execution-job-transfer/v2";
|
|
22
|
+
export const NATIVE_EXECUTION_TRANSFER_MANIFEST = "execution-transfer.json";
|
|
23
|
+
|
|
24
|
+
const SUCCESS_FILES = [
|
|
25
|
+
"native-job-context.json",
|
|
26
|
+
"native-proof.json",
|
|
27
|
+
"native-reuse-decision.json",
|
|
28
|
+
"runtime-selection.json",
|
|
29
|
+
"two-phase-native-result.json",
|
|
30
|
+
"warrant.json",
|
|
31
|
+
];
|
|
32
|
+
const FAILURE_FILES = [
|
|
33
|
+
"failure-provider-settlement.json",
|
|
34
|
+
"failure.json",
|
|
35
|
+
"native-job-context.json",
|
|
36
|
+
"runtime-selection.json",
|
|
37
|
+
"warrant.json",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const V4_RUNTIME_SELECTOR =
|
|
41
|
+
/^(?:v4|v4-alpha|train\/v4\/v4\.0\/[a-z0-9][a-z0-9._-]*)$/u;
|
|
42
|
+
|
|
43
|
+
function requiredFiles(outcome) {
|
|
44
|
+
if (outcome === "succeeded") return SUCCESS_FILES;
|
|
45
|
+
if (outcome === "failed") return FAILURE_FILES;
|
|
46
|
+
throw new Error("native execution transfer outcome is unsupported");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function relativePath(value) {
|
|
50
|
+
const normalized = String(value || "").replaceAll("\\", "/");
|
|
51
|
+
if (
|
|
52
|
+
!normalized ||
|
|
53
|
+
normalized.startsWith("/") ||
|
|
54
|
+
normalized.endsWith("/") ||
|
|
55
|
+
normalized.split("/").some((part) => !part || part === "." || part === "..")
|
|
56
|
+
) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"transfer file path must be a normalized archive-relative path",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return normalized;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function exactPathSet(paths, label) {
|
|
65
|
+
if (!Array.isArray(paths)) throw new Error(`${label} must be an array`);
|
|
66
|
+
const normalized = paths.map(relativePath);
|
|
67
|
+
if (new Set(normalized).size !== normalized.length) {
|
|
68
|
+
throw new Error(`${label} contains duplicate paths`);
|
|
69
|
+
}
|
|
70
|
+
const caseFolded = normalized.map((entry) =>
|
|
71
|
+
entry.toLocaleLowerCase("en-US"),
|
|
72
|
+
);
|
|
73
|
+
if (new Set(caseFolded).size !== caseFolded.length) {
|
|
74
|
+
throw new Error(`${label} contains case-colliding paths`);
|
|
75
|
+
}
|
|
76
|
+
return normalized.sort();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function fileBytes(directory, relative) {
|
|
80
|
+
const root = path.resolve(directory);
|
|
81
|
+
const file = path.resolve(root, relativePath(relative));
|
|
82
|
+
if (file === root || !file.startsWith(`${root}${path.sep}`)) {
|
|
83
|
+
throw new Error("transfer file escapes the artifact directory");
|
|
84
|
+
}
|
|
85
|
+
const before = fs.lstatSync(file, { bigint: true });
|
|
86
|
+
if (before.isSymbolicLink() || !before.isFile()) {
|
|
87
|
+
throw new Error(`${relative} is not a regular transfer file`);
|
|
88
|
+
}
|
|
89
|
+
const bytes = fs.readFileSync(file);
|
|
90
|
+
const after = fs.lstatSync(file, { bigint: true });
|
|
91
|
+
for (const field of ["dev", "ino", "size", "mtimeNs", "ctimeNs"]) {
|
|
92
|
+
if (before[field] !== after[field]) {
|
|
93
|
+
throw new Error(`transfer file mutated while reading: ${relative}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return bytes;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function sha256Bytes(bytes) {
|
|
100
|
+
return `sha256:${crypto.createHash("sha256").update(bytes).digest("hex")}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function transferFile(directory, relative) {
|
|
104
|
+
return {
|
|
105
|
+
path: relativePath(relative),
|
|
106
|
+
byteRoot: sha256Bytes(fileBytes(directory, relative)),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function canonicalJsonFile(directory, relative, label) {
|
|
111
|
+
const bytes = fileBytes(directory, relative);
|
|
112
|
+
const value = JSON.parse(bytes);
|
|
113
|
+
const canonical = Buffer.from(`${JSON.stringify(value, null, 2)}\n`);
|
|
114
|
+
if (!bytes.equals(canonical)) {
|
|
115
|
+
throw new Error(`${label} is not canonical JSON bytes`);
|
|
116
|
+
}
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function producerBinding(input = {}) {
|
|
121
|
+
const producer = {
|
|
122
|
+
workflowRunId: positiveInteger(input.workflowRunId, "workflow run id"),
|
|
123
|
+
workflowRunAttempt: positiveInteger(
|
|
124
|
+
input.workflowRunAttempt,
|
|
125
|
+
"workflow run attempt",
|
|
126
|
+
),
|
|
127
|
+
job: text(input.job),
|
|
128
|
+
runnerEnvironment: text(input.runnerEnvironment),
|
|
129
|
+
runnerName: text(input.runnerName),
|
|
130
|
+
runnerOs: text(input.runnerOs),
|
|
131
|
+
runnerArch: text(input.runnerArch),
|
|
132
|
+
};
|
|
133
|
+
if (Object.values(producer).some((value) => value === "")) {
|
|
134
|
+
throw new Error("native execution producer binding is incomplete");
|
|
135
|
+
}
|
|
136
|
+
return producer;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function sealerBinding(input = {}) {
|
|
140
|
+
const sealer = {
|
|
141
|
+
job: text(input.job),
|
|
142
|
+
runnerEnvironment: text(input.runnerEnvironment),
|
|
143
|
+
runnerName: text(input.runnerName),
|
|
144
|
+
runnerOs: text(input.runnerOs),
|
|
145
|
+
runnerArch: text(input.runnerArch),
|
|
146
|
+
};
|
|
147
|
+
if (Object.values(sealer).some((value) => value === "")) {
|
|
148
|
+
throw new Error("native execution sealer binding is incomplete");
|
|
149
|
+
}
|
|
150
|
+
if (sealer.runnerEnvironment !== "github-hosted") {
|
|
151
|
+
throw new Error(
|
|
152
|
+
"native execution seal requires a GitHub-hosted fresh runner",
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return sealer;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function runtimeBinding(input = {}) {
|
|
159
|
+
const runtime = {
|
|
160
|
+
repository: repository(input.repository),
|
|
161
|
+
selector: text(input.selector, "runtime selector"),
|
|
162
|
+
resolvedSha: exactSha(input.resolvedSha, "resolved runtime SHA"),
|
|
163
|
+
selectionRoot: exactRoot(input.selectionRoot, "runtime selection root"),
|
|
164
|
+
};
|
|
165
|
+
if (!V4_RUNTIME_SELECTOR.test(runtime.selector)) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
"runtime selector must be v4, v4-alpha, or train/v4/v4.0/<capability>",
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
return runtime;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function nativeJobContext(input = {}) {
|
|
174
|
+
return {
|
|
175
|
+
schema: "kungfu.buildchain.native-job-context/v1",
|
|
176
|
+
workflowRunId: positiveInteger(input.workflowRunId, "workflow run id"),
|
|
177
|
+
workflowRunAttempt: positiveInteger(
|
|
178
|
+
input.workflowRunAttempt,
|
|
179
|
+
"workflow run attempt",
|
|
180
|
+
),
|
|
181
|
+
job: text(input.job),
|
|
182
|
+
runnerEnvironment: text(input.runnerEnvironment),
|
|
183
|
+
runnerName: text(input.runnerName),
|
|
184
|
+
runnerOs: text(input.runnerOs),
|
|
185
|
+
runnerArch: text(input.runnerArch),
|
|
186
|
+
outcome: text(input.outcome),
|
|
187
|
+
evidenceCompletedAt: timestamp(
|
|
188
|
+
input.evidenceCompletedAt,
|
|
189
|
+
"native evidence completion time",
|
|
190
|
+
),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function verifiedRuntimeSelection(directory, expected = {}) {
|
|
195
|
+
const selection = canonicalJsonFile(
|
|
196
|
+
directory,
|
|
197
|
+
"runtime-selection.json",
|
|
198
|
+
"runtime selection",
|
|
199
|
+
);
|
|
200
|
+
const normalized = {
|
|
201
|
+
schema: "kungfu.buildchain.dev-delivery-runtime-selection/v1",
|
|
202
|
+
repository: repository(selection.repository),
|
|
203
|
+
selector: text(selection.selector, "runtime selector"),
|
|
204
|
+
resolvedSha: exactSha(selection.resolvedSha, "resolved runtime SHA"),
|
|
205
|
+
};
|
|
206
|
+
if (JSON.stringify(selection) !== JSON.stringify(normalized)) {
|
|
207
|
+
throw new Error("runtime selection is not exact canonical content");
|
|
208
|
+
}
|
|
209
|
+
if (!V4_RUNTIME_SELECTOR.test(normalized.selector)) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
"runtime selector must be v4, v4-alpha, or train/v4/v4.0/<capability>",
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
const selectionRoot = sha256Bytes(
|
|
215
|
+
Buffer.from(`${JSON.stringify(normalized, null, 2)}\n`),
|
|
216
|
+
);
|
|
217
|
+
const runtime = runtimeBinding({ ...normalized, selectionRoot });
|
|
218
|
+
for (const [field, value] of Object.entries(expected)) {
|
|
219
|
+
if (value !== undefined && runtime[field] !== value) {
|
|
220
|
+
throw new Error(`runtime selection ${field} mismatch`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return runtime;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function warrantBinding(input = {}) {
|
|
227
|
+
return {
|
|
228
|
+
repository: repository(input.repository),
|
|
229
|
+
protectedBase: protectedBase(input.protectedBase),
|
|
230
|
+
stateRoot: exactRoot(input.stateRoot, "Warrant state root"),
|
|
231
|
+
candidateId: exactRoot(input.candidateId, "Warrant candidate id"),
|
|
232
|
+
fencingToken: exactRoot(input.fencingToken, "Warrant fencing token"),
|
|
233
|
+
generation: positiveInteger(input.generation, "Warrant generation"),
|
|
234
|
+
pullRequestNumber: positiveInteger(
|
|
235
|
+
input.pullRequestNumber,
|
|
236
|
+
"Warrant pull request number",
|
|
237
|
+
),
|
|
238
|
+
sourceHead: exactSha(input.sourceHead, "Warrant source head"),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function warrantEvidence(directory, transfer) {
|
|
243
|
+
const result = canonicalJsonFile(directory, "warrant.json", "Warrant result");
|
|
244
|
+
const warrant = result.observation?.activeWarrant || result.warrant;
|
|
245
|
+
if (!warrant) throw new Error("native transfer Warrant evidence is missing");
|
|
246
|
+
const derived = warrantBinding({
|
|
247
|
+
...warrant,
|
|
248
|
+
repository: warrant.repository || result.observation?.repository,
|
|
249
|
+
protectedBase: warrant.protectedBase || result.observation?.protectedBase,
|
|
250
|
+
stateRoot: result.observation?.stateRoot || result.after?.stateRoot,
|
|
251
|
+
});
|
|
252
|
+
if (JSON.stringify(derived) !== JSON.stringify(transfer.warrant)) {
|
|
253
|
+
throw new Error("native transfer Warrant evidence binding mismatch");
|
|
254
|
+
}
|
|
255
|
+
return warrant;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function enumerate(directory, relativeDirectory = "") {
|
|
259
|
+
const absolute = path.join(directory, relativeDirectory);
|
|
260
|
+
const entries = [];
|
|
261
|
+
for (const name of fs.readdirSync(absolute)) {
|
|
262
|
+
const relative = relativeDirectory
|
|
263
|
+
? `${relativeDirectory.replaceAll(path.sep, "/")}/${name}`
|
|
264
|
+
: name;
|
|
265
|
+
const normalized = relativePath(relative);
|
|
266
|
+
const entry = fs.lstatSync(path.join(directory, relative), {
|
|
267
|
+
bigint: true,
|
|
268
|
+
});
|
|
269
|
+
if (entry.isSymbolicLink()) {
|
|
270
|
+
throw new Error(`transfer artifact contains symlink: ${normalized}`);
|
|
271
|
+
}
|
|
272
|
+
if (entry.isDirectory()) {
|
|
273
|
+
entries.push({ path: normalized, type: "directory" });
|
|
274
|
+
entries.push(...enumerate(directory, relative));
|
|
275
|
+
} else if (entry.isFile()) {
|
|
276
|
+
entries.push({
|
|
277
|
+
path: normalized,
|
|
278
|
+
type: "file",
|
|
279
|
+
byteRoot: sha256Bytes(fileBytes(directory, relative)),
|
|
280
|
+
});
|
|
281
|
+
} else {
|
|
282
|
+
throw new Error(
|
|
283
|
+
`transfer artifact contains non-regular entry: ${normalized}`,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return entries.sort((left, right) => left.path.localeCompare(right.path));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function verifyClosedDirectory(directory, declaredFiles) {
|
|
291
|
+
const expected = exactPathSet(
|
|
292
|
+
[NATIVE_EXECUTION_TRANSFER_MANIFEST, ...declaredFiles],
|
|
293
|
+
"transfer artifact membership",
|
|
294
|
+
);
|
|
295
|
+
const first = enumerate(path.resolve(directory));
|
|
296
|
+
exactPathSet(
|
|
297
|
+
first.map((entry) => entry.path),
|
|
298
|
+
"downloaded transfer artifact",
|
|
299
|
+
);
|
|
300
|
+
if (first.some((entry) => entry.type !== "file")) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
"downloaded transfer artifact contains a non-regular entry",
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
if (
|
|
306
|
+
JSON.stringify(first.map((entry) => entry.path)) !==
|
|
307
|
+
JSON.stringify(expected)
|
|
308
|
+
) {
|
|
309
|
+
throw new Error("downloaded transfer artifact membership mismatch");
|
|
310
|
+
}
|
|
311
|
+
return first;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function sameSnapshot(left, right) {
|
|
315
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function stageNativeExecutionTransfer({
|
|
319
|
+
sourceDirectory,
|
|
320
|
+
stagingDirectory,
|
|
321
|
+
files,
|
|
322
|
+
} = {}) {
|
|
323
|
+
const source = path.resolve(sourceDirectory);
|
|
324
|
+
const staging = path.resolve(stagingDirectory);
|
|
325
|
+
const declared = exactPathSet(files, "native transfer files");
|
|
326
|
+
fs.mkdirSync(staging, { recursive: false });
|
|
327
|
+
for (const relative of declared) {
|
|
328
|
+
const destination = path.join(staging, relative);
|
|
329
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
330
|
+
fs.writeFileSync(destination, fileBytes(source, relative), {
|
|
331
|
+
flag: "wx",
|
|
332
|
+
mode: 0o444,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
return staging;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export function createNativeExecutionTransfer({
|
|
339
|
+
directory,
|
|
340
|
+
files,
|
|
341
|
+
outcome,
|
|
342
|
+
producer,
|
|
343
|
+
sealer,
|
|
344
|
+
runtime,
|
|
345
|
+
warrant,
|
|
346
|
+
nativeProofRoot = null,
|
|
347
|
+
nativeReuseDecisionRoot = null,
|
|
348
|
+
completedAt,
|
|
349
|
+
sealedAt,
|
|
350
|
+
} = {}) {
|
|
351
|
+
const declared = exactPathSet(files, "native transfer files");
|
|
352
|
+
if (JSON.stringify(declared) !== JSON.stringify(requiredFiles(outcome))) {
|
|
353
|
+
throw new Error("native execution transfer required file set mismatch");
|
|
354
|
+
}
|
|
355
|
+
const body = {
|
|
356
|
+
schema: NATIVE_EXECUTION_TRANSFER_SCHEMA,
|
|
357
|
+
outcome,
|
|
358
|
+
producer: producerBinding(producer),
|
|
359
|
+
sealer: sealerBinding(sealer),
|
|
360
|
+
runtime: runtimeBinding(runtime),
|
|
361
|
+
warrant: warrantBinding(warrant),
|
|
362
|
+
nativeProofRoot: nativeProofRoot
|
|
363
|
+
? exactRoot(nativeProofRoot, "native proof root")
|
|
364
|
+
: null,
|
|
365
|
+
nativeReuseDecisionRoot: nativeReuseDecisionRoot
|
|
366
|
+
? exactRoot(nativeReuseDecisionRoot, "native reuse decision root")
|
|
367
|
+
: null,
|
|
368
|
+
files: declared.map((file) => transferFile(directory, file)),
|
|
369
|
+
completedAt: timestamp(completedAt, "native transfer completion time"),
|
|
370
|
+
sealedAt: timestamp(sealedAt, "native transfer seal time"),
|
|
371
|
+
};
|
|
372
|
+
if (body.producer.runnerEnvironment !== "github-hosted") {
|
|
373
|
+
throw new Error("native execution requires a GitHub-hosted fresh runner");
|
|
374
|
+
}
|
|
375
|
+
if (
|
|
376
|
+
body.producer.job === body.sealer.job ||
|
|
377
|
+
body.producer.runnerName === body.sealer.runnerName
|
|
378
|
+
) {
|
|
379
|
+
throw new Error("native execution and seal domains are not distinct");
|
|
380
|
+
}
|
|
381
|
+
if (Date.parse(body.completedAt) >= Date.parse(body.sealedAt)) {
|
|
382
|
+
throw new Error("native execution seal must follow native completion");
|
|
383
|
+
}
|
|
384
|
+
return { ...body, transferRoot: devDeliveryContentRoot(body) };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function verifySuccessOutcome(transfer, directory, warrant) {
|
|
388
|
+
exactRoot(transfer.nativeProofRoot, "native proof root");
|
|
389
|
+
exactRoot(transfer.nativeReuseDecisionRoot, "native reuse decision root");
|
|
390
|
+
const result = canonicalJsonFile(
|
|
391
|
+
directory,
|
|
392
|
+
"two-phase-native-result.json",
|
|
393
|
+
"two-phase native result",
|
|
394
|
+
);
|
|
395
|
+
const proof = canonicalJsonFile(
|
|
396
|
+
directory,
|
|
397
|
+
"native-proof.json",
|
|
398
|
+
"native proof",
|
|
399
|
+
);
|
|
400
|
+
const decision = canonicalJsonFile(
|
|
401
|
+
directory,
|
|
402
|
+
"native-reuse-decision.json",
|
|
403
|
+
"native proof reuse decision",
|
|
404
|
+
);
|
|
405
|
+
const proofVerification = verifyNativeQualificationProof(proof);
|
|
406
|
+
const decisionBody = clone(decision);
|
|
407
|
+
const decisionRoot = exactRoot(
|
|
408
|
+
decisionBody.decisionRoot,
|
|
409
|
+
"native proof reuse decision root",
|
|
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
|
+
});
|
|
431
|
+
if (
|
|
432
|
+
!proofVerification.ok ||
|
|
433
|
+
!reuseVerification.ok ||
|
|
434
|
+
result.nativeProofRoot !== transfer.nativeProofRoot ||
|
|
435
|
+
proof.proofRoot !== transfer.nativeProofRoot ||
|
|
436
|
+
result.nativeReuseDecisionRoot !== transfer.nativeReuseDecisionRoot ||
|
|
437
|
+
decisionRoot !== transfer.nativeReuseDecisionRoot ||
|
|
438
|
+
devDeliveryContentRoot(decisionBody) !== decisionRoot ||
|
|
439
|
+
decision.proofRoot !== proof.proofRoot ||
|
|
440
|
+
decision.reusable !== true ||
|
|
441
|
+
proof.repository !== transfer.warrant.repository ||
|
|
442
|
+
proof.protectedBase !== transfer.warrant.protectedBase ||
|
|
443
|
+
proof.sourceHead !== transfer.warrant.sourceHead ||
|
|
444
|
+
proof.sourceIdentityRoot !== warrant.sourceIdentityRoot ||
|
|
445
|
+
proof.sourcePatchRoot !== warrant.sourcePatchRoot ||
|
|
446
|
+
proof.planRoot !== warrant.planRoot ||
|
|
447
|
+
proof.closureRoot !== warrant.closureRoot ||
|
|
448
|
+
proof.dependencyRoot !== warrant.dependencyRoot ||
|
|
449
|
+
proof.toolchainRoot !== warrant.toolchainRoot ||
|
|
450
|
+
proof.environmentRoot !== warrant.environmentRoot ||
|
|
451
|
+
proof.nativeCommandRoot !== warrant.nativeCommandContract?.commandRoot ||
|
|
452
|
+
JSON.stringify(proof.affectedPaths) !==
|
|
453
|
+
JSON.stringify(warrant.affectedPaths) ||
|
|
454
|
+
JSON.stringify(proof.shardEvidenceRoots) !==
|
|
455
|
+
JSON.stringify(
|
|
456
|
+
[
|
|
457
|
+
...(warrant.shardEvidenceRoots || []),
|
|
458
|
+
proof.nativeExecutionReceiptRoot,
|
|
459
|
+
].sort(),
|
|
460
|
+
)
|
|
461
|
+
) {
|
|
462
|
+
throw new Error(
|
|
463
|
+
"native execution transfer proof or reuse semantics mismatch",
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
return { failure: null, failureSettlement: null };
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export function verifyNativeExecutionTransfer(
|
|
470
|
+
transferInput,
|
|
471
|
+
{ directory, expected = {} } = {},
|
|
472
|
+
) {
|
|
473
|
+
const diskManifest = canonicalJsonFile(
|
|
474
|
+
directory,
|
|
475
|
+
NATIVE_EXECUTION_TRANSFER_MANIFEST,
|
|
476
|
+
"native execution transfer manifest",
|
|
477
|
+
);
|
|
478
|
+
if (JSON.stringify(diskManifest) !== JSON.stringify(transferInput)) {
|
|
479
|
+
throw new Error(
|
|
480
|
+
"downloaded transfer manifest does not match verified content",
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
const transfer = clone(transferInput || {});
|
|
484
|
+
if (transfer.schema !== NATIVE_EXECUTION_TRANSFER_SCHEMA) {
|
|
485
|
+
throw new Error("native execution transfer schema is unsupported");
|
|
486
|
+
}
|
|
487
|
+
const transferRoot = exactRoot(
|
|
488
|
+
transfer.transferRoot,
|
|
489
|
+
"execution transfer root",
|
|
490
|
+
);
|
|
491
|
+
delete transfer.transferRoot;
|
|
492
|
+
if (devDeliveryContentRoot(transfer) !== transferRoot) {
|
|
493
|
+
throw new Error("native execution transfer root drift");
|
|
494
|
+
}
|
|
495
|
+
const declared = exactPathSet(
|
|
496
|
+
(transfer.files || []).map((entry) => entry?.path),
|
|
497
|
+
"native execution transfer manifest",
|
|
498
|
+
);
|
|
499
|
+
if (
|
|
500
|
+
JSON.stringify(declared) !== JSON.stringify(requiredFiles(transfer.outcome))
|
|
501
|
+
) {
|
|
502
|
+
throw new Error("native execution transfer required file set mismatch");
|
|
503
|
+
}
|
|
504
|
+
if (transfer.files.length !== declared.length) {
|
|
505
|
+
throw new Error(
|
|
506
|
+
"native execution transfer contains duplicate file records",
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
if (
|
|
510
|
+
JSON.stringify(transfer.producer) !==
|
|
511
|
+
JSON.stringify(producerBinding(transfer.producer)) ||
|
|
512
|
+
JSON.stringify(transfer.sealer) !==
|
|
513
|
+
JSON.stringify(sealerBinding(transfer.sealer)) ||
|
|
514
|
+
JSON.stringify(transfer.runtime) !==
|
|
515
|
+
JSON.stringify(runtimeBinding(transfer.runtime)) ||
|
|
516
|
+
JSON.stringify(transfer.warrant) !==
|
|
517
|
+
JSON.stringify(warrantBinding(transfer.warrant))
|
|
518
|
+
) {
|
|
519
|
+
throw new Error("native execution transfer binding is not normalized");
|
|
520
|
+
}
|
|
521
|
+
timestamp(transfer.completedAt, "native transfer completion time");
|
|
522
|
+
timestamp(transfer.sealedAt, "native transfer seal time");
|
|
523
|
+
if (
|
|
524
|
+
transfer.producer.job === transfer.sealer.job ||
|
|
525
|
+
transfer.producer.runnerName === transfer.sealer.runnerName ||
|
|
526
|
+
Date.parse(transfer.completedAt) >= Date.parse(transfer.sealedAt)
|
|
527
|
+
) {
|
|
528
|
+
throw new Error("native execution and seal isolation is missing");
|
|
529
|
+
}
|
|
530
|
+
const first = verifyClosedDirectory(directory, declared);
|
|
531
|
+
for (const [field, expectedValue] of Object.entries(expected)) {
|
|
532
|
+
const actual = field.startsWith("producer.")
|
|
533
|
+
? transfer.producer?.[field.slice(9)]
|
|
534
|
+
: field.startsWith("runtime.")
|
|
535
|
+
? transfer.runtime?.[field.slice(8)]
|
|
536
|
+
: field.startsWith("warrant.")
|
|
537
|
+
? transfer.warrant?.[field.slice(8)]
|
|
538
|
+
: transfer[field];
|
|
539
|
+
if (expectedValue !== undefined && actual !== expectedValue) {
|
|
540
|
+
throw new Error(`native execution transfer ${field} mismatch`);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (transfer.producer?.runnerEnvironment !== "github-hosted") {
|
|
544
|
+
throw new Error("native execution runner is not GitHub-hosted");
|
|
545
|
+
}
|
|
546
|
+
const byPath = new Map(first.map((entry) => [entry.path, entry]));
|
|
547
|
+
for (const entry of transfer.files) {
|
|
548
|
+
exactRoot(entry.byteRoot, `${entry.path} byte root`);
|
|
549
|
+
if (byPath.get(entry.path)?.byteRoot !== entry.byteRoot) {
|
|
550
|
+
throw new Error(`native execution transfer byte drift: ${entry.path}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const runtime = verifiedRuntimeSelection(directory, {
|
|
554
|
+
repository: transfer.runtime.repository,
|
|
555
|
+
selector: transfer.runtime.selector,
|
|
556
|
+
resolvedSha: transfer.runtime.resolvedSha,
|
|
557
|
+
selectionRoot: transfer.runtime.selectionRoot,
|
|
558
|
+
});
|
|
559
|
+
const warrant = warrantEvidence(directory, transfer);
|
|
560
|
+
const contextInput = canonicalJsonFile(
|
|
561
|
+
directory,
|
|
562
|
+
"native-job-context.json",
|
|
563
|
+
"native job context",
|
|
564
|
+
);
|
|
565
|
+
const context = nativeJobContext(contextInput);
|
|
566
|
+
if (
|
|
567
|
+
JSON.stringify(context) !== JSON.stringify(contextInput) ||
|
|
568
|
+
context.workflowRunId !== transfer.producer.workflowRunId ||
|
|
569
|
+
context.workflowRunAttempt !== transfer.producer.workflowRunAttempt ||
|
|
570
|
+
context.job !== transfer.producer.job ||
|
|
571
|
+
context.runnerEnvironment !== transfer.producer.runnerEnvironment ||
|
|
572
|
+
context.runnerName !== transfer.producer.runnerName ||
|
|
573
|
+
context.runnerOs !== transfer.producer.runnerOs ||
|
|
574
|
+
context.runnerArch !== transfer.producer.runnerArch ||
|
|
575
|
+
context.outcome !== transfer.outcome ||
|
|
576
|
+
context.evidenceCompletedAt !== transfer.completedAt
|
|
577
|
+
) {
|
|
578
|
+
throw new Error("native job context does not match execution transfer");
|
|
579
|
+
}
|
|
580
|
+
const outcome =
|
|
581
|
+
transfer.outcome === "succeeded"
|
|
582
|
+
? verifySuccessOutcome(transfer, directory, warrant)
|
|
583
|
+
: verifyNativeExecutionFailureOutcome(transfer, {
|
|
584
|
+
readCanonical: (relative, label) =>
|
|
585
|
+
canonicalJsonFile(directory, relative, label),
|
|
586
|
+
});
|
|
587
|
+
const second = verifyClosedDirectory(directory, declared);
|
|
588
|
+
if (!sameSnapshot(first, second)) {
|
|
589
|
+
throw new Error("native execution transfer mutated during verification");
|
|
590
|
+
}
|
|
591
|
+
return { ...transfer, runtime, transferRoot, ...outcome };
|
|
592
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
devDeliveryContentRoot,
|
|
3
|
+
devDeliveryExactSha as exactSha,
|
|
4
|
+
devDeliveryTimestamp as timestamp,
|
|
5
|
+
} from "./dev-delivery-common.js";
|
|
6
|
+
import {
|
|
7
|
+
DEV_DELIVERY_MERGE_GROUP_ADMISSION_SCHEMA,
|
|
8
|
+
transition,
|
|
9
|
+
} from "./dev-delivery-authority-state.js";
|
|
10
|
+
import { schedulerWake } from "./dev-delivery-authority-qualification.js";
|
|
11
|
+
import { assertDevDeliveryLandingWarrant } from "./dev-delivery-authority-settlement.js";
|
|
12
|
+
import { normalizeDevDeliveryProviderAttempt } from "./dev-delivery-provider-attempt.js";
|
|
13
|
+
|
|
14
|
+
// This transition accepts an already verified provider attempt and is kept
|
|
15
|
+
// outside the package export graph. Production callers must enter through the
|
|
16
|
+
// live GitHub readback adapter in dev-delivery-authority-landing.js.
|
|
17
|
+
export function admitDevDeliveryMergeGroupWithVerifiedProviderAttempt(
|
|
18
|
+
stateInput,
|
|
19
|
+
authorityInput,
|
|
20
|
+
{ mergeGroupHead, providerAttempt, now = new Date().toISOString() } = {},
|
|
21
|
+
) {
|
|
22
|
+
const currentTime = timestamp(now, "now");
|
|
23
|
+
const exactMergeGroupHead = exactSha(mergeGroupHead, "mergeGroupHead");
|
|
24
|
+
const transaction = transition(
|
|
25
|
+
stateInput,
|
|
26
|
+
(state, before) => {
|
|
27
|
+
const warrant = assertDevDeliveryLandingWarrant(
|
|
28
|
+
before,
|
|
29
|
+
authorityInput,
|
|
30
|
+
currentTime,
|
|
31
|
+
);
|
|
32
|
+
const candidate = state.candidates.find(
|
|
33
|
+
(entry) => entry.candidateId === warrant.candidateId,
|
|
34
|
+
);
|
|
35
|
+
const exactProviderAttempt = normalizeDevDeliveryProviderAttempt(
|
|
36
|
+
providerAttempt,
|
|
37
|
+
{
|
|
38
|
+
repository: before.repository,
|
|
39
|
+
sourceHead: candidate.sourceHead,
|
|
40
|
+
mergeGroupHead: exactMergeGroupHead,
|
|
41
|
+
protectedBase: before.protectedBase,
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
if (warrant.mergeGroupHead) {
|
|
45
|
+
if (warrant.mergeGroupHead !== exactMergeGroupHead) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
"Landing Warrant already admitted a different merge-group head",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
if (
|
|
51
|
+
JSON.stringify(warrant.providerAttempt) !==
|
|
52
|
+
JSON.stringify(exactProviderAttempt)
|
|
53
|
+
) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"Landing Warrant already admitted a different provider attempt",
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
admission: {
|
|
60
|
+
schema: DEV_DELIVERY_MERGE_GROUP_ADMISSION_SCHEMA,
|
|
61
|
+
admitted: true,
|
|
62
|
+
authority: "exclusive-landing-warrant",
|
|
63
|
+
repository: before.repository,
|
|
64
|
+
protectedBase: before.protectedBase,
|
|
65
|
+
candidateId: candidate.candidateId,
|
|
66
|
+
pullRequestNumber: candidate.pullRequestNumber,
|
|
67
|
+
sourceHead: candidate.sourceHead,
|
|
68
|
+
mergeGroupHead: warrant.mergeGroupHead,
|
|
69
|
+
landingWarrantToken: warrant.token,
|
|
70
|
+
landingWarrantGeneration: warrant.generation,
|
|
71
|
+
expectedOldStateRoot: warrant.expectedAdmissionStateRoot,
|
|
72
|
+
admittedAt: warrant.admittedAt,
|
|
73
|
+
providerAttempt: warrant.providerAttempt,
|
|
74
|
+
},
|
|
75
|
+
admissionRoot: warrant.mergeGroupAdmissionRoot,
|
|
76
|
+
action: "duplicate-merge-group-admission-noop",
|
|
77
|
+
mutated: false,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const admission = {
|
|
81
|
+
schema: DEV_DELIVERY_MERGE_GROUP_ADMISSION_SCHEMA,
|
|
82
|
+
admitted: true,
|
|
83
|
+
authority: "exclusive-landing-warrant",
|
|
84
|
+
repository: before.repository,
|
|
85
|
+
protectedBase: before.protectedBase,
|
|
86
|
+
candidateId: candidate.candidateId,
|
|
87
|
+
pullRequestNumber: candidate.pullRequestNumber,
|
|
88
|
+
sourceHead: candidate.sourceHead,
|
|
89
|
+
mergeGroupHead: exactMergeGroupHead,
|
|
90
|
+
landingWarrantToken: warrant.token,
|
|
91
|
+
landingWarrantGeneration: warrant.generation,
|
|
92
|
+
expectedOldStateRoot: before.stateRoot,
|
|
93
|
+
admittedAt: currentTime,
|
|
94
|
+
providerAttempt: exactProviderAttempt,
|
|
95
|
+
};
|
|
96
|
+
const admissionRoot = devDeliveryContentRoot(admission);
|
|
97
|
+
state.landingWarrant.mergeGroupHead = exactMergeGroupHead;
|
|
98
|
+
state.landingWarrant.mergeGroupAdmissionRoot = admissionRoot;
|
|
99
|
+
state.landingWarrant.expectedAdmissionStateRoot = before.stateRoot;
|
|
100
|
+
state.landingWarrant.admittedAt = currentTime;
|
|
101
|
+
state.landingWarrant.providerAttempt = exactProviderAttempt;
|
|
102
|
+
return {
|
|
103
|
+
admission,
|
|
104
|
+
admissionRoot,
|
|
105
|
+
action: "merge-group-head-admitted",
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
currentTime,
|
|
109
|
+
);
|
|
110
|
+
const result = transaction.result;
|
|
111
|
+
return {
|
|
112
|
+
state: transaction.after,
|
|
113
|
+
receipt: result.admission,
|
|
114
|
+
receiptRoot: result.admissionRoot,
|
|
115
|
+
admission: result.admission,
|
|
116
|
+
admissionRoot: result.admissionRoot,
|
|
117
|
+
wake: schedulerWake(transaction.after),
|
|
118
|
+
};
|
|
119
|
+
}
|