@kungfu-tech/buildchain 4.0.2-alpha.4 → 4.0.2-alpha.40
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/AGENTS.md +10 -1
- package/CONTRIBUTING.md +24 -0
- package/README.md +2 -2
- package/actions/promote-buildchain-ref/README.md +1 -1
- package/actions/v4-release-candidate-promote/README.md +28 -0
- package/architecture/agent-change-map.md +66 -7
- package/architecture/ci-lane-change-budget.json +366 -0
- package/architecture/internal-capabilities.json +52 -8
- package/architecture/maintainability-debt.json +148 -92
- package/architecture/maintainability-policy.json +34 -30
- package/architecture/release-tail-contract-inventory.json +6 -6
- package/architecture/v4-architecture-constitution.md +23 -20
- package/architecture/v4-cross-platform-adopter-qualification.json +1 -1
- package/architecture/v4-delivery-authority-parity.json +2 -2
- package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.json +1 -1
- package/architecture/v4-floating-consumer-policy.json +2 -2
- package/architecture/v4-next-development-parity.json +5 -5
- package/architecture/v4-partial-mutation-recovery-qualification.json +4 -1
- package/architecture/v4-platform-stage-checkpoints.json +1 -1
- package/architecture/v4-provider-operation-journal-contract.json +5 -2
- package/architecture/v4-publication-facade-patches.json +53 -0
- package/architecture/v4-release-activation-shadow-domain.json +5 -2
- package/architecture/v4-release-topology.json +143 -35
- package/architecture/v4-rust-wasm-production-authority.json +58 -0
- package/architecture/v4-stable-publication-fence.json +5 -2
- package/architecture/v4-stage-capsule-qualification.json +2 -2
- package/architecture/v4-tail-reseal-parity.json +8 -6
- package/architecture/v4-universal-workflow-bootstrap.json +171 -0
- package/architecture/v4-universal-workflow-fault-campaign.json +72 -0
- package/architecture/v4-universal-workflow-train-admission.json +40 -0
- package/architecture/workflow-taxonomy.json +1451 -0
- package/bin/buildchain.mjs +7 -7
- package/contracts/buildchain-v2-residuals-v1.json +57 -48
- package/contracts/dev-delivery-authority-v2.schema.json +16 -2
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/contracts/v4-canonical-contracts-v1.schema.json +1 -0
- package/dist/site/buildchain-contract.json +93 -48
- package/dist/site/buildchain-site.json +222 -69
- package/dist/site/capability-registry.json +4 -4
- package/dist/site/controller-registry.json +50 -13
- package/dist/site/kfd-claims.json +2569 -982
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +13 -13
- package/dist/site/node-api-registry.json +88 -83
- package/dist/site/page-registry.json +204 -51
- package/dist/site/public-surface-audit.json +3207 -1219
- package/dist/site/publication-authority-registry.json +1124 -362
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +16 -2
- package/dist/site/site-manifest.json +17 -17
- package/dist/site/workflow-registry.json +5063 -1659
- package/docs/MAP.md +1 -0
- package/docs/auditable-demo.md +1 -1
- package/docs/cli.md +3 -3
- package/docs/dev-delivery-warrant.md +1 -1
- package/docs/engineering-housekeeper.md +3 -3
- package/docs/kfd-support.md +26 -2
- package/docs/next-development-transition.md +2 -2
- package/docs/node-api-reference.md +129 -129
- package/docs/publication-authority.md +2 -2
- package/docs/release-candidate.md +3 -3
- package/docs/release-flow.md +67 -19
- package/docs/release-governance.md +8 -8
- package/docs/release-passport.md +27 -2
- package/docs/reusable-build-surface.md +16 -0
- package/docs/v4-adopter-delivery.md +2 -2
- package/docs/v4-production-release.md +71 -8
- package/docs/v4-rust-wasm-production-authority.md +58 -0
- package/docs/v4-stage-capsule.md +2 -2
- package/docs/v4-tail-reseal.md +1 -1
- package/docs/workflow-catalog.md +154 -0
- package/docs/workflow-path-migration.md +109 -0
- package/package.json +10 -6
- package/packages/core/artifact-verification-envelope.js +5 -5
- package/packages/core/buildchain-contract.js +1 -1
- package/packages/core/buildchain-kfd-claims.js +2 -2
- package/packages/core/buildchain-publication-authority.js +42 -26
- package/packages/core/buildchain-v4-domain.wasm +0 -0
- package/packages/core/ci-lane-change-budget.js +14 -1
- package/packages/core/controller-evidence.js +4 -1
- package/packages/core/dev-delivery-candidate-identity.js +40 -6
- package/packages/core/dev-delivery-warrant-state.js +3 -4
- package/packages/core/dev-delivery-warrant.js +12 -2
- package/packages/core/github-governance-authority.js +4 -4
- package/packages/core/kfd-adopter-manifest.js +46 -47
- package/packages/core/next-development-projection.js +2 -2
- package/packages/core/release-line-dry-run.js +1 -1
- package/packages/core/release-passport-contract.js +5 -5
- package/packages/core/release-passport.js +5 -4
- package/packages/core/release-tail-product-capabilities.js +29 -0
- package/packages/core/release-tail-provider-plane.js +68 -1048
- package/packages/core/v4-canonical-contracts.js +14 -75
- package/packages/core/v4-domain-wasm-artifact.js +7 -0
- package/packages/core/v4-domain-wasm.js +216 -0
- package/packages/core/v4-floating-consumer-evidence.js +1 -1
- package/packages/core/v4-floating-consumer-policy.js +1 -1
- package/packages/core/v4-partial-mutation-recovery-qualification.js +9 -386
- package/packages/core/v4-product-publication.js +57 -0
- package/packages/core/v4-protected-publication-source.js +19 -21
- package/packages/core/v4-provider-operation-journal.js +18 -487
- package/packages/core/v4-provider-readback-idempotency.js +12 -97
- package/packages/core/v4-release-activation-shadow.js +13 -500
- package/packages/core/v4-release-invocation.js +28 -389
- package/packages/core/v4-stable-publication-fence.js +13 -342
- package/packages/core/v4-universal-workflow-bootstrap.js +586 -0
- package/scripts/binary-publication-evidence.mjs +135 -0
- package/scripts/build-contract-core.mjs +1 -14
- package/scripts/build-v4-domain-wasm.mjs +189 -0
- package/scripts/check-action-bundles.mjs +12 -2
- package/scripts/check-ci-lane-change-budget.mjs +4 -1
- package/scripts/check-core-mechanism-inventory.mjs +4 -3
- package/scripts/check-inventory.mjs +35 -44
- package/scripts/check-maintainability.mjs +16 -113
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +2 -2
- package/scripts/check-v4-public-dogfood-contract.mjs +22 -9
- package/scripts/check-v4-release-topology.mjs +214 -50
- package/scripts/check-v4-runtime-semantic-closure.mjs +4 -1
- package/scripts/check-v4-universal-workflow-bootstrap.mjs +266 -0
- package/scripts/check-workflow-taxonomy.mjs +6 -0
- package/scripts/copy-v4-domain-wasm.mjs +14 -0
- package/scripts/dev-delivery-authority-command-adapters.mjs +1 -1
- package/scripts/dev-delivery-authority.mjs +8 -8
- package/scripts/dev-delivery-source-proof-reuse.mjs +2 -1
- package/scripts/dev-delivery-warrant-options.mjs +14 -9
- package/scripts/dev-delivery-warrant-store.mjs +73 -7
- package/scripts/dev-delivery-warrant-transition.mjs +109 -0
- package/scripts/dev-delivery-warrant.mjs +76 -99
- package/scripts/dev-pr-auto-merge.mjs +15 -15
- package/scripts/generate-channel-build-workflow.mjs +7 -2
- package/scripts/generate-channel-promotion-workflow.mjs +7 -5
- package/scripts/generate-site-bundle.mjs +11 -11
- package/scripts/generate-v4-universal-workflow-facades.mjs +458 -0
- package/scripts/generate-workflow-taxonomy.mjs +25 -0
- package/scripts/github-output.mjs +31 -0
- package/scripts/maintainability-metrics.mjs +16 -5
- package/scripts/maintainability-public-surface.mjs +115 -0
- package/scripts/next-development-review.mjs +307 -0
- package/scripts/release-asset-client.mjs +91 -0
- package/scripts/release-candidate-resolver.mjs +17 -18
- package/scripts/resume-from-candidate-run.mjs +1 -1
- package/scripts/source-verification-evidence.mjs +344 -0
- package/scripts/stable-candidate-qualification.mjs +1 -1
- package/scripts/universal-facade-maintainability.mjs +82 -0
- package/scripts/v4-product-publication-intent.mjs +121 -0
- package/scripts/v4-publication-settlement.mjs +211 -0
- package/scripts/v4-universal-workflow-backflow.mjs +212 -0
- package/scripts/v4-universal-workflow-engine.mjs +618 -0
- package/scripts/v4-universal-workflow-self-dogfood.mjs +205 -0
- package/scripts/verify-version-state-delta.mjs +173 -0
- package/scripts/workflow-taxonomy.mjs +507 -0
- package/templates/native-dev-delivery.yml +4 -7
- package/templates/universal-buildchain-bootstrap-recovery.yml +282 -0
- package/templates/universal-buildchain-bootstrap.yml +28 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { currentWorkflowPath, projectWorkflowIdentities } from "./workflow-taxonomy.mjs";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
+
const contract = JSON.parse(
|
|
11
|
+
fs.readFileSync(
|
|
12
|
+
path.join(root, "architecture/v4-universal-workflow-bootstrap.json"),
|
|
13
|
+
"utf8",
|
|
14
|
+
),
|
|
15
|
+
);
|
|
16
|
+
const admissionPolicy = JSON.parse(
|
|
17
|
+
fs.readFileSync(
|
|
18
|
+
path.join(root, "architecture/v4-universal-workflow-train-admission.json"),
|
|
19
|
+
"utf8",
|
|
20
|
+
),
|
|
21
|
+
);
|
|
22
|
+
const fileRoot = (relative) =>
|
|
23
|
+
`sha256:${crypto
|
|
24
|
+
.createHash("sha256")
|
|
25
|
+
.update(fs.readFileSync(path.join(root, relative)))
|
|
26
|
+
.digest("hex")}`;
|
|
27
|
+
|
|
28
|
+
const workflowRoot = path.join(root, ".github/workflows");
|
|
29
|
+
const discovered = projectWorkflowIdentities(root, fs.readdirSync(workflowRoot)
|
|
30
|
+
.filter((name) => /\.ya?ml$/u.test(name))
|
|
31
|
+
.map((name) => ({ path: `.github/workflows/${name}`, text: fs.readFileSync(path.join(workflowRoot, name), "utf8") })))
|
|
32
|
+
.filter((entry) => /(?:^|\n)\s*workflow_call:\s*(?:\n|$)/u.test(entry.text))
|
|
33
|
+
.map((entry) => entry.path).sort();
|
|
34
|
+
|
|
35
|
+
assert.equal(
|
|
36
|
+
contract.schema,
|
|
37
|
+
"kungfu-buildchain-v4-universal-workflow-bootstrap/v1",
|
|
38
|
+
);
|
|
39
|
+
assert.deepEqual(
|
|
40
|
+
contract.inventoryWorkflows,
|
|
41
|
+
[...new Set(contract.inventoryWorkflows)].sort(),
|
|
42
|
+
"inventoried public workflows must be sorted and duplicate-free",
|
|
43
|
+
);
|
|
44
|
+
assert.deepEqual(
|
|
45
|
+
contract.inventoryWorkflows,
|
|
46
|
+
discovered,
|
|
47
|
+
"every active public workflow_call surface must be inventoried",
|
|
48
|
+
);
|
|
49
|
+
for (const relative of contract.inventoryWorkflows)
|
|
50
|
+
assert.ok(
|
|
51
|
+
fs.statSync(path.join(root, currentWorkflowPath(root, relative))).isFile(),
|
|
52
|
+
`inventoried workflow is unavailable: ${relative}`,
|
|
53
|
+
);
|
|
54
|
+
assert.deepEqual(
|
|
55
|
+
contract.bootstrapGovernedWorkflows,
|
|
56
|
+
[...new Set(contract.bootstrapGovernedWorkflows)].sort(),
|
|
57
|
+
"Bootstrap-governed workflows must be sorted and duplicate-free",
|
|
58
|
+
);
|
|
59
|
+
assert.deepEqual(
|
|
60
|
+
contract.retiredWorkflowSurfaces,
|
|
61
|
+
[...new Set(contract.retiredWorkflowSurfaces)].sort(),
|
|
62
|
+
"retired workflow surfaces must be sorted and duplicate-free",
|
|
63
|
+
);
|
|
64
|
+
for (const relative of contract.retiredWorkflowSurfaces) {
|
|
65
|
+
assert.ok(
|
|
66
|
+
contract.inventoryWorkflows.includes(relative),
|
|
67
|
+
`retired workflow is outside the inventory: ${relative}`,
|
|
68
|
+
);
|
|
69
|
+
assert.match(
|
|
70
|
+
fs.readFileSync(path.join(root, relative), "utf8"),
|
|
71
|
+
/retired/u,
|
|
72
|
+
`retired workflow does not fail closed explicitly: ${relative}`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
const activeWorkflows = contract.inventoryWorkflows.filter(
|
|
76
|
+
(relative) =>
|
|
77
|
+
!contract.retiredWorkflowSurfaces.includes(relative) &&
|
|
78
|
+
relative !== contract.bootstrap.consumerRecoveryWorkflow,
|
|
79
|
+
);
|
|
80
|
+
assert.deepEqual(
|
|
81
|
+
contract.bootstrapGovernedWorkflows,
|
|
82
|
+
activeWorkflows,
|
|
83
|
+
"every active public reusable workflow must be Bootstrap-governed",
|
|
84
|
+
);
|
|
85
|
+
for (const relative of contract.bootstrapGovernedWorkflows) {
|
|
86
|
+
assert.ok(
|
|
87
|
+
contract.inventoryWorkflows.includes(relative),
|
|
88
|
+
`Bootstrap-governed workflow is outside the inventory: ${relative}`,
|
|
89
|
+
);
|
|
90
|
+
const source = fs.readFileSync(path.join(root, currentWorkflowPath(root, relative)), "utf8");
|
|
91
|
+
assert.ok(
|
|
92
|
+
relative === contract.bootstrap.publicWorkflow ||
|
|
93
|
+
/uses:\s+(?:\.\/)?\.github\/workflows\/bootstrap\.yml/u.test(source) ||
|
|
94
|
+
/uses:\s+kungfu-systems\/buildchain\/\.github\/workflows\/bootstrap\.yml@/u.test(
|
|
95
|
+
source,
|
|
96
|
+
),
|
|
97
|
+
`workflow is declared governed without a Bootstrap edge: ${relative}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
assert.deepEqual(contract.complexityBudget, {
|
|
102
|
+
bootstrapAbis: 1,
|
|
103
|
+
requestEnvelopes: 1,
|
|
104
|
+
candidateResolvers: 1,
|
|
105
|
+
candidateEngines: 1,
|
|
106
|
+
terminalReceiptAuthorities: 1,
|
|
107
|
+
perWorkflowBootstrapImplementations: 0,
|
|
108
|
+
buildchainOnlySelfReleasePaths: 0,
|
|
109
|
+
perIncidentConsumerEdits: 0,
|
|
110
|
+
});
|
|
111
|
+
assert.equal(contract.bootstrap.discoveryRefExecutionAuthority, false);
|
|
112
|
+
assert.equal(contract.bootstrap.exactShaExecutionAuthority, true);
|
|
113
|
+
assert.equal(contract.bootstrap.consumerSourceMutationPerIncident, false);
|
|
114
|
+
assert.equal(contract.bootstrap.buildchainReleasePromotionPerIncident, false);
|
|
115
|
+
assert.equal(contract.bootstrap.selfDogfoodUsesPublicContract, true);
|
|
116
|
+
assert.equal(contract.bootstrap.recoveryDependsOnPublishedBuildchain, false);
|
|
117
|
+
assert.equal(
|
|
118
|
+
contract.bootstrap.reviewRuntimeBinding,
|
|
119
|
+
"reviewed-head-or-protected-alpha-merge-to-exact-runtime",
|
|
120
|
+
);
|
|
121
|
+
assert.deepEqual(
|
|
122
|
+
contract.capabilityAdapters.map(({ id }) => id).sort(),
|
|
123
|
+
[...admissionPolicy.allowedCapabilities].sort(),
|
|
124
|
+
"every admitted capability must have one real candidate adapter",
|
|
125
|
+
);
|
|
126
|
+
for (const adapter of contract.capabilityAdapters) {
|
|
127
|
+
assert.match(adapter.execution, /^exact-candidate-/u);
|
|
128
|
+
assert.ok(adapter.payloadSchema.endsWith("/v1"));
|
|
129
|
+
}
|
|
130
|
+
assert.ok(!admissionPolicy.allowedCapabilities.includes("workflow-contract"));
|
|
131
|
+
assert.deepEqual(
|
|
132
|
+
admissionPolicy.contractRoots,
|
|
133
|
+
[
|
|
134
|
+
fileRoot("architecture/v4-universal-workflow-bootstrap.json"),
|
|
135
|
+
fileRoot(contract.bootstrap.faultCampaign),
|
|
136
|
+
fileRoot("packages/core/v4-universal-workflow-bootstrap.js"),
|
|
137
|
+
fileRoot("scripts/v4-universal-workflow-backflow.mjs"),
|
|
138
|
+
fileRoot("scripts/v4-universal-workflow-engine.mjs"),
|
|
139
|
+
fileRoot("scripts/v4-universal-workflow-self-dogfood.mjs"),
|
|
140
|
+
fileRoot(contract.bootstrap.publicWorkflow),
|
|
141
|
+
].sort(),
|
|
142
|
+
);
|
|
143
|
+
assert.doesNotMatch(
|
|
144
|
+
fs.readFileSync(
|
|
145
|
+
path.join(root, contract.bootstrap.candidateEnginePath),
|
|
146
|
+
"utf8",
|
|
147
|
+
),
|
|
148
|
+
/request\.capability\.id\s*===\s*["']workflow-contract["']/u,
|
|
149
|
+
"contract-only validation must not be admitted as successful execution",
|
|
150
|
+
);
|
|
151
|
+
const bootstrapSource = fs.readFileSync(
|
|
152
|
+
path.join(root, contract.bootstrap.publicWorkflow),
|
|
153
|
+
"utf8",
|
|
154
|
+
);
|
|
155
|
+
assert.doesNotMatch(
|
|
156
|
+
bootstrapSource,
|
|
157
|
+
/admission-policy-json:/u,
|
|
158
|
+
"callers must not supply their own admission authority",
|
|
159
|
+
);
|
|
160
|
+
execFileSync(
|
|
161
|
+
process.execPath,
|
|
162
|
+
[path.join(root, contract.migration.compatibilityFacadeGenerator), "--check"],
|
|
163
|
+
{ stdio: "inherit" },
|
|
164
|
+
);
|
|
165
|
+
assert.doesNotMatch(bootstrapSource, /^ permissions:/mu);
|
|
166
|
+
assert.match(
|
|
167
|
+
bootstrapSource,
|
|
168
|
+
/execute:[\s\S]*BUILDCHAIN_PROMOTION_TOKEN: \$\{\{ secrets\.BUILDCHAIN_PROMOTION_TOKEN \}\}[\s\S]*GH_TOKEN: \$\{\{ github\.token \}\}/u,
|
|
169
|
+
"the privileged candidate executor must receive the protected-ref mutation token",
|
|
170
|
+
);
|
|
171
|
+
assert.match(bootstrapSource, /settle:[\s\S]*needs: \[admit, execute\]/u);
|
|
172
|
+
assert.match(
|
|
173
|
+
bootstrapSource,
|
|
174
|
+
/ascii_downcase\) == "kungfu-origin"[\s\S]*\.name == "check"[\s\S]*map\(select\(\.status == "completed" and \(\.conclusion \/\/ ""\) != ""\)\)[\s\S]*candidate\/architecture\/v4-universal-workflow-train-admission\.json/u,
|
|
175
|
+
);
|
|
176
|
+
assert.match(
|
|
177
|
+
bootstrapSource,
|
|
178
|
+
/CANDIDATE_MODE:[\s\S]*\.merged == true[\s\S]*\.merge_commit_sha == \$expected[\s\S]*\.base\.ref == "alpha\/v4\/v4\.0"[\s\S]*commitSha: \$expected[\s\S]*runtimeBinding:/u,
|
|
179
|
+
);
|
|
180
|
+
const consumerTemplate = fs.readFileSync(
|
|
181
|
+
path.join(root, contract.bootstrap.consumerTemplate),
|
|
182
|
+
"utf8",
|
|
183
|
+
);
|
|
184
|
+
const consumerRecoveryTemplate = fs.readFileSync(
|
|
185
|
+
path.join(root, contract.bootstrap.consumerRecoveryTemplate),
|
|
186
|
+
"utf8",
|
|
187
|
+
);
|
|
188
|
+
const consumerRecoveryWorkflow = fs.readFileSync(
|
|
189
|
+
path.join(root, contract.bootstrap.consumerRecoveryWorkflow),
|
|
190
|
+
"utf8",
|
|
191
|
+
);
|
|
192
|
+
const selfDogfood = fs.readFileSync(
|
|
193
|
+
path.join(root, contract.bootstrap.selfDogfoodWorkflow),
|
|
194
|
+
"utf8",
|
|
195
|
+
);
|
|
196
|
+
assert.match(
|
|
197
|
+
consumerTemplate,
|
|
198
|
+
/uses:\s+kungfu-systems\/buildchain\/\.github\/workflows\/bootstrap\.yml@v4/u,
|
|
199
|
+
);
|
|
200
|
+
assert.match(
|
|
201
|
+
consumerRecoveryTemplate,
|
|
202
|
+
/recovery-admit:[\s\S]*recovery-execute:/u,
|
|
203
|
+
);
|
|
204
|
+
assert.match(
|
|
205
|
+
consumerRecoveryTemplate,
|
|
206
|
+
/Parse exact recovery coordinates without Buildchain code[\s\S]*Prove exact independent review before candidate code runs[\s\S]*ascii_downcase\) == "kungfu-origin"[\s\S]*\.name == "check"[\s\S]*Admit candidate policy and exact-head checks[\s\S]*map\(select\(\.status == "completed" and \(\.conclusion \/\/ ""\) != ""\)\)/u,
|
|
207
|
+
);
|
|
208
|
+
assert.match(
|
|
209
|
+
consumerRecoveryTemplate,
|
|
210
|
+
/recovery-execute:[\s\S]*needs: recovery-admit[\s\S]*contents: write/u,
|
|
211
|
+
);
|
|
212
|
+
assert.doesNotMatch(
|
|
213
|
+
consumerRecoveryTemplate,
|
|
214
|
+
/uses:\s+kungfu-systems\/buildchain\/\.github\/workflows\//u,
|
|
215
|
+
"consumer recovery must not parse any published Buildchain workflow",
|
|
216
|
+
);
|
|
217
|
+
assert.equal(
|
|
218
|
+
consumerRecoveryWorkflow,
|
|
219
|
+
consumerRecoveryTemplate,
|
|
220
|
+
"the Buildchain self-consumer must exercise the exact distributable recovery shell",
|
|
221
|
+
);
|
|
222
|
+
assert.match(
|
|
223
|
+
selfDogfood,
|
|
224
|
+
/uses:\s+kungfu-systems\/buildchain\/\.github\/workflows\/bootstrap\.yml@train\/v4\/v4\.0\/universal-reusable-workflow-bootstrap/u,
|
|
225
|
+
);
|
|
226
|
+
assert.match(
|
|
227
|
+
selfDogfood,
|
|
228
|
+
/uses:\s+\.\/\.github\/workflows\/universal-bootstrap-recovery\.yml/u,
|
|
229
|
+
);
|
|
230
|
+
assert.doesNotMatch(
|
|
231
|
+
selfDogfood,
|
|
232
|
+
/\.github\/workflows\/bootstrap\.yml@(?:v4|v4-alpha)(?:\s|$)/u,
|
|
233
|
+
"Train self-dogfood must not depend on an alpha or stable Bootstrap tag",
|
|
234
|
+
);
|
|
235
|
+
for (const channel of ["conformance", "alpha", "stable"]) {
|
|
236
|
+
assert.match(selfDogfood, new RegExp(`primary-${channel}:`, "u"));
|
|
237
|
+
assert.match(selfDogfood, new RegExp(`recovery-${channel}:`, "u"));
|
|
238
|
+
}
|
|
239
|
+
assert.match(
|
|
240
|
+
selfDogfood,
|
|
241
|
+
/\.state == "APPROVED"[\s\S]*ascii_downcase\) == "kungfu-origin"[\s\S]*\.name == "check"[\s\S]*\.conclusion == "success"/u,
|
|
242
|
+
);
|
|
243
|
+
assert.match(
|
|
244
|
+
consumerRecoveryTemplate,
|
|
245
|
+
/result-json:[\s\S]*value:\s*\$\{\{ jobs\.recovery-execute\.outputs\.result-json \}\}/u,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
console.log(
|
|
249
|
+
JSON.stringify({
|
|
250
|
+
ok: true,
|
|
251
|
+
schema: contract.schema,
|
|
252
|
+
publicWorkflowCount: discovered.length,
|
|
253
|
+
activePublicWorkflowCount: activeWorkflows.length,
|
|
254
|
+
retiredWorkflowCount: contract.retiredWorkflowSurfaces.length,
|
|
255
|
+
inventoriedWorkflowCount: contract.inventoryWorkflows.length,
|
|
256
|
+
inventoryCoveragePercent: 100,
|
|
257
|
+
governedWorkflowCount: contract.bootstrapGovernedWorkflows.length,
|
|
258
|
+
governedCoveragePercent: Number(
|
|
259
|
+
(
|
|
260
|
+
(contract.bootstrapGovernedWorkflows.length / activeWorkflows.length) *
|
|
261
|
+
100
|
|
262
|
+
).toFixed(2),
|
|
263
|
+
),
|
|
264
|
+
migration: contract.migration,
|
|
265
|
+
}),
|
|
266
|
+
);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
6
|
+
const destination = path.resolve(process.argv[2] || "");
|
|
7
|
+
if (!process.argv[2] || path.basename(destination) !== "dist") {
|
|
8
|
+
throw new Error("usage: copy-v4-domain-wasm.mjs ACTION_DIST_DIRECTORY");
|
|
9
|
+
}
|
|
10
|
+
fs.mkdirSync(destination, { recursive: true });
|
|
11
|
+
fs.copyFileSync(
|
|
12
|
+
path.join(root, "packages", "core", "buildchain-v4-domain.wasm"),
|
|
13
|
+
path.join(destination, "buildchain-v4-domain.wasm"),
|
|
14
|
+
);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import {
|
|
4
3
|
acquireDevDeliveryLandingWarrant,
|
|
@@ -66,6 +65,7 @@ function submitCandidate(state, options) {
|
|
|
66
65
|
{
|
|
67
66
|
pullRequestNumber: options.pullRequestNumber,
|
|
68
67
|
sourceHead: options.sourceHead,
|
|
68
|
+
sourceRoot: options.sourceRoot,
|
|
69
69
|
assignmentRoot: options.assignmentRoot,
|
|
70
70
|
initiativeRoot: options.initiativeRoot,
|
|
71
71
|
sourceIdentityRoot: options.sourceIdentityRoot,
|
|
@@ -276,16 +276,16 @@ export function devDeliveryAuthorityCliOptions(
|
|
|
276
276
|
"source-head",
|
|
277
277
|
environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_HEAD,
|
|
278
278
|
),
|
|
279
|
-
|
|
279
|
+
sourceRoot: flag(
|
|
280
280
|
rest,
|
|
281
|
-
"
|
|
282
|
-
environment.
|
|
283
|
-
|
|
284
|
-
initiativeRoot: flag(
|
|
285
|
-
rest,
|
|
286
|
-
"initiative-root",
|
|
287
|
-
environment.BUILDCHAIN_DEV_DELIVERY_INITIATIVE_ROOT,
|
|
281
|
+
"source-root",
|
|
282
|
+
environment.BUILDCHAIN_WORK_SOURCE_ROOT ||
|
|
283
|
+
environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_ROOT,
|
|
288
284
|
),
|
|
285
|
+
// Retired roots remain readable in persisted historical candidates.
|
|
286
|
+
// New authority submissions accept only the Work sourceRoot above.
|
|
287
|
+
// Retired CLI flags are intentionally ignored by this parser.
|
|
288
|
+
// This prevents producers from reconstructing obsolete identities.
|
|
289
289
|
sourceIdentityRoot: flag(
|
|
290
290
|
rest,
|
|
291
291
|
"source-identity-root",
|
|
@@ -479,6 +479,7 @@ function cliInput(args) {
|
|
|
479
479
|
export function sourceProofReuseCli(args = process.argv.slice(2)) {
|
|
480
480
|
const [command = "", ...rest] = args;
|
|
481
481
|
const input = cliInput(rest);
|
|
482
|
+
if (command === "predicates") return sourceQualificationPredicates(input);
|
|
482
483
|
if (command === "seal") {
|
|
483
484
|
const proof = sealSourceQualificationProof(input);
|
|
484
485
|
writeJson(input.outputPath, proof);
|
|
@@ -527,7 +528,7 @@ export function sourceProofReuseCli(args = process.argv.slice(2)) {
|
|
|
527
528
|
return decision;
|
|
528
529
|
}
|
|
529
530
|
throw new Error(
|
|
530
|
-
"usage: dev-delivery-source-proof-reuse.mjs <seal|verify> [options]",
|
|
531
|
+
"usage: dev-delivery-source-proof-reuse.mjs <predicates|seal|verify> [options]",
|
|
531
532
|
);
|
|
532
533
|
}
|
|
533
534
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
export function flag(args, name, fallback = "") {
|
|
2
3
|
const index = args.indexOf(`--${name}`);
|
|
3
4
|
return index === -1 ? fallback : args[index + 1] || "";
|
|
4
5
|
}
|
|
5
|
-
|
|
6
6
|
export function hasFlag(args, name) {
|
|
7
7
|
return args.includes(`--${name}`);
|
|
8
8
|
}
|
|
@@ -79,16 +79,16 @@ export function devDeliveryCliOptions(args = [], environment = process.env) {
|
|
|
79
79
|
"source-head",
|
|
80
80
|
environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_HEAD,
|
|
81
81
|
),
|
|
82
|
-
|
|
83
|
-
rest,
|
|
84
|
-
"expected-source-head",
|
|
85
|
-
environment.BUILDCHAIN_DEV_DELIVERY_EXPECTED_SOURCE_HEAD,
|
|
86
|
-
),
|
|
87
|
-
observedSourceHead: flag(
|
|
82
|
+
sourceRoot: flag(
|
|
88
83
|
rest,
|
|
89
|
-
"
|
|
90
|
-
environment.
|
|
84
|
+
"source-root",
|
|
85
|
+
environment.BUILDCHAIN_WORK_SOURCE_ROOT ||
|
|
86
|
+
environment.BUILDCHAIN_DEV_DELIVERY_SOURCE_ROOT ||
|
|
87
|
+
sourceRootFromGitHubEvent(environment),
|
|
91
88
|
),
|
|
89
|
+
// prettier-ignore
|
|
90
|
+
...{ expectedSourceHead: flag(rest, "expected-source-head", environment.BUILDCHAIN_DEV_DELIVERY_EXPECTED_SOURCE_HEAD), observedSourceHead: flag(rest, "observed-source-head", environment.BUILDCHAIN_DEV_DELIVERY_OBSERVED_SOURCE_HEAD) },
|
|
91
|
+
|
|
92
92
|
assignmentRoot: flag(
|
|
93
93
|
rest,
|
|
94
94
|
"assignment-root",
|
|
@@ -291,3 +291,8 @@ export function devDeliveryCliOptions(args = [], environment = process.env) {
|
|
|
291
291
|
json: hasFlag(rest, "json"),
|
|
292
292
|
};
|
|
293
293
|
}
|
|
294
|
+
|
|
295
|
+
// prettier-ignore
|
|
296
|
+
function sourceRootFromGitHubEvent(environment) {
|
|
297
|
+
try { const event = JSON.parse(fs.readFileSync(environment.GITHUB_EVENT_PATH, "utf8")); return event.client_payload?.candidate?.sourceRoot || JSON.parse(event.inputs?.["native-roots-json"] || "{}").sourceRoot || ""; } catch { return ""; }
|
|
298
|
+
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { normalizeDevDeliveryAuthorityState } from "../packages/core/dev-delivery-authority-state.js";
|
|
8
8
|
|
|
9
9
|
const STATE_PATH = "queue.json";
|
|
10
|
+
const READ_ATTEMPTS = 3;
|
|
10
11
|
|
|
11
12
|
function text(value = "") {
|
|
12
13
|
return String(value ?? "").trim();
|
|
@@ -20,6 +21,14 @@ function exactSha(value, label) {
|
|
|
20
21
|
return normalized;
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
function positiveInteger(value, label) {
|
|
25
|
+
const parsed = Number(value);
|
|
26
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
27
|
+
throw new Error(`${label} must be a positive integer`);
|
|
28
|
+
}
|
|
29
|
+
return parsed;
|
|
30
|
+
}
|
|
31
|
+
|
|
23
32
|
function normalizeRepository(value) {
|
|
24
33
|
const normalized = text(value);
|
|
25
34
|
const match = normalized.match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
@@ -35,11 +44,10 @@ function encodeRef(ref) {
|
|
|
35
44
|
return ref.split("/").map(encodeURIComponent).join("/");
|
|
36
45
|
}
|
|
37
46
|
|
|
38
|
-
function
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
);
|
|
47
|
+
function decodeGitHubBlobEnvelope(raw) {
|
|
48
|
+
if (!/"encoding"\s*:\s*"base64"/.test(raw)) return raw;
|
|
49
|
+
const blob = JSON.parse(raw);
|
|
50
|
+
if (blob?.encoding !== "base64") return raw;
|
|
43
51
|
return Buffer.from(
|
|
44
52
|
String(blob.content || "").replace(/\s+/g, ""),
|
|
45
53
|
"base64",
|
|
@@ -71,6 +79,7 @@ export class GitHubDevDeliveryStore {
|
|
|
71
79
|
apiUrl = "https://api.github.com",
|
|
72
80
|
fetchImpl = globalThis.fetch,
|
|
73
81
|
createInitialState = createDevDeliveryQueue,
|
|
82
|
+
readAttempts = READ_ATTEMPTS,
|
|
74
83
|
} = {}) {
|
|
75
84
|
this.repository = normalizeRepository(repository);
|
|
76
85
|
if (!fetchImpl) throw new Error("fetch is required");
|
|
@@ -82,6 +91,22 @@ export class GitHubDevDeliveryStore {
|
|
|
82
91
|
this.apiUrl = apiUrl.replace(/\/+$/, "");
|
|
83
92
|
this.fetch = fetchImpl;
|
|
84
93
|
this.createInitialState = createInitialState;
|
|
94
|
+
this.readAttempts = positiveInteger(readAttempts, "readAttempts");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async retryParseableRead(operation) {
|
|
98
|
+
let failure;
|
|
99
|
+
for (let attempt = 1; attempt <= this.readAttempts; attempt += 1) {
|
|
100
|
+
try {
|
|
101
|
+
return await operation();
|
|
102
|
+
} catch (error) {
|
|
103
|
+
failure = error;
|
|
104
|
+
if (!(error instanceof SyntaxError) || attempt === this.readAttempts) {
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
throw failure;
|
|
85
110
|
}
|
|
86
111
|
|
|
87
112
|
async request(method, requestPath, body) {
|
|
@@ -108,7 +133,42 @@ export class GitHubDevDeliveryStore {
|
|
|
108
133
|
return data;
|
|
109
134
|
}
|
|
110
135
|
|
|
136
|
+
async requestRaw(method, requestPath) {
|
|
137
|
+
const response = await this.fetch(`${this.apiUrl}${requestPath}`, {
|
|
138
|
+
method,
|
|
139
|
+
headers: {
|
|
140
|
+
accept: "application/vnd.github.raw+json",
|
|
141
|
+
authorization: `Bearer ${this.token}`,
|
|
142
|
+
"x-github-api-version": "2022-11-28",
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
const raw = await response.text();
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
let message = raw || `${method} ${requestPath} failed`;
|
|
148
|
+
try {
|
|
149
|
+
message = JSON.parse(raw)?.message || message;
|
|
150
|
+
} catch {
|
|
151
|
+
// Raw blob responses are not required to be JSON.
|
|
152
|
+
}
|
|
153
|
+
const error = new Error(message);
|
|
154
|
+
error.status = response.status;
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
return raw;
|
|
158
|
+
}
|
|
159
|
+
|
|
111
160
|
async read({ stateRef, protectedBase, now, allowLegacyV3Readback = false }) {
|
|
161
|
+
return this.retryParseableRead(async () =>
|
|
162
|
+
this.readOnce({ stateRef, protectedBase, now, allowLegacyV3Readback }),
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async readOnce({
|
|
167
|
+
stateRef,
|
|
168
|
+
protectedBase,
|
|
169
|
+
now,
|
|
170
|
+
allowLegacyV3Readback = false,
|
|
171
|
+
}) {
|
|
112
172
|
let ref;
|
|
113
173
|
try {
|
|
114
174
|
ref = await this.request(
|
|
@@ -135,6 +195,12 @@ export class GitHubDevDeliveryStore {
|
|
|
135
195
|
}
|
|
136
196
|
|
|
137
197
|
async readCommit(commitShaInput, { allowLegacyV3Readback = false } = {}) {
|
|
198
|
+
return this.retryParseableRead(async () =>
|
|
199
|
+
this.readCommitOnce(commitShaInput, { allowLegacyV3Readback }),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async readCommitOnce(commitShaInput, { allowLegacyV3Readback = false } = {}) {
|
|
138
204
|
const commitSha = exactSha(commitShaInput, "state commit");
|
|
139
205
|
const commit = await this.request(
|
|
140
206
|
"GET",
|
|
@@ -149,11 +215,11 @@ export class GitHubDevDeliveryStore {
|
|
|
149
215
|
);
|
|
150
216
|
if (!entry?.sha)
|
|
151
217
|
throw new Error(`${commitSha} does not contain ${STATE_PATH}`);
|
|
152
|
-
const
|
|
218
|
+
const raw = await this.requestRaw(
|
|
153
219
|
"GET",
|
|
154
220
|
`/repos/${this.repository.fullName}/git/blobs/${entry.sha}`,
|
|
155
221
|
);
|
|
156
|
-
const bytes =
|
|
222
|
+
const bytes = decodeGitHubBlobEnvelope(raw);
|
|
157
223
|
const queue = validateStoredState(JSON.parse(bytes), {
|
|
158
224
|
allowLegacyV3Readback,
|
|
159
225
|
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { recoverExpiredDevDeliveryWarrant } from "../packages/core/dev-delivery-warrant.js";
|
|
2
|
+
|
|
3
|
+
async function recoverExpiredLeaseBeforeReselection({
|
|
4
|
+
store,
|
|
5
|
+
options,
|
|
6
|
+
loaded,
|
|
7
|
+
initialLoaded,
|
|
8
|
+
}) {
|
|
9
|
+
const recovery = recoverExpiredDevDeliveryWarrant(loaded.queue, {
|
|
10
|
+
now: options.now,
|
|
11
|
+
});
|
|
12
|
+
if (recovery.receipt.expectedOldStateRoot !== loaded.queue.stateRoot) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
"expired Warrant recovery receipt expected-old root does not match the loaded authority",
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
await store.write({
|
|
18
|
+
stateRef: options.stateRef,
|
|
19
|
+
queue: recovery.queue,
|
|
20
|
+
expectedCommitSha: loaded.commitSha,
|
|
21
|
+
expectedStateRoot: loaded.queue.stateRoot,
|
|
22
|
+
receiptRoot: recovery.receiptRoot,
|
|
23
|
+
});
|
|
24
|
+
const reloaded = await store.read({
|
|
25
|
+
stateRef: options.stateRef,
|
|
26
|
+
protectedBase: options.branch,
|
|
27
|
+
now: options.now,
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
loaded: reloaded,
|
|
31
|
+
concurrencyRecovery: {
|
|
32
|
+
schema: "kungfu.buildchain.dev-delivery-concurrency-recovery/v1",
|
|
33
|
+
action: "expired-lease-recovered-before-reselection",
|
|
34
|
+
initialCommitSha: initialLoaded.commitSha,
|
|
35
|
+
recoveredStateRoot: recovery.queue.stateRoot,
|
|
36
|
+
observedCommitSha: reloaded.commitSha,
|
|
37
|
+
observedStateRoot: reloaded.queue.stateRoot,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function persistDevDeliveryTransition({
|
|
43
|
+
store,
|
|
44
|
+
options,
|
|
45
|
+
loaded,
|
|
46
|
+
initialLoaded,
|
|
47
|
+
changed,
|
|
48
|
+
transitionFor,
|
|
49
|
+
}) {
|
|
50
|
+
let concurrencyRecovery = null;
|
|
51
|
+
if (
|
|
52
|
+
options.execute &&
|
|
53
|
+
options.command === "select" &&
|
|
54
|
+
changed.recoveryReceipt?.action === "recovered-expired-lease"
|
|
55
|
+
) {
|
|
56
|
+
const recovery = await recoverExpiredLeaseBeforeReselection({
|
|
57
|
+
store,
|
|
58
|
+
options,
|
|
59
|
+
loaded,
|
|
60
|
+
initialLoaded,
|
|
61
|
+
});
|
|
62
|
+
loaded = recovery.loaded;
|
|
63
|
+
changed = transitionFor(options.command, loaded.queue, options);
|
|
64
|
+
concurrencyRecovery = recovery.concurrencyRecovery;
|
|
65
|
+
}
|
|
66
|
+
if (!options.execute || changed.queue.stateRoot === loaded.queue.stateRoot)
|
|
67
|
+
return { loaded, changed, write: null, concurrencyRecovery };
|
|
68
|
+
if (changed.receipt.expectedOldStateRoot !== loaded.queue.stateRoot) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
"transition receipt expected-old root does not match the loaded authority",
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const write = await store.write({
|
|
75
|
+
stateRef: options.stateRef,
|
|
76
|
+
queue: changed.queue,
|
|
77
|
+
expectedCommitSha: loaded.commitSha,
|
|
78
|
+
expectedStateRoot: loaded.queue.stateRoot,
|
|
79
|
+
receiptRoot: changed.receiptRoot,
|
|
80
|
+
});
|
|
81
|
+
return { loaded, changed, write, concurrencyRecovery };
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (options.command !== "settle" || options.expectedOldStateRoot)
|
|
84
|
+
throw error;
|
|
85
|
+
const latest = await store.read({
|
|
86
|
+
stateRef: options.stateRef,
|
|
87
|
+
protectedBase: options.branch,
|
|
88
|
+
now: options.now,
|
|
89
|
+
});
|
|
90
|
+
const reconciled = transitionFor(options.command, latest.queue, options);
|
|
91
|
+
if (
|
|
92
|
+
reconciled.queue.stateRoot !== latest.queue.stateRoot ||
|
|
93
|
+
reconciled.receipt.action !== "duplicate-terminal-event-noop"
|
|
94
|
+
)
|
|
95
|
+
throw error;
|
|
96
|
+
return {
|
|
97
|
+
loaded: latest,
|
|
98
|
+
changed: reconciled,
|
|
99
|
+
write: null,
|
|
100
|
+
concurrencyRecovery: {
|
|
101
|
+
schema: "kungfu.buildchain.dev-delivery-concurrency-recovery/v1",
|
|
102
|
+
action: "terminal-settlement-race-noop",
|
|
103
|
+
initialCommitSha: initialLoaded.commitSha,
|
|
104
|
+
observedCommitSha: latest.commitSha,
|
|
105
|
+
observedStateRoot: latest.queue.stateRoot,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|