@kungfu-tech/buildchain 4.0.1-alpha.4 → 4.0.1-alpha.5
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/decisions/0002-next-development-transition.md +101 -0
- package/architecture/internal-capabilities.json +52 -3
- package/architecture/maintainability-policy.json +22 -10
- package/architecture/v3-core-mechanism-inventory.json +89 -0
- package/architecture/v4-capability-state-machine-manifest.json +73 -0
- package/architecture/v4-compatibility-facts-parity.json +77 -0
- package/architecture/v4-next-development-parity.json +192 -0
- package/architecture/v4-release-train-parity.json +1 -0
- package/architecture/v4-tail-reseal-parity.json +150 -0
- package/bin/buildchain.mjs +16 -1
- package/bin/internal/command-registry.mjs +2 -0
- package/bin/internal/compatibility-facts-cli.mjs +101 -0
- package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
- package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
- package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
- package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
- package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +7 -1
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
- package/contracts/next-development-request-v1.schema.json +66 -0
- package/contracts/next-development-transition-v1.schema.json +292 -0
- package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
- package/contracts/v4-tail-reseal-v1.schema.json +276 -0
- package/dist/site/agent-index.json +1 -0
- package/dist/site/artifact-schemas.json +2 -0
- package/dist/site/buildchain-contract.json +1388 -23
- package/dist/site/buildchain-site.json +257 -19
- package/dist/site/capability-registry.json +10 -8
- package/dist/site/cli-registry.json +106 -0
- package/dist/site/kfd-claims.json +187 -12
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +35 -4
- package/dist/site/node-api-registry.json +6579 -4493
- package/dist/site/page-registry.json +233 -11
- package/dist/site/public-surface-audit.json +241 -7
- package/dist/site/publication-authority-registry.json +6 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +7 -0
- package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
- package/dist/site/site-manifest.json +24 -8
- package/dist/site/workflow-registry.json +36 -4
- package/docs/MAP.md +4 -1
- package/docs/cli-reference.md +125 -0
- package/docs/dev-alpha-candidate-patrol.md +8 -0
- package/docs/next-development-transition.md +118 -0
- package/docs/node-api-reference.md +250 -117
- package/docs/v4-compatibility-facts.md +91 -0
- package/docs/v4-tail-reseal.md +73 -0
- package/docs/versioning.md +4 -3
- package/package.json +14 -5
- package/packages/core/buildchain-agent-manuals.js +2 -0
- package/packages/core/buildchain-compatibility-authority.js +336 -0
- package/packages/core/buildchain-compatibility-fact.js +394 -0
- package/packages/core/buildchain-compatibility-facts.json +217 -0
- package/packages/core/buildchain-compatibility-proof.js +566 -0
- package/packages/core/buildchain-contract.js +93 -141
- package/packages/core/channel-candidate.js +8 -0
- package/packages/core/channel-promotion-baseline.js +55 -0
- package/packages/core/dev-alpha-candidate-selection.js +10 -2
- package/packages/core/dev-delivery-execution-transfer.js +21 -1
- package/packages/core/dev-delivery-process-boundary.js +19 -6
- package/packages/core/dev-delivery-provider-heartbeat.js +18 -6
- package/packages/core/kungfu-temporal-fact.js +557 -0
- package/packages/core/next-development-candidate-reservation.js +186 -0
- package/packages/core/next-development-controller.js +728 -0
- package/packages/core/next-development-projection.js +287 -0
- package/packages/core/next-development-transition.js +738 -0
- package/packages/core/paper-agent-entry.js +11 -5
- package/packages/core/paper.js +16 -5
- package/packages/core/v4-canonical-contracts.js +5 -0
- package/packages/core/v4-floating-consumer-policy.js +29 -9
- package/packages/core/v4-tail-reseal-contract.js +26 -0
- package/packages/core/v4-tail-reseal-github.js +178 -0
- package/packages/core/v4-tail-reseal-receipt.js +225 -0
- package/packages/core/v4-tail-reseal.js +594 -0
- package/scripts/buildchain-cli-help.mjs +20 -0
- package/scripts/check-inventory.mjs +11 -0
- package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
- package/scripts/generate-next-development-guidance.mjs +49 -0
- package/scripts/generate-site-bundle.mjs +16 -3
- package/scripts/init-repo.mjs +22 -4
- package/scripts/next-development-self-dogfood-harness.mjs +460 -0
- package/scripts/next-development-self-dogfood.mjs +597 -0
- package/scripts/next-development-transition.mjs +47 -0
- package/scripts/site-capability-metadata.mjs +12 -0
- package/scripts/v4-consumer-policy.mjs +1 -5
- package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
- package/scripts/v4-tail-reseal.mjs +416 -0
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
stableJson,
|
|
13
13
|
workCheck,
|
|
14
14
|
} from "./paper-repository.js";
|
|
15
|
+
import { mergeNextDevelopmentAgentInstructions } from "./next-development-projection.js";
|
|
15
16
|
|
|
16
17
|
export const PAPER_AGENT_ENTRY_CONTRACT = "kungfu-buildchain-paper-agent-entry";
|
|
17
18
|
export const PAPER_AGENT_ENTRY_SCHEMA_VERSION = 1;
|
|
@@ -167,9 +168,11 @@ export function paperAgentEntryFiles({
|
|
|
167
168
|
[PAPER_PATHS.agentEntry, jsonText(entry)],
|
|
168
169
|
[
|
|
169
170
|
PAPER_PATHS.agentInstructions,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
mergeNextDevelopmentAgentInstructions(
|
|
172
|
+
mergePaperAgentEntryInstructions(currentAgents, {
|
|
173
|
+
developmentRef: resolvedDevelopmentRef,
|
|
174
|
+
}),
|
|
175
|
+
),
|
|
173
176
|
],
|
|
174
177
|
]);
|
|
175
178
|
}
|
|
@@ -205,8 +208,11 @@ function ciContext({ env, developmentRef }) {
|
|
|
205
208
|
sourceBranch.startsWith(versionStatePrefix) &&
|
|
206
209
|
/^[0-9a-f]{12}$/i.test(sourceBranch.slice(versionStatePrefix.length));
|
|
207
210
|
const branchOk = pullRequest
|
|
208
|
-
? (PAPER_WORK_BRANCH_PATTERN.test(sourceBranch) &&
|
|
209
|
-
|
|
211
|
+
? (PAPER_WORK_BRANCH_PATTERN.test(sourceBranch) &&
|
|
212
|
+
targetBranch === developmentRef) ||
|
|
213
|
+
(channelIndex > 0 &&
|
|
214
|
+
sourceBranch === channelBranches[channelIndex - 1]) ||
|
|
215
|
+
generatedVersionState
|
|
210
216
|
: channelBranches.includes(refName);
|
|
211
217
|
return {
|
|
212
218
|
mode: "ci",
|
package/packages/core/paper.js
CHANGED
|
@@ -46,6 +46,11 @@ import {
|
|
|
46
46
|
scaffoldReadme,
|
|
47
47
|
} from "./paper-scaffold-content.js";
|
|
48
48
|
import { executePaperNpmBootstrap as executePaperNpmBootstrapOperation } from "./paper-npm-bootstrap.js";
|
|
49
|
+
import {
|
|
50
|
+
appendNextDevelopmentToml,
|
|
51
|
+
nextDevelopmentWorkflowHeader,
|
|
52
|
+
projectNextDevelopmentToml,
|
|
53
|
+
} from "./next-development-projection.js";
|
|
49
54
|
|
|
50
55
|
export { PAPER_PATHS, resolvePaperRepository } from "./paper-repository.js";
|
|
51
56
|
export {
|
|
@@ -350,7 +355,7 @@ immutable_base_url = ${tomlString(joinUrl(siteBaseUrl, "archive").replace(/\/$/,
|
|
|
350
355
|
registry_path = ".buildchain/publication/publication-registry.json"
|
|
351
356
|
`
|
|
352
357
|
: "";
|
|
353
|
-
return `schema = 1
|
|
358
|
+
return appendNextDevelopmentToml(`schema = 1
|
|
354
359
|
|
|
355
360
|
[project]
|
|
356
361
|
type = "publication-artifact"
|
|
@@ -384,14 +389,14 @@ command = "make pdf"
|
|
|
384
389
|
|
|
385
390
|
[lifecycle.verify]
|
|
386
391
|
command = "make check"
|
|
387
|
-
|
|
392
|
+
`);
|
|
388
393
|
}
|
|
389
394
|
|
|
390
395
|
function scaffoldBuildWorkflow(
|
|
391
396
|
buildchainSha,
|
|
392
397
|
{ artifactName = "paper-publication" } = {},
|
|
393
398
|
) {
|
|
394
|
-
return
|
|
399
|
+
return `${nextDevelopmentWorkflowHeader()}name: Build
|
|
395
400
|
|
|
396
401
|
on:
|
|
397
402
|
workflow_dispatch:
|
|
@@ -419,7 +424,7 @@ jobs:
|
|
|
419
424
|
}
|
|
420
425
|
|
|
421
426
|
function scaffoldVerifyWorkflow(buildchainSha) {
|
|
422
|
-
return
|
|
427
|
+
return `${nextDevelopmentWorkflowHeader()}name: Verify
|
|
423
428
|
|
|
424
429
|
on:
|
|
425
430
|
pull_request:
|
|
@@ -450,7 +455,7 @@ function scaffoldReleaseWorkflow(
|
|
|
450
455
|
const passportInput = releasePassportProductName
|
|
451
456
|
? ` release-passport-product-name: ${JSON.stringify(releasePassportProductName)}\n`
|
|
452
457
|
: "";
|
|
453
|
-
return
|
|
458
|
+
return `${nextDevelopmentWorkflowHeader()}name: Paper Release
|
|
454
459
|
|
|
455
460
|
on:
|
|
456
461
|
workflow_dispatch:
|
|
@@ -997,6 +1002,12 @@ function migrationFiles({
|
|
|
997
1002
|
runtimeIdentity.version,
|
|
998
1003
|
);
|
|
999
1004
|
const files = new Map([
|
|
1005
|
+
[
|
|
1006
|
+
PAPER_PATHS.config,
|
|
1007
|
+
projectNextDevelopmentToml(
|
|
1008
|
+
fs.readFileSync(path.resolve(cwd, PAPER_PATHS.config), "utf8"),
|
|
1009
|
+
),
|
|
1010
|
+
],
|
|
1000
1011
|
[PAPER_PATHS.contractLock, contractLockText],
|
|
1001
1012
|
[PAPER_PATHS.versionPin, `${runtimeIdentity.version}\n`],
|
|
1002
1013
|
[PAPER_PATHS.buildWorkflow, buildWorkflow],
|
|
@@ -56,6 +56,11 @@ const ROOT_DOMAINS = new Set([
|
|
|
56
56
|
"stable-publication-fence",
|
|
57
57
|
"partial-mutation-recovery-checkpoint",
|
|
58
58
|
"partial-mutation-recovery-plan",
|
|
59
|
+
"tail-reseal-plan",
|
|
60
|
+
"tail-reseal-admission",
|
|
61
|
+
"tail-reseal-artifact-files",
|
|
62
|
+
"tail-reseal-receipt",
|
|
63
|
+
"release-candidate-passport",
|
|
59
64
|
]);
|
|
60
65
|
const FAULT_CLASSES = new Set([
|
|
61
66
|
"validation",
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
createBuildchainContractWorld,
|
|
6
|
+
evaluateBuildchainContractLock,
|
|
7
|
+
} from "./buildchain-contract.js";
|
|
4
8
|
import { parseYamlUses } from "./workflow-yaml-contract.js";
|
|
5
9
|
import {
|
|
6
10
|
V4_FLOATING_CONSUMER_POLICY,
|
|
@@ -29,6 +33,7 @@ const TRANSIENT_ACTION =
|
|
|
29
33
|
const SCANNER_PATHS = Object.freeze([
|
|
30
34
|
"architecture/v4-floating-consumer-policy.json",
|
|
31
35
|
"contracts/v4-floating-consumer-policy-receipt-v1.schema.json",
|
|
36
|
+
"packages/core/buildchain-contract.js",
|
|
32
37
|
"packages/core/v4-floating-consumer-policy.js",
|
|
33
38
|
"packages/core/workflow-yaml-contract.js",
|
|
34
39
|
"scripts/v4-consumer-policy.mjs",
|
|
@@ -381,15 +386,30 @@ export function scanV4FloatingConsumerPolicy({
|
|
|
381
386
|
expectedInvocationChannel,
|
|
382
387
|
);
|
|
383
388
|
const selectedLock = selected?.channel === "alpha" ? alpha : stable;
|
|
384
|
-
if (
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
389
|
+
if (selected?.channel && selectedLock) {
|
|
390
|
+
try {
|
|
391
|
+
const compatibility = evaluateBuildchainContractLock({
|
|
392
|
+
lock: selectedLock,
|
|
393
|
+
current: createBuildchainContractWorld({ root: defaultRuntimeRoot() }),
|
|
394
|
+
runtimeRef: selected.selector,
|
|
395
|
+
runtimeSha: normalizedWorkflowSha,
|
|
396
|
+
runtimeClass: selected.channel,
|
|
397
|
+
workflowShellRef: selected.selector,
|
|
398
|
+
expectedChannel: selected.channel,
|
|
399
|
+
expectedMajor: "v4",
|
|
400
|
+
});
|
|
401
|
+
if (!compatibility.ok) {
|
|
402
|
+
failures.push({
|
|
403
|
+
code: "selected-lock-runtime-incompatible",
|
|
404
|
+
message: `${selected.channel} contract lock rejects resolved workflow shell ${normalizedWorkflowSha}: ${compatibility.reasons?.join("; ") || compatibility.status}`,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
} catch (error) {
|
|
408
|
+
failures.push({
|
|
409
|
+
code: "selected-lock-contract-world-invalid",
|
|
410
|
+
message: `cannot evaluate ${selected.channel} contract lock against the resolved workflow shell: ${error.message}`,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
393
413
|
}
|
|
394
414
|
|
|
395
415
|
const policyRoot = sha256(stableJson(policy));
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const V4_TAIL_RESEAL_REQUEST_CONTRACT =
|
|
2
|
+
"kungfu-buildchain-v4-tail-reseal-request/v1";
|
|
3
|
+
export const V4_TAIL_RESEAL_PLAN_CONTRACT =
|
|
4
|
+
"kungfu-buildchain-v4-tail-reseal-plan/v1";
|
|
5
|
+
|
|
6
|
+
export const V4_TAIL_RESEAL_PLATFORMS = Object.freeze([
|
|
7
|
+
"linux-arm64",
|
|
8
|
+
"linux-x64",
|
|
9
|
+
"macos-arm64",
|
|
10
|
+
"windows-x64",
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export const V4_TAIL_RESEAL_PLATFORM_RUNNERS = Object.freeze({
|
|
14
|
+
"linux-arm64": '["ubuntu-24.04-arm"]',
|
|
15
|
+
"linux-x64": '["ubuntu-24.04"]',
|
|
16
|
+
"macos-arm64": '["macos-15"]',
|
|
17
|
+
"windows-x64": '["windows-2025"]',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const V4_TAIL_RESEAL_REUSED_STAGE_KEYS = Object.freeze(
|
|
21
|
+
V4_TAIL_RESEAL_PLATFORMS.flatMap((platformId) =>
|
|
22
|
+
["build", "install", "package", "verify"].map(
|
|
23
|
+
(stage) => `${platformId}:${stage}`,
|
|
24
|
+
),
|
|
25
|
+
).sort(),
|
|
26
|
+
);
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { v4ContentRoot } from "./v4-canonical-contracts.js";
|
|
2
|
+
import {
|
|
3
|
+
V4TailResealFault,
|
|
4
|
+
normalizeV4TailResealRequest,
|
|
5
|
+
} from "./v4-tail-reseal.js";
|
|
6
|
+
|
|
7
|
+
export const V4_TAIL_RESEAL_ADMISSION_CONTRACT =
|
|
8
|
+
"kungfu-buildchain-v4-tail-reseal-admission/v1";
|
|
9
|
+
|
|
10
|
+
const REQUIRED_SUCCESS_JOBS = Object.freeze([
|
|
11
|
+
"build / Linux ARM64",
|
|
12
|
+
"build / Linux x64",
|
|
13
|
+
"build / macOS ARM64",
|
|
14
|
+
"build / Windows x64",
|
|
15
|
+
"build / Control detached signing Linux ARM64",
|
|
16
|
+
"build / Control detached signing Linux x64",
|
|
17
|
+
"build / Control detached signing macOS ARM64",
|
|
18
|
+
"build / Control detached signing Windows x64",
|
|
19
|
+
"build / Finalize signed artifact Linux ARM64",
|
|
20
|
+
"build / Finalize signed artifact Linux x64",
|
|
21
|
+
"build / Finalize signed artifact Windows x64",
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
function fault(code, path, message) {
|
|
25
|
+
throw new V4TailResealFault(code, path, message);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function exactArtifact(artifacts, name, digest, path) {
|
|
29
|
+
const matches = artifacts.filter((artifact) => artifact.name === name);
|
|
30
|
+
if (matches.length !== 1)
|
|
31
|
+
fault(
|
|
32
|
+
"tail-reseal-artifact-ambiguous",
|
|
33
|
+
path,
|
|
34
|
+
"retained artifact must resolve exactly once",
|
|
35
|
+
);
|
|
36
|
+
const artifact = matches[0];
|
|
37
|
+
if (artifact.expired === true)
|
|
38
|
+
fault("tail-reseal-retention-expired", path, "retained artifact expired");
|
|
39
|
+
if (artifact.digest !== digest)
|
|
40
|
+
fault(
|
|
41
|
+
"tail-reseal-artifact-archive-mismatch",
|
|
42
|
+
path,
|
|
43
|
+
"GitHub artifact archive digest drifted",
|
|
44
|
+
);
|
|
45
|
+
return artifact;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function validateSourceIdentity(normalized, sourceCommit, sourceRun) {
|
|
49
|
+
if (
|
|
50
|
+
sourceCommit?.sha !== normalized.source.sha ||
|
|
51
|
+
sourceCommit?.tree?.sha !== normalized.source.treeSha
|
|
52
|
+
)
|
|
53
|
+
fault(
|
|
54
|
+
"tail-reseal-source-mismatch",
|
|
55
|
+
"$/sourceCommit",
|
|
56
|
+
"source commit or tree readback drifted",
|
|
57
|
+
);
|
|
58
|
+
const workflowFile = String(sourceRun?.path || "")
|
|
59
|
+
.split("@")[0]
|
|
60
|
+
.replace(/^\.github\/workflows\//u, "");
|
|
61
|
+
if (
|
|
62
|
+
Number(sourceRun?.id) !== normalized.source.runId ||
|
|
63
|
+
Number(sourceRun?.run_attempt) !== normalized.source.runAttempt ||
|
|
64
|
+
sourceRun?.event !== "workflow_dispatch" ||
|
|
65
|
+
sourceRun?.status !== "completed" ||
|
|
66
|
+
sourceRun?.conclusion !== "failure" ||
|
|
67
|
+
sourceRun?.head_sha !== normalized.source.sha ||
|
|
68
|
+
workflowFile !== normalized.source.workflowFile ||
|
|
69
|
+
sourceRun?.name !== normalized.source.workflowName
|
|
70
|
+
)
|
|
71
|
+
fault(
|
|
72
|
+
"tail-reseal-source-run-mismatch",
|
|
73
|
+
"$/sourceRun",
|
|
74
|
+
"source run is not the exact retained failed Build run",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function validateSourceJobs(normalized, sourceJobs) {
|
|
79
|
+
const jobs = Array.isArray(sourceJobs) ? sourceJobs : [];
|
|
80
|
+
const byName = new Map(jobs.map((job) => [job.name, job]));
|
|
81
|
+
for (const name of REQUIRED_SUCCESS_JOBS)
|
|
82
|
+
if (byName.get(name)?.conclusion !== "success")
|
|
83
|
+
fault(
|
|
84
|
+
"tail-reseal-prior-stage-mismatch",
|
|
85
|
+
"$/sourceJobs",
|
|
86
|
+
`required source job did not succeed: ${name}`,
|
|
87
|
+
);
|
|
88
|
+
const failedJob = jobs.find(
|
|
89
|
+
(job) => Number(job.id) === normalized.failure.jobId,
|
|
90
|
+
);
|
|
91
|
+
const failedSteps = (failedJob?.steps || [])
|
|
92
|
+
.filter((step) => step.conclusion === "failure")
|
|
93
|
+
.map((step) => step.name);
|
|
94
|
+
if (
|
|
95
|
+
failedJob?.name !== normalized.failure.jobName ||
|
|
96
|
+
failedJob?.conclusion !== "failure" ||
|
|
97
|
+
JSON.stringify(failedSteps) !==
|
|
98
|
+
JSON.stringify([normalized.failure.stepName])
|
|
99
|
+
)
|
|
100
|
+
fault(
|
|
101
|
+
"tail-reseal-failure-mismatch",
|
|
102
|
+
"$/sourceJobs",
|
|
103
|
+
"observed failure is not the single authorized macOS tail failure",
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function validateRetainedArtifacts(normalized, sourceArtifacts) {
|
|
108
|
+
for (const [index, platform] of normalized.platforms.entries()) {
|
|
109
|
+
exactArtifact(
|
|
110
|
+
sourceArtifacts || [],
|
|
111
|
+
platform.artifactName,
|
|
112
|
+
platform.artifactArchiveRoot,
|
|
113
|
+
`$/sourceArtifacts/${index}/payload`,
|
|
114
|
+
);
|
|
115
|
+
exactArtifact(
|
|
116
|
+
sourceArtifacts || [],
|
|
117
|
+
platform.manifestArtifactName,
|
|
118
|
+
platform.manifestArchiveRoot,
|
|
119
|
+
`$/sourceArtifacts/${index}/manifest`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function validateSigningAuthority(normalized, signingRun, signingArtifacts) {
|
|
125
|
+
if (
|
|
126
|
+
Number(signingRun?.id) !== normalized.signing.authorityRunId ||
|
|
127
|
+
signingRun?.status !== "completed" ||
|
|
128
|
+
signingRun?.conclusion !== "success" ||
|
|
129
|
+
signingRun?.head_sha !== normalized.signing.runtimeSha
|
|
130
|
+
)
|
|
131
|
+
fault(
|
|
132
|
+
"tail-reseal-signer-authority-mismatch",
|
|
133
|
+
"$/signingRun",
|
|
134
|
+
"signing authority run readback drifted",
|
|
135
|
+
);
|
|
136
|
+
exactArtifact(
|
|
137
|
+
signingArtifacts || [],
|
|
138
|
+
normalized.signing.resultArtifact,
|
|
139
|
+
normalized.signing.resultArtifactRoot,
|
|
140
|
+
"$/signingArtifacts/result",
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function validateV4TailResealGitHubEvidence({
|
|
145
|
+
request,
|
|
146
|
+
sourceCommit,
|
|
147
|
+
sourceRun,
|
|
148
|
+
sourceJobs,
|
|
149
|
+
sourceArtifacts,
|
|
150
|
+
signingRun,
|
|
151
|
+
signingArtifacts,
|
|
152
|
+
} = {}) {
|
|
153
|
+
const normalized = normalizeV4TailResealRequest(request);
|
|
154
|
+
validateSourceIdentity(normalized, sourceCommit, sourceRun);
|
|
155
|
+
validateSourceJobs(normalized, sourceJobs);
|
|
156
|
+
validateRetainedArtifacts(normalized, sourceArtifacts);
|
|
157
|
+
validateSigningAuthority(normalized, signingRun, signingArtifacts);
|
|
158
|
+
const payload = {
|
|
159
|
+
schema: V4_TAIL_RESEAL_ADMISSION_CONTRACT,
|
|
160
|
+
repository: normalized.repository,
|
|
161
|
+
sourceRoot: normalized.source.sourceRoot,
|
|
162
|
+
sourceRunId: normalized.source.runId,
|
|
163
|
+
signingAuthorityRunId: normalized.signing.authorityRunId,
|
|
164
|
+
retainedArchiveRoots: normalized.platforms.flatMap((platform) => [
|
|
165
|
+
platform.artifactArchiveRoot,
|
|
166
|
+
platform.manifestArchiveRoot,
|
|
167
|
+
]),
|
|
168
|
+
priorFailureRoot: normalized.failure.evidenceRoot,
|
|
169
|
+
retentionReadbackRoot: normalized.retention.readbackRoot,
|
|
170
|
+
warrantReadbackRoot: normalized.warrant.stateReadbackRoot,
|
|
171
|
+
};
|
|
172
|
+
return {
|
|
173
|
+
...payload,
|
|
174
|
+
admissionRoot: v4ContentRoot("tail-reseal-admission", payload),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export { REQUIRED_SUCCESS_JOBS as V4_TAIL_RESEAL_REQUIRED_SUCCESS_JOBS };
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { v4ContentRoot, validateV4Root } from "./v4-canonical-contracts.js";
|
|
2
|
+
import { validateReleaseCandidatePassport } from "./release-candidate.js";
|
|
3
|
+
import {
|
|
4
|
+
V4_TAIL_RESEAL_PLAN_CONTRACT,
|
|
5
|
+
V4_TAIL_RESEAL_PLATFORMS,
|
|
6
|
+
V4TailResealFault,
|
|
7
|
+
normalizeV4TailResealRequest,
|
|
8
|
+
planV4TailReseal,
|
|
9
|
+
} from "./v4-tail-reseal.js";
|
|
10
|
+
|
|
11
|
+
export const V4_TAIL_RESEAL_RECEIPT_CONTRACT =
|
|
12
|
+
"kungfu-buildchain-v4-tail-reseal-receipt/v1";
|
|
13
|
+
|
|
14
|
+
function fault(code, path, message) {
|
|
15
|
+
throw new V4TailResealFault(code, path, message);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function root(value, path) {
|
|
19
|
+
try {
|
|
20
|
+
validateV4Root(value, path);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
fault("invalid-tail-reseal-root", path, error.message);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function exactKeys(value, expected, path) {
|
|
28
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
29
|
+
fault("invalid-tail-reseal-shape", path, "object required");
|
|
30
|
+
const actual = Object.keys(value).sort();
|
|
31
|
+
const canonical = [...expected].sort();
|
|
32
|
+
if (
|
|
33
|
+
actual.length !== canonical.length ||
|
|
34
|
+
actual.some((key, index) => key !== canonical[index])
|
|
35
|
+
)
|
|
36
|
+
fault(
|
|
37
|
+
"invalid-tail-reseal-shape",
|
|
38
|
+
path,
|
|
39
|
+
"keys do not match the closed tail-reseal contract",
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function positiveInteger(value, path) {
|
|
44
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
45
|
+
fault(
|
|
46
|
+
"invalid-tail-reseal-integer",
|
|
47
|
+
path,
|
|
48
|
+
"positive safe integer required",
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function validateReadbacks(readbacks, request) {
|
|
53
|
+
if (
|
|
54
|
+
!Array.isArray(readbacks) ||
|
|
55
|
+
readbacks.length !== V4_TAIL_RESEAL_PLATFORMS.length
|
|
56
|
+
)
|
|
57
|
+
fault(
|
|
58
|
+
"tail-reseal-readback-mismatch",
|
|
59
|
+
"$/readbacks",
|
|
60
|
+
"one readback per platform required",
|
|
61
|
+
);
|
|
62
|
+
const normalized = [...readbacks].sort((left, right) =>
|
|
63
|
+
Buffer.from(left.platformId || "").compare(
|
|
64
|
+
Buffer.from(right.platformId || ""),
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
for (const [index, readback] of normalized.entries()) {
|
|
68
|
+
const path = `$/readbacks/${index}`;
|
|
69
|
+
exactKeys(
|
|
70
|
+
readback,
|
|
71
|
+
[
|
|
72
|
+
"platformId",
|
|
73
|
+
"artifactRoot",
|
|
74
|
+
"manifestRoot",
|
|
75
|
+
"capsuleRoot",
|
|
76
|
+
"byteIdentical",
|
|
77
|
+
"providerReadbackRoot",
|
|
78
|
+
],
|
|
79
|
+
path,
|
|
80
|
+
);
|
|
81
|
+
const expected = request.platforms[index];
|
|
82
|
+
if (readback.platformId !== expected.id)
|
|
83
|
+
fault(
|
|
84
|
+
"tail-reseal-platform-mismatch",
|
|
85
|
+
`${path}/platformId`,
|
|
86
|
+
"unexpected platform readback",
|
|
87
|
+
);
|
|
88
|
+
for (const field of ["artifactRoot", "manifestRoot", "capsuleRoot"])
|
|
89
|
+
root(readback[field], `${path}/${field}`);
|
|
90
|
+
if (readback.capsuleRoot !== expected.capsuleRoot)
|
|
91
|
+
fault(
|
|
92
|
+
"tail-reseal-capsule-mismatch",
|
|
93
|
+
`${path}/capsuleRoot`,
|
|
94
|
+
"Stage Capsule identity changed during tail execution",
|
|
95
|
+
);
|
|
96
|
+
if (readback.platformId === "macos-arm64") {
|
|
97
|
+
if (readback.byteIdentical !== false)
|
|
98
|
+
fault(
|
|
99
|
+
"tail-reseal-macos-effect-missing",
|
|
100
|
+
`${path}/byteIdentical`,
|
|
101
|
+
"macOS signed bytes must be independently resealed",
|
|
102
|
+
);
|
|
103
|
+
root(readback.providerReadbackRoot, `${path}/providerReadbackRoot`);
|
|
104
|
+
} else if (
|
|
105
|
+
readback.byteIdentical !== true ||
|
|
106
|
+
readback.artifactRoot !== expected.artifactRoot ||
|
|
107
|
+
readback.manifestRoot !== expected.manifestRoot ||
|
|
108
|
+
readback.providerReadbackRoot !== null
|
|
109
|
+
)
|
|
110
|
+
fault(
|
|
111
|
+
"tail-reseal-nontail-byte-drift",
|
|
112
|
+
path,
|
|
113
|
+
"non-macOS retained artifacts must remain byte-identical and effect-free",
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return normalized;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function createV4TailResealReceipt({
|
|
120
|
+
request,
|
|
121
|
+
plan,
|
|
122
|
+
readbacks,
|
|
123
|
+
passport,
|
|
124
|
+
protectedReadbackRoot,
|
|
125
|
+
currentRun,
|
|
126
|
+
} = {}) {
|
|
127
|
+
const normalized = normalizeV4TailResealRequest(request);
|
|
128
|
+
const expectedPlan = planV4TailReseal(normalized);
|
|
129
|
+
const { planRoot: _planRoot, ...planBody } = plan || {};
|
|
130
|
+
if (
|
|
131
|
+
plan?.schema !== V4_TAIL_RESEAL_PLAN_CONTRACT ||
|
|
132
|
+
plan.planRoot !== expectedPlan.planRoot ||
|
|
133
|
+
v4ContentRoot("tail-reseal-plan", planBody) !== expectedPlan.planRoot
|
|
134
|
+
)
|
|
135
|
+
fault(
|
|
136
|
+
"tail-reseal-plan-drift",
|
|
137
|
+
"$/plan",
|
|
138
|
+
"plan does not match the exact request",
|
|
139
|
+
);
|
|
140
|
+
const platformReadbacks = validateReadbacks(readbacks, normalized);
|
|
141
|
+
const passportValidation = validateReleaseCandidatePassport({
|
|
142
|
+
passport,
|
|
143
|
+
repository: normalized.repository,
|
|
144
|
+
targetChannel: normalized.target.channel,
|
|
145
|
+
version: normalized.target.version,
|
|
146
|
+
sourceHeadSha: normalized.source.sha,
|
|
147
|
+
requirePlatforms: true,
|
|
148
|
+
});
|
|
149
|
+
if (!passportValidation.ok)
|
|
150
|
+
fault(
|
|
151
|
+
"tail-reseal-passport-invalid",
|
|
152
|
+
"$/passport",
|
|
153
|
+
passportValidation.errors.join("; "),
|
|
154
|
+
);
|
|
155
|
+
if (
|
|
156
|
+
passport.contract !== "kungfu-buildchain-release-candidate-passport" ||
|
|
157
|
+
passport.buildchain?.sha !== normalized.runtime.sha ||
|
|
158
|
+
passport.consumerPolicy?.receiptRoot !==
|
|
159
|
+
normalized.runtime.consumerPolicyReceiptRoot
|
|
160
|
+
)
|
|
161
|
+
fault(
|
|
162
|
+
"tail-reseal-passport-binding-mismatch",
|
|
163
|
+
"$/passport",
|
|
164
|
+
"standard v4 candidate Passport bindings drifted",
|
|
165
|
+
);
|
|
166
|
+
root(protectedReadbackRoot, "$/protectedReadbackRoot");
|
|
167
|
+
exactKeys(currentRun, ["id", "attempt"], "$/currentRun");
|
|
168
|
+
positiveInteger(currentRun.id, "$/currentRun/id");
|
|
169
|
+
positiveInteger(currentRun.attempt, "$/currentRun/attempt");
|
|
170
|
+
const payload = {
|
|
171
|
+
schema: V4_TAIL_RESEAL_RECEIPT_CONTRACT,
|
|
172
|
+
action: "reused-retained-tail",
|
|
173
|
+
repository: normalized.repository,
|
|
174
|
+
source: normalized.source,
|
|
175
|
+
target: normalized.target,
|
|
176
|
+
runtime: normalized.runtime,
|
|
177
|
+
planRoot: expectedPlan.planRoot,
|
|
178
|
+
warrant: normalized.warrant,
|
|
179
|
+
retention: normalized.retention,
|
|
180
|
+
originalFailure: normalized.failure,
|
|
181
|
+
signingAuthority: normalized.signing,
|
|
182
|
+
releaseTail: normalized.releaseTail,
|
|
183
|
+
platformReadbacks,
|
|
184
|
+
skippedStages: expectedPlan.skippedStages,
|
|
185
|
+
rerunStages: expectedPlan.rerunStages,
|
|
186
|
+
passport: {
|
|
187
|
+
contract: passport.contract,
|
|
188
|
+
candidateHash: passport.candidateHash,
|
|
189
|
+
passportRoot: v4ContentRoot("release-candidate-passport", passport),
|
|
190
|
+
},
|
|
191
|
+
protectedReadbackRoot,
|
|
192
|
+
currentRun,
|
|
193
|
+
};
|
|
194
|
+
return {
|
|
195
|
+
...payload,
|
|
196
|
+
receiptRoot: v4ContentRoot("tail-reseal-receipt", payload),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function verifyV4TailResealReceipt({ receipt, request, passport } = {}) {
|
|
201
|
+
try {
|
|
202
|
+
const expected = createV4TailResealReceipt({
|
|
203
|
+
request,
|
|
204
|
+
plan: planV4TailReseal(request),
|
|
205
|
+
readbacks: receipt.platformReadbacks,
|
|
206
|
+
passport,
|
|
207
|
+
protectedReadbackRoot: receipt.protectedReadbackRoot,
|
|
208
|
+
currentRun: receipt.currentRun,
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
ok: expected.receiptRoot === receipt.receiptRoot,
|
|
212
|
+
receiptRoot: expected.receiptRoot,
|
|
213
|
+
failures:
|
|
214
|
+
expected.receiptRoot === receipt.receiptRoot
|
|
215
|
+
? []
|
|
216
|
+
: [{ code: "tail-reseal-receipt-root-mismatch" }],
|
|
217
|
+
};
|
|
218
|
+
} catch (error) {
|
|
219
|
+
return {
|
|
220
|
+
ok: false,
|
|
221
|
+
receiptRoot: "",
|
|
222
|
+
failures: [{ code: error.code || "tail-reseal-receipt-invalid" }],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|