@kungfu-tech/buildchain 4.0.1-alpha.5 → 4.0.1-alpha.55
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/README.md +9 -6
- package/actions/release-tail/README.md +8 -0
- package/actions/v4-release-candidate-promote/README.md +3 -0
- package/architecture/agent-change-map.md +1015 -0
- package/architecture/ci-lane-change-budget.json +256 -0
- package/architecture/decisions/0004-v4-declarative-release-tail.md +47 -0
- package/architecture/internal-capabilities.json +220 -15
- package/architecture/maintainability-debt.json +3229 -0
- package/architecture/maintainability-policy.json +489 -132
- package/architecture/release-tail-contract-inventory.json +5 -2
- package/architecture/v3-core-mechanism-inventory.json +4 -3
- package/architecture/v3-v4-capability-closure.md +59 -0
- package/architecture/v3-v4-live-capability-inventory.json +102404 -0
- package/architecture/v4-adopter-delivery-parity.json +32 -5
- package/architecture/v4-cross-platform-adopter-qualification.json +52 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +3 -3
- package/architecture/v4-floating-consumer-policy.json +6 -0
- package/architecture/v4-publication-rehearsal-parity.json +84 -0
- package/architecture/v4-runtime-semantic-closure.json +710 -0
- package/bin/buildchain.mjs +7 -1
- package/bin/internal/adopter-delivery-cli.mjs +70 -0
- package/bin/internal/command-registry.mjs +1 -0
- package/bin/internal/trust-release-release-handlers.mjs +5 -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-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 +1 -1
- package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
- package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
- package/contracts/v4-publication-qualification-v1.schema.json +51 -0
- package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/dist/site/agent-index.json +4 -0
- package/dist/site/artifact-schemas.json +8 -0
- package/dist/site/buildchain-contract.json +125 -34
- package/dist/site/buildchain-site.json +308 -33
- package/dist/site/capability-registry.json +13 -12
- package/dist/site/cli-registry.json +57 -2
- package/dist/site/controller-registry.json +41 -5
- package/dist/site/kfd-claims.json +438 -24
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +22 -7
- package/dist/site/node-api-registry.json +12843 -9314
- package/dist/site/page-registry.json +288 -21
- package/dist/site/public-surface-audit.json +281 -21
- package/dist/site/publication-authority-registry.json +66 -8
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +20 -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-publication-qualification-v1.schema.json +51 -0
- package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/dist/site/site-manifest.json +19 -11
- package/dist/site/workflow-registry.json +195 -17
- package/docs/MAP.md +4 -2
- package/docs/cli-reference.md +70 -2
- package/docs/cli.md +8 -0
- package/docs/dev-delivery-warrant.md +34 -10
- package/docs/node-api-reference.md +524 -279
- package/docs/publish-transaction.md +11 -4
- package/docs/release-candidate.md +7 -0
- package/docs/release-tail-provider-plane.md +30 -0
- package/docs/v4-adopter-delivery.md +152 -0
- package/docs/v4-declarative-release-tail-migration.md +43 -0
- package/docs/v4-publication-rehearsal.md +130 -0
- package/package.json +24 -4
- package/packages/core/adopter-delivery-passport.js +295 -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 +1 -0
- package/packages/core/buildchain-config.js +84 -0
- package/packages/core/buildchain-contract.js +31 -1
- 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 +2 -0
- package/packages/core/ci-lane-change-budget.js +247 -0
- package/packages/core/dev-delivery-native-proof.js +43 -27
- package/packages/core/dev-delivery-warrant-settlement.js +239 -3
- package/packages/core/dev-delivery-warrant-state.js +46 -10
- package/packages/core/dev-delivery-warrant.js +29 -7
- package/packages/core/github-governance-authority.js +5 -4
- package/packages/core/index.js +6 -0
- package/packages/core/legacy-kfd-adopter-driver.js +167 -0
- package/packages/core/paper-scaffold-content.js +401 -0
- package/packages/core/paper.js +12 -378
- package/packages/core/publication-control-plane-audit.js +135 -111
- package/packages/core/publication-rehearsal-projection.js +104 -0
- package/packages/core/publication-rehearsal-runtime.js +190 -0
- package/packages/core/published-delivery-authority.js +266 -0
- package/packages/core/release-candidate-recovery.js +71 -15
- package/packages/core/release-candidate.js +2 -2
- package/packages/core/release-passport.js +95 -170
- package/packages/core/v4-adopter-delivery-parity.js +3 -3
- package/packages/core/v4-adopter-delivery.js +359 -0
- package/packages/core/v4-cross-platform-adopter-qualification.js +343 -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 +139 -35
- package/packages/core/v4-floating-consumer-release-passport.js +154 -8
- package/packages/core/v4-publication-qualification.js +222 -0
- 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/workflow-yaml-contract.js +24 -2
- package/scripts/artifact-signing-controller-core.mjs +1 -1
- package/scripts/audit-publication-control-plane.mjs +17 -17
- package/scripts/buildchain-cli-help.mjs +13 -0
- package/scripts/check-ci-lane-change-budget.mjs +100 -0
- package/scripts/check-internal-architecture.mjs +23 -3
- package/scripts/check-inventory.mjs +24 -6
- package/scripts/check-maintainability.mjs +145 -4
- package/scripts/check-project-cut-merge-queue-admission.mjs +87 -0
- package/scripts/check-v3-v4-capability-inventory.mjs +574 -0
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +30 -4
- package/scripts/check-v4-runtime-semantic-closure.mjs +266 -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-warrant-options.mjs +27 -11
- package/scripts/dev-delivery-warrant-store.mjs +10 -6
- package/scripts/dev-delivery-warrant.mjs +50 -6
- package/scripts/dev-pr-auto-merge.mjs +25 -11
- package/scripts/dev-pr-delivery-warrant.mjs +45 -0
- package/scripts/dev-pr-prequeue-guard.mjs +0 -18
- package/scripts/dev-qualification-patrol.mjs +9 -9
- package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
- package/scripts/generate-agent-change-map.mjs +118 -0
- package/scripts/generate-channel-promotion-workflow.mjs +2 -2
- package/scripts/generate-release-candidate-passport.mjs +177 -0
- package/scripts/generate-site-bundle.mjs +36 -0
- package/scripts/maintainability-domain-metrics.mjs +237 -0
- package/scripts/maintainability-governance.mjs +473 -0
- package/scripts/maintainability-metrics.mjs +28 -6
- package/scripts/project-cut-merge-queue-admission.mjs +394 -0
- package/scripts/promotion-channel-router.mjs +4 -4
- package/scripts/promotion-routing-evidence.mjs +35 -6
- package/scripts/release-candidate-anchor-provenance.mjs +99 -0
- package/scripts/release-candidate-resolver.mjs +44 -0
- package/scripts/release-line-policy.mjs +25 -15
- package/scripts/release-tail.mjs +68 -3
- package/scripts/resume-from-candidate-run.mjs +595 -65
- package/scripts/run-lifecycle-core.mjs +341 -376
- package/scripts/site-capability-metadata.mjs +21 -0
- package/scripts/stable-candidate-qualification.mjs +9 -10
- package/scripts/v3-v4-capability-catalog.mjs +439 -0
- package/scripts/v4-consumer-policy.mjs +4 -0
- package/scripts/v4-cross-platform-adopter-qualification.mjs +228 -0
- package/scripts/v4-declarative-promotion-admission.mjs +42 -0
- package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
executeReleaseTailTransaction,
|
|
7
|
+
releaseTailRoot,
|
|
8
|
+
validateReleaseTailTransaction,
|
|
9
|
+
} from "./release-tail-provider-plane.js";
|
|
10
|
+
import {
|
|
11
|
+
V4_PUBLICATION_REHEARSAL_AUTHORITY_CONTRACT,
|
|
12
|
+
V4_PUBLICATION_REHEARSAL_CAPSULE_CONTRACT,
|
|
13
|
+
V4_PUBLICATION_REHEARSAL_CORE_VERSION,
|
|
14
|
+
V4_PUBLICATION_REHEARSAL_EVIDENCE_CONTRACT,
|
|
15
|
+
V4_PUBLICATION_REHEARSAL_PROVIDER_POLICY_CONTRACT,
|
|
16
|
+
V4PublicationRehearsalFault,
|
|
17
|
+
assertPublicationRehearsalExactKeys as exactKeys,
|
|
18
|
+
createV4PublicationRehearsalCapsule,
|
|
19
|
+
createV4PublicationRehearsalProviderPolicy,
|
|
20
|
+
publicationRehearsalFault as fault,
|
|
21
|
+
publicationRehearsalRoot,
|
|
22
|
+
validateV4PublicationRehearsalCapsule,
|
|
23
|
+
validateV4PublicationRehearsalProviderBindings,
|
|
24
|
+
} from "./v4-publication-rehearsal-capsule.js";
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
V4_PUBLICATION_REHEARSAL_AUTHORITY_CONTRACT,
|
|
28
|
+
V4_PUBLICATION_REHEARSAL_CAPSULE_CONTRACT,
|
|
29
|
+
V4_PUBLICATION_REHEARSAL_CORE_VERSION,
|
|
30
|
+
V4_PUBLICATION_REHEARSAL_EVIDENCE_CONTRACT,
|
|
31
|
+
V4_PUBLICATION_REHEARSAL_PROVIDER_POLICY_CONTRACT,
|
|
32
|
+
V4PublicationRehearsalFault,
|
|
33
|
+
createV4PublicationRehearsalCapsule,
|
|
34
|
+
createV4PublicationRehearsalProviderPolicy,
|
|
35
|
+
validateV4PublicationRehearsalCapsule,
|
|
36
|
+
validateV4PublicationRehearsalProviderBindings,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const ROOT = /^sha256:[0-9a-f]{64}$/u;
|
|
40
|
+
const MODES = new Set(["simulate", "replay", "provider"]);
|
|
41
|
+
|
|
42
|
+
function fileRoot(filePath) {
|
|
43
|
+
const hash = crypto.createHash("sha256");
|
|
44
|
+
const descriptor = fs.openSync(filePath, "r");
|
|
45
|
+
const buffer = Buffer.allocUnsafe(8 * 1024 * 1024);
|
|
46
|
+
try {
|
|
47
|
+
for (;;) {
|
|
48
|
+
const count = fs.readSync(descriptor, buffer, 0, buffer.length, null);
|
|
49
|
+
if (count === 0) break;
|
|
50
|
+
hash.update(buffer.subarray(0, count));
|
|
51
|
+
}
|
|
52
|
+
} finally {
|
|
53
|
+
fs.closeSync(descriptor);
|
|
54
|
+
}
|
|
55
|
+
return `sha256:${hash.digest("hex")}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveCandidateFile(candidateRootPath, relative, location) {
|
|
59
|
+
const base = fs.realpathSync(candidateRootPath);
|
|
60
|
+
const resolved = path.resolve(base, relative);
|
|
61
|
+
if (!resolved.startsWith(`${base}${path.sep}`))
|
|
62
|
+
fault(
|
|
63
|
+
"publication-rehearsal-path-escape",
|
|
64
|
+
location,
|
|
65
|
+
"candidate path escapes the explicit root",
|
|
66
|
+
);
|
|
67
|
+
if (
|
|
68
|
+
!fs.existsSync(resolved) ||
|
|
69
|
+
!fs.statSync(resolved).isFile() ||
|
|
70
|
+
fs.lstatSync(resolved).isSymbolicLink() ||
|
|
71
|
+
!fs.realpathSync(resolved).startsWith(`${base}${path.sep}`)
|
|
72
|
+
)
|
|
73
|
+
fault(
|
|
74
|
+
"publication-rehearsal-file-missing",
|
|
75
|
+
location,
|
|
76
|
+
"regular in-root file required",
|
|
77
|
+
);
|
|
78
|
+
return resolved;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function resolveCandidateOutput(candidateRootPath, relative, location) {
|
|
82
|
+
const base = fs.realpathSync(candidateRootPath);
|
|
83
|
+
const resolved = path.resolve(base, relative);
|
|
84
|
+
if (!resolved.startsWith(`${base}${path.sep}`))
|
|
85
|
+
fault(
|
|
86
|
+
"publication-rehearsal-path-escape",
|
|
87
|
+
location,
|
|
88
|
+
"candidate output escapes the explicit root",
|
|
89
|
+
);
|
|
90
|
+
for (
|
|
91
|
+
let current = path.dirname(resolved);
|
|
92
|
+
current !== base;
|
|
93
|
+
current = path.dirname(current)
|
|
94
|
+
) {
|
|
95
|
+
if (!fs.existsSync(current)) continue;
|
|
96
|
+
if (
|
|
97
|
+
fs.lstatSync(current).isSymbolicLink() ||
|
|
98
|
+
!fs.statSync(current).isDirectory() ||
|
|
99
|
+
!fs.realpathSync(current).startsWith(`${base}${path.sep}`)
|
|
100
|
+
)
|
|
101
|
+
fault(
|
|
102
|
+
"publication-rehearsal-path-escape",
|
|
103
|
+
location,
|
|
104
|
+
"candidate output ancestor is not a regular in-root directory",
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
if (
|
|
108
|
+
fs.existsSync(resolved) &&
|
|
109
|
+
(fs.lstatSync(resolved).isSymbolicLink() ||
|
|
110
|
+
!fs.statSync(resolved).isFile() ||
|
|
111
|
+
!fs.realpathSync(resolved).startsWith(`${base}${path.sep}`))
|
|
112
|
+
)
|
|
113
|
+
fault(
|
|
114
|
+
"publication-rehearsal-path-escape",
|
|
115
|
+
location,
|
|
116
|
+
"candidate output is not a regular in-root file",
|
|
117
|
+
);
|
|
118
|
+
return resolved;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function verifyV4PublicationRehearsalCapsule({
|
|
122
|
+
capsule,
|
|
123
|
+
candidateRoot: candidateRootPath,
|
|
124
|
+
}) {
|
|
125
|
+
const normalized = validateV4PublicationRehearsalCapsule(capsule);
|
|
126
|
+
if (!path.isAbsolute(candidateRootPath || ""))
|
|
127
|
+
fault(
|
|
128
|
+
"publication-rehearsal-implicit-root-forbidden",
|
|
129
|
+
"$/candidateRoot",
|
|
130
|
+
"explicit absolute candidate root required",
|
|
131
|
+
);
|
|
132
|
+
if (
|
|
133
|
+
!fs.existsSync(candidateRootPath) ||
|
|
134
|
+
!fs.statSync(candidateRootPath).isDirectory()
|
|
135
|
+
)
|
|
136
|
+
fault(
|
|
137
|
+
"publication-rehearsal-candidate-missing",
|
|
138
|
+
"$/candidateRoot",
|
|
139
|
+
"candidate directory required",
|
|
140
|
+
);
|
|
141
|
+
for (const entry of normalized.files) {
|
|
142
|
+
const resolved = resolveCandidateFile(
|
|
143
|
+
candidateRootPath,
|
|
144
|
+
entry.path,
|
|
145
|
+
`$/files/${entry.path}`,
|
|
146
|
+
);
|
|
147
|
+
if (
|
|
148
|
+
fs.statSync(resolved).size !== entry.size ||
|
|
149
|
+
fileRoot(resolved) !== entry.root
|
|
150
|
+
)
|
|
151
|
+
fault(
|
|
152
|
+
"publication-rehearsal-file-tampered",
|
|
153
|
+
`$/files/${entry.path}`,
|
|
154
|
+
"candidate bytes do not match the capsule",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return normalized;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function resolveV4PublicationRehearsalProviderBindings({
|
|
161
|
+
capsule,
|
|
162
|
+
candidateRoot: candidateRootPath,
|
|
163
|
+
providerBindings = capsule?.providerBindings,
|
|
164
|
+
}) {
|
|
165
|
+
const normalized = verifyV4PublicationRehearsalCapsule({
|
|
166
|
+
capsule,
|
|
167
|
+
candidateRoot: candidateRootPath,
|
|
168
|
+
});
|
|
169
|
+
const supplied =
|
|
170
|
+
validateV4PublicationRehearsalProviderBindings(providerBindings);
|
|
171
|
+
if (
|
|
172
|
+
releaseTailRoot(supplied) !== normalized.providerBindingsRoot ||
|
|
173
|
+
JSON.stringify(supplied) !== JSON.stringify(normalized.providerBindings)
|
|
174
|
+
)
|
|
175
|
+
fault(
|
|
176
|
+
"publication-rehearsal-provider-bindings-root-mismatch",
|
|
177
|
+
"$/providerBindings",
|
|
178
|
+
"external provider bindings do not match the capsule",
|
|
179
|
+
);
|
|
180
|
+
return {
|
|
181
|
+
schema: supplied.schema,
|
|
182
|
+
artifacts: Object.fromEntries(
|
|
183
|
+
Object.entries(supplied.artifacts).map(([role, binding]) => [
|
|
184
|
+
role,
|
|
185
|
+
{
|
|
186
|
+
...binding,
|
|
187
|
+
path: resolveCandidateFile(
|
|
188
|
+
candidateRootPath,
|
|
189
|
+
binding.path,
|
|
190
|
+
`$/providerBindings/artifacts/${role}/path`,
|
|
191
|
+
),
|
|
192
|
+
},
|
|
193
|
+
]),
|
|
194
|
+
),
|
|
195
|
+
documents: Object.fromEntries(
|
|
196
|
+
Object.entries(supplied.documents).map(([capabilityId, binding]) => [
|
|
197
|
+
capabilityId,
|
|
198
|
+
{
|
|
199
|
+
...binding,
|
|
200
|
+
path: resolveCandidateFile(
|
|
201
|
+
candidateRootPath,
|
|
202
|
+
binding.path,
|
|
203
|
+
`$/providerBindings/documents/${capabilityId}/path`,
|
|
204
|
+
),
|
|
205
|
+
},
|
|
206
|
+
]),
|
|
207
|
+
),
|
|
208
|
+
evidence: {
|
|
209
|
+
inputs: supplied.evidence.inputs.map((inputPath, index) =>
|
|
210
|
+
resolveCandidateFile(
|
|
211
|
+
candidateRootPath,
|
|
212
|
+
inputPath,
|
|
213
|
+
`$/providerBindings/evidence/inputs/${index}`,
|
|
214
|
+
),
|
|
215
|
+
),
|
|
216
|
+
output: resolveCandidateOutput(
|
|
217
|
+
candidateRootPath,
|
|
218
|
+
supplied.evidence.output,
|
|
219
|
+
"$/providerBindings/evidence/output",
|
|
220
|
+
),
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function simulatedAdapters(declaration, transcript) {
|
|
226
|
+
const applied = new Set();
|
|
227
|
+
return Object.fromEntries(
|
|
228
|
+
declaration.capabilities.map((capability) => [
|
|
229
|
+
capability.adapter,
|
|
230
|
+
{
|
|
231
|
+
async readback(effect) {
|
|
232
|
+
const response = applied.has(effect.operationId)
|
|
233
|
+
? {
|
|
234
|
+
outcome: "observed",
|
|
235
|
+
subjectRoot: effect.subjectRoot,
|
|
236
|
+
targetRoot: effect.targetRoot,
|
|
237
|
+
evidenceRoots: [effect.targetRoot],
|
|
238
|
+
providerCode: "simulation-observed",
|
|
239
|
+
}
|
|
240
|
+
: {
|
|
241
|
+
outcome: "absent",
|
|
242
|
+
subjectRoot: "",
|
|
243
|
+
targetRoot: "",
|
|
244
|
+
evidenceRoots: [],
|
|
245
|
+
providerCode: "simulation-absent",
|
|
246
|
+
};
|
|
247
|
+
transcript.push({
|
|
248
|
+
operationId: effect.operationId,
|
|
249
|
+
method: "readback",
|
|
250
|
+
requestRoot: effect.effectRoot,
|
|
251
|
+
response,
|
|
252
|
+
});
|
|
253
|
+
return response;
|
|
254
|
+
},
|
|
255
|
+
async apply(effect) {
|
|
256
|
+
applied.add(effect.operationId);
|
|
257
|
+
transcript.push({
|
|
258
|
+
operationId: effect.operationId,
|
|
259
|
+
method: "apply",
|
|
260
|
+
requestRoot: effect.effectRoot,
|
|
261
|
+
response: { outcome: "simulated", code: "effect-disabled" },
|
|
262
|
+
});
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
]),
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function observationAdapters(capsule, transcript, adapters, mode) {
|
|
270
|
+
const records = new Map(
|
|
271
|
+
capsule.expectedObservations.entries.map((entry) => [
|
|
272
|
+
entry.operationId,
|
|
273
|
+
{ ...entry, index: 0 },
|
|
274
|
+
]),
|
|
275
|
+
);
|
|
276
|
+
return Object.fromEntries(
|
|
277
|
+
capsule.declaration.capabilities.map((capability) => {
|
|
278
|
+
const provided = adapters?.[capability.adapter];
|
|
279
|
+
if (
|
|
280
|
+
mode === "provider" &&
|
|
281
|
+
(!provided ||
|
|
282
|
+
typeof provided.readback !== "function" ||
|
|
283
|
+
typeof provided.apply !== "function")
|
|
284
|
+
)
|
|
285
|
+
fault(
|
|
286
|
+
"publication-rehearsal-provider-adapter-missing",
|
|
287
|
+
"$/adapters",
|
|
288
|
+
`adapter missing: ${capability.adapter}`,
|
|
289
|
+
);
|
|
290
|
+
return [
|
|
291
|
+
capability.adapter,
|
|
292
|
+
{
|
|
293
|
+
async readback(effect) {
|
|
294
|
+
const record = records.get(effect.operationId);
|
|
295
|
+
const expected = record?.readbacks[record.index++];
|
|
296
|
+
if (!expected)
|
|
297
|
+
fault(
|
|
298
|
+
"publication-rehearsal-observation-exhausted",
|
|
299
|
+
"$/expectedObservations",
|
|
300
|
+
`readback missing for ${effect.operationId}`,
|
|
301
|
+
);
|
|
302
|
+
const response =
|
|
303
|
+
mode === "replay"
|
|
304
|
+
? structuredClone(expected)
|
|
305
|
+
: await provided.readback(effect);
|
|
306
|
+
if (JSON.stringify(response) !== JSON.stringify(expected))
|
|
307
|
+
fault(
|
|
308
|
+
"publication-rehearsal-provider-readback-mismatch",
|
|
309
|
+
"$/expectedObservations",
|
|
310
|
+
`provider readback drifted for ${effect.operationId}`,
|
|
311
|
+
);
|
|
312
|
+
transcript.push({
|
|
313
|
+
operationId: effect.operationId,
|
|
314
|
+
method: "readback",
|
|
315
|
+
requestRoot: effect.effectRoot,
|
|
316
|
+
response: structuredClone(response),
|
|
317
|
+
});
|
|
318
|
+
return response;
|
|
319
|
+
},
|
|
320
|
+
async apply(effect) {
|
|
321
|
+
const record = records.get(effect.operationId);
|
|
322
|
+
if (!record)
|
|
323
|
+
fault(
|
|
324
|
+
"publication-rehearsal-observation-exhausted",
|
|
325
|
+
"$/expectedObservations",
|
|
326
|
+
`apply missing for ${effect.operationId}`,
|
|
327
|
+
);
|
|
328
|
+
if (mode === "provider") await provided.apply(effect);
|
|
329
|
+
transcript.push({
|
|
330
|
+
operationId: effect.operationId,
|
|
331
|
+
method: "apply",
|
|
332
|
+
requestRoot: effect.effectRoot,
|
|
333
|
+
response: structuredClone(record.apply),
|
|
334
|
+
});
|
|
335
|
+
if (record.apply.outcome !== "applied") {
|
|
336
|
+
const error = new Error(
|
|
337
|
+
"expected provider apply did not succeed",
|
|
338
|
+
);
|
|
339
|
+
error.releaseTailCode = record.apply.code;
|
|
340
|
+
error.releaseTailClass = record.apply.classification;
|
|
341
|
+
throw error;
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
];
|
|
346
|
+
}),
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function createV4PublicationRehearsalAuthority(
|
|
351
|
+
capsule,
|
|
352
|
+
{ authorizationRoot } = {},
|
|
353
|
+
) {
|
|
354
|
+
const normalized = validateV4PublicationRehearsalCapsule(capsule);
|
|
355
|
+
const body = {
|
|
356
|
+
schema: V4_PUBLICATION_REHEARSAL_AUTHORITY_CONTRACT,
|
|
357
|
+
mode: "provider-rehearsal",
|
|
358
|
+
capsuleRoot: normalized.capsuleRoot,
|
|
359
|
+
providerPolicyRoot: normalized.providerPolicy.root,
|
|
360
|
+
providerBindingsRoot: normalized.providerBindingsRoot,
|
|
361
|
+
authorizationRoot: publicationRehearsalRoot(
|
|
362
|
+
authorizationRoot,
|
|
363
|
+
"$/authorizationRoot",
|
|
364
|
+
),
|
|
365
|
+
productionAuthority: false,
|
|
366
|
+
};
|
|
367
|
+
return { ...body, authorityRoot: releaseTailRoot(body) };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function validateAuthority(value, capsule) {
|
|
371
|
+
exactKeys(
|
|
372
|
+
value,
|
|
373
|
+
[
|
|
374
|
+
"schema",
|
|
375
|
+
"mode",
|
|
376
|
+
"capsuleRoot",
|
|
377
|
+
"providerPolicyRoot",
|
|
378
|
+
"providerBindingsRoot",
|
|
379
|
+
"authorizationRoot",
|
|
380
|
+
"productionAuthority",
|
|
381
|
+
"authorityRoot",
|
|
382
|
+
],
|
|
383
|
+
"$/authority",
|
|
384
|
+
);
|
|
385
|
+
const body = { ...value };
|
|
386
|
+
delete body.authorityRoot;
|
|
387
|
+
if (
|
|
388
|
+
value.schema !== V4_PUBLICATION_REHEARSAL_AUTHORITY_CONTRACT ||
|
|
389
|
+
value.mode !== "provider-rehearsal" ||
|
|
390
|
+
value.productionAuthority !== false ||
|
|
391
|
+
value.capsuleRoot !== capsule.capsuleRoot ||
|
|
392
|
+
value.providerPolicyRoot !== capsule.providerPolicy.root ||
|
|
393
|
+
value.providerBindingsRoot !== capsule.providerBindingsRoot ||
|
|
394
|
+
!ROOT.test(value.authorizationRoot) ||
|
|
395
|
+
value.authorityRoot !== releaseTailRoot(body)
|
|
396
|
+
)
|
|
397
|
+
fault(
|
|
398
|
+
"publication-rehearsal-authority-mismatch",
|
|
399
|
+
"$/authority",
|
|
400
|
+
"exact rehearsal-only authority required",
|
|
401
|
+
);
|
|
402
|
+
return structuredClone(value);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export async function executeV4PublicationRehearsal({
|
|
406
|
+
capsule,
|
|
407
|
+
candidateRoot: candidateRootPath,
|
|
408
|
+
mode = "simulate",
|
|
409
|
+
adapters = {},
|
|
410
|
+
authority = null,
|
|
411
|
+
transaction = null,
|
|
412
|
+
checkpoint,
|
|
413
|
+
} = {}) {
|
|
414
|
+
if (!MODES.has(mode))
|
|
415
|
+
fault(
|
|
416
|
+
"invalid-publication-rehearsal-mode",
|
|
417
|
+
"$/mode",
|
|
418
|
+
"simulate, replay, or provider required",
|
|
419
|
+
);
|
|
420
|
+
const normalized = verifyV4PublicationRehearsalCapsule({
|
|
421
|
+
capsule,
|
|
422
|
+
candidateRoot: candidateRootPath,
|
|
423
|
+
});
|
|
424
|
+
const providerAuthority =
|
|
425
|
+
mode === "provider" ? validateAuthority(authority, normalized) : null;
|
|
426
|
+
const current = transaction
|
|
427
|
+
? structuredClone(transaction)
|
|
428
|
+
: structuredClone(normalized.transaction);
|
|
429
|
+
const validation = validateReleaseTailTransaction(current);
|
|
430
|
+
if (
|
|
431
|
+
!validation.valid ||
|
|
432
|
+
current.transactionRoot !== normalized.transaction.transactionRoot ||
|
|
433
|
+
current.declarationRoot !== normalized.transaction.declarationRoot ||
|
|
434
|
+
current.planRoot !== normalized.transaction.planRoot
|
|
435
|
+
)
|
|
436
|
+
fault(
|
|
437
|
+
"publication-rehearsal-transaction-mismatch",
|
|
438
|
+
"$/transaction",
|
|
439
|
+
"transaction is not bound to this capsule",
|
|
440
|
+
);
|
|
441
|
+
const transcript = [];
|
|
442
|
+
const selectedAdapters =
|
|
443
|
+
mode === "simulate"
|
|
444
|
+
? simulatedAdapters(normalized.declaration, transcript)
|
|
445
|
+
: observationAdapters(normalized, transcript, adapters, mode);
|
|
446
|
+
const result = await executeReleaseTailTransaction(current, {
|
|
447
|
+
adapters: selectedAdapters,
|
|
448
|
+
checkpoint,
|
|
449
|
+
});
|
|
450
|
+
const body = {
|
|
451
|
+
schema: V4_PUBLICATION_REHEARSAL_EVIDENCE_CONTRACT,
|
|
452
|
+
mode,
|
|
453
|
+
truth:
|
|
454
|
+
mode === "provider"
|
|
455
|
+
? "provider-observed"
|
|
456
|
+
: mode === "replay"
|
|
457
|
+
? "recorded-replay"
|
|
458
|
+
: "simulation-only",
|
|
459
|
+
capsuleRoot: normalized.capsuleRoot,
|
|
460
|
+
sourceRoot: normalized.source.root,
|
|
461
|
+
candidateRoot: normalized.candidate.root,
|
|
462
|
+
manifestRoot: normalized.manifest.root,
|
|
463
|
+
configRoot: normalized.config.root,
|
|
464
|
+
providerBindingsRoot: normalized.providerBindingsRoot,
|
|
465
|
+
providerPolicyRoot: normalized.providerPolicy.root,
|
|
466
|
+
expectedObservationRoot: normalized.expectedObservations.root,
|
|
467
|
+
coreVersion: normalized.coreVersion,
|
|
468
|
+
transactionRoot: result.transactionRoot,
|
|
469
|
+
stateRoot: result.stateRoot,
|
|
470
|
+
receiptRoots: result.receipts.map(({ receiptRoot }) => receiptRoot),
|
|
471
|
+
transcript,
|
|
472
|
+
transcriptRoot: releaseTailRoot(transcript),
|
|
473
|
+
authorityRoot: providerAuthority?.authorityRoot || null,
|
|
474
|
+
productionAuthority: false,
|
|
475
|
+
releasePassport: null,
|
|
476
|
+
};
|
|
477
|
+
return {
|
|
478
|
+
transaction: result,
|
|
479
|
+
evidence: { ...body, evidenceRoot: releaseTailRoot(body) },
|
|
480
|
+
};
|
|
481
|
+
}
|
|
@@ -67,8 +67,7 @@ function directEntries(lines, parentLine) {
|
|
|
67
67
|
const trimmed = line.trim();
|
|
68
68
|
const indent = indentation(line);
|
|
69
69
|
if (trimmed && indent <= parentIndent) break;
|
|
70
|
-
if (!trimmed || trimmed.startsWith("#") || indent !== childIndent)
|
|
71
|
-
continue;
|
|
70
|
+
if (!trimmed || trimmed.startsWith("#") || indent !== childIndent) continue;
|
|
72
71
|
const match = line.match(/^\s*([A-Za-z0-9_.-]+):(?:\s*(.*))?$/);
|
|
73
72
|
if (match) {
|
|
74
73
|
entries.push({
|
|
@@ -263,6 +262,28 @@ export function parseWorkflowCallJobs(text) {
|
|
|
263
262
|
.filter((job) => job.uses);
|
|
264
263
|
}
|
|
265
264
|
|
|
265
|
+
export function parseWorkflowJobs(text) {
|
|
266
|
+
const lines = String(text || "").split(/\r?\n/);
|
|
267
|
+
const jobs = topLevelEntry(lines, "jobs");
|
|
268
|
+
if (!jobs) return [];
|
|
269
|
+
return directEntries(lines, jobs.line)
|
|
270
|
+
.map((job) => {
|
|
271
|
+
const properties = directEntries(lines, job.line);
|
|
272
|
+
const property = (name) =>
|
|
273
|
+
properties.find((entry) => entry.key === name) || null;
|
|
274
|
+
return {
|
|
275
|
+
id: job.key,
|
|
276
|
+
name: unquote(property("name")?.value || job.key),
|
|
277
|
+
uses: unquote(property("uses")?.value || ""),
|
|
278
|
+
runsOn: unquote(property("runs-on")?.value || ""),
|
|
279
|
+
timeoutMinutes: property("timeout-minutes")
|
|
280
|
+
? scalar(property("timeout-minutes").value)
|
|
281
|
+
: null,
|
|
282
|
+
};
|
|
283
|
+
})
|
|
284
|
+
.sort((left, right) => compareCodeUnits(left.id, right.id));
|
|
285
|
+
}
|
|
286
|
+
|
|
266
287
|
function blockScalarIndent(lines, lineIndex, parentIndent) {
|
|
267
288
|
for (let index = lineIndex + 1; index < lines.length; index += 1) {
|
|
268
289
|
if (!lines[index].trim()) continue;
|
|
@@ -315,6 +336,7 @@ export function parseWorkflowDocument(text) {
|
|
|
315
336
|
return {
|
|
316
337
|
triggers: parseTriggers(String(text || "").split(/\r?\n/)),
|
|
317
338
|
interface: parseReusableWorkflowInterface(text),
|
|
339
|
+
jobs: parseWorkflowJobs(text),
|
|
318
340
|
callJobs: parseWorkflowCallJobs(text),
|
|
319
341
|
};
|
|
320
342
|
}
|
|
@@ -374,6 +374,7 @@ export function assertArtifactSigningControlRequestContext(
|
|
|
374
374
|
export function artifactSigningControlRequestOutputs(request) {
|
|
375
375
|
const value = validateArtifactSigningControlRequest(request);
|
|
376
376
|
return {
|
|
377
|
+
"source-run-attempt": String(value.source.runAttempt),
|
|
377
378
|
"request-count": String(value.request.count),
|
|
378
379
|
"request-artifact": value.request.artifact,
|
|
379
380
|
"request-root": value.request.root,
|
|
@@ -384,7 +385,6 @@ export function artifactSigningControlRequestOutputs(request) {
|
|
|
384
385
|
"runtime-sha": value.runtime.sha,
|
|
385
386
|
};
|
|
386
387
|
}
|
|
387
|
-
|
|
388
388
|
export function normalizeControllerStatus(value) {
|
|
389
389
|
const normalized = required(value, "authority status");
|
|
390
390
|
if (
|
|
@@ -11,10 +11,9 @@ import {
|
|
|
11
11
|
|
|
12
12
|
const GITHUB_JSON_MAX_BUFFER = 16 * 1024 * 1024;
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
14
|
+
const flag = (name, fallback = "") => process.argv.includes(`--${name}`)
|
|
15
|
+
? String(process.argv[process.argv.indexOf(`--${name}`) + 1] || "")
|
|
16
|
+
: fallback;
|
|
18
17
|
|
|
19
18
|
function commandJson(command, args, label, { publicReadFallback = false } = {}) {
|
|
20
19
|
const options = {
|
|
@@ -42,13 +41,9 @@ function commandJson(command, args, label, { publicReadFallback = false } = {})
|
|
|
42
41
|
}
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
return commandJson("gh", ["api", apiPath, "-H", "Accept: application/vnd.github+json"], label, { publicReadFallback });
|
|
47
|
-
}
|
|
44
|
+
const githubJson = (apiPath, label, { publicReadFallback = false } = {}) => commandJson("gh", ["api", apiPath, "-H", "Accept: application/vnd.github+json"], label, { publicReadFallback });
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
return githubJson(apiPath, label, { publicReadFallback: true });
|
|
51
|
-
}
|
|
46
|
+
const githubPublicJson = (apiPath, label) => githubJson(apiPath, label, { publicReadFallback: true });
|
|
52
47
|
|
|
53
48
|
function githubJsonOptional(apiPath, label, fallback, fallbackPattern = /404|not found/i) {
|
|
54
49
|
const result = spawnSyncCommand("gh", ["api", apiPath, "-H", "Accept: application/vnd.github+json"], {
|
|
@@ -68,9 +63,7 @@ function githubJsonOptional(apiPath, label, fallback, fallbackPattern = /404|not
|
|
|
68
63
|
}
|
|
69
64
|
}
|
|
70
65
|
|
|
71
|
-
|
|
72
|
-
return githubJsonOptional(apiPath, label, fallback, /401|403|404|unauthorized|forbidden|not found/i);
|
|
73
|
-
}
|
|
66
|
+
const githubJsonReadLimited = (apiPath, label, fallback) => githubJsonOptional(apiPath, label, fallback, /401|403|404|unauthorized|forbidden|not found/i);
|
|
74
67
|
|
|
75
68
|
function rulesetIncludesBranch(ruleset, branch, defaultBranch) {
|
|
76
69
|
const includes = ruleset.conditions?.ref_name?.include || [];
|
|
@@ -102,7 +95,7 @@ function normalizeRulesetBranchPolicy(rulesets, branch, defaultBranch) {
|
|
|
102
95
|
};
|
|
103
96
|
}
|
|
104
97
|
|
|
105
|
-
function jobBlock(workflowText, jobId) {
|
|
98
|
+
function jobBlock(workflowText, jobId, delegated = false) {
|
|
106
99
|
const lines = String(workflowText).split(/\r?\n/);
|
|
107
100
|
const jobsIndex = lines.findIndex((line) => /^jobs:\s*$/.test(line));
|
|
108
101
|
if (jobsIndex === -1) return "";
|
|
@@ -115,14 +108,21 @@ function jobBlock(workflowText, jobId) {
|
|
|
115
108
|
break;
|
|
116
109
|
}
|
|
117
110
|
}
|
|
118
|
-
|
|
111
|
+
const block = lines.slice(start, end).join("\n");
|
|
112
|
+
if (delegated) return block;
|
|
113
|
+
const declarations = [...block.matchAll(/^ {4}# buildchain-publication-authority-job:\s*([A-Za-z0-9_-]+)\s*$/gm)];
|
|
114
|
+
if (declarations.length > 1 || declarations[0]?.[1] === jobId) throw new Error(`publication authority job delegation is invalid: ${jobId}`);
|
|
115
|
+
if (!declarations.length) return block;
|
|
116
|
+
const delegatedJobId = declarations[0][1];
|
|
117
|
+
const delegatedBlock = jobBlock(workflowText, delegatedJobId, true);
|
|
118
|
+
if (!delegatedBlock) throw new Error(`delegated publication workflow job is missing: ${delegatedJobId}`);
|
|
119
|
+
return delegatedBlock;
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
function first(value, keys) {
|
|
122
123
|
for (const key of keys) {
|
|
123
|
-
const parts = key.split(".");
|
|
124
124
|
let current = value;
|
|
125
|
-
for (const part of
|
|
125
|
+
for (const part of key.split(".")) current = current && typeof current === "object" ? current[part] : undefined;
|
|
126
126
|
if (current !== undefined && current !== null && current !== "") return current;
|
|
127
127
|
}
|
|
128
128
|
return undefined;
|
|
@@ -8,6 +8,11 @@ export const BUILDCHAIN_USAGE = `Usage:
|
|
|
8
8
|
buildchain architecture qualify --authority-revision <git-revision>
|
|
9
9
|
[--candidate-revision <git-revision>]
|
|
10
10
|
[--cwd <dir>] [--json]
|
|
11
|
+
buildchain adopter-delivery run --input <json-or-path> [--output <path>]
|
|
12
|
+
buildchain adopter-delivery verify --input <json-or-path>
|
|
13
|
+
--readback <json-or-path> [--output <path>]
|
|
14
|
+
buildchain adopter-delivery bootstrap --input <json-or-path> [--output <path>]
|
|
15
|
+
buildchain adopter-delivery archive --input <json-or-path> [--output <path>]
|
|
11
16
|
buildchain portable-cache plan --manifest <file-or-json> [--output <file>]
|
|
12
17
|
[--github-output <file>] [--json]
|
|
13
18
|
buildchain portable-cache receipt --plan <file-or-json> [--matched-key <key>]
|
|
@@ -44,6 +49,13 @@ export const BUILDCHAIN_USAGE = `Usage:
|
|
|
44
49
|
buildchain release-tail init --declaration <json-or-path> [--state <path>]
|
|
45
50
|
buildchain release-tail <status|verify> [--state <path>] [--output <path>]
|
|
46
51
|
buildchain release-tail compat --hooks-json <json-or-path> [--output <path>]
|
|
52
|
+
buildchain release-tail rehearse --capsule <absolute-path>
|
|
53
|
+
--candidate-root <absolute-path>
|
|
54
|
+
[--capsule-root <absolute-path>]
|
|
55
|
+
[--environment-json <empty-json-or-path>]
|
|
56
|
+
--mode <simulate|replay>
|
|
57
|
+
--state <absolute-path>
|
|
58
|
+
--evidence <absolute-path>
|
|
47
59
|
buildchain tail-reseal plan --request <request.json> [--output <plan.json>]
|
|
48
60
|
buildchain tail-reseal admit --request <request.json> [--output <admission.json>]
|
|
49
61
|
buildchain tail-reseal verify-platform --request <request.json> --platform <id>
|
|
@@ -60,6 +72,7 @@ export const BUILDCHAIN_USAGE = `Usage:
|
|
|
60
72
|
buildchain release <inspect|recover|finalize|abort> ...
|
|
61
73
|
buildchain transaction inspect ...
|
|
62
74
|
buildchain collect github-release --tag <tag> [--repository <owner/repo>]
|
|
75
|
+
[--adopter-delivery-json <legacy-json-or-path>]
|
|
63
76
|
[--assets-dir <dir>] [--assets-json <json-or-path>]
|
|
64
77
|
[--release-json <json-or-path>] [--package-set-json <json-or-path>]
|
|
65
78
|
[--product-name <name>]
|