@kungfu-tech/buildchain 3.0.6-alpha.1 → 3.0.6-alpha.2

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.
Files changed (68) hide show
  1. package/README.md +4 -4
  2. package/actions/promote-buildchain-ref/README.md +8 -0
  3. package/bin/buildchain.mjs +13 -1
  4. package/contracts/auditable-demo-scenario-v1.schema.json +52 -0
  5. package/dist/site/buildchain-contract.json +47 -27
  6. package/dist/site/buildchain-site.json +158 -42
  7. package/dist/site/capability-registry.json +3 -3
  8. package/dist/site/cli-registry.json +40 -4
  9. package/dist/site/controller-registry.json +20 -4
  10. package/dist/site/kfd-claims.json +139 -19
  11. package/dist/site/kfd-upstream-aggregate.json +1 -1
  12. package/dist/site/manual-registry.json +9 -9
  13. package/dist/site/node-api-registry.json +1161 -180
  14. package/dist/site/page-registry.json +145 -29
  15. package/dist/site/public-surface-audit.json +385 -19
  16. package/dist/site/publication-authority-registry.json +61 -1
  17. package/dist/site/publication-registry.json +4 -4
  18. package/dist/site/release-provenance.json +1 -0
  19. package/dist/site/site-manifest.json +13 -13
  20. package/dist/site/workflow-registry.json +150 -13
  21. package/docs/MAP.md +1 -0
  22. package/docs/auditable-demo.md +58 -11
  23. package/docs/aws-us-elastic-runner-burst-plane.md +23 -19
  24. package/docs/cli-reference.md +154 -0
  25. package/docs/dev-alpha-candidate-patrol.md +13 -5
  26. package/docs/dev-delivery-warrant.md +158 -0
  27. package/docs/node-api-reference.md +54 -15
  28. package/docs/publication-authority.md +11 -0
  29. package/docs/release-candidate.md +19 -2
  30. package/docs/release-governance.md +52 -0
  31. package/docs/reusable-build-surface.md +11 -1
  32. package/docs/shifu-gate-profiles.md +12 -1
  33. package/docs/versioning.md +2 -0
  34. package/package.json +2 -1
  35. package/packages/core/buildchain-publication-authority.js +3 -1
  36. package/packages/core/channel-candidate.js +2 -21
  37. package/packages/core/channel-promotion-baseline.js +199 -0
  38. package/packages/core/dev-delivery-candidate-identity.js +94 -0
  39. package/packages/core/dev-delivery-common.js +73 -0
  40. package/packages/core/dev-delivery-proof.js +252 -0
  41. package/packages/core/dev-delivery-warrant-cancellation.js +94 -0
  42. package/packages/core/dev-delivery-warrant-settlement.js +73 -0
  43. package/packages/core/dev-delivery-warrant.js +591 -0
  44. package/scripts/auditable-demo-bundle-verification.mjs +148 -0
  45. package/scripts/auditable-demo-platform.mjs +86 -50
  46. package/scripts/auditable-demo-presentation.mjs +83 -0
  47. package/scripts/auditable-demo-renditions.mjs +264 -0
  48. package/scripts/auditable-demo.mjs +24 -30
  49. package/scripts/build-contract-core.mjs +58 -3
  50. package/scripts/buildchain-cli-help.mjs +8 -0
  51. package/scripts/buildchain-patrol.mjs +9 -0
  52. package/scripts/check-inventory.mjs +1 -0
  53. package/scripts/dev-alpha-candidate-patrol.mjs +45 -48
  54. package/scripts/dev-delivery-proof.mjs +193 -0
  55. package/scripts/dev-delivery-warrant.mjs +426 -0
  56. package/scripts/dev-pr-auto-merge.mjs +488 -46
  57. package/scripts/dev-pr-delivery-warrant.mjs +209 -0
  58. package/scripts/gate-profile-core.mjs +24 -0
  59. package/scripts/generate-site-bundle.mjs +2 -2
  60. package/scripts/git-fetch-process-tree.mjs +142 -0
  61. package/scripts/lifecycle-substage-evidence.mjs +274 -0
  62. package/scripts/locked-source-checkout.mjs +6 -3
  63. package/scripts/resolve-artifact-transfer-mode.mjs +9 -0
  64. package/scripts/resolve-build-contract.mjs +7 -0
  65. package/scripts/route-offline-runners.mjs +1 -0
  66. package/scripts/run-lifecycle-core.mjs +9 -9
  67. package/scripts/shifu-gate-profile.mjs +10 -16
  68. package/scripts/site-capability-metadata.mjs +14 -0
