@kungfu-tech/buildchain 3.0.9-alpha.4 → 3.0.9-alpha.6
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/bin/buildchain.mjs +5 -1
- package/dist/site/buildchain-contract.json +9 -9
- package/dist/site/buildchain-site.json +56 -26
- package/dist/site/cli-registry.json +18 -2
- package/dist/site/kfd-claims.json +8 -5
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +6 -6
- package/dist/site/node-api-registry.json +290 -49
- package/dist/site/page-registry.json +46 -16
- package/dist/site/public-surface-audit.json +122 -6
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +10 -10
- package/dist/site/workflow-registry.json +12 -6
- package/docs/cli-reference.md +79 -13
- package/docs/dev-delivery-warrant.md +53 -19
- package/docs/kfd-support.md +7 -2
- package/docs/node-api-reference.md +59 -51
- package/docs/release-governance.md +5 -4
- package/docs/release-passport.md +8 -2
- package/docs/versioning.md +1 -0
- package/package.json +3 -2
- package/packages/core/artifact-verification-envelope.js +5 -5
- package/packages/core/dev-delivery-native-proof.js +333 -0
- package/packages/core/dev-delivery-warrant-settlement.js +5 -1
- package/packages/core/dev-delivery-warrant.js +40 -6
- package/packages/core/kfd-adopter-manifest.js +46 -47
- package/packages/core/release-passport-contract.js +5 -5
- package/packages/core/release-passport.js +5 -4
- package/scripts/aws-macos-jit-controller-core.mjs +82 -2
- package/scripts/aws-macos-jit-controller.mjs +81 -1
- package/scripts/aws-macos-jit-source-rebind.mjs +147 -24
- package/scripts/buildchain-cli-help.mjs +6 -2
- package/scripts/check-internal-architecture.mjs +1 -0
- package/scripts/dev-delivery-native-run.mjs +187 -0
- package/scripts/dev-delivery-proof.mjs +54 -2
- package/scripts/dev-delivery-two-phase.mjs +598 -0
- package/scripts/dev-delivery-warrant.mjs +38 -3
- package/scripts/dev-pr-delivery-warrant.mjs +6 -1
- package/scripts/generate-channel-build-workflow.mjs +4 -0
- package/scripts/init-repo.mjs +16 -0
- package/scripts/release-candidate-tail-reseal.mjs +426 -0
- package/templates/native-dev-delivery.yml +82 -0
package/docs/release-passport.md
CHANGED
|
@@ -410,8 +410,14 @@ Pass the standard full-cut declaration with `--kfd-adopter-manifest-json`
|
|
|
410
410
|
together with three `--kfd-product-gate-json` arguments for KFD-4, KFD-5, and
|
|
411
411
|
KFD-7. The collector invokes the verifier from the exact installed
|
|
412
412
|
`@kungfu-tech/kfd` package, binds the package artifact, registry and verifier
|
|
413
|
-
roots, the exact
|
|
414
|
-
and the existing product-gate roots.
|
|
413
|
+
roots, the collector's exact product repository and source, the
|
|
414
|
+
manifest/report/bundle witness roots, and the existing product-gate roots. A
|
|
415
|
+
non-Buildchain adopter is accepted only when its manifest identity, artifact
|
|
416
|
+
coordinate, and all three product-gate repositories match the collector's
|
|
417
|
+
`--repository` value. The lower-level Node API exposes the same boundary as
|
|
418
|
+
`expectedAdopterId`, `expectedSourceRepository`, and `expectedSourceSha`, while
|
|
419
|
+
omitted identity/repository values retain the Buildchain self-release default.
|
|
420
|
+
It emits `kfd-adopter-manifest.json`,
|
|
415
421
|
`kfd-adopter-manifest-gate.json`, and a legacy `kfd-support.json` projection.
|
|
416
422
|
The release passport and `artifact-evidence.json` carry the same rooted
|
|
417
423
|
`kfdAdopter` binding.
|
package/docs/versioning.md
CHANGED
|
@@ -79,6 +79,7 @@ repository changes are patch".
|
|
|
79
79
|
|
|
80
80
|
| Date | Action | Line | Faces | Class | Rationale | PR |
|
|
81
81
|
| ---------- | ------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
|
|
82
|
+
| 2026-08-11 | extend-minor | `v3.0` | provisional-delivery-warrant, qualified-delivery-warrant, native-qualification-proof, native-proof-reuse-decision, generated-native-delivery-workflow | additive | Reserve protected-dev order after exact source acceptance and approval, then require an atomic native-proof-bound qualification before merge-queue admission. Reuse is limited to stable semantic source, closure, dependencies, and toolchain with a fully attributed disjoint base delta; overlap and uncertainty fail closed to revalidation. | |
|
|
82
83
|
| 2026-08-11 | extend-minor | `v3.0` | release-blocker-repair-contract, release-blocker-priority-claim, release-train-node-export, dev-delivery-warrant-node-export, next-development-transition, next-development-projection | additive | Add rooted successor candidate generations on a frozen Release Cut, exact semantic patch identity across cut and Dev landings, publication blocking through conflict or mismatch, a narrow non-preemptive Warrant blocker lane, and a separate rooted transition that preserves completed Alpha success while the following development version is materialized. | |
|
|
83
84
|
| 2026-08-10 | extend-minor | `v3.0` | release-cut-contract, release-train-state-machine, release-train-node-export | additive | Add a provider-neutral rooted Release Cut and idempotent Release Train state contract that freezes candidate, tree, Alpha base, runtime, generation, and authority while treating later Dev movement as observation rather than implicit supersession. | |
|
|
84
85
|
| 2026-08-05 | extend-minor | `v3.0` | auditable-demo-scenario, auditable-demo-capture, auditable-demo-adapter | additive | Add an opt-in deterministic readable-playback declaration that preserves captured terminal payloads and order while normalizing only presentation timing; omitted playback continues to use observed PTY timestamps, and existing composition modes remain unchanged. | |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "3.0.9-alpha.
|
|
3
|
+
"version": "3.0.9-alpha.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
|
|
6
6
|
"repository": "https://github.com/kungfu-systems/buildchain",
|
|
@@ -111,7 +111,8 @@
|
|
|
111
111
|
"SECURITY.md",
|
|
112
112
|
"docs/*.md",
|
|
113
113
|
"actions/*/README.md",
|
|
114
|
-
"fixtures/*/README.md"
|
|
114
|
+
"fixtures/*/README.md",
|
|
115
|
+
"templates/"
|
|
115
116
|
],
|
|
116
117
|
"engines": {
|
|
117
118
|
"node": ">=22.14.0"
|
|
@@ -417,7 +417,7 @@ function kfdBindingBody({ manifest, manifestGate, legacyProjection, paths }) {
|
|
|
417
417
|
gatePath: paths.gate,
|
|
418
418
|
gateRoot: manifestGate.gateRoot,
|
|
419
419
|
},
|
|
420
|
-
source: jsonClone(manifestGate.source),
|
|
420
|
+
adopter: jsonClone(manifestGate.adopter), source: jsonClone(manifestGate.source),
|
|
421
421
|
standardPackage: jsonClone(manifestGate.standardPackage),
|
|
422
422
|
witness: {
|
|
423
423
|
decisionRoot: manifestGate.decisionWitness.root,
|
|
@@ -445,10 +445,10 @@ export function createKfdAdopterReleaseBinding({
|
|
|
445
445
|
manifestPath = "kfd-adopter-manifest.json",
|
|
446
446
|
gatePath = "kfd-adopter-manifest-gate.json",
|
|
447
447
|
legacyProjectionPath = "kfd-support.json",
|
|
448
|
-
expectedSourceSha = "",
|
|
448
|
+
expectedAdopterId = "kungfu-systems/buildchain", expectedSourceRepository = "", expectedSourceSha = "",
|
|
449
449
|
} = {}) {
|
|
450
450
|
const gate = validateKfdAdopterManifestGate(manifestGate, {
|
|
451
|
-
expectedSourceSha,
|
|
451
|
+
expectedAdopterId, expectedSourceRepository, expectedSourceSha,
|
|
452
452
|
checkedAt: manifestGate?.checkedAt,
|
|
453
453
|
});
|
|
454
454
|
const legacy = validateKfdLegacySupportMatrixProjection(legacyProjection, { manifest, manifestGate });
|
|
@@ -468,7 +468,7 @@ export function validateKfdAdopterReleaseBinding(binding, {
|
|
|
468
468
|
manifest,
|
|
469
469
|
manifestGate,
|
|
470
470
|
legacyProjection,
|
|
471
|
-
expectedSourceSha = "",
|
|
471
|
+
expectedAdopterId = "kungfu-systems/buildchain", expectedSourceRepository = "", expectedSourceSha = "",
|
|
472
472
|
} = {}) {
|
|
473
473
|
const issues = [];
|
|
474
474
|
if (!binding || binding.schemaVersion !== 1 || binding.contract !== KFD_ADOPTER_RELEASE_BINDING_CONTRACT) {
|
|
@@ -488,7 +488,7 @@ export function validateKfdAdopterReleaseBinding(binding, {
|
|
|
488
488
|
manifestPath: binding.authority?.manifestPath,
|
|
489
489
|
gatePath: binding.authority?.gatePath,
|
|
490
490
|
legacyProjectionPath: binding.legacyProjection?.path,
|
|
491
|
-
expectedSourceSha,
|
|
491
|
+
expectedAdopterId, expectedSourceRepository, expectedSourceSha,
|
|
492
492
|
});
|
|
493
493
|
if (stableJson(binding) !== stableJson(expected)) {
|
|
494
494
|
issues.push({ code: "adopter-release-binding-drift", path: "", message: "release binding differs from the exact manifest, gate, or legacy projection closure" });
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import {
|
|
2
|
+
devDeliveryClone as clone,
|
|
3
|
+
devDeliveryContentRoot,
|
|
4
|
+
devDeliveryExactRoot as exactRoot,
|
|
5
|
+
devDeliveryExactSha as exactSha,
|
|
6
|
+
devDeliveryProtectedBase as protectedBase,
|
|
7
|
+
devDeliveryRepository as repository,
|
|
8
|
+
devDeliveryText as text,
|
|
9
|
+
devDeliveryTimestamp as timestamp,
|
|
10
|
+
} from "./dev-delivery-common.js";
|
|
11
|
+
|
|
12
|
+
export const NATIVE_QUALIFICATION_PROOF_SCHEMA =
|
|
13
|
+
"kungfu.buildchain.native-qualification-proof/v1";
|
|
14
|
+
export const NATIVE_PROOF_REUSE_DECISION_SCHEMA =
|
|
15
|
+
"kungfu.buildchain.native-proof-reuse-decision/v1";
|
|
16
|
+
export const DEV_DELIVERY_QUALIFICATION_RECEIPT_SCHEMA =
|
|
17
|
+
"kungfu.buildchain.dev-delivery-qualification-receipt/v1";
|
|
18
|
+
|
|
19
|
+
const NATIVE_QUALIFICATION_ROOT_SEMANTICS = "semantic-native-identity-v1";
|
|
20
|
+
|
|
21
|
+
function exactRoots(values, label) {
|
|
22
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
23
|
+
throw new Error(`${label} must contain at least one content root`);
|
|
24
|
+
}
|
|
25
|
+
return [...new Set(values.map((value) => exactRoot(value, label)))].sort();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizedPaths(values) {
|
|
29
|
+
if (!Array.isArray(values)) throw new Error("paths must be a JSON array");
|
|
30
|
+
return [
|
|
31
|
+
...new Set(
|
|
32
|
+
values
|
|
33
|
+
.map(text)
|
|
34
|
+
.map((entry) => entry.replace(/^\.\//u, "").replace(/\/+$/u, ""))
|
|
35
|
+
.filter(Boolean),
|
|
36
|
+
),
|
|
37
|
+
].sort();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function proofIdentity(body) {
|
|
41
|
+
const identity = clone(body);
|
|
42
|
+
delete identity.qualifiedAt;
|
|
43
|
+
delete identity.observationRoot;
|
|
44
|
+
return identity;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function proofObservation(body) {
|
|
48
|
+
return { qualifiedAt: body.qualifiedAt };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function rooted(body) {
|
|
52
|
+
return { ...body, decisionRoot: devDeliveryContentRoot(body) };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createNativeQualificationProof(input = {}) {
|
|
56
|
+
const body = {
|
|
57
|
+
schema: NATIVE_QUALIFICATION_PROOF_SCHEMA,
|
|
58
|
+
rootSemantics: NATIVE_QUALIFICATION_ROOT_SEMANTICS,
|
|
59
|
+
repository: repository(input.repository),
|
|
60
|
+
protectedBase: protectedBase(input.protectedBase),
|
|
61
|
+
sourceIdentityRoot: exactRoot(
|
|
62
|
+
input.sourceIdentityRoot,
|
|
63
|
+
"sourceIdentityRoot",
|
|
64
|
+
),
|
|
65
|
+
sourcePatchRoot: exactRoot(input.sourcePatchRoot, "sourcePatchRoot"),
|
|
66
|
+
planRoot: exactRoot(input.planRoot, "planRoot"),
|
|
67
|
+
closureRoot: exactRoot(input.closureRoot, "closureRoot"),
|
|
68
|
+
dependencyRoot: exactRoot(input.dependencyRoot, "dependencyRoot"),
|
|
69
|
+
toolchainRoot: exactRoot(input.toolchainRoot, "toolchainRoot"),
|
|
70
|
+
qualifiedBase: exactSha(input.qualifiedBase, "qualifiedBase"),
|
|
71
|
+
affectedPaths: normalizedPaths(input.affectedPaths || []),
|
|
72
|
+
shardEvidenceRoots: exactRoots(
|
|
73
|
+
input.shardEvidenceRoots,
|
|
74
|
+
"shardEvidenceRoots",
|
|
75
|
+
),
|
|
76
|
+
qualifiedAt: timestamp(input.qualifiedAt, "qualifiedAt"),
|
|
77
|
+
};
|
|
78
|
+
body.observationRoot = devDeliveryContentRoot(proofObservation(body));
|
|
79
|
+
return {
|
|
80
|
+
...body,
|
|
81
|
+
proofRoot: devDeliveryContentRoot(proofIdentity(body)),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function verifyNativeQualificationProof(proofInput, expected = {}) {
|
|
86
|
+
try {
|
|
87
|
+
const proof = clone(proofInput || {});
|
|
88
|
+
if (proof.schema !== NATIVE_QUALIFICATION_PROOF_SCHEMA) {
|
|
89
|
+
return { ok: false, reason: "unsupported-schema" };
|
|
90
|
+
}
|
|
91
|
+
const proofRoot = proof.proofRoot;
|
|
92
|
+
delete proof.proofRoot;
|
|
93
|
+
if (proof.rootSemantics !== NATIVE_QUALIFICATION_ROOT_SEMANTICS) {
|
|
94
|
+
return { ok: false, reason: "unsupported-root-semantics" };
|
|
95
|
+
}
|
|
96
|
+
if (devDeliveryContentRoot(proofIdentity(proof)) !== proofRoot) {
|
|
97
|
+
return { ok: false, reason: "proof-root-drift" };
|
|
98
|
+
}
|
|
99
|
+
if (
|
|
100
|
+
devDeliveryContentRoot(proofObservation(proof)) !== proof.observationRoot
|
|
101
|
+
) {
|
|
102
|
+
return { ok: false, reason: "observation-root-drift" };
|
|
103
|
+
}
|
|
104
|
+
for (const [field, value] of Object.entries(expected)) {
|
|
105
|
+
if (value !== undefined && proof[field] !== value) {
|
|
106
|
+
return { ok: false, reason: `${field}-mismatch` };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
createNativeQualificationProof(proof);
|
|
110
|
+
return { ok: true, reason: "exact-native-proof", proofRoot };
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return { ok: false, reason: "invalid-proof", error: error.message };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function pathsOverlap(changedPath, affectedPath) {
|
|
117
|
+
if (affectedPath === "*" || affectedPath === "**") return true;
|
|
118
|
+
return (
|
|
119
|
+
changedPath === affectedPath ||
|
|
120
|
+
changedPath.startsWith(`${affectedPath}/`) ||
|
|
121
|
+
affectedPath.startsWith(`${changedPath}/`)
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function nativeReuseBody(proof, current) {
|
|
126
|
+
const verification = verifyNativeQualificationProof(proof);
|
|
127
|
+
const currentBase = current.currentBase
|
|
128
|
+
? exactSha(current.currentBase, "currentBase")
|
|
129
|
+
: null;
|
|
130
|
+
const changedPaths = normalizedPaths(current.changedPaths || []);
|
|
131
|
+
const base = {
|
|
132
|
+
schema: NATIVE_PROOF_REUSE_DECISION_SCHEMA,
|
|
133
|
+
proofRoot: verification.ok ? proof.proofRoot : null,
|
|
134
|
+
repository: verification.ok ? proof.repository : null,
|
|
135
|
+
protectedBase: verification.ok ? proof.protectedBase : null,
|
|
136
|
+
qualifiedBase: verification.ok ? proof.qualifiedBase : null,
|
|
137
|
+
currentBase,
|
|
138
|
+
graphKnown: current.graphKnown === true,
|
|
139
|
+
changedPaths,
|
|
140
|
+
overlappingPaths: [],
|
|
141
|
+
reusable: false,
|
|
142
|
+
action: "rerun-full-native-qualification",
|
|
143
|
+
reason: verification.reason,
|
|
144
|
+
requiredValidation: "full-native",
|
|
145
|
+
};
|
|
146
|
+
if (!verification.ok) return base;
|
|
147
|
+
|
|
148
|
+
const semanticFields = [
|
|
149
|
+
"sourceIdentityRoot",
|
|
150
|
+
"sourcePatchRoot",
|
|
151
|
+
"planRoot",
|
|
152
|
+
"closureRoot",
|
|
153
|
+
"dependencyRoot",
|
|
154
|
+
"toolchainRoot",
|
|
155
|
+
];
|
|
156
|
+
for (const field of semanticFields) {
|
|
157
|
+
if (!current[field] || current[field] !== proof[field]) {
|
|
158
|
+
return {
|
|
159
|
+
...base,
|
|
160
|
+
reason: `${field}-changed-or-unknown`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (!currentBase) {
|
|
165
|
+
return { ...base, reason: "current-base-unknown" };
|
|
166
|
+
}
|
|
167
|
+
if (currentBase === proof.qualifiedBase) {
|
|
168
|
+
return {
|
|
169
|
+
...base,
|
|
170
|
+
reusable: true,
|
|
171
|
+
action: "accept-fresh-native-proof",
|
|
172
|
+
reason: "exact-qualified-base",
|
|
173
|
+
requiredValidation: "exact-merge-group",
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (current.graphKnown !== true || !Array.isArray(current.changedPaths)) {
|
|
177
|
+
return { ...base, reason: "base-delta-attribution-unknown" };
|
|
178
|
+
}
|
|
179
|
+
if (proof.affectedPaths.length === 0) {
|
|
180
|
+
return { ...base, reason: "affected-closure-paths-unknown" };
|
|
181
|
+
}
|
|
182
|
+
const overlap = changedPaths.filter((changedPath) =>
|
|
183
|
+
proof.affectedPaths.some((affectedPath) =>
|
|
184
|
+
pathsOverlap(changedPath, affectedPath),
|
|
185
|
+
),
|
|
186
|
+
);
|
|
187
|
+
if (overlap.length > 0) {
|
|
188
|
+
return {
|
|
189
|
+
...base,
|
|
190
|
+
action: "rerun-affected-native-shards",
|
|
191
|
+
reason: "base-delta-overlaps-affected-closure",
|
|
192
|
+
requiredValidation: "incremental-or-full-native",
|
|
193
|
+
overlappingPaths: overlap,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
...base,
|
|
198
|
+
reusable: true,
|
|
199
|
+
action: "reuse-native-proof",
|
|
200
|
+
reason: "semantic-source-stable-and-base-delta-disjoint",
|
|
201
|
+
requiredValidation: "exact-merge-group",
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function createNativeProofReuseDecision({ proof, current = {} } = {}) {
|
|
206
|
+
return rooted(nativeReuseBody(proof, current));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function verifyNativeProofReuseDecision(
|
|
210
|
+
decisionInput,
|
|
211
|
+
{ proof, current = {} } = {},
|
|
212
|
+
) {
|
|
213
|
+
try {
|
|
214
|
+
const decision = clone(decisionInput || {});
|
|
215
|
+
if (decision.schema !== NATIVE_PROOF_REUSE_DECISION_SCHEMA) {
|
|
216
|
+
return { ok: false, reason: "unsupported-schema" };
|
|
217
|
+
}
|
|
218
|
+
const decisionRoot = decision.decisionRoot;
|
|
219
|
+
delete decision.decisionRoot;
|
|
220
|
+
if (devDeliveryContentRoot(decision) !== decisionRoot) {
|
|
221
|
+
return { ok: false, reason: "decision-root-drift" };
|
|
222
|
+
}
|
|
223
|
+
const recomputed = nativeReuseBody(proof, current);
|
|
224
|
+
if (JSON.stringify(decision) !== JSON.stringify(recomputed)) {
|
|
225
|
+
return { ok: false, reason: "decision-input-drift" };
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
ok: decision.reusable === true,
|
|
229
|
+
reason: decision.reason,
|
|
230
|
+
decisionRoot,
|
|
231
|
+
action: decision.action,
|
|
232
|
+
};
|
|
233
|
+
} catch (error) {
|
|
234
|
+
return { ok: false, reason: "invalid-decision", error: error.message };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function createDevDeliveryWarrantQualifier({
|
|
239
|
+
transition,
|
|
240
|
+
assertWarrantMutation,
|
|
241
|
+
} = {}) {
|
|
242
|
+
return function qualifyDevDeliveryWarrant(
|
|
243
|
+
queueInput,
|
|
244
|
+
warrant,
|
|
245
|
+
{
|
|
246
|
+
nativeProof,
|
|
247
|
+
reuseDecision,
|
|
248
|
+
current = {},
|
|
249
|
+
now = new Date().toISOString(),
|
|
250
|
+
} = {},
|
|
251
|
+
) {
|
|
252
|
+
const currentTime = timestamp(now, "now");
|
|
253
|
+
const transaction = transition(
|
|
254
|
+
queueInput,
|
|
255
|
+
(queue, before) => {
|
|
256
|
+
assertWarrantMutation(before, warrant, currentTime);
|
|
257
|
+
if (before.activeWarrant.phase !== "provisional") {
|
|
258
|
+
throw new Error(
|
|
259
|
+
"only a provisional Delivery Warrant can be qualified",
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
const active = before.activeWarrant;
|
|
263
|
+
const proofVerification = verifyNativeQualificationProof(nativeProof, {
|
|
264
|
+
repository: before.repository,
|
|
265
|
+
protectedBase: before.protectedBase,
|
|
266
|
+
sourceIdentityRoot: active.sourceIdentityRoot,
|
|
267
|
+
sourcePatchRoot: active.sourcePatchRoot,
|
|
268
|
+
planRoot: active.planRoot,
|
|
269
|
+
closureRoot: active.closureRoot,
|
|
270
|
+
dependencyRoot: active.dependencyRoot,
|
|
271
|
+
toolchainRoot: active.toolchainRoot,
|
|
272
|
+
});
|
|
273
|
+
if (!proofVerification.ok) {
|
|
274
|
+
throw new Error(`native proof rejected: ${proofVerification.reason}`);
|
|
275
|
+
}
|
|
276
|
+
const reuseVerification = verifyNativeProofReuseDecision(
|
|
277
|
+
reuseDecision,
|
|
278
|
+
{
|
|
279
|
+
proof: nativeProof,
|
|
280
|
+
current: {
|
|
281
|
+
...current,
|
|
282
|
+
sourceIdentityRoot: active.sourceIdentityRoot,
|
|
283
|
+
sourcePatchRoot: active.sourcePatchRoot,
|
|
284
|
+
planRoot: active.planRoot,
|
|
285
|
+
closureRoot: active.closureRoot,
|
|
286
|
+
dependencyRoot: active.dependencyRoot,
|
|
287
|
+
toolchainRoot: active.toolchainRoot,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
);
|
|
291
|
+
if (!reuseVerification.ok) {
|
|
292
|
+
throw new Error(
|
|
293
|
+
`native proof is not reusable: ${reuseVerification.reason}`,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
queue.activeWarrant.phase = "qualified";
|
|
297
|
+
queue.activeWarrant.nativeProofRoot = proofVerification.proofRoot;
|
|
298
|
+
queue.activeWarrant.nativeProofReuseRoot =
|
|
299
|
+
reuseVerification.decisionRoot;
|
|
300
|
+
queue.activeWarrant.qualifiedAt = currentTime;
|
|
301
|
+
queue.activeWarrant.nextAction =
|
|
302
|
+
"Enter the GitHub merge queue at the exact fenced PR head; merge_group remains final authority.";
|
|
303
|
+
const candidate = queue.candidates.find(
|
|
304
|
+
(entry) => entry.candidateId === queue.activeWarrant.candidateId,
|
|
305
|
+
);
|
|
306
|
+
candidate.status = "qualified";
|
|
307
|
+
candidate.updatedAt = currentTime;
|
|
308
|
+
return { candidate, warrant: queue.activeWarrant };
|
|
309
|
+
},
|
|
310
|
+
currentTime,
|
|
311
|
+
);
|
|
312
|
+
const receipt = {
|
|
313
|
+
schema: DEV_DELIVERY_QUALIFICATION_RECEIPT_SCHEMA,
|
|
314
|
+
action: "qualified-warrant",
|
|
315
|
+
candidateId: transaction.result.candidate.candidateId,
|
|
316
|
+
fencingToken: transaction.result.warrant.fencingToken,
|
|
317
|
+
leaseGeneration: transaction.result.warrant.generation,
|
|
318
|
+
phase: transaction.result.warrant.phase,
|
|
319
|
+
nativeProofRoot: transaction.result.warrant.nativeProofRoot,
|
|
320
|
+
nativeProofReuseRoot: transaction.result.warrant.nativeProofReuseRoot,
|
|
321
|
+
qualifiedAt: transaction.result.warrant.qualifiedAt,
|
|
322
|
+
expectedOldStateRoot: transaction.expectedOldStateRoot,
|
|
323
|
+
nextStateRoot: transaction.after.stateRoot,
|
|
324
|
+
nextAction: transaction.result.warrant.nextAction,
|
|
325
|
+
};
|
|
326
|
+
return {
|
|
327
|
+
queue: transaction.after,
|
|
328
|
+
warrant: transaction.result.warrant,
|
|
329
|
+
receipt,
|
|
330
|
+
receiptRoot: devDeliveryContentRoot(receipt),
|
|
331
|
+
};
|
|
332
|
+
};
|
|
333
|
+
}
|
|
@@ -33,7 +33,11 @@ export function createDevDeliveryTerminalSettler({ normalizeQueue, closeWarrant,
|
|
|
33
33
|
};
|
|
34
34
|
if (!terminalStates.has(identity.outcome)) throw new Error(`outcome must be one of ${[...terminalStates].join(", ")}`);
|
|
35
35
|
const samePullRequest = queue.candidates.filter((entry) => entry.pullRequestNumber === identity.pullRequestNumber);
|
|
36
|
-
const
|
|
36
|
+
const matchingHead = samePullRequest.filter((entry) => entry.sourceHead === identity.sourceHead);
|
|
37
|
+
const activeCandidate = queue.activeWarrant
|
|
38
|
+
? matchingHead.find((entry) => entry.candidateId === queue.activeWarrant.candidateId)
|
|
39
|
+
: null;
|
|
40
|
+
const candidate = activeCandidate || matchingHead.at(-1) || null;
|
|
37
41
|
if (!candidate && samePullRequest.length > 0) throw new Error("terminal event sourceHead does not match the recorded candidate");
|
|
38
42
|
|
|
39
43
|
if (candidate && terminalStates.has(candidate.status)) {
|
|
@@ -2,10 +2,12 @@ import { devDeliveryClone as clone, devDeliveryContentRoot, devDeliveryExactRoot
|
|
|
2
2
|
import { chainedDevDeliveryAttemptInput, createDevDeliveryCandidateIdentity, validateDevDeliveryCandidateChain } from "./dev-delivery-candidate-identity.js";
|
|
3
3
|
import { DEV_DELIVERY_CANCELLATION_RECEIPT_SCHEMA, createCancelQueuedDevDeliveryCandidate } from "./dev-delivery-warrant-cancellation.js";
|
|
4
4
|
import { DEV_DELIVERY_SETTLEMENT_RECEIPT_SCHEMA, createDevDeliveryTerminalSettler } from "./dev-delivery-warrant-settlement.js";
|
|
5
|
+
import { createDevDeliveryWarrantQualifier } from "./dev-delivery-native-proof.js";
|
|
5
6
|
import { compareReleaseBlockerPriority, normalizeReleaseBlockerPriorityClaim } from "./release-blocker-priority.js";
|
|
6
7
|
export { devDeliveryContentRoot } from "./dev-delivery-common.js";
|
|
7
8
|
export { RELEASE_BLOCKER_PRIORITY_CLAIM_SCHEMA, createReleaseBlockerPriorityClaim } from "./release-blocker-priority.js";
|
|
8
9
|
export { SOURCE_QUALIFICATION_PROOF_SCHEMA, PROJECT_CUT_REPLAY_PROOF_SCHEMA, INTEGRATION_DELIVERY_PROOF_SCHEMA, classifyDevDeliveryDelta, createIntegrationDeliveryProof, createProjectCutReplayPlan, createProjectCutReplayProof, createSourceQualificationProof, verifyIntegrationDeliveryProof, verifyProjectCutReplayProof, verifySourceQualificationProof } from "./dev-delivery-proof.js";
|
|
10
|
+
export { DEV_DELIVERY_QUALIFICATION_RECEIPT_SCHEMA, NATIVE_QUALIFICATION_PROOF_SCHEMA, NATIVE_PROOF_REUSE_DECISION_SCHEMA, createNativeProofReuseDecision, createNativeQualificationProof, verifyNativeProofReuseDecision, verifyNativeQualificationProof } from "./dev-delivery-native-proof.js";
|
|
9
11
|
export { DEV_DELIVERY_CANCELLATION_RECEIPT_SCHEMA };
|
|
10
12
|
export const DEV_DELIVERY_QUEUE_CONTRACT = "kungfu-buildchain-dev-delivery-warrant-queue";
|
|
11
13
|
export const DEV_DELIVERY_WARRANT_SCHEMA = "kungfu.buildchain.dev-delivery-warrant/v1";
|
|
@@ -59,7 +61,7 @@ function normalizeCandidate(input, expected) {
|
|
|
59
61
|
throw new Error(`candidateId mismatch for PR #${identity.pullRequestNumber}`);
|
|
60
62
|
}
|
|
61
63
|
const status = text(input.status || "queued");
|
|
62
|
-
if (!["queued", "selected", "proving", "waiting", "blocked", ...TERMINAL_STATES].includes(status)) {
|
|
64
|
+
if (!["queued", "selected", "provisional", "proving", "qualified", "waiting", "blocked", ...TERMINAL_STATES].includes(status)) {
|
|
63
65
|
throw new Error(`unsupported candidate status ${status || "<empty>"}`);
|
|
64
66
|
}
|
|
65
67
|
const candidate = {
|
|
@@ -80,6 +82,10 @@ function normalizeCandidate(input, expected) {
|
|
|
80
82
|
status,
|
|
81
83
|
terminal: input.terminal || null,
|
|
82
84
|
};
|
|
85
|
+
if (Object.hasOwn(input, "affectedPaths")) {
|
|
86
|
+
if (!Array.isArray(input.affectedPaths)) throw new Error("candidate affectedPaths must be an array");
|
|
87
|
+
candidate.affectedPaths = [...new Set(input.affectedPaths.map(text).filter(Boolean))].sort();
|
|
88
|
+
}
|
|
83
89
|
if (Object.hasOwn(input, "releaseBlockerPriority")) candidate.releaseBlockerPriority = normalizeReleaseBlockerPriorityClaim(input.releaseBlockerPriority, candidate, expected);
|
|
84
90
|
return candidate;
|
|
85
91
|
}
|
|
@@ -140,12 +146,20 @@ export function normalizeDevDeliveryQueue(input, expected = {}) {
|
|
|
140
146
|
warrant.generation = positiveInteger(warrant.generation, "Warrant generation");
|
|
141
147
|
warrant.issuedAt = timestamp(warrant.issuedAt, "Warrant issuedAt");
|
|
142
148
|
warrant.expiresAt = timestamp(warrant.expiresAt, "Warrant expiresAt");
|
|
143
|
-
const
|
|
149
|
+
const warrantPhase = text(warrant.phase || "qualified");
|
|
150
|
+
if (!["provisional", "qualified"].includes(warrantPhase)) throw new Error("active Warrant phase is unsupported");
|
|
151
|
+
if (Object.hasOwn(warrant, "phase")) warrant.phase = warrantPhase;
|
|
152
|
+
if (warrantPhase === "qualified") {
|
|
153
|
+
if (warrant.nativeProofRoot) warrant.nativeProofRoot = exactRoot(warrant.nativeProofRoot, "Warrant nativeProofRoot");
|
|
154
|
+
if (warrant.nativeProofReuseRoot) warrant.nativeProofReuseRoot = exactRoot(warrant.nativeProofReuseRoot, "Warrant nativeProofReuseRoot");
|
|
155
|
+
if (warrant.qualifiedAt) warrant.qualifiedAt = timestamp(warrant.qualifiedAt, "Warrant qualifiedAt");
|
|
156
|
+
}
|
|
157
|
+
const activeCandidates = queue.candidates.filter((candidate) => ["selected", "provisional", "proving", "qualified", "waiting", "blocked"].includes(candidate.status));
|
|
144
158
|
if (activeCandidates.length !== 1 || activeCandidates[0].candidateId !== warrant.candidateId) {
|
|
145
159
|
throw new Error("exactly one active candidate must match the active Warrant");
|
|
146
160
|
}
|
|
147
161
|
if (warrant.releaseBlockerPriority?.claimRoot !== activeCandidates[0].releaseBlockerPriority?.claimRoot) throw new Error("active Warrant release-blocker priority drift");
|
|
148
|
-
} else if (queue.candidates.some((candidate) => ["selected", "proving", "waiting", "blocked"].includes(candidate.status))) {
|
|
162
|
+
} else if (queue.candidates.some((candidate) => ["selected", "provisional", "proving", "qualified", "waiting", "blocked"].includes(candidate.status))) {
|
|
149
163
|
throw new Error("active candidate exists without an active Warrant");
|
|
150
164
|
}
|
|
151
165
|
const rooted = withQueueRoot(queue);
|
|
@@ -184,6 +198,7 @@ function submissionReceipt({ before, after, candidate, action, now }) {
|
|
|
184
198
|
closureRoot: candidate.closureRoot,
|
|
185
199
|
dependencyRoot: candidate.dependencyRoot,
|
|
186
200
|
toolchainRoot: candidate.toolchainRoot,
|
|
201
|
+
...(Object.hasOwn(candidate, "affectedPaths") ? { affectedPaths: candidate.affectedPaths } : {}),
|
|
187
202
|
sourceWorkflowRunId: candidate.sourceWorkflowRunId,
|
|
188
203
|
...(Object.hasOwn(candidate, "releaseBlockerPriority") ? { releaseBlockerPriority: candidate.releaseBlockerPriority } : {}),
|
|
189
204
|
deliveryClass: candidate.deliveryClass,
|
|
@@ -225,7 +240,7 @@ export function submitDevDeliveryCandidate(queueInput, input, { now = new Date()
|
|
|
225
240
|
throw new Error(`candidate ${attemptedCandidate.candidateId} is terminal and cannot be resubmitted`);
|
|
226
241
|
}
|
|
227
242
|
const exactProofFields = ["sourcePatchRoot", "sourceProofRoot", "planRoot", "closureRoot", "dependencyRoot", "toolchainRoot"];
|
|
228
|
-
const exactProofMatches = exactProofFields.every((field) => existing[field] === attemptedCandidate[field]) && existing.releaseBlockerPriority?.claimRoot === attemptedCandidate.releaseBlockerPriority?.claimRoot;
|
|
243
|
+
const exactProofMatches = exactProofFields.every((field) => existing[field] === attemptedCandidate[field]) && JSON.stringify(existing.affectedPaths || []) === JSON.stringify(attemptedCandidate.affectedPaths || []) && existing.releaseBlockerPriority?.claimRoot === attemptedCandidate.releaseBlockerPriority?.claimRoot;
|
|
229
244
|
if (existing.sourceHead === attemptedCandidate.sourceHead && exactProofMatches) {
|
|
230
245
|
action = "duplicate-noop";
|
|
231
246
|
selected = existing;
|
|
@@ -241,6 +256,8 @@ export function submitDevDeliveryCandidate(queueInput, input, { now = new Date()
|
|
|
241
256
|
const headChanged = existing.sourceHead !== attemptedCandidate.sourceHead;
|
|
242
257
|
existing.sourceHead = attemptedCandidate.sourceHead;
|
|
243
258
|
for (const field of exactProofFields) existing[field] = attemptedCandidate[field];
|
|
259
|
+
if (Object.hasOwn(attemptedCandidate, "affectedPaths")) existing.affectedPaths = attemptedCandidate.affectedPaths;
|
|
260
|
+
else delete existing.affectedPaths;
|
|
244
261
|
if (Object.hasOwn(attemptedCandidate, "releaseBlockerPriority")) existing.releaseBlockerPriority = attemptedCandidate.releaseBlockerPriority;
|
|
245
262
|
else delete existing.releaseBlockerPriority;
|
|
246
263
|
existing.updatedAt = currentTime;
|
|
@@ -379,15 +396,17 @@ export function selectDevDeliveryWarrant(queueInput, { now = new Date().toISOStr
|
|
|
379
396
|
closureRoot: candidate.closureRoot,
|
|
380
397
|
dependencyRoot: candidate.dependencyRoot,
|
|
381
398
|
toolchainRoot: candidate.toolchainRoot,
|
|
399
|
+
...(Object.hasOwn(candidate, "affectedPaths") ? { affectedPaths: candidate.affectedPaths } : {}),
|
|
382
400
|
sourceWorkflowRunId: candidate.sourceWorkflowRunId,
|
|
383
401
|
...(Object.hasOwn(candidate, "releaseBlockerPriority") ? { releaseBlockerPriority: candidate.releaseBlockerPriority } : {}),
|
|
384
402
|
deliveryClass: candidate.deliveryClass,
|
|
385
403
|
generation: next.fencingCounter,
|
|
404
|
+
phase: "provisional",
|
|
386
405
|
expectedOldStateRoot: before.stateRoot,
|
|
387
406
|
issuedAt,
|
|
388
407
|
expiresAt: new Date(Date.parse(issuedAt) + effectiveLeaseSeconds * 1000).toISOString(),
|
|
389
408
|
fencingToken: "",
|
|
390
|
-
nextAction: "
|
|
409
|
+
nextAction: "Run or reuse the exact semantic native proof, then atomically qualify this fenced Warrant.",
|
|
391
410
|
};
|
|
392
411
|
warrant.fencingToken = warrantToken(warrant);
|
|
393
412
|
next.activeWarrant = warrant;
|
|
@@ -454,7 +473,7 @@ export function heartbeatDevDeliveryWarrant(queueInput, warrant, { now = new Dat
|
|
|
454
473
|
const effectiveLeaseSeconds = positiveInteger(leaseSeconds, "leaseSeconds", queue.policy.leaseSeconds);
|
|
455
474
|
queue.activeWarrant.expiresAt = new Date(Date.parse(currentTime) + effectiveLeaseSeconds * 1000).toISOString();
|
|
456
475
|
const candidate = queue.candidates.find((entry) => entry.candidateId === queue.activeWarrant.candidateId);
|
|
457
|
-
candidate.status = "proving";
|
|
476
|
+
candidate.status = queue.activeWarrant.phase === "qualified" ? "qualified" : "proving";
|
|
458
477
|
candidate.updatedAt = currentTime;
|
|
459
478
|
return { candidate, warrant: queue.activeWarrant };
|
|
460
479
|
},
|
|
@@ -478,6 +497,8 @@ export function heartbeatDevDeliveryWarrant(queueInput, warrant, { now = new Dat
|
|
|
478
497
|
};
|
|
479
498
|
}
|
|
480
499
|
|
|
500
|
+
const qualifyDevDeliveryWarrantTransition = createDevDeliveryWarrantQualifier({ transition, assertWarrantMutation });
|
|
501
|
+
export function qualifyDevDeliveryWarrant(queueInput, warrant, options) { return qualifyDevDeliveryWarrantTransition(queueInput, warrant, options); }
|
|
481
502
|
export function recoverExpiredDevDeliveryWarrant(queueInput, { now = new Date().toISOString() } = {}) {
|
|
482
503
|
const currentTime = timestamp(now, "now");
|
|
483
504
|
const before = normalizeDevDeliveryQueue(queueInput);
|
|
@@ -604,6 +625,19 @@ export function observeDevDeliveryQueue(queueInput, { now = new Date().toISOStri
|
|
|
604
625
|
queueAgeSeconds: entry.priority.ageSeconds,
|
|
605
626
|
attempts: entry.candidate.attempts,
|
|
606
627
|
recoveries: entry.candidate.recoveries,
|
|
628
|
+
assignmentRoot: entry.candidate.assignmentRoot,
|
|
629
|
+
initiativeRoot: entry.candidate.initiativeRoot,
|
|
630
|
+
sourceIdentityRoot: entry.candidate.sourceIdentityRoot,
|
|
631
|
+
sourcePatchRoot: entry.candidate.sourcePatchRoot,
|
|
632
|
+
sourceProofRoot: entry.candidate.sourceProofRoot,
|
|
633
|
+
planRoot: entry.candidate.planRoot,
|
|
634
|
+
closureRoot: entry.candidate.closureRoot,
|
|
635
|
+
dependencyRoot: entry.candidate.dependencyRoot,
|
|
636
|
+
toolchainRoot: entry.candidate.toolchainRoot,
|
|
637
|
+
sourceWorkflowRunId: entry.candidate.sourceWorkflowRunId || 0,
|
|
638
|
+
affectedPaths: entry.candidate.affectedPaths || [],
|
|
639
|
+
deliveryClass: entry.candidate.deliveryClass,
|
|
640
|
+
priority: entry.candidate.priority,
|
|
607
641
|
nextAction: index === 0 && !queue.activeWarrant ? "eligible-for-selection" : "wait-for-active-warrant",
|
|
608
642
|
})),
|
|
609
643
|
observedAt: currentTime,
|