@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
|
@@ -7,6 +7,15 @@ import {
|
|
|
7
7
|
normalizeBuildchainRef,
|
|
8
8
|
parseBuildchainRefIdentity,
|
|
9
9
|
} from "./buildchain-channel-identity.js";
|
|
10
|
+
import {
|
|
11
|
+
createHistoricalBuildchainCompatibilityFacts,
|
|
12
|
+
createHistoricalBuildchainCompatibilityProofs,
|
|
13
|
+
} from "./buildchain-compatibility-fact.js";
|
|
14
|
+
import {
|
|
15
|
+
assertBuildchainCompatibilityProjection,
|
|
16
|
+
createBuildchainCompatibilityProofRegistry,
|
|
17
|
+
} from "./buildchain-compatibility-authority.js";
|
|
18
|
+
import { evaluateBuildchainContractLock as evaluateFactBackedBuildchainContractLock } from "./buildchain-compatibility-proof.js";
|
|
10
19
|
import { createControllerRegistry } from "./controller-evidence.js";
|
|
11
20
|
import { enumerateWorkflowInputs } from "./public-surface-audit.js";
|
|
12
21
|
import { devDeliveryWorkflowContractSurface } from "./dev-delivery-contract-surface.js";
|
|
@@ -18,11 +27,47 @@ export {
|
|
|
18
27
|
parseBuildchainRefIdentity,
|
|
19
28
|
};
|
|
20
29
|
|
|
30
|
+
export {
|
|
31
|
+
BUILDCHAIN_COMPATIBILITY_FACT_REGISTRY_SCHEMA,
|
|
32
|
+
BUILDCHAIN_COMPATIBILITY_FACT_SCHEMA,
|
|
33
|
+
BUILDCHAIN_COMPATIBILITY_PROOF_REGISTRY_SCHEMA,
|
|
34
|
+
BUILDCHAIN_COMPATIBILITY_PROOF_SCHEMA,
|
|
35
|
+
BUILDCHAIN_COMPATIBILITY_RELEASE_EVIDENCE_SCHEMA,
|
|
36
|
+
createBuildchainCompatibilityFact,
|
|
37
|
+
createBuildchainCompatibilityFactRegistry,
|
|
38
|
+
createBuildchainCompatibilityProof,
|
|
39
|
+
createHistoricalBuildchainCompatibilityFacts,
|
|
40
|
+
createHistoricalBuildchainCompatibilityProofs,
|
|
41
|
+
verifyBuildchainCompatibilityFact,
|
|
42
|
+
verifyBuildchainCompatibilityFactRegistry,
|
|
43
|
+
verifyBuildchainCompatibilityProof,
|
|
44
|
+
} from "./buildchain-compatibility-fact.js";
|
|
45
|
+
export {
|
|
46
|
+
assertBuildchainCompatibilityProjection,
|
|
47
|
+
createBuildchainCompatibilityPathQuery,
|
|
48
|
+
createBuildchainCompatibilityProofRegistry,
|
|
49
|
+
createBuildchainCompatibilityReleaseEvidence,
|
|
50
|
+
resolveBuildchainCompatibilityProof,
|
|
51
|
+
verifyBuildchainCompatibilityPath,
|
|
52
|
+
} from "./buildchain-compatibility-authority.js";
|
|
53
|
+
export {
|
|
54
|
+
BUILDCHAIN_COMPATIBILITY_VERIFICATION_RECEIPT_SCHEMA,
|
|
55
|
+
BUILDCHAIN_LEGACY_COMPATIBILITY_VERIFICATION_RECEIPT_SCHEMA,
|
|
56
|
+
createBuildchainCompatibilityVerificationReceipt,
|
|
57
|
+
verifyBuildchainCompatibilityVerificationReceipt,
|
|
58
|
+
} from "./buildchain-compatibility-proof.js";
|
|
59
|
+
|
|
21
60
|
export const BUILDCHAIN_RUNTIME_CONTRACT_WORLD = "kungfu-buildchain-runtime-contract-world";
|
|
22
61
|
export const BUILDCHAIN_CONTRACT_LOCK = "kungfu-buildchain-contract-lock";
|
|
23
62
|
|
|
24
63
|
const DEFAULT_POLICY = "major-compatible";
|
|
25
|
-
|
|
64
|
+
|
|
65
|
+
function compatibilityAuthority() {
|
|
66
|
+
return {
|
|
67
|
+
compatibilityFacts: createHistoricalBuildchainCompatibilityFacts(),
|
|
68
|
+
compatibilityProofs: createHistoricalBuildchainCompatibilityProofs(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
26
71
|
|
|
27
72
|
function optionalString(value) {
|
|
28
73
|
return value === undefined || value === null ? "" : String(value);
|
|
@@ -796,26 +841,60 @@ export function createBuildchainContractWorld({
|
|
|
796
841
|
version: pkg.version || "",
|
|
797
842
|
repository: pkg.repository?.url || pkg.repository || "https://github.com/kungfu-systems/buildchain",
|
|
798
843
|
},
|
|
799
|
-
majorLine,
|
|
800
|
-
|
|
844
|
+
majorLine, compatibilityPolicy: DEFAULT_POLICY,
|
|
845
|
+
...compatibilityAuthority(),
|
|
801
846
|
surfaces,
|
|
802
847
|
};
|
|
803
848
|
return finalizeBuildchainContractWorld(base);
|
|
804
849
|
}
|
|
805
850
|
|
|
806
851
|
export function finalizeBuildchainContractWorld(contractWorld) {
|
|
852
|
+
if (!Array.isArray(contractWorld?.compatibilityFacts) || !Array.isArray(contractWorld?.compatibilityProofs)) {
|
|
853
|
+
throw new Error("contract world requires compatibility Facts and their proof projection");
|
|
854
|
+
}
|
|
855
|
+
const sourceSurfaces = (contractWorld.surfaces || []).map((entry) => ({ ...entry }));
|
|
856
|
+
const proofRegistry = createBuildchainCompatibilityProofRegistry({
|
|
857
|
+
proofs: contractWorld.compatibilityProofs,
|
|
858
|
+
surfaces: sourceSurfaces,
|
|
859
|
+
majorLine: contractWorld.majorLine,
|
|
860
|
+
});
|
|
861
|
+
if (JSON.stringify(contractWorld.compatibilityFacts) !== JSON.stringify(proofRegistry.facts)) {
|
|
862
|
+
throw new Error("contract world compatibility Facts drift from the verified registry");
|
|
863
|
+
}
|
|
807
864
|
const world = {
|
|
808
865
|
...contractWorld,
|
|
809
|
-
|
|
866
|
+
compatibilityFacts: proofRegistry.facts,
|
|
867
|
+
compatibilityFactRegistryRoot: proofRegistry.factRegistryRoot,
|
|
868
|
+
compatibilityFactCutRoot: proofRegistry.factCutRoot,
|
|
869
|
+
compatibilityProofs: proofRegistry.proofs,
|
|
870
|
+
compatibilityProofRegistryRoot: proofRegistry.registryRoot,
|
|
871
|
+
surfaces: sourceSurfaces.map((entry) => {
|
|
872
|
+
const surfaceEntry = { ...entry };
|
|
873
|
+
delete surfaceEntry.compatibleBreakingDigests;
|
|
874
|
+
delete surfaceEntry.compatibilityProofRoots;
|
|
875
|
+
delete surfaceEntry.compatibilityFactRoots;
|
|
876
|
+
const projection = proofRegistry.projections[entry.id] || [];
|
|
877
|
+
if (projection.length > 0) {
|
|
878
|
+
surfaceEntry.compatibleBreakingDigests = projection.map((item) => item.breakingDigest);
|
|
879
|
+
surfaceEntry.compatibilityProofRoots = projection.map((item) => item.proofRoot);
|
|
880
|
+
surfaceEntry.compatibilityFactRoots = projection.map((item) => item.factRoot);
|
|
881
|
+
}
|
|
882
|
+
return surfaceEntry;
|
|
883
|
+
}),
|
|
810
884
|
};
|
|
811
885
|
const compatibilityModel = {
|
|
812
886
|
schemaVersion: world.schemaVersion,
|
|
813
887
|
contract: world.contract,
|
|
814
888
|
majorLine: world.majorLine,
|
|
889
|
+
compatibilityFactRegistryRoot: world.compatibilityFactRegistryRoot,
|
|
890
|
+
compatibilityFactCutRoot: world.compatibilityFactCutRoot,
|
|
891
|
+
compatibilityProofRegistryRoot: world.compatibilityProofRegistryRoot,
|
|
815
892
|
surfaces: world.surfaces.map((entry) => ({
|
|
816
893
|
id: entry.id,
|
|
817
894
|
kind: entry.kind,
|
|
818
895
|
breakingDigest: entry.breakingDigest,
|
|
896
|
+
compatibilityProofRoots: entry.compatibilityProofRoots || [],
|
|
897
|
+
compatibilityFactRoots: entry.compatibilityFactRoots || [],
|
|
819
898
|
})),
|
|
820
899
|
};
|
|
821
900
|
const digestModel = {
|
|
@@ -847,6 +926,9 @@ export function createBuildchainContractLock({
|
|
|
847
926
|
contract: contractWorld.contract,
|
|
848
927
|
contractDigest: contractWorld.contractDigest,
|
|
849
928
|
compatibilityDigest: contractWorld.compatibilityDigest,
|
|
929
|
+
compatibilityFactRegistryRoot: contractWorld.compatibilityFactRegistryRoot,
|
|
930
|
+
compatibilityFactCutRoot: contractWorld.compatibilityFactCutRoot,
|
|
931
|
+
compatibilityProofRegistryRoot: contractWorld.compatibilityProofRegistryRoot,
|
|
850
932
|
majorLine: contractWorld.majorLine,
|
|
851
933
|
compatibilityPolicy,
|
|
852
934
|
acceptedAt,
|
|
@@ -854,6 +936,8 @@ export function createBuildchainContractLock({
|
|
|
854
936
|
id: entry.id,
|
|
855
937
|
kind: entry.kind,
|
|
856
938
|
breakingDigest: entry.breakingDigest,
|
|
939
|
+
compatibilityProofRoots: entry.compatibilityProofRoots || [],
|
|
940
|
+
compatibilityFactRoots: entry.compatibilityFactRoots || [],
|
|
857
941
|
})),
|
|
858
942
|
},
|
|
859
943
|
};
|
|
@@ -890,143 +974,8 @@ export function readBuildchainContractLock(filePath) {
|
|
|
890
974
|
return value;
|
|
891
975
|
}
|
|
892
976
|
|
|
893
|
-
function
|
|
894
|
-
return
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
function evaluateChannelBinding({
|
|
898
|
-
lock,
|
|
899
|
-
workflowShellRef,
|
|
900
|
-
runtimeRef,
|
|
901
|
-
expectedChannel,
|
|
902
|
-
expectedMajor,
|
|
903
|
-
allowOpaqueRuntime,
|
|
904
|
-
}) {
|
|
905
|
-
if (!workflowShellRef && !expectedChannel && !expectedMajor) return null;
|
|
906
|
-
return evaluateBuildchainChannelBinding({
|
|
907
|
-
workflowShellRef,
|
|
908
|
-
runtimeRef,
|
|
909
|
-
lockRef: lock?.buildchain?.ref || "",
|
|
910
|
-
lockMajorLine: lock?.buildchain?.majorLine || "",
|
|
911
|
-
expectedChannel,
|
|
912
|
-
expectedMajor,
|
|
913
|
-
allowOpaqueRuntime,
|
|
914
|
-
});
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
export function evaluateBuildchainContractLock({
|
|
918
|
-
lock,
|
|
919
|
-
current,
|
|
920
|
-
runtimeRef = "",
|
|
921
|
-
runtimeSha = "",
|
|
922
|
-
runtimeClass = "",
|
|
923
|
-
compatibilityPolicy = "",
|
|
924
|
-
workflowShellRef = "",
|
|
925
|
-
expectedChannel = "",
|
|
926
|
-
expectedMajor = "",
|
|
927
|
-
allowOpaqueRuntime = false,
|
|
928
|
-
} = {}) {
|
|
929
|
-
if (!current || current.contract !== BUILDCHAIN_RUNTIME_CONTRACT_WORLD) {
|
|
930
|
-
throw new Error("current must be a Buildchain runtime contract world");
|
|
931
|
-
}
|
|
932
|
-
const binding = evaluateChannelBinding({
|
|
933
|
-
lock,
|
|
934
|
-
workflowShellRef,
|
|
935
|
-
runtimeRef,
|
|
936
|
-
expectedChannel,
|
|
937
|
-
expectedMajor,
|
|
938
|
-
allowOpaqueRuntime,
|
|
939
|
-
});
|
|
940
|
-
if (binding?.ok === false) {
|
|
941
|
-
return {
|
|
942
|
-
ok: false,
|
|
943
|
-
status: binding.status,
|
|
944
|
-
drift: false,
|
|
945
|
-
compatible: false,
|
|
946
|
-
issueRecommended: false,
|
|
947
|
-
reasons: binding.reasons,
|
|
948
|
-
channelBinding: binding,
|
|
949
|
-
};
|
|
950
|
-
}
|
|
951
|
-
const floatingRuntime = FLOATING_CLASSES.has(runtimeClass);
|
|
952
|
-
if (!floatingRuntime) {
|
|
953
|
-
return {
|
|
954
|
-
ok: true,
|
|
955
|
-
status: "non-floating-runtime",
|
|
956
|
-
drift: false,
|
|
957
|
-
compatible: true,
|
|
958
|
-
issueRecommended: false,
|
|
959
|
-
reason: `runtime class ${runtimeClass || "unknown"} is not a stable floating ref`,
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
if (!lock) {
|
|
963
|
-
return {
|
|
964
|
-
ok: true,
|
|
965
|
-
status: "missing-lock",
|
|
966
|
-
drift: false,
|
|
967
|
-
compatible: true,
|
|
968
|
-
issueRecommended: false,
|
|
969
|
-
reason: "consumer repository has no Buildchain contract lock",
|
|
970
|
-
};
|
|
971
|
-
}
|
|
972
|
-
const accepted = lock.buildchain || {};
|
|
973
|
-
const policy = compatibilityPolicy || accepted.compatibilityPolicy || DEFAULT_POLICY;
|
|
974
|
-
const shaDrift = !!accepted.resolvedSha && !!runtimeSha && accepted.resolvedSha !== runtimeSha;
|
|
975
|
-
const contractDrift = !!accepted.contractDigest && accepted.contractDigest !== current.contractDigest;
|
|
976
|
-
if (!shaDrift && !contractDrift) {
|
|
977
|
-
return {
|
|
978
|
-
ok: true,
|
|
979
|
-
status: "unchanged",
|
|
980
|
-
drift: false,
|
|
981
|
-
compatible: true,
|
|
982
|
-
issueRecommended: false,
|
|
983
|
-
policy,
|
|
984
|
-
accepted,
|
|
985
|
-
current: contractSummary(current, runtimeRef, runtimeSha),
|
|
986
|
-
};
|
|
987
|
-
}
|
|
988
|
-
const reasons = [];
|
|
989
|
-
if (accepted.contract !== current.contract) {
|
|
990
|
-
reasons.push(`contract changed from ${accepted.contract || "(unknown)"} to ${current.contract}`);
|
|
991
|
-
}
|
|
992
|
-
if (accepted.majorLine && accepted.majorLine !== current.majorLine) {
|
|
993
|
-
reasons.push(`major line changed from ${accepted.majorLine} to ${current.majorLine}`);
|
|
994
|
-
}
|
|
995
|
-
if (policy === "exact" && accepted.contractDigest !== current.contractDigest) {
|
|
996
|
-
reasons.push("exact policy requires the contract digest to remain unchanged");
|
|
997
|
-
}
|
|
998
|
-
if (!["major-compatible", "allow-additive", "exact"].includes(policy)) {
|
|
999
|
-
reasons.push(`unsupported compatibility policy: ${policy}`);
|
|
1000
|
-
}
|
|
1001
|
-
const currentSurfaces = surfaceMap(current.surfaces);
|
|
1002
|
-
for (const oldSurface of accepted.surfaces || []) {
|
|
1003
|
-
const nextSurface = currentSurfaces.get(oldSurface.id);
|
|
1004
|
-
if (!nextSurface) {
|
|
1005
|
-
reasons.push(`surface removed: ${oldSurface.id}`);
|
|
1006
|
-
continue;
|
|
1007
|
-
}
|
|
1008
|
-
const compatibleBreakingDigests = new Set(nextSurface.compatibleBreakingDigests || []);
|
|
1009
|
-
if (
|
|
1010
|
-
nextSurface.breakingDigest !== oldSurface.breakingDigest
|
|
1011
|
-
&& !compatibleBreakingDigests.has(oldSurface.breakingDigest)
|
|
1012
|
-
) {
|
|
1013
|
-
reasons.push(`surface breaking digest changed: ${oldSurface.id}`);
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
const compatible = reasons.length === 0;
|
|
1017
|
-
return {
|
|
1018
|
-
ok: compatible,
|
|
1019
|
-
status: compatible ? "compatible-drift" : "breaking-drift",
|
|
1020
|
-
drift: shaDrift || contractDrift,
|
|
1021
|
-
shaDrift,
|
|
1022
|
-
contractDrift,
|
|
1023
|
-
compatible,
|
|
1024
|
-
issueRecommended: true,
|
|
1025
|
-
policy,
|
|
1026
|
-
reasons,
|
|
1027
|
-
accepted,
|
|
1028
|
-
current: contractSummary(current, runtimeRef, runtimeSha),
|
|
1029
|
-
};
|
|
977
|
+
export function evaluateBuildchainContractLock(options = {}) {
|
|
978
|
+
return evaluateFactBackedBuildchainContractLock(options);
|
|
1030
979
|
}
|
|
1031
980
|
|
|
1032
981
|
export function contractSummary(contractWorld, runtimeRef = "", runtimeSha = "") {
|
|
@@ -1036,6 +985,9 @@ export function contractSummary(contractWorld, runtimeRef = "", runtimeSha = "")
|
|
|
1036
985
|
contract: contractWorld.contract,
|
|
1037
986
|
contractDigest: contractWorld.contractDigest,
|
|
1038
987
|
compatibilityDigest: contractWorld.compatibilityDigest,
|
|
988
|
+
compatibilityFactRegistryRoot: contractWorld.compatibilityFactRegistryRoot,
|
|
989
|
+
compatibilityFactCutRoot: contractWorld.compatibilityFactCutRoot,
|
|
990
|
+
compatibilityProofRegistryRoot: contractWorld.compatibilityProofRegistryRoot,
|
|
1039
991
|
majorLine: contractWorld.majorLine,
|
|
1040
992
|
surfaceCount: Array.isArray(contractWorld.surfaces) ? contractWorld.surfaces.length : 0,
|
|
1041
993
|
};
|
|
@@ -97,6 +97,14 @@ export function decideChannelCandidate(input) {
|
|
|
97
97
|
const sourceSha = sha(input.sourceSha, "sourceSha");
|
|
98
98
|
const targetSha = sha(input.targetSha, "targetSha");
|
|
99
99
|
const selection = normalizeChannelCandidateSelection(input.selection);
|
|
100
|
+
if (
|
|
101
|
+
selection?.versionReservation &&
|
|
102
|
+
selection.versionReservation.candidateSha !== sourceSha
|
|
103
|
+
) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
"selection version reservation must bind the selected source SHA",
|
|
106
|
+
);
|
|
107
|
+
}
|
|
100
108
|
const now = required(input.now || new Date().toISOString(), "now");
|
|
101
109
|
const maxAgeSeconds = Number(input.maxAgeSeconds ?? 7 * 24 * 60 * 60);
|
|
102
110
|
if (!Number.isSafeInteger(maxAgeSeconds) || maxAgeSeconds <= 0) {
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
nextDevelopmentPatrolContext,
|
|
5
|
+
normalizeNextDevelopmentVersionReservation,
|
|
6
|
+
} from "./next-development-candidate-reservation.js";
|
|
7
|
+
|
|
3
8
|
const SHA = /^[0-9a-f]{40}$/;
|
|
4
9
|
|
|
5
10
|
function text(value = "") {
|
|
@@ -59,6 +64,18 @@ export function normalizeChannelCandidateSelection(input) {
|
|
|
59
64
|
),
|
|
60
65
|
}
|
|
61
66
|
: {}),
|
|
67
|
+
...(input.versionReservation
|
|
68
|
+
? {
|
|
69
|
+
versionReservation: normalizeNextDevelopmentVersionReservation(
|
|
70
|
+
input.versionReservation,
|
|
71
|
+
{
|
|
72
|
+
reservationSha: input.versionReservation.reservationSha,
|
|
73
|
+
candidateSha: input.versionReservation.candidateSha,
|
|
74
|
+
targetVersion: input.versionReservation.targetVersion,
|
|
75
|
+
},
|
|
76
|
+
),
|
|
77
|
+
}
|
|
78
|
+
: {}),
|
|
62
79
|
};
|
|
63
80
|
}
|
|
64
81
|
|
|
@@ -117,6 +134,7 @@ export async function resolvePatrolSourceInputs({
|
|
|
117
134
|
let comparison = headComparison;
|
|
118
135
|
let workflowEvidence = [];
|
|
119
136
|
let skippedNewerCommitCount = 0;
|
|
137
|
+
let versionReservation;
|
|
120
138
|
let qualificationError;
|
|
121
139
|
if (
|
|
122
140
|
headComparison.status === "ahead" &&
|
|
@@ -130,6 +148,8 @@ export async function resolvePatrolSourceInputs({
|
|
|
130
148
|
),
|
|
131
149
|
]);
|
|
132
150
|
try {
|
|
151
|
+
const { preparations, ignoredSourceShas } =
|
|
152
|
+
nextDevelopmentPatrolContext(sourceHistory);
|
|
133
153
|
const selected = selectQualifiedSource({
|
|
134
154
|
sourceHistory,
|
|
135
155
|
workflowRunsByPath: new Map(
|
|
@@ -141,8 +161,42 @@ export async function resolvePatrolSourceInputs({
|
|
|
141
161
|
requiredWorkflowPaths,
|
|
142
162
|
now: options.now,
|
|
143
163
|
maxAgeSeconds: options.maxAgeSeconds,
|
|
164
|
+
ignoredSourceShas,
|
|
144
165
|
});
|
|
145
166
|
({ sourceSha, skippedNewerCommitCount, workflowEvidence } = selected);
|
|
167
|
+
const selectedIndex = sourceHistory.findIndex(
|
|
168
|
+
(entry) =>
|
|
169
|
+
(typeof entry === "string" ? entry : entry?.sha) === sourceSha,
|
|
170
|
+
);
|
|
171
|
+
const reservation = preparations.find(
|
|
172
|
+
({ index }) => index > selectedIndex,
|
|
173
|
+
)?.preparation;
|
|
174
|
+
if (reservation) {
|
|
175
|
+
if (
|
|
176
|
+
typeof client.readNextDevelopmentVersionReservation !== "function"
|
|
177
|
+
) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
"candidate follows next-development preparation but reservation readback is unavailable",
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
versionReservation = normalizeNextDevelopmentVersionReservation(
|
|
183
|
+
await client.readNextDevelopmentVersionReservation({
|
|
184
|
+
reservationSha: reservation.sha,
|
|
185
|
+
candidateSha: sourceSha,
|
|
186
|
+
targetVersion: reservation.targetVersion,
|
|
187
|
+
}),
|
|
188
|
+
{
|
|
189
|
+
reservationSha: reservation.sha,
|
|
190
|
+
candidateSha: sourceSha,
|
|
191
|
+
targetVersion: reservation.targetVersion,
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
if (versionReservation.status !== "current") {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`next-development version reservation is ${versionReservation.status} for candidate ${sourceSha}`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
146
200
|
if (sourceSha !== observedSourceHeadSha)
|
|
147
201
|
comparison = await client.compare(boundarySha, sourceSha);
|
|
148
202
|
} catch (error) {
|
|
@@ -158,6 +212,7 @@ export async function resolvePatrolSourceInputs({
|
|
|
158
212
|
comparison,
|
|
159
213
|
workflowEvidence,
|
|
160
214
|
skippedNewerCommitCount,
|
|
215
|
+
versionReservation,
|
|
161
216
|
qualificationError,
|
|
162
217
|
};
|
|
163
218
|
}
|
|
@@ -82,7 +82,9 @@ export function selectLatestQualifiedSource({
|
|
|
82
82
|
requiredWorkflowPaths,
|
|
83
83
|
now,
|
|
84
84
|
maxAgeSeconds,
|
|
85
|
+
ignoredSourceShas = [],
|
|
85
86
|
}) {
|
|
87
|
+
const ignored = new Set(ignoredSourceShas);
|
|
86
88
|
const latestByPath = new Map(
|
|
87
89
|
requiredWorkflowPaths.map((workflow) => [
|
|
88
90
|
workflow,
|
|
@@ -90,7 +92,11 @@ export function selectLatestQualifiedSource({
|
|
|
90
92
|
]),
|
|
91
93
|
);
|
|
92
94
|
for (let index = 0; index < sourceHistory.length; index += 1) {
|
|
93
|
-
const sourceSha =
|
|
95
|
+
const sourceSha =
|
|
96
|
+
typeof sourceHistory[index] === "string"
|
|
97
|
+
? sourceHistory[index]
|
|
98
|
+
: sourceHistory[index]?.sha;
|
|
99
|
+
if (ignored.has(sourceSha)) continue;
|
|
94
100
|
const rows = requiredWorkflowPaths.map((workflow) =>
|
|
95
101
|
latestByPath.get(workflow).get(sourceSha),
|
|
96
102
|
);
|
|
@@ -115,7 +121,9 @@ export function selectLatestQualifiedSource({
|
|
|
115
121
|
};
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
|
-
const staleSuccessfulPair = sourceHistory.some((
|
|
124
|
+
const staleSuccessfulPair = sourceHistory.some((entry) => {
|
|
125
|
+
const sourceSha = typeof entry === "string" ? entry : entry?.sha;
|
|
126
|
+
if (ignored.has(sourceSha)) return false;
|
|
119
127
|
const rows = requiredWorkflowPaths.map((workflow) =>
|
|
120
128
|
latestByPath.get(workflow).get(sourceSha),
|
|
121
129
|
);
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
devDeliveryText as text,
|
|
14
14
|
devDeliveryTimestamp as timestamp,
|
|
15
15
|
} from "./dev-delivery-common.js";
|
|
16
|
+
import { verifyNativeProofReuseDecision } from "./dev-delivery-native-proof.js";
|
|
16
17
|
import { verifyNativeQualificationProof } from "./dev-delivery-native-proof.js";
|
|
17
18
|
import { verifyNativeExecutionFailureOutcome } from "./dev-delivery-execution-failure.js";
|
|
18
19
|
|
|
@@ -408,8 +409,28 @@ function verifySuccessOutcome(transfer, directory, warrant) {
|
|
|
408
409
|
"native proof reuse decision root",
|
|
409
410
|
);
|
|
410
411
|
delete decisionBody.decisionRoot;
|
|
412
|
+
const reuseVerification = verifyNativeProofReuseDecision(decision, {
|
|
413
|
+
proof,
|
|
414
|
+
current: {
|
|
415
|
+
sourceHead: warrant.sourceHead,
|
|
416
|
+
sourceIdentityRoot: warrant.sourceIdentityRoot,
|
|
417
|
+
sourcePatchRoot: warrant.sourcePatchRoot,
|
|
418
|
+
planRoot: warrant.planRoot,
|
|
419
|
+
closureRoot: warrant.closureRoot,
|
|
420
|
+
dependencyRoot: warrant.dependencyRoot,
|
|
421
|
+
toolchainRoot: warrant.toolchainRoot,
|
|
422
|
+
environmentRoot: warrant.environmentRoot,
|
|
423
|
+
nativeCommandRoot: warrant.nativeCommandContract?.commandRoot,
|
|
424
|
+
currentBase: decision.currentBase,
|
|
425
|
+
graphKnown: decision.graphKnown,
|
|
426
|
+
attributionComplete: decision.attributionComplete,
|
|
427
|
+
changedPaths: decision.changedPaths,
|
|
428
|
+
renames: decision.renames,
|
|
429
|
+
},
|
|
430
|
+
});
|
|
411
431
|
if (
|
|
412
432
|
!proofVerification.ok ||
|
|
433
|
+
!reuseVerification.ok ||
|
|
413
434
|
result.nativeProofRoot !== transfer.nativeProofRoot ||
|
|
414
435
|
proof.proofRoot !== transfer.nativeProofRoot ||
|
|
415
436
|
result.nativeReuseDecisionRoot !== transfer.nativeReuseDecisionRoot ||
|
|
@@ -420,7 +441,6 @@ function verifySuccessOutcome(transfer, directory, warrant) {
|
|
|
420
441
|
proof.repository !== transfer.warrant.repository ||
|
|
421
442
|
proof.protectedBase !== transfer.warrant.protectedBase ||
|
|
422
443
|
proof.sourceHead !== transfer.warrant.sourceHead ||
|
|
423
|
-
proof.qualifiedBase !== warrant.qualifiedBase ||
|
|
424
444
|
proof.sourceIdentityRoot !== warrant.sourceIdentityRoot ||
|
|
425
445
|
proof.sourcePatchRoot !== warrant.sourcePatchRoot ||
|
|
426
446
|
proof.planRoot !== warrant.planRoot ||
|
|
@@ -30,7 +30,7 @@ const PROVIDER_CREDENTIAL_NAME =
|
|
|
30
30
|
/(?:^(?:ACTIONS_ID_TOKEN_REQUEST_TOKEN|ACTIONS_RUNTIME_TOKEN|AWS_SESSION_TOKEN|AWS_WEB_IDENTITY_TOKEN_FILE|AZURE_FEDERATED_TOKEN_FILE|GH_TOKEN|GITHUB_TOKEN|GOOGLE_GHA_CREDS_PATH|KUNGFU_GITHUB_TOKEN|NPM_TOKEN|SSH_AUTH_SOCK)$|(?:^|_)(?:(?:api|access|auth|publish|release|provider|promotion|governance|approval|mutation|write)_?(?:key|token)|authorization|credentials?|password|secret|private_key|client_secret|signing_key)(?:_|$))/iu;
|
|
31
31
|
const HOSTED_RUNNER_BOUNDARY = "github-actions-runner-worker/v1";
|
|
32
32
|
const GITHUB_HOSTED_LINUX_WORKER =
|
|
33
|
-
/^\/home\/runner\/runners\/(\d+\.\d+\.\d+)\/bin\/Runner\.Worker$/u;
|
|
33
|
+
/^\/home\/runner\/(?:runners|actions-runner\/cached)\/(\d+\.\d+\.\d+)\/bin\/Runner\.Worker$/u;
|
|
34
34
|
function text(value, label) {
|
|
35
35
|
const normalized = normalizedText(value);
|
|
36
36
|
if (!normalized) throw new Error(`${label} is required`);
|
|
@@ -61,10 +61,16 @@ function processCredentialNames(procRoot, pid, readFileSync) {
|
|
|
61
61
|
.split("\0")
|
|
62
62
|
.filter((entry) => {
|
|
63
63
|
const separator = entry.indexOf("=");
|
|
64
|
+
const name = entry.slice(0, separator);
|
|
65
|
+
const value = entry.slice(separator + 1);
|
|
64
66
|
return (
|
|
65
67
|
separator > 0 &&
|
|
66
|
-
Boolean(
|
|
67
|
-
|
|
68
|
+
Boolean(value) &&
|
|
69
|
+
!(
|
|
70
|
+
name === "BUILDCHAIN_CREDENTIAL_ANCESTRY_BOUNDARY" &&
|
|
71
|
+
value === HOSTED_RUNNER_BOUNDARY
|
|
72
|
+
) &&
|
|
73
|
+
isCredentialVariableName(name)
|
|
68
74
|
);
|
|
69
75
|
})
|
|
70
76
|
.map((entry) => entry.slice(0, entry.indexOf("=")));
|
|
@@ -108,9 +114,11 @@ function hostedBoundary(procRoot, pid, command, readlinkSync) {
|
|
|
108
114
|
const executable = ancestryRead(readlinkSync, link);
|
|
109
115
|
if (
|
|
110
116
|
!GITHUB_HOSTED_LINUX_WORKER.test(executable) ||
|
|
111
|
-
|
|
117
|
+
path.posix.basename(command[0]) !== path.posix.basename(executable)
|
|
112
118
|
) {
|
|
113
|
-
throw new Error(
|
|
119
|
+
throw new Error(
|
|
120
|
+
`credential ancestry found an untrusted Runner.Worker: executable=${JSON.stringify(executable)} argv0=${JSON.stringify(command[0])}`,
|
|
121
|
+
);
|
|
114
122
|
}
|
|
115
123
|
return { pid, kind: HOSTED_RUNNER_BOUNDARY, executable };
|
|
116
124
|
}
|
|
@@ -163,7 +171,11 @@ export function assertCredentiallessProcessAncestry(options = {}) {
|
|
|
163
171
|
return inspection;
|
|
164
172
|
}
|
|
165
173
|
|
|
166
|
-
export function devDeliveryProviderJobByName(
|
|
174
|
+
export function devDeliveryProviderJobByName(
|
|
175
|
+
jobs,
|
|
176
|
+
expectedName,
|
|
177
|
+
{ allowMissing = false } = {},
|
|
178
|
+
) {
|
|
167
179
|
const exactName = text(expectedName, "provider job name");
|
|
168
180
|
const suffix = ` / ${exactName}`;
|
|
169
181
|
const matches = jobs.filter((job) => {
|
|
@@ -173,6 +185,7 @@ export function devDeliveryProviderJobByName(jobs, expectedName) {
|
|
|
173
185
|
(actualName.endsWith(suffix) && actualName.length > suffix.length)
|
|
174
186
|
);
|
|
175
187
|
});
|
|
188
|
+
if (matches.length === 0 && allowMissing) return null;
|
|
176
189
|
if (matches.length !== 1) {
|
|
177
190
|
throw new Error(`expected exactly one provider job named ${exactName}`);
|
|
178
191
|
}
|
|
@@ -13,7 +13,7 @@ export const DEV_DELIVERY_PROVIDER_HEARTBEAT_SCHEMA =
|
|
|
13
13
|
"kungfu.buildchain.provider-heartbeat-receipt/v1";
|
|
14
14
|
|
|
15
15
|
function selectedWarrant(result = {}) {
|
|
16
|
-
return result.
|
|
16
|
+
return result.warrant || result.observation?.activeWarrant || null;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function exactWarrant(input = {}) {
|
|
@@ -40,8 +40,9 @@ function exactWarrant(input = {}) {
|
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
function job(entries, name) {
|
|
44
|
-
const entry = devDeliveryProviderJobByName(entries, name);
|
|
43
|
+
function job(entries, name, options) {
|
|
44
|
+
const entry = devDeliveryProviderJobByName(entries, name, options);
|
|
45
|
+
if (!entry) return null;
|
|
45
46
|
const providerJob = {
|
|
46
47
|
id: positiveInteger(entry.id, `${name} job id`),
|
|
47
48
|
name,
|
|
@@ -66,13 +67,16 @@ function job(entries, name) {
|
|
|
66
67
|
return providerJob;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
function providerJobs(input, expected) {
|
|
70
|
+
function providerJobs(input, expected, { allowMissingSeal = false } = {}) {
|
|
70
71
|
const entries = Array.isArray(input) ? input : input?.jobs;
|
|
71
72
|
if (!Array.isArray(entries)) {
|
|
72
73
|
throw new Error("provider heartbeat jobs readback must contain jobs");
|
|
73
74
|
}
|
|
74
75
|
const native = job(entries, expected.nativeJobName);
|
|
75
|
-
const seal = job(entries, expected.sealJobName
|
|
76
|
+
const seal = job(entries, expected.sealJobName, {
|
|
77
|
+
allowMissing: allowMissingSeal,
|
|
78
|
+
});
|
|
79
|
+
if (!seal) return null;
|
|
76
80
|
if (native.id === seal.id) {
|
|
77
81
|
throw new Error("provider heartbeat native and seal jobs must be distinct");
|
|
78
82
|
}
|
|
@@ -206,7 +210,15 @@ export async function runDevDeliveryProviderHeartbeat(
|
|
|
206
210
|
const entry = heartbeatEntry(result, previousStateRoot);
|
|
207
211
|
heartbeats.push(entry);
|
|
208
212
|
previousStateRoot = entry.nextStateRoot;
|
|
209
|
-
jobs = providerJobs(
|
|
213
|
+
jobs = providerJobs(
|
|
214
|
+
await readJobs(),
|
|
215
|
+
{ nativeJobName, sealJobName },
|
|
216
|
+
{ allowMissingSeal: true },
|
|
217
|
+
);
|
|
218
|
+
if (!jobs) {
|
|
219
|
+
await wait(cadence * 1000);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
210
222
|
if (terminal(jobs.native) && terminal(jobs.seal)) break;
|
|
211
223
|
await wait(cadence * 1000);
|
|
212
224
|
}
|