@@ -0,0 +1,252 @@
1
+ import { devDeliveryClone as clone, devDeliveryContentRoot, devDeliveryExactRoot as exactRoot, devDeliveryExactSha as exactSha, devDeliveryPositiveInteger as positiveInteger, devDeliveryProtectedBase as protectedBase, devDeliveryRepository as repository, devDeliveryText as text, devDeliveryTimestamp as timestamp } from "./dev-delivery-common.js";
2
+
3
+ export const SOURCE_QUALIFICATION_PROOF_SCHEMA = "kungfu.buildchain.source-qualification-proof/v1";
4
+ export const PROJECT_CUT_REPLAY_PROOF_SCHEMA = "kungfu.buildchain.project-cut-replay-proof/v1";
5
+ export const INTEGRATION_DELIVERY_PROOF_SCHEMA = "kungfu.buildchain.integration-delivery-proof/v1";
6
+
7
+ const SOURCE_QUALIFICATION_ROOT_SEMANTICS = "qualification-identity-v1";
8
+
9
+ const DEV_DELIVERY_WARRANT_SCHEMA = "kungfu.buildchain.dev-delivery-warrant/v1";
10
+
11
+ function exactRoots(values, label) {
12
+ if (!Array.isArray(values) || values.length === 0) throw new Error(`${label} must contain at least one content root`);
13
+ return [...new Set(values.map((value) => exactRoot(value, label)))].sort();
14
+ }
15
+
16
+ function rootedProof(body) {
17
+ return { ...body, proofRoot: devDeliveryContentRoot(body) };
18
+ }
19
+
20
+ function sourceQualificationIdentity(body) {
21
+ const identity = clone(body);
22
+ delete identity.qualifiedAt;
23
+ delete identity.observationRoot;
24
+ return identity;
25
+ }
26
+
27
+ function sourceQualificationObservation(body) {
28
+ return { qualifiedAt: body.qualifiedAt };
29
+ }
30
+
31
+ export function createSourceQualificationProof(input = {}) {
32
+ const body = {
33
+ schema: SOURCE_QUALIFICATION_PROOF_SCHEMA,
34
+ rootSemantics: SOURCE_QUALIFICATION_ROOT_SEMANTICS,
35
+ repository: repository(input.repository),
36
+ protectedBase: protectedBase(input.protectedBase),
37
+ sourceIdentityRoot: exactRoot(input.sourceIdentityRoot, "sourceIdentityRoot"),
38
+ sourceHead: exactSha(input.sourceHead, "sourceHead"),
39
+ sourcePatchRoot: exactRoot(input.sourcePatchRoot, "sourcePatchRoot"),
40
+ planRoot: exactRoot(input.planRoot, "planRoot"),
41
+ closureRoot: exactRoot(input.closureRoot, "closureRoot"),
42
+ dependencyRoot: exactRoot(input.dependencyRoot, "dependencyRoot"),
43
+ toolchainRoot: exactRoot(input.toolchainRoot, "toolchainRoot"),
44
+ affectedPaths: [...new Set((input.affectedPaths || []).map(text).filter(Boolean))].sort(),
45
+ shardEvidenceRoots: exactRoots(input.shardEvidenceRoots, "shardEvidenceRoots"),
46
+ qualifiedAt: timestamp(input.qualifiedAt, "qualifiedAt"),
47
+ };
48
+ body.observationRoot = devDeliveryContentRoot(sourceQualificationObservation(body));
49
+ return { ...body, proofRoot: devDeliveryContentRoot(sourceQualificationIdentity(body)) };
50
+ }
51
+
52
+ export function verifySourceQualificationProof(proofInput, expected = {}) {
53
+ try {
54
+ const proof = clone(proofInput || {});
55
+ if (proof.schema !== SOURCE_QUALIFICATION_PROOF_SCHEMA) return { ok: false, reason: "unsupported-schema" };
56
+ const proofRoot = proof.proofRoot;
57
+ delete proof.proofRoot;
58
+ if (proof.rootSemantics === undefined) {
59
+ if (devDeliveryContentRoot(proof) !== proofRoot) return { ok: false, reason: "proof-root-drift" };
60
+ } else {
61
+ if (proof.rootSemantics !== SOURCE_QUALIFICATION_ROOT_SEMANTICS) return { ok: false, reason: "unsupported-root-semantics" };
62
+ if (devDeliveryContentRoot(sourceQualificationIdentity(proof)) !== proofRoot) return { ok: false, reason: "proof-root-drift" };
63
+ if (devDeliveryContentRoot(sourceQualificationObservation(proof)) !== proof.observationRoot) return { ok: false, reason: "observation-root-drift" };
64
+ }
65
+ for (const [field, value] of Object.entries(expected)) {
66
+ if (value !== undefined && proof[field] !== value) return { ok: false, reason: `${field}-mismatch` };
67
+ }
68
+ createSourceQualificationProof(proof);
69
+ return { ok: true, reason: "exact-source-proof", proofRoot };
70
+ } catch (error) {
71
+ return { ok: false, reason: "invalid-proof", error: error.message };
72
+ }
73
+ }
74
+
75
+ export function classifyDevDeliveryDelta({ proof, current = {} } = {}) {
76
+ const verification = verifySourceQualificationProof(proof);
77
+ if (!verification.ok)
78
+ return {
79
+ action: "rerun-full-source-qualification",
80
+ reason: verification.reason,
81
+ reusable: false,
82
+ };
83
+ const exactPredicates = ["sourceIdentityRoot", "sourcePatchRoot", "planRoot", "closureRoot", "dependencyRoot", "toolchainRoot"];
84
+ for (const field of exactPredicates) {
85
+ if (!current[field] || current[field] !== proof[field]) {
86
+ return {
87
+ action: "rerun-full-source-qualification",
88
+ reason: `${field}-changed-or-unknown`,
89
+ reusable: false,
90
+ };
91
+ }
92
+ }
93
+ if (current.graphKnown !== true || !Array.isArray(current.changedPaths)) {
94
+ return {
95
+ action: "rerun-full-source-qualification",
96
+ reason: "dependency-attribution-unknown",
97
+ reusable: false,
98
+ };
99
+ }
100
+ const affected = new Set(proof.affectedPaths || []);
101
+ const overlap = [...new Set(current.changedPaths.map(text).filter((entry) => affected.has(entry)))].sort();
102
+ if (overlap.length > 0) {
103
+ return {
104
+ action: "rerun-affected-source-shards",
105
+ reason: "dev-delta-overlaps-affected-closure",
106
+ reusable: false,
107
+ overlappingPaths: overlap,
108
+ requiredFinalGate: "exact-integration-delivery-proof",
109
+ };
110
+ }
111
+ return {
112
+ action: "reuse-source-qualification",
113
+ reason: "unrelated-dev-delta",
114
+ reusable: true,
115
+ proofRoot: proof.proofRoot,
116
+ requiredReplay: "cheap-project-cut-replay",
117
+ requiredFinalGate: "exact-integration-delivery-proof",
118
+ };
119
+ }
120
+
121
+ export function createProjectCutReplayPlan(input = {}) {
122
+ const sourceHead = exactSha(input.sourceHead, "sourceHead");
123
+ const previousBase = exactSha(input.previousBase, "previousBase");
124
+ const currentBase = exactSha(input.currentBase, "currentBase");
125
+ return rootedProof({
126
+ schema: "kungfu.buildchain.project-cut-replay-plan/v1",
127
+ repository: repository(input.repository),
128
+ protectedBase: protectedBase(input.protectedBase),
129
+ pullRequestNumber: positiveInteger(input.pullRequestNumber, "pullRequestNumber"),
130
+ sourceHead,
131
+ previousBase,
132
+ currentBase,
133
+ sourcePatchRoot: exactRoot(input.sourcePatchRoot, "sourcePatchRoot"),
134
+ replayTree: exactSha(input.replayTree, "replayTree"),
135
+ sourceHeadMutationRequired: false,
136
+ action: previousBase === currentBase ? "verify-existing-replay" : "replay-on-latest-base",
137
+ finalAuthority: "github-merge-group",
138
+ });
139
+ }
140
+
141
+ export function createProjectCutReplayProof(input = {}) {
142
+ const qualification = input.qualificationReceipt || input.qualification || {};
143
+ if (qualification.schema !== "project.cut.merge-queue-admission/v1") throw new Error("Project Cut qualification schema is invalid");
144
+ if (qualification.ok !== true || qualification.decision !== "qualified") throw new Error("Project Cut qualification is not qualified");
145
+ if (!Array.isArray(qualification.reasonCodes) || qualification.reasonCodes.length !== 0) throw new Error("Project Cut qualification retains failure reasons");
146
+ if (typeof qualification.compositionChanged !== "boolean") throw new Error("Project Cut qualification compositionChanged is invalid");
147
+ if (!qualification.compositionChanged && qualification.compositionRoot != null) throw new Error("unchanged Project Cut qualification must not claim a compositionRoot");
148
+ const sourceHead = exactSha(input.sourceHead, "sourceHead");
149
+ const currentBase = exactSha(input.currentBase, "currentBase");
150
+ const replayTree = exactSha(input.replayTree, "replayTree");
151
+ if (exactSha(qualification.baseCommitOid, "qualification.baseCommitOid") !== currentBase) throw new Error("Project Cut qualification base does not match currentBase");
152
+ if (exactSha(qualification.headCommitOid, "qualification.headCommitOid") !== sourceHead) throw new Error("Project Cut qualification head does not match sourceHead");
153
+ if (exactSha(qualification.candidateTreeOid, "qualification.candidateTreeOid") !== replayTree) throw new Error("Project Cut qualification tree does not match replayTree");
154
+ const normalizedQualification = {
155
+ schema: qualification.schema,
156
+ ok: true,
157
+ decision: "qualified",
158
+ baseCommitOid: currentBase,
159
+ headCommitOid: sourceHead,
160
+ candidateCommitOid: exactSha(qualification.candidateCommitOid, "qualification.candidateCommitOid"),
161
+ candidateTreeOid: replayTree,
162
+ replayedCommitCount: positiveInteger(qualification.replayedCommitCount, "qualification.replayedCommitCount"),
163
+ compositionChanged: Boolean(qualification.compositionChanged),
164
+ compositionRoot: qualification.compositionChanged
165
+ ? exactRoot(qualification.compositionRoot, "qualification.compositionRoot")
166
+ : null,
167
+ reasonCodes: [],
168
+ };
169
+ return rootedProof({
170
+ schema: PROJECT_CUT_REPLAY_PROOF_SCHEMA,
171
+ repository: repository(input.repository),
172
+ protectedBase: protectedBase(input.protectedBase),
173
+ pullRequestNumber: positiveInteger(input.pullRequestNumber, "pullRequestNumber"),
174
+ sourceHead,
175
+ sourcePatchRoot: exactRoot(input.sourcePatchRoot, "sourcePatchRoot"),
176
+ currentBase,
177
+ replayTree,
178
+ qualification: normalizedQualification,
179
+ qualificationRoot: devDeliveryContentRoot(normalizedQualification),
180
+ requiredContextRoots: exactRoots(input.requiredContextRoots, "requiredContextRoots"),
181
+ verifiedAt: timestamp(input.verifiedAt, "verifiedAt"),
182
+ sourceHeadMutationRequired: false,
183
+ finalAuthority: "exact-project-cut-replay",
184
+ });
185
+ }
186
+
187
+ export function verifyProjectCutReplayProof(proofInput, expected = {}) {
188
+ try {
189
+ const proof = clone(proofInput || {});
190
+ if (proof.schema !== PROJECT_CUT_REPLAY_PROOF_SCHEMA) return { ok: false, reason: "unsupported-schema" };
191
+ const proofRoot = proof.proofRoot;
192
+ delete proof.proofRoot;
193
+ if (devDeliveryContentRoot(proof) !== proofRoot) return { ok: false, reason: "proof-root-drift" };
194
+ if (devDeliveryContentRoot(proof.qualification) !== proof.qualificationRoot) return { ok: false, reason: "qualification-root-drift" };
195
+ if (proof.finalAuthority !== "exact-project-cut-replay") return { ok: false, reason: "non-exact-final-authority" };
196
+ if (proof.sourceHeadMutationRequired !== false) return { ok: false, reason: "source-head-mutation-required" };
197
+ for (const [field, value] of Object.entries(expected)) {
198
+ if (value !== undefined && proof[field] !== value) return { ok: false, reason: `${field}-mismatch` };
199
+ }
200
+ createProjectCutReplayProof(proof);
201
+ return { ok: true, reason: "exact-project-cut-replay", proofRoot };
202
+ } catch (error) {
203
+ return { ok: false, reason: "invalid-proof", error: error.message };
204
+ }
205
+ }
206
+
207
+ export function createIntegrationDeliveryProof(input = {}) {
208
+ const warrant = input.warrant || {};
209
+ if (warrant.schema !== DEV_DELIVERY_WARRANT_SCHEMA) throw new Error("integration proof requires a Delivery Warrant");
210
+ return rootedProof({
211
+ schema: INTEGRATION_DELIVERY_PROOF_SCHEMA,
212
+ repository: repository(input.repository),
213
+ protectedBase: protectedBase(input.protectedBase),
214
+ sourceProofRoot: exactRoot(input.sourceProofRoot, "sourceProofRoot"),
215
+ currentBase: exactSha(input.currentBase, "currentBase"),
216
+ replayTree: exactSha(input.replayTree, "replayTree"),
217
+ mergeGroupHead: exactSha(input.mergeGroupHead, "mergeGroupHead"),
218
+ mergeGroupTree: exactSha(input.mergeGroupTree, "mergeGroupTree"),
219
+ warrantCandidateId: exactRoot(warrant.candidateId, "Warrant candidateId"),
220
+ warrantFencingToken: exactRoot(warrant.fencingToken, "Warrant fencingToken"),
221
+ warrantGeneration: positiveInteger(warrant.generation, "Warrant generation"),
222
+ requiredContextRoots: exactRoots(input.requiredContextRoots, "requiredContextRoots"),
223
+ verifiedAt: timestamp(input.verifiedAt, "verifiedAt"),
224
+ finalAuthority: "exact-github-merge-group",
225
+ });
226
+ }
227
+
228
+ export function verifyIntegrationDeliveryProof(proofInput, expected = {}) {
229
+ try {
230
+ const proof = clone(proofInput || {});
231
+ if (proof.schema !== INTEGRATION_DELIVERY_PROOF_SCHEMA) return { ok: false, reason: "unsupported-schema" };
232
+ const proofRoot = proof.proofRoot;
233
+ delete proof.proofRoot;
234
+ if (devDeliveryContentRoot(proof) !== proofRoot) return { ok: false, reason: "proof-root-drift" };
235
+ if (proof.finalAuthority !== "exact-github-merge-group") return { ok: false, reason: "non-exact-final-authority" };
236
+ for (const [field, value] of Object.entries(expected)) {
237
+ if (value !== undefined && proof[field] !== value) return { ok: false, reason: `${field}-mismatch` };
238
+ }
239
+ createIntegrationDeliveryProof({
240
+ ...proof,
241
+ warrant: {
242
+ schema: DEV_DELIVERY_WARRANT_SCHEMA,
243
+ candidateId: proof.warrantCandidateId,
244
+ fencingToken: proof.warrantFencingToken,
245
+ generation: proof.warrantGeneration,
246
+ },
247
+ });
248
+ return { ok: true, reason: "exact-integration-proof", proofRoot };
249
+ } catch (error) {
250
+ return { ok: false, reason: "invalid-proof", error: error.message };
251
+ }
252
+ }
@@ -0,0 +1,94 @@
1
+ import { devDeliveryClone as clone, devDeliveryContentRoot, devDeliveryExactRoot as exactRoot, devDeliveryExactSha as exactSha, devDeliveryPositiveInteger as positiveInteger, devDeliveryText as text, devDeliveryTimestamp as timestamp } from "./dev-delivery-common.js";
2
+
3
+ export const DEV_DELIVERY_CANCELLATION_RECEIPT_SCHEMA = "kungfu.buildchain.dev-delivery-cancellation-receipt/v1";
4
+
5
+ const TERMINAL_STATES = new Set(["merged", "terminal-failure", "dequeued", "cancelled"]);
6
+ const QUEUED_CANCELLATION_EVENTS = new Map([
7
+ ["closed", "cancelled"],
8
+ ["cancelled", "cancelled"],
9
+ ["dequeued", "dequeued"],
10
+ ]);
11
+
12
+ function cancellationReceipt(queue, candidate, identity, action, expectedOldStateRoot) {
13
+ return {
14
+ schema: DEV_DELIVERY_CANCELLATION_RECEIPT_SCHEMA,
15
+ action,
16
+ repository: queue.repository,
17
+ protectedBase: queue.protectedBase,
18
+ candidateId: candidate.candidateId,
19
+ pullRequestNumber: candidate.pullRequestNumber,
20
+ expectedSourceHead: identity.expectedSourceHead,
21
+ observedSourceHead: identity.observedSourceHead,
22
+ eventAction: identity.eventAction,
23
+ outcome: identity.outcome,
24
+ evidenceRoot: identity.evidenceRoot,
25
+ expectedOldStateRoot,
26
+ nextStateRoot: queue.stateRoot,
27
+ nextAction: "Select the next queued candidate, if any.",
28
+ };
29
+ }
30
+
31
+ function normalizeCancellation(input) {
32
+ const identity = {
33
+ candidateId: exactRoot(input?.candidateId, "candidateId"),
34
+ pullRequestNumber: positiveInteger(input?.pullRequestNumber, "pullRequestNumber"),
35
+ expectedSourceHead: exactSha(input?.expectedSourceHead, "expectedSourceHead"),
36
+ observedSourceHead: exactSha(input?.observedSourceHead, "observedSourceHead"),
37
+ eventAction: text(input?.eventAction).toLowerCase(),
38
+ outcome: text(input?.outcome).toLowerCase(),
39
+ evidenceRoot: exactRoot(input?.evidenceRoot, "evidenceRoot"),
40
+ reason: text(input?.reason),
41
+ };
42
+ const requiredOutcome = QUEUED_CANCELLATION_EVENTS.get(identity.eventAction);
43
+ if (!requiredOutcome) throw new Error(`eventAction must be one of ${[...QUEUED_CANCELLATION_EVENTS.keys()].join(", ")}`);
44
+ if (identity.outcome !== requiredOutcome) throw new Error(`eventAction ${identity.eventAction} requires outcome ${requiredOutcome}`);
45
+ return identity;
46
+ }
47
+
48
+ function terminalBinding(identity) {
49
+ return {
50
+ outcome: identity.outcome,
51
+ reason: identity.reason,
52
+ evidenceRoot: identity.evidenceRoot,
53
+ authority: "queued-candidate-cancellation",
54
+ expectedSourceHead: identity.expectedSourceHead,
55
+ observedSourceHead: identity.observedSourceHead,
56
+ eventAction: identity.eventAction,
57
+ };
58
+ }
59
+
60
+ export function createCancelQueuedDevDeliveryCandidate(normalizeDevDeliveryQueue) {
61
+ return function cancelQueuedDevDeliveryCandidate(queueInput, input, { now = new Date().toISOString() } = {}) {
62
+ const currentTime = timestamp(now, "now");
63
+ const identity = normalizeCancellation(input);
64
+ const before = normalizeDevDeliveryQueue(queueInput);
65
+ const candidate = before.candidates.find((entry) => entry.candidateId === identity.candidateId);
66
+ if (!candidate) throw new Error(`queued candidate ${identity.candidateId} does not exist`);
67
+ if (candidate.pullRequestNumber !== identity.pullRequestNumber) throw new Error("queued candidate PR mismatch");
68
+ if (candidate.sourceHead !== identity.expectedSourceHead) throw new Error("queued candidate recorded sourceHead mismatch");
69
+ if (before.activeWarrant?.candidateId === identity.candidateId) throw new Error("active candidate requires fenced Warrant closeout");
70
+
71
+ const terminal = terminalBinding(identity);
72
+ if (TERMINAL_STATES.has(candidate.status)) {
73
+ if (!Object.entries(terminal).every(([field, value]) => candidate.terminal?.[field] === value)) {
74
+ throw new Error("terminal candidate does not match the exact queued cancellation evidence");
75
+ }
76
+ const receipt = cancellationReceipt(before, candidate, identity, "duplicate-cancellation-noop", before.stateRoot);
77
+ return { queue: before, receipt, receiptRoot: devDeliveryContentRoot(receipt) };
78
+ }
79
+ if (candidate.status !== "queued") throw new Error(`candidate status ${candidate.status} requires fenced Warrant closeout`);
80
+
81
+ const queue = clone(before);
82
+ delete queue.stateRoot;
83
+ const queued = queue.candidates.find((entry) => entry.candidateId === identity.candidateId);
84
+ queued.status = identity.outcome;
85
+ queued.updatedAt = currentTime;
86
+ queued.terminal = { ...terminal, closedAt: currentTime };
87
+ queue.generation += 1;
88
+ queue.updatedAt = currentTime;
89
+ queue.stateRoot = devDeliveryContentRoot(queue);
90
+ const after = normalizeDevDeliveryQueue(queue);
91
+ const receipt = cancellationReceipt(after, queued, identity, "queued-candidate-cancelled", before.stateRoot);
92
+ return { queue: after, receipt, receiptRoot: devDeliveryContentRoot(receipt) };
93
+ };
94
+ }
@@ -0,0 +1,73 @@
1
+ import { devDeliveryContentRoot, devDeliveryExactRoot as exactRoot, devDeliveryExactSha as exactSha, devDeliveryPositiveInteger as positiveInteger, devDeliveryText as text } from "./dev-delivery-common.js";
2
+
3
+ export const DEV_DELIVERY_SETTLEMENT_RECEIPT_SCHEMA = "kungfu.buildchain.dev-delivery-settlement-receipt/v1";
4
+
5
+ function noopReceipt(queue, identity, candidate, action) {
6
+ return {
7
+ schema: DEV_DELIVERY_SETTLEMENT_RECEIPT_SCHEMA,
8
+ action,
9
+ applicable: action !== "terminal-event-not-applicable",
10
+ repository: queue.repository,
11
+ protectedBase: queue.protectedBase,
12
+ pullRequestNumber: identity.pullRequestNumber,
13
+ sourceHead: identity.sourceHead,
14
+ outcome: identity.outcome,
15
+ candidateId: candidate?.candidateId || null,
16
+ evidenceRoot: candidate?.terminal?.evidenceRoot || identity.evidenceRoot || null,
17
+ expectedOldStateRoot: queue.stateRoot,
18
+ nextStateRoot: queue.stateRoot,
19
+ nextAction: "No fenced delivery state remains for this terminal event.",
20
+ };
21
+ }
22
+
23
+ export function createDevDeliveryTerminalSettler({ normalizeQueue, closeWarrant, cancelQueued, terminalStates }) {
24
+ return function settleDevDeliveryTerminalEvent(queueInput, input, { now = new Date().toISOString() } = {}) {
25
+ const queue = normalizeQueue(queueInput);
26
+ const identity = {
27
+ pullRequestNumber: positiveInteger(input?.pullRequestNumber, "pullRequestNumber"),
28
+ sourceHead: exactSha(input?.sourceHead, "sourceHead"),
29
+ outcome: text(input?.outcome),
30
+ evidenceRoot: input?.evidenceRoot ? exactRoot(input.evidenceRoot, "evidenceRoot") : "",
31
+ reason: text(input?.reason),
32
+ eventAction: text(input?.eventAction).toLowerCase(),
33
+ };
34
+ if (!terminalStates.has(identity.outcome)) throw new Error(`outcome must be one of ${[...terminalStates].join(", ")}`);
35
+ const samePullRequest = queue.candidates.filter((entry) => entry.pullRequestNumber === identity.pullRequestNumber);
36
+ const candidate = samePullRequest.find((entry) => entry.sourceHead === identity.sourceHead) || null;
37
+ if (!candidate && samePullRequest.length > 0) throw new Error("terminal event sourceHead does not match the recorded candidate");
38
+
39
+ if (candidate && terminalStates.has(candidate.status)) {
40
+ if (candidate.status !== identity.outcome) throw new Error("terminal candidate outcome does not match the terminal event");
41
+ const receipt = noopReceipt(queue, identity, candidate, "duplicate-terminal-event-noop");
42
+ return { queue, receipt, receiptRoot: devDeliveryContentRoot(receipt) };
43
+ }
44
+
45
+ if (queue.activeWarrant) {
46
+ if (!candidate || queue.activeWarrant.candidateId !== candidate.candidateId) throw new Error("terminal event does not match the active Delivery Warrant");
47
+ if (!identity.evidenceRoot) throw new Error("active terminal settlement requires evidenceRoot");
48
+ return closeWarrant(queue, {
49
+ candidateId: candidate.candidateId,
50
+ fencingToken: exactRoot(input?.fencingToken, "fencingToken"),
51
+ generation: positiveInteger(input?.leaseGeneration, "leaseGeneration"),
52
+ }, { outcome: identity.outcome, evidenceRoot: identity.evidenceRoot, reason: identity.reason, now });
53
+ }
54
+
55
+ if (!candidate) {
56
+ const receipt = noopReceipt(queue, identity, null, "terminal-event-not-applicable");
57
+ return { queue, receipt, receiptRoot: devDeliveryContentRoot(receipt) };
58
+ }
59
+ if (candidate.status !== "queued") throw new Error(`candidate status ${candidate.status} requires an active Delivery Warrant`);
60
+ if (!["cancelled", "dequeued"].includes(identity.outcome)) throw new Error("queued candidate cannot settle as merged or terminal-failure");
61
+ if (!identity.evidenceRoot) throw new Error("queued terminal settlement requires evidenceRoot");
62
+ return cancelQueued(queue, {
63
+ candidateId: candidate.candidateId,
64
+ pullRequestNumber: identity.pullRequestNumber,
65
+ expectedSourceHead: identity.sourceHead,
66
+ observedSourceHead: identity.sourceHead,
67
+ eventAction: identity.eventAction || (identity.outcome === "dequeued" ? "dequeued" : "closed"),
68
+ outcome: identity.outcome,
69
+ evidenceRoot: identity.evidenceRoot,
70
+ reason: identity.reason,
71
+ }, { now });
72
+ };
73
+ }