@kungfu-tech/buildchain 4.0.2-alpha.0 → 4.0.2-alpha.11
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/architecture/agent-change-map.md +52 -5
- package/architecture/ci-lane-change-budget.json +408 -8
- package/architecture/internal-capabilities.json +44 -1
- package/architecture/maintainability-debt.json +65 -133
- package/architecture/maintainability-policy.json +20 -15
- package/architecture/release-tail-contract-inventory.json +4 -4
- package/architecture/v3-core-mechanism-inventory.json +1 -0
- package/architecture/v3-v4-live-capability-inventory.json +17 -17
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +5 -5
- package/architecture/v4-release-invocation-fixtures.json +139 -0
- package/architecture/v4-release-topology.json +317 -0
- package/architecture/v4-runtime-semantic-closure.json +4 -1
- package/architecture/v4-universal-workflow-bootstrap.json +181 -0
- package/architecture/v4-universal-workflow-fault-campaign.json +72 -0
- package/architecture/v4-universal-workflow-train-admission.json +40 -0
- package/contracts/buildchain-v2-residuals-v1.json +0 -9
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/contracts/v4-release-invocation-v1.schema.json +134 -0
- package/dist/site/buildchain-contract.json +87 -42
- package/dist/site/buildchain-site.json +15 -10
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/controller-registry.json +46 -10
- package/dist/site/kfd-claims.json +176 -69
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +2 -2
- package/dist/site/node-api-registry.json +97 -22
- package/dist/site/page-registry.json +9 -4
- package/dist/site/public-surface-audit.json +190 -71
- package/dist/site/publication-authority-registry.json +68 -3
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +6 -6
- package/dist/site/workflow-registry.json +221 -72
- package/docs/node-api-reference.md +28 -25
- package/docs/reusable-build-surface.md +16 -0
- package/package.json +2 -2
- package/packages/core/buildchain-publication-authority.js +3 -0
- package/packages/core/ci-lane-change-budget.js +14 -1
- package/packages/core/dev-delivery-candidate-identity.js +45 -17
- package/packages/core/dev-delivery-execution-transfer.js +6 -7
- package/packages/core/dev-delivery-provider-heartbeat.js +22 -6
- package/packages/core/dev-delivery-warrant-legacy-recovery.js +4 -2
- package/packages/core/dev-delivery-warrant-native-compatibility.js +33 -0
- package/packages/core/dev-delivery-warrant-state.js +54 -54
- package/packages/core/dev-delivery-warrant.js +8 -0
- package/packages/core/dev-delivery-writer-protocol-transition.js +72 -0
- package/packages/core/release-tail-product-capabilities.js +29 -0
- package/packages/core/release-tail-provider-plane.js +3 -3
- package/packages/core/v4-canonical-contracts.js +14 -0
- package/packages/core/v4-floating-consumer-policy.js +4 -1
- package/packages/core/v4-product-publication.js +387 -0
- package/packages/core/v4-protected-publication-source.js +93 -0
- package/packages/core/v4-publication-qualification.js +1 -0
- package/packages/core/v4-release-invocation.js +425 -0
- package/packages/core/v4-universal-workflow-bootstrap.js +596 -0
- package/scripts/audit-publication-control-plane.mjs +0 -1
- package/scripts/check-inventory.mjs +27 -59
- package/scripts/check-maintainability.mjs +29 -118
- package/scripts/check-v3-v4-capability-inventory.mjs +3 -61
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +10 -20
- package/scripts/check-v4-release-topology.mjs +493 -0
- package/scripts/check-v4-universal-workflow-bootstrap.mjs +257 -0
- package/scripts/dev-delivery-warrant.mjs +26 -5
- package/scripts/generate-channel-build-workflow.mjs +5 -1
- package/scripts/generate-channel-promotion-workflow.mjs +17 -57
- package/scripts/generate-v4-universal-workflow-facades.mjs +415 -0
- package/scripts/maintainability-public-surface.mjs +115 -0
- package/scripts/release-candidate-resolver.mjs +23 -24
- package/scripts/resume-from-candidate-run.mjs +16 -17
- package/scripts/universal-facade-maintainability.mjs +82 -0
- package/scripts/v3-v4-capability-catalog.mjs +107 -0
- package/scripts/v4-declarative-promotion-admission.mjs +4 -1
- package/scripts/v4-product-publication-intent.mjs +114 -0
- package/scripts/v4-release-candidate-adapter.mjs +41 -0
- package/scripts/v4-universal-workflow-backflow.mjs +212 -0
- package/scripts/v4-universal-workflow-engine.mjs +586 -0
- package/scripts/v4-universal-workflow-self-dogfood.mjs +205 -0
- package/templates/universal-buildchain-bootstrap-recovery.yml +282 -0
- package/templates/universal-buildchain-bootstrap.yml +28 -0
- package/scripts/capture-package-release-propagation.mjs +0 -263
- package/scripts/publication-commit-evidence.mjs +0 -444
- package/scripts/publish-github-artifact-attestation-evidence.mjs +0 -201
- package/scripts/stage-github-artifact-attestation-inputs.mjs +0 -65
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
|
+
const contract = JSON.parse(
|
|
10
|
+
fs.readFileSync(
|
|
11
|
+
path.join(root, "architecture/v4-universal-workflow-bootstrap.json"),
|
|
12
|
+
"utf8",
|
|
13
|
+
),
|
|
14
|
+
);
|
|
15
|
+
const admissionPolicy = JSON.parse(
|
|
16
|
+
fs.readFileSync(
|
|
17
|
+
path.join(root, "architecture/v4-universal-workflow-train-admission.json"),
|
|
18
|
+
"utf8",
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
const fileRoot = (relative) =>
|
|
22
|
+
`sha256:${crypto
|
|
23
|
+
.createHash("sha256")
|
|
24
|
+
.update(fs.readFileSync(path.join(root, relative)))
|
|
25
|
+
.digest("hex")}`;
|
|
26
|
+
|
|
27
|
+
const workflowRoot = path.join(root, ".github/workflows");
|
|
28
|
+
const discovered = fs
|
|
29
|
+
.readdirSync(workflowRoot)
|
|
30
|
+
.filter((name) => /\.ya?ml$/u.test(name))
|
|
31
|
+
.map((name) => `.github/workflows/${name}`)
|
|
32
|
+
.filter((relative) =>
|
|
33
|
+
/(?:^|\n)\s*workflow_call:\s*(?:\n|$)/u.test(
|
|
34
|
+
fs.readFileSync(path.join(root, relative), "utf8"),
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
.sort();
|
|
38
|
+
|
|
39
|
+
assert.equal(
|
|
40
|
+
contract.schema,
|
|
41
|
+
"kungfu-buildchain-v4-universal-workflow-bootstrap/v1",
|
|
42
|
+
);
|
|
43
|
+
assert.deepEqual(
|
|
44
|
+
contract.inventoryWorkflows,
|
|
45
|
+
[...new Set(contract.inventoryWorkflows)].sort(),
|
|
46
|
+
"inventoried public workflows must be sorted and duplicate-free",
|
|
47
|
+
);
|
|
48
|
+
assert.deepEqual(
|
|
49
|
+
contract.inventoryWorkflows,
|
|
50
|
+
discovered,
|
|
51
|
+
"every active public workflow_call surface must be inventoried",
|
|
52
|
+
);
|
|
53
|
+
for (const relative of contract.inventoryWorkflows)
|
|
54
|
+
assert.ok(
|
|
55
|
+
fs.statSync(path.join(root, relative)).isFile(),
|
|
56
|
+
`inventoried workflow is unavailable: ${relative}`,
|
|
57
|
+
);
|
|
58
|
+
assert.deepEqual(
|
|
59
|
+
contract.bootstrapGovernedWorkflows,
|
|
60
|
+
[...new Set(contract.bootstrapGovernedWorkflows)].sort(),
|
|
61
|
+
"Bootstrap-governed workflows must be sorted and duplicate-free",
|
|
62
|
+
);
|
|
63
|
+
assert.deepEqual(
|
|
64
|
+
contract.retiredWorkflowSurfaces,
|
|
65
|
+
[...new Set(contract.retiredWorkflowSurfaces)].sort(),
|
|
66
|
+
"retired workflow surfaces must be sorted and duplicate-free",
|
|
67
|
+
);
|
|
68
|
+
for (const relative of contract.retiredWorkflowSurfaces) {
|
|
69
|
+
assert.ok(
|
|
70
|
+
contract.inventoryWorkflows.includes(relative),
|
|
71
|
+
`retired workflow is outside the inventory: ${relative}`,
|
|
72
|
+
);
|
|
73
|
+
assert.match(
|
|
74
|
+
fs.readFileSync(path.join(root, relative), "utf8"),
|
|
75
|
+
/retired/u,
|
|
76
|
+
`retired workflow does not fail closed explicitly: ${relative}`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const activeWorkflows = contract.inventoryWorkflows.filter(
|
|
80
|
+
(relative) =>
|
|
81
|
+
!contract.retiredWorkflowSurfaces.includes(relative) &&
|
|
82
|
+
relative !== contract.bootstrap.consumerRecoveryWorkflow,
|
|
83
|
+
);
|
|
84
|
+
assert.deepEqual(
|
|
85
|
+
contract.bootstrapGovernedWorkflows,
|
|
86
|
+
activeWorkflows,
|
|
87
|
+
"every active public reusable workflow must be Bootstrap-governed",
|
|
88
|
+
);
|
|
89
|
+
for (const relative of contract.bootstrapGovernedWorkflows) {
|
|
90
|
+
assert.ok(
|
|
91
|
+
contract.inventoryWorkflows.includes(relative),
|
|
92
|
+
`Bootstrap-governed workflow is outside the inventory: ${relative}`,
|
|
93
|
+
);
|
|
94
|
+
const source = fs.readFileSync(path.join(root, relative), "utf8");
|
|
95
|
+
assert.ok(
|
|
96
|
+
relative === contract.bootstrap.publicWorkflow ||
|
|
97
|
+
/uses:\s+(?:\.\/)?\.github\/workflows\/bootstrap\.yml/u.test(source) ||
|
|
98
|
+
/uses:\s+kungfu-systems\/buildchain\/\.github\/workflows\/bootstrap\.yml@/u.test(
|
|
99
|
+
source,
|
|
100
|
+
),
|
|
101
|
+
`workflow is declared governed without a Bootstrap edge: ${relative}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
assert.deepEqual(contract.complexityBudget, {
|
|
106
|
+
bootstrapAbis: 1,
|
|
107
|
+
requestEnvelopes: 1,
|
|
108
|
+
candidateResolvers: 1,
|
|
109
|
+
candidateEngines: 1,
|
|
110
|
+
terminalReceiptAuthorities: 1,
|
|
111
|
+
perWorkflowBootstrapImplementations: 0,
|
|
112
|
+
buildchainOnlySelfReleasePaths: 0,
|
|
113
|
+
perIncidentConsumerEdits: 0,
|
|
114
|
+
});
|
|
115
|
+
assert.equal(contract.bootstrap.discoveryRefExecutionAuthority, false);
|
|
116
|
+
assert.equal(contract.bootstrap.exactShaExecutionAuthority, true);
|
|
117
|
+
assert.equal(contract.bootstrap.consumerSourceMutationPerIncident, false);
|
|
118
|
+
assert.equal(contract.bootstrap.buildchainReleasePromotionPerIncident, false);
|
|
119
|
+
assert.equal(contract.bootstrap.selfDogfoodUsesPublicContract, true);
|
|
120
|
+
assert.equal(contract.bootstrap.recoveryDependsOnPublishedBuildchain, false);
|
|
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(bootstrapSource, /settle:[\s\S]*needs: \[admit, execute\]/u);
|
|
167
|
+
assert.match(
|
|
168
|
+
bootstrapSource,
|
|
169
|
+
/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,
|
|
170
|
+
);
|
|
171
|
+
const consumerTemplate = fs.readFileSync(
|
|
172
|
+
path.join(root, contract.bootstrap.consumerTemplate),
|
|
173
|
+
"utf8",
|
|
174
|
+
);
|
|
175
|
+
const consumerRecoveryTemplate = fs.readFileSync(
|
|
176
|
+
path.join(root, contract.bootstrap.consumerRecoveryTemplate),
|
|
177
|
+
"utf8",
|
|
178
|
+
);
|
|
179
|
+
const consumerRecoveryWorkflow = fs.readFileSync(
|
|
180
|
+
path.join(root, contract.bootstrap.consumerRecoveryWorkflow),
|
|
181
|
+
"utf8",
|
|
182
|
+
);
|
|
183
|
+
const selfDogfood = fs.readFileSync(
|
|
184
|
+
path.join(root, contract.bootstrap.selfDogfoodWorkflow),
|
|
185
|
+
"utf8",
|
|
186
|
+
);
|
|
187
|
+
assert.match(
|
|
188
|
+
consumerTemplate,
|
|
189
|
+
/uses:\s+kungfu-systems\/buildchain\/\.github\/workflows\/bootstrap\.yml@v4/u,
|
|
190
|
+
);
|
|
191
|
+
assert.match(
|
|
192
|
+
consumerRecoveryTemplate,
|
|
193
|
+
/recovery-admit:[\s\S]*recovery-execute:/u,
|
|
194
|
+
);
|
|
195
|
+
assert.match(
|
|
196
|
+
consumerRecoveryTemplate,
|
|
197
|
+
/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,
|
|
198
|
+
);
|
|
199
|
+
assert.match(
|
|
200
|
+
consumerRecoveryTemplate,
|
|
201
|
+
/recovery-execute:[\s\S]*needs: recovery-admit[\s\S]*contents: write/u,
|
|
202
|
+
);
|
|
203
|
+
assert.doesNotMatch(
|
|
204
|
+
consumerRecoveryTemplate,
|
|
205
|
+
/uses:\s+kungfu-systems\/buildchain\/\.github\/workflows\//u,
|
|
206
|
+
"consumer recovery must not parse any published Buildchain workflow",
|
|
207
|
+
);
|
|
208
|
+
assert.equal(
|
|
209
|
+
consumerRecoveryWorkflow,
|
|
210
|
+
consumerRecoveryTemplate,
|
|
211
|
+
"the Buildchain self-consumer must exercise the exact distributable recovery shell",
|
|
212
|
+
);
|
|
213
|
+
assert.match(
|
|
214
|
+
selfDogfood,
|
|
215
|
+
/uses:\s+kungfu-systems\/buildchain\/\.github\/workflows\/bootstrap\.yml@train\/v4\/v4\.0\/universal-reusable-workflow-bootstrap/u,
|
|
216
|
+
);
|
|
217
|
+
assert.match(
|
|
218
|
+
selfDogfood,
|
|
219
|
+
/uses:\s+\.\/\.github\/workflows\/universal-bootstrap-recovery\.yml/u,
|
|
220
|
+
);
|
|
221
|
+
assert.doesNotMatch(
|
|
222
|
+
selfDogfood,
|
|
223
|
+
/\.github\/workflows\/bootstrap\.yml@(?:v4|v4-alpha)(?:\s|$)/u,
|
|
224
|
+
"Train self-dogfood must not depend on an alpha or stable Bootstrap tag",
|
|
225
|
+
);
|
|
226
|
+
for (const channel of ["conformance", "alpha", "stable"]) {
|
|
227
|
+
assert.match(selfDogfood, new RegExp(`primary-${channel}:`, "u"));
|
|
228
|
+
assert.match(selfDogfood, new RegExp(`recovery-${channel}:`, "u"));
|
|
229
|
+
}
|
|
230
|
+
assert.match(
|
|
231
|
+
selfDogfood,
|
|
232
|
+
/\.state == "APPROVED"[\s\S]*ascii_downcase\) == "kungfu-origin"[\s\S]*\.name == "check"[\s\S]*\.conclusion == "success"/u,
|
|
233
|
+
);
|
|
234
|
+
assert.match(
|
|
235
|
+
consumerRecoveryTemplate,
|
|
236
|
+
/result-json:[\s\S]*value:\s*\$\{\{ jobs\.recovery-execute\.outputs\.result-json \}\}/u,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
console.log(
|
|
240
|
+
JSON.stringify({
|
|
241
|
+
ok: true,
|
|
242
|
+
schema: contract.schema,
|
|
243
|
+
publicWorkflowCount: discovered.length,
|
|
244
|
+
activePublicWorkflowCount: activeWorkflows.length,
|
|
245
|
+
retiredWorkflowCount: contract.retiredWorkflowSurfaces.length,
|
|
246
|
+
inventoriedWorkflowCount: contract.inventoryWorkflows.length,
|
|
247
|
+
inventoryCoveragePercent: 100,
|
|
248
|
+
governedWorkflowCount: contract.bootstrapGovernedWorkflows.length,
|
|
249
|
+
governedCoveragePercent: Number(
|
|
250
|
+
(
|
|
251
|
+
(contract.bootstrapGovernedWorkflows.length / activeWorkflows.length) *
|
|
252
|
+
100
|
|
253
|
+
).toFixed(2),
|
|
254
|
+
),
|
|
255
|
+
migration: contract.migration,
|
|
256
|
+
}),
|
|
257
|
+
);
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
closeDevDeliveryWarrant,
|
|
7
7
|
createDevDeliveryQueue,
|
|
8
8
|
createNativeCommandContract,
|
|
9
|
+
fenceDevDeliveryWriterProtocol,
|
|
9
10
|
heartbeatDevDeliveryWarrant,
|
|
10
11
|
observeDevDeliveryQueue,
|
|
11
12
|
qualifyDevDeliveryWarrant,
|
|
@@ -91,6 +92,13 @@ function requireTerminalEvidenceCas(options) {
|
|
|
91
92
|
"terminal evidence reconciliation execute requires expected-old CAS",
|
|
92
93
|
);
|
|
93
94
|
}
|
|
95
|
+
if (
|
|
96
|
+
options.command === "fence-writer-protocol" &&
|
|
97
|
+
options.execute &&
|
|
98
|
+
!options.expectedOldStateRoot
|
|
99
|
+
) {
|
|
100
|
+
throw new Error("writer protocol fence execute requires expected-old CAS");
|
|
101
|
+
}
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
function normalizeRepository(value) {
|
|
@@ -175,6 +183,9 @@ function jsonObject(value, label) {
|
|
|
175
183
|
}
|
|
176
184
|
|
|
177
185
|
function transitionFor(command, queue, options) {
|
|
186
|
+
if (command === "fence-writer-protocol") {
|
|
187
|
+
return fenceDevDeliveryWriterProtocol(queue, { now: options.now });
|
|
188
|
+
}
|
|
178
189
|
if (command === "submit") {
|
|
179
190
|
const nativeCommandContract = options.environmentRoot
|
|
180
191
|
? createNativeCommandContract(options.nativeCommand)
|
|
@@ -430,20 +441,29 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
|
|
|
430
441
|
options.command,
|
|
431
442
|
),
|
|
432
443
|
});
|
|
444
|
+
let concurrencyRecovery = null;
|
|
433
445
|
if (
|
|
434
446
|
options.expectedOldStateRoot &&
|
|
435
447
|
loaded.queue.stateRoot !== options.expectedOldStateRoot
|
|
436
448
|
) {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
449
|
+
if (options.command !== "heartbeat") {
|
|
450
|
+
throw new Error(
|
|
451
|
+
`expected-old state drift: ${loaded.queue.stateRoot} != ${options.expectedOldStateRoot}`,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
concurrencyRecovery = {
|
|
455
|
+
schema: "kungfu.buildchain.dev-delivery-concurrency-recovery/v1",
|
|
456
|
+
action: "heartbeat-state-root-rebased",
|
|
457
|
+
requestedStateRoot: options.expectedOldStateRoot,
|
|
458
|
+
observedStateRoot: loaded.queue.stateRoot,
|
|
459
|
+
observedCommitSha: loaded.commitSha,
|
|
460
|
+
};
|
|
440
461
|
}
|
|
441
462
|
if (options.command === "observe") return observeQueue(loaded, options);
|
|
442
463
|
const initialLoaded = loaded;
|
|
443
464
|
let changed = transitionFor(options.command, loaded.queue, options);
|
|
444
465
|
let mutates = changed.queue.stateRoot !== loaded.queue.stateRoot;
|
|
445
466
|
let write = null;
|
|
446
|
-
let concurrencyRecovery = null;
|
|
447
467
|
if (options.execute && mutates) {
|
|
448
468
|
if (changed.receipt.expectedOldStateRoot !== loaded.queue.stateRoot) {
|
|
449
469
|
throw new Error(
|
|
@@ -508,7 +528,7 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
|
|
|
508
528
|
}
|
|
509
529
|
|
|
510
530
|
function usage() {
|
|
511
|
-
return "Usage:\n buildchain dev warrant <submit|select|heartbeat|qualify|recover|recover-legacy-terminal|close|settle|reconcile-terminal-evidence|cancel-queued|observe> --repository owner/repo --branch dev/vN/vN.M [--execute] [--output FILE] [--json]\n\nLegacy terminal recovery:\n recover-legacy-terminal --expected-old sha256:... --legacy-terminal-recovery FILE [--execute]\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";
|
|
531
|
+
return "Usage:\n buildchain dev warrant <fence-writer-protocol|submit|select|heartbeat|qualify|recover|recover-legacy-terminal|close|settle|reconcile-terminal-evidence|cancel-queued|observe> --repository owner/repo --branch dev/vN/vN.M [--execute] [--output FILE] [--json]\n\nWriter protocol fence:\n fence-writer-protocol --expected-old sha256:... [--execute]\n\nLegacy terminal recovery:\n recover-legacy-terminal --expected-old sha256:... --legacy-terminal-recovery FILE [--execute]\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";
|
|
512
532
|
}
|
|
513
533
|
|
|
514
534
|
async function main() {
|
|
@@ -520,6 +540,7 @@ async function main() {
|
|
|
520
540
|
const options = devDeliveryCliOptions(args);
|
|
521
541
|
if (
|
|
522
542
|
![
|
|
543
|
+
"fence-writer-protocol",
|
|
523
544
|
"submit",
|
|
524
545
|
"select",
|
|
525
546
|
"heartbeat",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { migrateV4UniversalWorkflowFacade } from "./generate-v4-universal-workflow-facades.mjs";
|
|
6
7
|
|
|
7
8
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
9
|
const sourcePath = path.join(root, ".github/workflows/.build.yml");
|
|
@@ -403,7 +404,10 @@ export function generateChannelBuildWorkflow(source) {
|
|
|
403
404
|
].join("\n"),
|
|
404
405
|
)
|
|
405
406
|
.replace("\n alpha:\n", `\n${routerControllerPlanJob()}\n\n alpha:\n`);
|
|
406
|
-
return
|
|
407
|
+
return migrateV4UniversalWorkflowFacade(
|
|
408
|
+
`${generated.trimEnd()}\n\n${routerControllerReceiptJob()}\n\n${routerAggregateJob()}\n`,
|
|
409
|
+
".github/workflows/build.yml",
|
|
410
|
+
);
|
|
407
411
|
}
|
|
408
412
|
|
|
409
413
|
function main() {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { migrateV4UniversalWorkflowFacade } from "./generate-v4-universal-workflow-facades.mjs";
|
|
6
7
|
|
|
7
8
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
9
|
const sourcePath = path.join(root, ".github/workflows/.release-candidate-promote.yml");
|
|
@@ -74,7 +75,7 @@ function publicInputs(inputBlock) {
|
|
|
74
75
|
function publicOutputs(outputBlock) {
|
|
75
76
|
const forwarded = entries(outputBlock).map((entry) => entry.source.replace(
|
|
76
77
|
/^ value:.*$/m,
|
|
77
|
-
` value: \${{ jobs.
|
|
78
|
+
` value: \${{ jobs.invoke.outputs.${entry.name} }}`,
|
|
78
79
|
));
|
|
79
80
|
const routed = [
|
|
80
81
|
["buildchain-channel", "Selected promotion workflow-shell channel", "channel"],
|
|
@@ -214,8 +215,9 @@ function consumerAdmissionJob() {
|
|
|
214
215
|
id: policy
|
|
215
216
|
env:
|
|
216
217
|
BUILDCHAIN_CONSUMER_ROOT: .buildchain/consumer
|
|
218
|
+
BUILDCHAIN_INVOCATION_SOURCE_PATH: \${{ inputs.publication-publisher-workflow-path == '.github/workflows/buildchain-ref-promotion-recovery.yml' && '.github/workflows/release-candidate-promote.yml' || inputs.publication-publisher-workflow-path }}
|
|
217
219
|
BUILDCHAIN_EXPECTED_INVOCATION_CHANNEL: \${{ needs.resolve-promotion.outputs.channel }}
|
|
218
|
-
BUILDCHAIN_INVOKED_WORKFLOW: .github/workflows/release-candidate-promote.yml
|
|
220
|
+
BUILDCHAIN_INVOKED_WORKFLOW: \${{ inputs.publication-publisher-workflow-path == '.github/workflows/buildchain-ref-promotion-recovery.yml' && '.github/workflows/.release-candidate-promote.yml' || '.github/workflows/release-candidate-promote.yml' }}
|
|
219
221
|
BUILDCHAIN_WORKFLOW_SHA: \${{ needs.resolve-promotion.outputs.router-sha }}
|
|
220
222
|
BUILDCHAIN_RUNTIME_SHA: \${{ needs.resolve-promotion.outputs.router-sha }}
|
|
221
223
|
BUILDCHAIN_STABLE_CONTRACT_LOCK_PATH: \${{ inputs.buildchain-stable-contract-lock-path }}
|
|
@@ -292,7 +294,7 @@ export function generateChannelPromotionWorkflow(source, { major = 2, shellRouti
|
|
|
292
294
|
},
|
|
293
295
|
};
|
|
294
296
|
const alphaRoute = validateWorkflowRoute("alpha", routes.alpha, `v${major}-alpha`);
|
|
295
|
-
|
|
297
|
+
validateWorkflowRoute("stable", routes.stable, `v${major}`);
|
|
296
298
|
const inputs = blockBetween(source, " inputs:\n", " secrets:\n");
|
|
297
299
|
const secrets = blockBetween(source, " secrets:\n", " outputs:\n");
|
|
298
300
|
const outputs = blockBetween(source, " outputs:\n", "\nconcurrency:\n");
|
|
@@ -300,9 +302,8 @@ export function generateChannelPromotionWorkflow(source, { major = 2, shellRouti
|
|
|
300
302
|
for (const required of ["buildchain-ref", "buildchain-contract-lock-path", "channel", "target-ref", ...internalInputs]) {
|
|
301
303
|
if (!inputNames.includes(required)) throw new Error(`advanced promotion workflow missing input: ${required}`);
|
|
302
304
|
}
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
return `# Generated by scripts/generate-channel-promotion-workflow.mjs. Do not edit directly.
|
|
305
|
+
const invokeForwarded = routedInputs(inputNames, alphaRoute, major);
|
|
306
|
+
return migrateV4UniversalWorkflowFacade(`# Generated by scripts/generate-channel-promotion-workflow.mjs. Do not edit directly.
|
|
306
307
|
name: Release Candidate Promote
|
|
307
308
|
|
|
308
309
|
on:
|
|
@@ -336,7 +337,7 @@ jobs:
|
|
|
336
337
|
target-ref: \${{ steps.route.outputs.target-ref }}
|
|
337
338
|
router-ref: \${{ steps.router.outputs.ref }}
|
|
338
339
|
router-sha: \${{ steps.router.outputs.sha }}
|
|
339
|
-
shell-ref: \${{ steps.
|
|
340
|
+
shell-ref: \${{ steps.identities.outputs.shell-ref }}
|
|
340
341
|
shell-call-ref: \${{ steps.identities.outputs.shell-call-ref }}
|
|
341
342
|
shell-sha: \${{ steps.identities.outputs.shell-sha }}
|
|
342
343
|
runtime-ref: \${{ steps.route.outputs.runtime-ref }}
|
|
@@ -425,32 +426,17 @@ jobs:
|
|
|
425
426
|
shell: bash
|
|
426
427
|
env:
|
|
427
428
|
GITHUB_TOKEN: \${{ github.token }}
|
|
428
|
-
|
|
429
|
-
SELECTED_SHELL_REF: \${{ steps.route.outputs.shell-ref }}
|
|
430
|
-
ALPHA_SHELL_REF: ${alphaRoute.logicalRef}
|
|
431
|
-
ALPHA_SHELL_CALL_REF: ${alphaRoute.callRef}
|
|
432
|
-
STABLE_SHELL_REF: ${stableRoute.logicalRef}
|
|
433
|
-
STABLE_SHELL_CALL_REF: ${stableRoute.callRef}
|
|
429
|
+
SELECTED_SHELL_REF: ${alphaRoute.logicalRef}
|
|
434
430
|
run: |
|
|
435
431
|
set -euo pipefail
|
|
436
|
-
if [[ "\${CHANNEL}" = "alpha" ]]; then
|
|
437
|
-
expected_ref="\${ALPHA_SHELL_REF}"
|
|
438
|
-
call_ref="\${ALPHA_SHELL_CALL_REF}"
|
|
439
|
-
else
|
|
440
|
-
expected_ref="\${STABLE_SHELL_REF}"
|
|
441
|
-
call_ref="\${STABLE_SHELL_CALL_REF}"
|
|
442
|
-
fi
|
|
443
|
-
if [[ "\${SELECTED_SHELL_REF}" != "\${expected_ref}" ]]; then
|
|
444
|
-
echo "::error::Selected promotion shell ref \${SELECTED_SHELL_REF} does not match configured \${expected_ref}"
|
|
445
|
-
exit 1
|
|
446
|
-
fi
|
|
447
432
|
node .buildchain/router/scripts/promotion-identity-resolver.mjs \\
|
|
448
433
|
--repository "\${{ steps.router.outputs.repository }}" \\
|
|
449
434
|
--router-ref "\${{ steps.router.outputs.ref }}" \\
|
|
450
435
|
--router-sha "\${{ steps.router.outputs.sha }}" \\
|
|
451
436
|
--shell-ref "\${SELECTED_SHELL_REF}" \\
|
|
452
|
-
--shell-call-ref "\${
|
|
437
|
+
--shell-call-ref "\${SELECTED_SHELL_REF}" \\
|
|
453
438
|
--runtime-ref "\${{ steps.route.outputs.runtime-ref }}"
|
|
439
|
+
echo "shell-ref=\${SELECTED_SHELL_REF}" >> "$GITHUB_OUTPUT"
|
|
454
440
|
|
|
455
441
|
- name: Checkout selected promotion workflow shell
|
|
456
442
|
uses: actions/checkout@v7.0.0
|
|
@@ -500,29 +486,21 @@ jobs:
|
|
|
500
486
|
- name: Verify immutable promotion checkouts
|
|
501
487
|
shell: bash
|
|
502
488
|
env:
|
|
503
|
-
|
|
504
|
-
ALPHA_SHELL_WORKFLOW_PATH: ${alphaRoute.workflowPath}
|
|
505
|
-
STABLE_SHELL_WORKFLOW_PATH: ${stableRoute.workflowPath}
|
|
489
|
+
SHELL_WORKFLOW_PATH: ${alphaRoute.workflowPath}
|
|
506
490
|
ROUTER_SHA: \${{ steps.router.outputs.sha }}
|
|
507
491
|
SHELL_SHA: \${{ steps.identities.outputs.shell-sha }}
|
|
508
492
|
RUNTIME_SHA: \${{ steps.identities.outputs.runtime-sha }}
|
|
509
493
|
run: |
|
|
510
494
|
set -euo pipefail
|
|
511
|
-
|
|
512
|
-
workflow_path="\${ALPHA_SHELL_WORKFLOW_PATH}"
|
|
513
|
-
else
|
|
514
|
-
workflow_path="\${STABLE_SHELL_WORKFLOW_PATH}"
|
|
515
|
-
fi
|
|
516
|
-
test -f ".buildchain/shell/\${workflow_path}"
|
|
495
|
+
test -f ".buildchain/shell/\${SHELL_WORKFLOW_PATH}"
|
|
517
496
|
[[ "$(git -C .buildchain/router rev-parse HEAD)" = "\${ROUTER_SHA}" ]] || { echo "::error::Promotion router checkout moved"; exit 1; }
|
|
518
497
|
[[ "$(git -C .buildchain/shell rev-parse HEAD)" = "\${SHELL_SHA}" ]] || { echo "::error::Promotion shell checkout moved"; exit 1; }
|
|
519
498
|
[[ "$(git -C .buildchain/runtime rev-parse HEAD)" = "\${RUNTIME_SHA}" ]] || { echo "::error::Promotion runtime checkout moved"; exit 1; }
|
|
520
499
|
|
|
521
500
|
${consumerAdmissionJob()}
|
|
522
|
-
|
|
523
|
-
name:
|
|
501
|
+
invoke:
|
|
502
|
+
name: Invoke the single v4 publisher adapter
|
|
524
503
|
needs: [resolve-promotion, consumer-admission]
|
|
525
|
-
if: \${{ needs.resolve-promotion.outputs.channel == 'alpha' }}
|
|
526
504
|
uses: kungfu-systems/buildchain/${alphaRoute.workflowPath}@${alphaRoute.callRef}
|
|
527
505
|
permissions:
|
|
528
506
|
actions: write
|
|
@@ -534,27 +512,9 @@ ${consumerAdmissionJob()}
|
|
|
534
512
|
issues: write
|
|
535
513
|
pull-requests: write
|
|
536
514
|
with:
|
|
537
|
-
${
|
|
538
|
-
secrets: inherit
|
|
539
|
-
|
|
540
|
-
stable:
|
|
541
|
-
name: Promote with stable workflow shell
|
|
542
|
-
needs: [resolve-promotion, consumer-admission]
|
|
543
|
-
if: \${{ needs.resolve-promotion.outputs.channel == 'stable' }}
|
|
544
|
-
uses: kungfu-systems/buildchain/${stableRoute.workflowPath}@${stableRoute.callRef}
|
|
545
|
-
permissions:
|
|
546
|
-
actions: write
|
|
547
|
-
artifact-metadata: write
|
|
548
|
-
attestations: write
|
|
549
|
-
checks: write
|
|
550
|
-
contents: write
|
|
551
|
-
id-token: write
|
|
552
|
-
issues: write
|
|
553
|
-
pull-requests: write
|
|
554
|
-
with:
|
|
555
|
-
${stableForwarded}
|
|
515
|
+
${invokeForwarded}
|
|
556
516
|
secrets: inherit
|
|
557
|
-
|
|
517
|
+
`, ".github/workflows/release-candidate-promote.yml");
|
|
558
518
|
}
|
|
559
519
|
|
|
560
520
|
function main() {
|