@kungfu-tech/buildchain 4.0.1-alpha.2 → 4.0.1-alpha.3
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/0003-two-phase-delivery-warrant.md +152 -0
- package/architecture/internal-capabilities.json +94 -0
- package/architecture/maintainability-policy.json +75 -8
- package/architecture/v3-core-mechanism-inventory.json +2 -0
- package/architecture/v4-delivery-authority-parity.json +250 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +29 -6
- package/bin/buildchain.mjs +9 -1
- package/contracts/dev-delivery-authority-v2.schema.json +662 -0
- package/dist/site/agent-index.json +1 -0
- package/dist/site/artifact-schemas.json +2 -0
- package/dist/site/buildchain-contract.json +89 -6
- package/dist/site/buildchain-site.json +169 -18
- package/dist/site/capability-registry.json +2 -2
- package/dist/site/cli-registry.json +46 -2
- package/dist/site/kfd-claims.json +43 -9
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +3 -3
- package/dist/site/node-api-registry.json +1694 -165
- package/dist/site/page-registry.json +162 -11
- package/dist/site/public-surface-audit.json +367 -7
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +2 -0
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +662 -0
- package/dist/site/site-manifest.json +7 -7
- package/dist/site/workflow-registry.json +22 -6
- package/docs/MAP.md +1 -0
- package/docs/cli-reference.md +211 -13
- package/docs/dev-delivery-qualification-landing-adr.md +251 -0
- package/docs/dev-delivery-warrant.md +326 -38
- package/docs/node-api-reference.md +113 -53
- package/package.json +5 -1
- package/packages/core/buildchain-contract.js +3 -2
- package/packages/core/dev-delivery-authority-candidate.js +270 -0
- package/packages/core/dev-delivery-authority-evidence.js +146 -0
- package/packages/core/dev-delivery-authority-landing.js +461 -0
- package/packages/core/dev-delivery-authority-observation.js +48 -0
- package/packages/core/dev-delivery-authority-qualification.js +591 -0
- package/packages/core/dev-delivery-authority-settlement.js +213 -0
- package/packages/core/dev-delivery-authority-state.js +583 -0
- package/packages/core/dev-delivery-candidate-identity.js +13 -0
- package/packages/core/dev-delivery-contract-surface.js +76 -0
- package/packages/core/dev-delivery-execution-failure.js +133 -0
- package/packages/core/dev-delivery-execution-transfer.js +572 -0
- package/packages/core/dev-delivery-landing-admission-core.js +119 -0
- package/packages/core/dev-delivery-landing-readback.js +598 -0
- package/packages/core/dev-delivery-landing-terminal-evidence.js +271 -0
- package/packages/core/dev-delivery-landing-testing-port.js +6 -0
- package/packages/core/dev-delivery-native-execution.js +110 -0
- package/packages/core/dev-delivery-native-proof.js +546 -0
- package/packages/core/dev-delivery-process-boundary.js +551 -0
- package/packages/core/dev-delivery-provider-attempt.js +127 -0
- package/packages/core/dev-delivery-provider-heartbeat.js +370 -0
- package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
- package/packages/core/dev-delivery-warrant-qualification.js +145 -0
- package/packages/core/dev-delivery-warrant-settlement.js +237 -36
- package/packages/core/dev-delivery-warrant-state.js +565 -0
- package/packages/core/dev-delivery-warrant.js +320 -368
- package/scripts/buildchain-cli-help.mjs +11 -2
- package/scripts/dev-delivery-authority-command-adapters.mjs +206 -0
- package/scripts/dev-delivery-authority-provider.mjs +28 -0
- package/scripts/dev-delivery-authority.mjs +490 -0
- package/scripts/dev-delivery-native-run.mjs +177 -0
- package/scripts/dev-delivery-process-boundary.mjs +260 -0
- package/scripts/dev-delivery-proof.mjs +67 -2
- package/scripts/dev-delivery-provider-heartbeat.mjs +215 -0
- package/scripts/dev-delivery-two-phase-resume.mjs +345 -0
- package/scripts/dev-delivery-two-phase.mjs +573 -0
- package/scripts/dev-delivery-warrant-options.mjs +266 -0
- package/scripts/dev-delivery-warrant-store.mjs +227 -0
- package/scripts/dev-delivery-warrant.mjs +227 -193
- package/scripts/dev-pr-delivery-warrant.mjs +10 -0
- package/scripts/generate-site-bundle.mjs +26 -4
- package/scripts/npm-publish-transaction.mjs +2 -2
- package/scripts/site-capability-metadata.mjs +12 -0
- package/templates/native-dev-delivery.yml +141 -0
|
@@ -1,310 +1,108 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import {
|
|
2
|
+
devDeliveryClone as clone,
|
|
3
|
+
devDeliveryContentRoot,
|
|
4
|
+
devDeliveryExactRoot as exactRoot,
|
|
5
|
+
devDeliveryPositiveInteger as positiveInteger,
|
|
6
|
+
devDeliveryText as text,
|
|
7
|
+
devDeliveryTimestamp as timestamp,
|
|
8
|
+
} from "./dev-delivery-common.js";
|
|
9
|
+
import {
|
|
10
|
+
DEV_DELIVERY_CANCELLATION_RECEIPT_SCHEMA,
|
|
11
|
+
createCancelQueuedDevDeliveryCandidate,
|
|
12
|
+
} from "./dev-delivery-warrant-cancellation.js";
|
|
13
|
+
import {
|
|
14
|
+
DEV_DELIVERY_SETTLEMENT_RECEIPT_SCHEMA,
|
|
15
|
+
createDevDeliveryTerminalSettler,
|
|
16
|
+
normalizeProviderFailureAuthorityBinding,
|
|
17
|
+
} from "./dev-delivery-warrant-settlement.js";
|
|
18
|
+
import {
|
|
19
|
+
normalizeNativeCommandContract,
|
|
20
|
+
validateActiveDevDeliveryWarrant,
|
|
21
|
+
} from "./dev-delivery-native-proof.js";
|
|
22
|
+
import { createDevDeliveryWarrantQualifier } from "./dev-delivery-warrant-qualification.js";
|
|
23
|
+
import {
|
|
24
|
+
DEV_DELIVERY_CLASSES,
|
|
25
|
+
DEV_DELIVERY_LEASE_RECEIPT_SCHEMA,
|
|
26
|
+
DEV_DELIVERY_PRIORITIES,
|
|
27
|
+
DEV_DELIVERY_QUEUE_CONTRACT,
|
|
28
|
+
DEV_DELIVERY_SELECTION_RECEIPT_SCHEMA,
|
|
29
|
+
DEV_DELIVERY_SUBMISSION_RECEIPT_SCHEMA,
|
|
30
|
+
DEV_DELIVERY_WARRANT_SCHEMA,
|
|
31
|
+
TERMINAL_STATES,
|
|
32
|
+
createDevDeliveryQueue,
|
|
33
|
+
issueDevDeliveryWarrant,
|
|
34
|
+
normalizeDevDeliveryQueue,
|
|
35
|
+
rankDevDeliveryCandidates,
|
|
36
|
+
submitDevDeliveryCandidate,
|
|
37
|
+
transitionDevDeliveryQueue as transition,
|
|
38
|
+
} from "./dev-delivery-warrant-state.js";
|
|
39
|
+
|
|
6
40
|
export { devDeliveryContentRoot } from "./dev-delivery-common.js";
|
|
7
|
-
export {
|
|
8
|
-
|
|
41
|
+
export {
|
|
42
|
+
RELEASE_BLOCKER_PRIORITY_CLAIM_SCHEMA,
|
|
43
|
+
createReleaseBlockerPriorityClaim,
|
|
44
|
+
} from "./release-blocker-priority.js";
|
|
45
|
+
export {
|
|
46
|
+
SOURCE_QUALIFICATION_PROOF_SCHEMA,
|
|
47
|
+
SOURCE_QUALIFICATION_PROOF_V2_SCHEMA,
|
|
48
|
+
PROJECT_CUT_REPLAY_PROOF_SCHEMA,
|
|
49
|
+
INTEGRATION_DELIVERY_PROOF_SCHEMA,
|
|
50
|
+
classifyDevDeliveryDelta,
|
|
51
|
+
createIntegrationDeliveryProof,
|
|
52
|
+
createProjectCutReplayPlan,
|
|
53
|
+
createProjectCutReplayProof,
|
|
54
|
+
createSourceQualificationProof,
|
|
55
|
+
createSourceQualificationProofV2,
|
|
56
|
+
verifyIntegrationDeliveryProof,
|
|
57
|
+
verifyProjectCutReplayProof,
|
|
58
|
+
verifySourceQualificationProof,
|
|
59
|
+
} from "./dev-delivery-proof.js";
|
|
60
|
+
export {
|
|
61
|
+
DEV_DELIVERY_QUALIFICATION_RECEIPT_SCHEMA,
|
|
62
|
+
NATIVE_COMMAND_CONTRACT_SCHEMA,
|
|
63
|
+
NATIVE_EXECUTION_BINDING_SCHEMA,
|
|
64
|
+
NATIVE_EXECUTION_RECEIPT_SCHEMA,
|
|
65
|
+
NATIVE_PROOF_BASE_DELTA_SCHEMA,
|
|
66
|
+
NATIVE_PROOF_REUSE_DECISION_SCHEMA,
|
|
67
|
+
NATIVE_QUALIFICATION_PROOF_SCHEMA,
|
|
68
|
+
createNativeCommandContract,
|
|
69
|
+
createNativeExecutionBinding,
|
|
70
|
+
createNativeExecutionReceipt,
|
|
71
|
+
createNativeProofBaseDelta,
|
|
72
|
+
createNativeProofReuseDecision,
|
|
73
|
+
createNativeQualificationProof,
|
|
74
|
+
normalizeNativeCommandContract,
|
|
75
|
+
verifyNativeExecutionReceipt,
|
|
76
|
+
verifyNativeProofReuseDecision,
|
|
77
|
+
verifyNativeQualificationProof,
|
|
78
|
+
} from "./dev-delivery-native-proof.js";
|
|
79
|
+
export {
|
|
80
|
+
DEV_DELIVERY_CLASSES,
|
|
81
|
+
DEV_DELIVERY_LEASE_RECEIPT_SCHEMA,
|
|
82
|
+
DEV_DELIVERY_PRIORITIES,
|
|
83
|
+
DEV_DELIVERY_QUEUE_CONTRACT,
|
|
84
|
+
DEV_DELIVERY_SELECTION_RECEIPT_SCHEMA,
|
|
85
|
+
DEV_DELIVERY_SUBMISSION_RECEIPT_SCHEMA,
|
|
86
|
+
DEV_DELIVERY_WARRANT_SCHEMA,
|
|
87
|
+
createDevDeliveryQueue,
|
|
88
|
+
normalizeDevDeliveryQueue,
|
|
89
|
+
rankDevDeliveryCandidates,
|
|
90
|
+
submitDevDeliveryCandidate,
|
|
91
|
+
};
|
|
9
92
|
export { DEV_DELIVERY_CANCELLATION_RECEIPT_SCHEMA };
|
|
10
|
-
export const DEV_DELIVERY_QUEUE_CONTRACT = "kungfu-buildchain-dev-delivery-warrant-queue";
|
|
11
|
-
export const DEV_DELIVERY_WARRANT_SCHEMA = "kungfu.buildchain.dev-delivery-warrant/v1";
|
|
12
|
-
export const DEV_DELIVERY_SUBMISSION_RECEIPT_SCHEMA = "kungfu.buildchain.dev-delivery-submission-receipt/v1";
|
|
13
|
-
export const DEV_DELIVERY_SELECTION_RECEIPT_SCHEMA = "kungfu.buildchain.dev-delivery-selection-receipt/v1";
|
|
14
|
-
export const DEV_DELIVERY_LEASE_RECEIPT_SCHEMA = "kungfu.buildchain.dev-delivery-lease-receipt/v1";
|
|
15
93
|
export { DEV_DELIVERY_SETTLEMENT_RECEIPT_SCHEMA };
|
|
16
|
-
export const DEV_DELIVERY_PRIORITIES = Object.freeze({
|
|
17
|
-
ordinary: 0,
|
|
18
|
-
expedited: 1,
|
|
19
|
-
emergency: 2,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
export const DEV_DELIVERY_CLASSES = Object.freeze(["non-native-fast", "native-proof-required", "cross-platform", "release"]);
|
|
23
|
-
|
|
24
|
-
const TERMINAL_STATES = new Set(["merged", "terminal-failure", "dequeued", "cancelled"]);
|
|
25
|
-
function nonNegativeInteger(value, label, fallback = 0) {
|
|
26
|
-
const parsed = Number(value ?? fallback);
|
|
27
|
-
if (!Number.isInteger(parsed) || parsed < 0) throw new Error(`${label} must be a non-negative integer`);
|
|
28
|
-
return parsed;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function priority(value = "ordinary") {
|
|
32
|
-
const normalized = text(value || "ordinary").toLowerCase();
|
|
33
|
-
if (!(normalized in DEV_DELIVERY_PRIORITIES)) {
|
|
34
|
-
throw new Error(`priority must be one of ${Object.keys(DEV_DELIVERY_PRIORITIES).join(", ")}`);
|
|
35
|
-
}
|
|
36
|
-
return normalized;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function deliveryClass(value) {
|
|
40
|
-
const normalized = text(value);
|
|
41
|
-
if (!DEV_DELIVERY_CLASSES.includes(normalized)) {
|
|
42
|
-
throw new Error(`deliveryClass must be one of ${DEV_DELIVERY_CLASSES.join(", ")}`);
|
|
43
|
-
}
|
|
44
|
-
return normalized;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function normalizePolicy(policy = {}) {
|
|
48
|
-
return {
|
|
49
|
-
agingSeconds: positiveInteger(policy.agingSeconds, "policy agingSeconds", 900),
|
|
50
|
-
maxPriority: positiveInteger(policy.maxPriority, "policy maxPriority", 2),
|
|
51
|
-
leaseSeconds: positiveInteger(policy.leaseSeconds, "policy leaseSeconds", 3600),
|
|
52
|
-
emergencyPolicy: text(policy.emergencyPolicy || "reviewed-explicit-only"),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function normalizeCandidate(input, expected) {
|
|
57
|
-
const identity = createDevDeliveryCandidateIdentity(input, expected, deliveryClass);
|
|
58
|
-
if (input.candidateId && input.candidateId !== identity.candidateId) {
|
|
59
|
-
throw new Error(`candidateId mismatch for PR #${identity.pullRequestNumber}`);
|
|
60
|
-
}
|
|
61
|
-
const status = text(input.status || "queued");
|
|
62
|
-
if (!["queued", "selected", "proving", "waiting", "blocked", ...TERMINAL_STATES].includes(status)) {
|
|
63
|
-
throw new Error(`unsupported candidate status ${status || "<empty>"}`);
|
|
64
|
-
}
|
|
65
|
-
const candidate = {
|
|
66
|
-
...identity,
|
|
67
|
-
sourceHead: exactSha(input.sourceHead, "sourceHead"),
|
|
68
|
-
sourcePatchRoot: exactRoot(input.sourcePatchRoot, "sourcePatchRoot"),
|
|
69
|
-
sourceProofRoot: exactRoot(input.sourceProofRoot, "sourceProofRoot"),
|
|
70
|
-
planRoot: exactRoot(input.planRoot, "planRoot"),
|
|
71
|
-
closureRoot: exactRoot(input.closureRoot, "closureRoot"),
|
|
72
|
-
dependencyRoot: exactRoot(input.dependencyRoot, "dependencyRoot"),
|
|
73
|
-
toolchainRoot: exactRoot(input.toolchainRoot, "toolchainRoot"), ...(input.environmentRoot ? { environmentRoot: exactRoot(input.environmentRoot, "environmentRoot") } : {}), ...(Object.hasOwn(input, "sourceWorkflowRunId") ? { sourceWorkflowRunId: nonNegativeInteger(input.sourceWorkflowRunId, "candidate sourceWorkflowRunId", 0) } : {}),
|
|
74
|
-
priority: priority(input.priority),
|
|
75
|
-
enqueuedAt: timestamp(input.enqueuedAt, "candidate enqueuedAt"),
|
|
76
|
-
updatedAt: timestamp(input.updatedAt || input.enqueuedAt, "candidate updatedAt"),
|
|
77
|
-
attempts: positiveInteger(input.attempts, "candidate attempts", 1),
|
|
78
|
-
recoveries: nonNegativeInteger(input.recoveries, "candidate recoveries", 0),
|
|
79
|
-
status,
|
|
80
|
-
terminal: input.terminal || null,
|
|
81
|
-
};
|
|
82
|
-
if (Object.hasOwn(input, "affectedPaths")) { if (!Array.isArray(input.affectedPaths)) throw new Error("candidate affectedPaths must be an array"); candidate.affectedPaths = [...new Set(input.affectedPaths.map(text).filter(Boolean))].sort(); } if (Object.hasOwn(input, "releaseBlockerPriority")) candidate.releaseBlockerPriority = normalizeReleaseBlockerPriorityClaim(input.releaseBlockerPriority, candidate, expected);
|
|
83
|
-
return candidate;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function queueBody(queue) {
|
|
87
|
-
const body = clone(queue);
|
|
88
|
-
delete body.stateRoot;
|
|
89
|
-
return body;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function withQueueRoot(queue) {
|
|
93
|
-
return {
|
|
94
|
-
...queueBody(queue),
|
|
95
|
-
stateRoot: devDeliveryContentRoot(queueBody(queue)),
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function createDevDeliveryQueue({ repository: repositoryInput, protectedBase: protectedBaseInput, policy = {}, now = new Date().toISOString() } = {}) {
|
|
100
|
-
return withQueueRoot({
|
|
101
|
-
schemaVersion: 1,
|
|
102
|
-
contract: DEV_DELIVERY_QUEUE_CONTRACT,
|
|
103
|
-
repository: repository(repositoryInput),
|
|
104
|
-
protectedBase: protectedBase(protectedBaseInput),
|
|
105
|
-
generation: 0,
|
|
106
|
-
fencingCounter: 0,
|
|
107
|
-
policy: normalizePolicy(policy),
|
|
108
|
-
activeWarrant: null,
|
|
109
|
-
candidates: [],
|
|
110
|
-
updatedAt: timestamp(now, "now"),
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function normalizeDevDeliveryQueue(input, expected = {}) {
|
|
115
|
-
const queue = clone(input || {});
|
|
116
|
-
if (queue.contract !== DEV_DELIVERY_QUEUE_CONTRACT || Number(queue.schemaVersion) !== 1) {
|
|
117
|
-
throw new Error(`dev delivery queue must use ${DEV_DELIVERY_QUEUE_CONTRACT} schemaVersion 1`);
|
|
118
|
-
}
|
|
119
|
-
queue.repository = repository(queue.repository);
|
|
120
|
-
queue.protectedBase = protectedBase(queue.protectedBase);
|
|
121
|
-
if (expected.repository && queue.repository !== expected.repository) {
|
|
122
|
-
throw new Error(`dev delivery queue repository mismatch: ${queue.repository} != ${expected.repository}`);
|
|
123
|
-
}
|
|
124
|
-
if (expected.protectedBase && queue.protectedBase !== expected.protectedBase) {
|
|
125
|
-
throw new Error(`dev delivery queue protectedBase mismatch: ${queue.protectedBase} != ${expected.protectedBase}`);
|
|
126
|
-
}
|
|
127
|
-
queue.generation = nonNegativeInteger(queue.generation, "queue generation");
|
|
128
|
-
queue.fencingCounter = nonNegativeInteger(queue.fencingCounter, "queue fencingCounter");
|
|
129
|
-
queue.policy = normalizePolicy(queue.policy);
|
|
130
|
-
queue.candidates = (queue.candidates || []).map((candidate) => normalizeCandidate(candidate, queue));
|
|
131
|
-
validateDevDeliveryCandidateChain(queue.candidates, TERMINAL_STATES);
|
|
132
|
-
queue.updatedAt = timestamp(queue.updatedAt, "queue updatedAt");
|
|
133
|
-
if (queue.activeWarrant) {
|
|
134
|
-
const warrant = queue.activeWarrant;
|
|
135
|
-
if (warrant.schema !== DEV_DELIVERY_WARRANT_SCHEMA) throw new Error("active Warrant schema is unsupported");
|
|
136
|
-
warrant.candidateId = exactRoot(warrant.candidateId, "Warrant candidateId");
|
|
137
|
-
warrant.fencingToken = exactRoot(warrant.fencingToken, "Warrant fencingToken");
|
|
138
|
-
warrant.expectedOldStateRoot = exactRoot(warrant.expectedOldStateRoot, "Warrant expectedOldStateRoot");
|
|
139
|
-
warrant.generation = positiveInteger(warrant.generation, "Warrant generation");
|
|
140
|
-
warrant.issuedAt = timestamp(warrant.issuedAt, "Warrant issuedAt");
|
|
141
|
-
warrant.expiresAt = timestamp(warrant.expiresAt, "Warrant expiresAt");
|
|
142
|
-
const activeCandidates = queue.candidates.filter((candidate) => ["selected", "proving", "waiting", "blocked"].includes(candidate.status));
|
|
143
|
-
if (activeCandidates.length !== 1 || activeCandidates[0].candidateId !== warrant.candidateId) {
|
|
144
|
-
throw new Error("exactly one active candidate must match the active Warrant");
|
|
145
|
-
}
|
|
146
|
-
if (warrant.releaseBlockerPriority?.claimRoot !== activeCandidates[0].releaseBlockerPriority?.claimRoot) throw new Error("active Warrant release-blocker priority drift");
|
|
147
|
-
} else if (queue.candidates.some((candidate) => ["selected", "proving", "waiting", "blocked"].includes(candidate.status))) {
|
|
148
|
-
throw new Error("active candidate exists without an active Warrant");
|
|
149
|
-
}
|
|
150
|
-
const rooted = withQueueRoot(queue);
|
|
151
|
-
if (input.stateRoot && input.stateRoot !== rooted.stateRoot) throw new Error("dev delivery queue stateRoot drift");
|
|
152
|
-
return rooted;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function transition(queueInput, mutate, nowInput) {
|
|
156
|
-
const before = normalizeDevDeliveryQueue(queueInput);
|
|
157
|
-
const expectedOldStateRoot = before.stateRoot;
|
|
158
|
-
const queue = clone(before);
|
|
159
|
-
delete queue.stateRoot;
|
|
160
|
-
const now = timestamp(nowInput, "now");
|
|
161
|
-
const result = mutate(queue, before, now);
|
|
162
|
-
queue.generation += 1;
|
|
163
|
-
queue.updatedAt = now;
|
|
164
|
-
const after = withQueueRoot(queue);
|
|
165
|
-
return { before, after, expectedOldStateRoot, result };
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function submissionReceipt({ before, after, candidate, action, now }) {
|
|
169
|
-
const queueAgeSeconds = Math.max(0, Math.floor((Date.parse(now) - Date.parse(candidate.enqueuedAt)) / 1000));
|
|
170
|
-
return {
|
|
171
|
-
schema: DEV_DELIVERY_SUBMISSION_RECEIPT_SCHEMA,
|
|
172
|
-
repository: after.repository,
|
|
173
|
-
protectedBase: after.protectedBase,
|
|
174
|
-
candidateId: candidate.candidateId,
|
|
175
|
-
pullRequestNumber: candidate.pullRequestNumber,
|
|
176
|
-
sourceHead: candidate.sourceHead,
|
|
177
|
-
assignmentRoot: candidate.assignmentRoot,
|
|
178
|
-
initiativeRoot: candidate.initiativeRoot,
|
|
179
|
-
sourceIdentityRoot: candidate.sourceIdentityRoot,
|
|
180
|
-
sourcePatchRoot: candidate.sourcePatchRoot,
|
|
181
|
-
sourceProofRoot: candidate.sourceProofRoot,
|
|
182
|
-
planRoot: candidate.planRoot,
|
|
183
|
-
closureRoot: candidate.closureRoot,
|
|
184
|
-
dependencyRoot: candidate.dependencyRoot,
|
|
185
|
-
toolchainRoot: candidate.toolchainRoot,
|
|
186
|
-
sourceWorkflowRunId: candidate.sourceWorkflowRunId,
|
|
187
|
-
...(Object.hasOwn(candidate, "releaseBlockerPriority") ? { releaseBlockerPriority: candidate.releaseBlockerPriority } : {}),
|
|
188
|
-
deliveryClass: candidate.deliveryClass,
|
|
189
|
-
priority: candidate.priority,
|
|
190
|
-
retainedEnqueuedAt: candidate.enqueuedAt,
|
|
191
|
-
queueAgeSeconds,
|
|
192
|
-
attempts: candidate.attempts,
|
|
193
|
-
action,
|
|
194
|
-
expectedOldStateRoot: before.stateRoot,
|
|
195
|
-
nextStateRoot: after.stateRoot,
|
|
196
|
-
nextAction: "Wait for deterministic Warrant selection or observe the active predecessor.",
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export function submitDevDeliveryCandidate(queueInput, input, { now = new Date().toISOString() } = {}) {
|
|
201
|
-
const transaction = transition(
|
|
202
|
-
queueInput,
|
|
203
|
-
(queue, before, currentTime) => {
|
|
204
|
-
const candidate = normalizeCandidate(
|
|
205
|
-
{
|
|
206
|
-
...input,
|
|
207
|
-
enqueuedAt: input.enqueuedAt || currentTime,
|
|
208
|
-
updatedAt: currentTime,
|
|
209
|
-
attempts: input.attempts || 1,
|
|
210
|
-
recoveries: input.recoveries || 0,
|
|
211
|
-
status: "queued",
|
|
212
|
-
},
|
|
213
|
-
queue,
|
|
214
|
-
);
|
|
215
|
-
const chainedInput = chainedDevDeliveryAttemptInput({ queue, candidate, input, currentTime, terminalStates: TERMINAL_STATES });
|
|
216
|
-
const attemptedCandidate = chainedInput ? normalizeCandidate(chainedInput, queue) : candidate;
|
|
217
|
-
const conflicting = queue.candidates.find((entry) => entry.pullRequestNumber === attemptedCandidate.pullRequestNumber && entry.candidateId !== attemptedCandidate.candidateId && !TERMINAL_STATES.has(entry.status));
|
|
218
|
-
if (conflicting) throw new Error(`PR #${candidate.pullRequestNumber} already has a different active semantic source`);
|
|
219
|
-
const existing = queue.candidates.find((entry) => entry.candidateId === attemptedCandidate.candidateId);
|
|
220
|
-
let action = "submitted";
|
|
221
|
-
let selected = attemptedCandidate;
|
|
222
|
-
if (existing) {
|
|
223
|
-
if (TERMINAL_STATES.has(existing.status)) {
|
|
224
|
-
throw new Error(`candidate ${attemptedCandidate.candidateId} is terminal and cannot be resubmitted`);
|
|
225
|
-
}
|
|
226
|
-
const exactProofFields = ["sourcePatchRoot", "sourceProofRoot", "planRoot", "closureRoot", "dependencyRoot", "toolchainRoot"];
|
|
227
|
-
const exactProofMatches = exactProofFields.every((field) => existing[field] === attemptedCandidate[field]) && existing.releaseBlockerPriority?.claimRoot === attemptedCandidate.releaseBlockerPriority?.claimRoot;
|
|
228
|
-
if (existing.sourceHead === attemptedCandidate.sourceHead && exactProofMatches) {
|
|
229
|
-
action = "duplicate-noop";
|
|
230
|
-
selected = existing;
|
|
231
|
-
} else {
|
|
232
|
-
if (before.activeWarrant?.candidateId === existing.candidateId) {
|
|
233
|
-
throw new Error("selected candidate sourceHead cannot change before terminal Warrant closeout");
|
|
234
|
-
}
|
|
235
|
-
const headChanged = existing.sourceHead !== attemptedCandidate.sourceHead;
|
|
236
|
-
existing.sourceHead = attemptedCandidate.sourceHead;
|
|
237
|
-
for (const field of exactProofFields) existing[field] = attemptedCandidate[field];
|
|
238
|
-
if (Object.hasOwn(attemptedCandidate, "releaseBlockerPriority")) existing.releaseBlockerPriority = attemptedCandidate.releaseBlockerPriority;
|
|
239
|
-
else delete existing.releaseBlockerPriority;
|
|
240
|
-
existing.updatedAt = currentTime;
|
|
241
|
-
existing.attempts += 1;
|
|
242
|
-
existing.status = "queued";
|
|
243
|
-
action = headChanged ? "safe-head-repair-retained-age" : "safe-proof-refresh-retained-age";
|
|
244
|
-
selected = existing;
|
|
245
|
-
}
|
|
246
|
-
} else {
|
|
247
|
-
queue.candidates.push(attemptedCandidate);
|
|
248
|
-
}
|
|
249
|
-
return { candidate: selected, action };
|
|
250
|
-
},
|
|
251
|
-
now,
|
|
252
|
-
);
|
|
253
|
-
const receipt = submissionReceipt({
|
|
254
|
-
before: transaction.before,
|
|
255
|
-
after: transaction.after,
|
|
256
|
-
candidate: transaction.result.candidate,
|
|
257
|
-
action: transaction.result.action,
|
|
258
|
-
now: timestamp(now, "now"),
|
|
259
|
-
});
|
|
260
|
-
return {
|
|
261
|
-
queue: transaction.after,
|
|
262
|
-
receipt,
|
|
263
|
-
receiptRoot: devDeliveryContentRoot(receipt),
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function effectivePriority(candidate, policy, now) {
|
|
268
|
-
const ageSeconds = Math.max(0, Math.floor((Date.parse(now) - Date.parse(candidate.enqueuedAt)) / 1000));
|
|
269
|
-
const agingBoost = Math.floor(ageSeconds / policy.agingSeconds);
|
|
270
|
-
return {
|
|
271
|
-
ageSeconds,
|
|
272
|
-
basePriority: DEV_DELIVERY_PRIORITIES[candidate.priority],
|
|
273
|
-
agingBoost,
|
|
274
|
-
score: Math.min(policy.maxPriority, DEV_DELIVERY_PRIORITIES[candidate.priority] + agingBoost),
|
|
275
|
-
releaseBlocker: Boolean(candidate.releaseBlockerPriority),
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
export function rankDevDeliveryCandidates(queueInput, { now = new Date().toISOString() } = {}) {
|
|
280
|
-
const queue = normalizeDevDeliveryQueue(queueInput);
|
|
281
|
-
const currentTime = timestamp(now, "now");
|
|
282
|
-
return queue.candidates
|
|
283
|
-
.filter((candidate) => candidate.status === "queued")
|
|
284
|
-
.map((candidate) => ({
|
|
285
|
-
candidate,
|
|
286
|
-
priority: effectivePriority(candidate, queue.policy, currentTime),
|
|
287
|
-
}))
|
|
288
|
-
.sort((left, right) => compareReleaseBlockerPriority(left, right) || right.priority.score - left.priority.score || left.candidate.enqueuedAt.localeCompare(right.candidate.enqueuedAt) || left.candidate.candidateId.localeCompare(right.candidate.candidateId));
|
|
289
|
-
}
|
|
290
94
|
|
|
291
|
-
function
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
protectedBase: input.protectedBase,
|
|
296
|
-
candidateId: input.candidateId,
|
|
297
|
-
generation: input.generation,
|
|
298
|
-
expectedOldStateRoot: input.expectedOldStateRoot,
|
|
299
|
-
issuedAt: input.issuedAt,
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
export function selectDevDeliveryWarrant(queueInput, { now = new Date().toISOString(), leaseSeconds } = {}) {
|
|
95
|
+
export function selectDevDeliveryWarrant(
|
|
96
|
+
queueInput,
|
|
97
|
+
{ now = new Date().toISOString(), leaseSeconds } = {},
|
|
98
|
+
) {
|
|
304
99
|
const currentTime = timestamp(now, "now");
|
|
305
100
|
let queue = normalizeDevDeliveryQueue(queueInput);
|
|
306
101
|
let recoveryReceipt = null;
|
|
307
|
-
if (
|
|
102
|
+
if (
|
|
103
|
+
queue.activeWarrant &&
|
|
104
|
+
Date.parse(queue.activeWarrant.expiresAt) <= Date.parse(currentTime)
|
|
105
|
+
) {
|
|
308
106
|
const recovered = recoverExpiredDevDeliveryWarrant(queue, {
|
|
309
107
|
now: currentTime,
|
|
310
108
|
});
|
|
@@ -312,16 +110,22 @@ export function selectDevDeliveryWarrant(queueInput, { now = new Date().toISOStr
|
|
|
312
110
|
recoveryReceipt = recovered.receipt;
|
|
313
111
|
}
|
|
314
112
|
if (queue.activeWarrant) {
|
|
113
|
+
const stopRequired =
|
|
114
|
+
recoveryReceipt?.action === "expired-lease-fenced-stop-required";
|
|
315
115
|
const receipt = {
|
|
316
116
|
schema: DEV_DELIVERY_SELECTION_RECEIPT_SCHEMA,
|
|
317
|
-
selected:
|
|
318
|
-
reason:
|
|
117
|
+
selected: !stopRequired,
|
|
118
|
+
reason: stopRequired
|
|
119
|
+
? "expired-provisional-warrant-stop-required"
|
|
120
|
+
: "non-preemptive-active-warrant",
|
|
319
121
|
candidateId: queue.activeWarrant.candidateId,
|
|
320
122
|
fencingToken: queue.activeWarrant.fencingToken,
|
|
321
123
|
leaseGeneration: queue.activeWarrant.generation,
|
|
322
124
|
expectedOldStateRoot: queue.stateRoot,
|
|
323
125
|
nextStateRoot: queue.stateRoot,
|
|
324
|
-
nextAction:
|
|
126
|
+
nextAction: stopRequired
|
|
127
|
+
? recoveryReceipt.nextAction
|
|
128
|
+
: "Continue the active delivery attempt; later candidates remain visibly queued.",
|
|
325
129
|
};
|
|
326
130
|
return {
|
|
327
131
|
queue,
|
|
@@ -353,37 +157,16 @@ export function selectDevDeliveryWarrant(queueInput, { now = new Date().toISOStr
|
|
|
353
157
|
const transaction = transition(
|
|
354
158
|
queue,
|
|
355
159
|
(next, before) => {
|
|
356
|
-
const candidate = next.candidates.find(
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
sourceHead: candidate.sourceHead,
|
|
367
|
-
assignmentRoot: candidate.assignmentRoot,
|
|
368
|
-
initiativeRoot: candidate.initiativeRoot,
|
|
369
|
-
sourceIdentityRoot: candidate.sourceIdentityRoot,
|
|
370
|
-
sourcePatchRoot: candidate.sourcePatchRoot,
|
|
371
|
-
sourceProofRoot: candidate.sourceProofRoot,
|
|
372
|
-
planRoot: candidate.planRoot,
|
|
373
|
-
closureRoot: candidate.closureRoot,
|
|
374
|
-
dependencyRoot: candidate.dependencyRoot,
|
|
375
|
-
toolchainRoot: candidate.toolchainRoot,
|
|
376
|
-
sourceWorkflowRunId: candidate.sourceWorkflowRunId,
|
|
377
|
-
...(Object.hasOwn(candidate, "releaseBlockerPriority") ? { releaseBlockerPriority: candidate.releaseBlockerPriority } : {}),
|
|
378
|
-
deliveryClass: candidate.deliveryClass,
|
|
379
|
-
generation: next.fencingCounter,
|
|
380
|
-
expectedOldStateRoot: before.stateRoot,
|
|
381
|
-
issuedAt,
|
|
382
|
-
expiresAt: new Date(Date.parse(issuedAt) + effectiveLeaseSeconds * 1000).toISOString(),
|
|
383
|
-
fencingToken: "",
|
|
384
|
-
nextAction: "Replay on the exact current dev base, prove required checks, then enqueue the unchanged PR head.",
|
|
385
|
-
};
|
|
386
|
-
warrant.fencingToken = warrantToken(warrant);
|
|
160
|
+
const candidate = next.candidates.find(
|
|
161
|
+
(entry) => entry.candidateId === selected.candidate.candidateId,
|
|
162
|
+
);
|
|
163
|
+
const warrant = issueDevDeliveryWarrant(
|
|
164
|
+
next,
|
|
165
|
+
before,
|
|
166
|
+
candidate,
|
|
167
|
+
currentTime,
|
|
168
|
+
leaseSeconds,
|
|
169
|
+
);
|
|
387
170
|
next.activeWarrant = warrant;
|
|
388
171
|
candidate.status = "selected";
|
|
389
172
|
candidate.updatedAt = currentTime;
|
|
@@ -394,7 +177,11 @@ export function selectDevDeliveryWarrant(queueInput, { now = new Date().toISOStr
|
|
|
394
177
|
const receipt = {
|
|
395
178
|
schema: DEV_DELIVERY_SELECTION_RECEIPT_SCHEMA,
|
|
396
179
|
selected: true,
|
|
397
|
-
reason: selected.priority.releaseBlocker
|
|
180
|
+
reason: selected.priority.releaseBlocker
|
|
181
|
+
? "release-blocker-bounded-priority"
|
|
182
|
+
: selected.priority.agingBoost > 0
|
|
183
|
+
? "fifo-aging-bounded-priority"
|
|
184
|
+
: "fifo-bounded-priority",
|
|
398
185
|
repository: transaction.after.repository,
|
|
399
186
|
protectedBase: transaction.after.protectedBase,
|
|
400
187
|
candidateId: transaction.result.candidate.candidateId,
|
|
@@ -410,7 +197,23 @@ export function selectDevDeliveryWarrant(queueInput, { now = new Date().toISOStr
|
|
|
410
197
|
dependencyRoot: transaction.result.candidate.dependencyRoot,
|
|
411
198
|
toolchainRoot: transaction.result.candidate.toolchainRoot,
|
|
412
199
|
sourceWorkflowRunId: transaction.result.candidate.sourceWorkflowRunId,
|
|
413
|
-
...(
|
|
200
|
+
...(transaction.result.candidate.nativeCommandContract
|
|
201
|
+
? {
|
|
202
|
+
nativeCommandContract:
|
|
203
|
+
transaction.result.candidate.nativeCommandContract,
|
|
204
|
+
}
|
|
205
|
+
: {}),
|
|
206
|
+
...(transaction.result.candidate.shardEvidenceRoots
|
|
207
|
+
? {
|
|
208
|
+
shardEvidenceRoots: transaction.result.candidate.shardEvidenceRoots,
|
|
209
|
+
}
|
|
210
|
+
: {}),
|
|
211
|
+
...(Object.hasOwn(transaction.result.candidate, "releaseBlockerPriority")
|
|
212
|
+
? {
|
|
213
|
+
releaseBlockerPriority:
|
|
214
|
+
transaction.result.candidate.releaseBlockerPriority,
|
|
215
|
+
}
|
|
216
|
+
: {}),
|
|
414
217
|
deliveryClass: transaction.result.candidate.deliveryClass,
|
|
415
218
|
queueAgeSeconds: selected.priority.ageSeconds,
|
|
416
219
|
basePriority: selected.priority.basePriority,
|
|
@@ -430,25 +233,48 @@ export function selectDevDeliveryWarrant(queueInput, { now = new Date().toISOStr
|
|
|
430
233
|
recoveryReceipt,
|
|
431
234
|
};
|
|
432
235
|
}
|
|
433
|
-
|
|
434
|
-
|
|
236
|
+
function assertWarrantMutation(
|
|
237
|
+
queue,
|
|
238
|
+
warrant,
|
|
239
|
+
now,
|
|
240
|
+
{ allowExpired = false } = {},
|
|
241
|
+
) {
|
|
435
242
|
if (!queue.activeWarrant) throw new Error("no active Delivery Warrant");
|
|
436
|
-
if (text(warrant?.fencingToken) !== queue.activeWarrant.fencingToken)
|
|
437
|
-
|
|
438
|
-
if (
|
|
439
|
-
|
|
243
|
+
if (text(warrant?.fencingToken) !== queue.activeWarrant.fencingToken)
|
|
244
|
+
throw new Error("stale fencing token");
|
|
245
|
+
if (Number(warrant?.generation) !== queue.activeWarrant.generation)
|
|
246
|
+
throw new Error("stale lease generation");
|
|
247
|
+
if (text(warrant?.candidateId) !== queue.activeWarrant.candidateId)
|
|
248
|
+
throw new Error("Warrant candidate mismatch");
|
|
249
|
+
if (
|
|
250
|
+
!allowExpired &&
|
|
251
|
+
Date.parse(queue.activeWarrant.expiresAt) <= Date.parse(now)
|
|
252
|
+
)
|
|
253
|
+
throw new Error("Delivery Warrant lease expired");
|
|
440
254
|
}
|
|
441
|
-
|
|
442
|
-
|
|
255
|
+
export function heartbeatDevDeliveryWarrant(
|
|
256
|
+
queueInput,
|
|
257
|
+
warrant,
|
|
258
|
+
{ now = new Date().toISOString(), leaseSeconds } = {},
|
|
259
|
+
) {
|
|
443
260
|
const currentTime = timestamp(now, "now");
|
|
444
261
|
const transaction = transition(
|
|
445
262
|
queueInput,
|
|
446
263
|
(queue, before) => {
|
|
447
264
|
assertWarrantMutation(before, warrant, currentTime);
|
|
448
|
-
const effectiveLeaseSeconds = positiveInteger(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
265
|
+
const effectiveLeaseSeconds = positiveInteger(
|
|
266
|
+
leaseSeconds,
|
|
267
|
+
"leaseSeconds",
|
|
268
|
+
queue.policy.leaseSeconds,
|
|
269
|
+
);
|
|
270
|
+
queue.activeWarrant.expiresAt = new Date(
|
|
271
|
+
Date.parse(currentTime) + effectiveLeaseSeconds * 1000,
|
|
272
|
+
).toISOString();
|
|
273
|
+
const candidate = queue.candidates.find(
|
|
274
|
+
(entry) => entry.candidateId === queue.activeWarrant.candidateId,
|
|
275
|
+
);
|
|
276
|
+
candidate.status =
|
|
277
|
+
queue.activeWarrant.phase === "qualified" ? "qualified" : "proving";
|
|
452
278
|
candidate.updatedAt = currentTime;
|
|
453
279
|
return { candidate, warrant: queue.activeWarrant };
|
|
454
280
|
},
|
|
@@ -471,18 +297,44 @@ export function heartbeatDevDeliveryWarrant(queueInput, warrant, { now = new Dat
|
|
|
471
297
|
receiptRoot: devDeliveryContentRoot(receipt),
|
|
472
298
|
};
|
|
473
299
|
}
|
|
474
|
-
|
|
475
|
-
|
|
300
|
+
export function recoverExpiredDevDeliveryWarrant(
|
|
301
|
+
queueInput,
|
|
302
|
+
{ now = new Date().toISOString() } = {},
|
|
303
|
+
) {
|
|
476
304
|
const currentTime = timestamp(now, "now");
|
|
477
305
|
const before = normalizeDevDeliveryQueue(queueInput);
|
|
478
|
-
if (
|
|
306
|
+
if (
|
|
307
|
+
!before.activeWarrant ||
|
|
308
|
+
Date.parse(before.activeWarrant.expiresAt) > Date.parse(currentTime)
|
|
309
|
+
) {
|
|
479
310
|
const receipt = {
|
|
480
311
|
schema: DEV_DELIVERY_LEASE_RECEIPT_SCHEMA,
|
|
481
312
|
action: "recovery-noop",
|
|
482
313
|
reason: before.activeWarrant ? "lease-active" : "no-active-warrant",
|
|
483
314
|
expectedOldStateRoot: before.stateRoot,
|
|
484
315
|
nextStateRoot: before.stateRoot,
|
|
485
|
-
nextAction: before.activeWarrant
|
|
316
|
+
nextAction: before.activeWarrant
|
|
317
|
+
? "Continue the active delivery attempt."
|
|
318
|
+
: "Select the next queued candidate.",
|
|
319
|
+
};
|
|
320
|
+
return {
|
|
321
|
+
queue: before,
|
|
322
|
+
receipt,
|
|
323
|
+
receiptRoot: devDeliveryContentRoot(receipt),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
if (before.activeWarrant.phase === "provisional") {
|
|
327
|
+
const receipt = {
|
|
328
|
+
schema: DEV_DELIVERY_LEASE_RECEIPT_SCHEMA,
|
|
329
|
+
action: "expired-lease-fenced-stop-required",
|
|
330
|
+
reason: "provisional-native-worker-stop-unproven",
|
|
331
|
+
candidateId: before.activeWarrant.candidateId,
|
|
332
|
+
rejectedFencingToken: before.activeWarrant.fencingToken,
|
|
333
|
+
expiredLeaseGeneration: before.activeWarrant.generation,
|
|
334
|
+
expectedOldStateRoot: before.stateRoot,
|
|
335
|
+
nextStateRoot: before.stateRoot,
|
|
336
|
+
nextAction:
|
|
337
|
+
"Prove the fenced native worker stopped, then close this exact generation with rooted terminal evidence.",
|
|
486
338
|
};
|
|
487
339
|
return {
|
|
488
340
|
queue: before,
|
|
@@ -494,7 +346,9 @@ export function recoverExpiredDevDeliveryWarrant(queueInput, { now = new Date().
|
|
|
494
346
|
before,
|
|
495
347
|
(queue) => {
|
|
496
348
|
const expired = clone(queue.activeWarrant);
|
|
497
|
-
const candidate = queue.candidates.find(
|
|
349
|
+
const candidate = queue.candidates.find(
|
|
350
|
+
(entry) => entry.candidateId === expired.candidateId,
|
|
351
|
+
);
|
|
498
352
|
candidate.status = "queued";
|
|
499
353
|
candidate.recoveries += 1;
|
|
500
354
|
candidate.attempts += 1;
|
|
@@ -513,7 +367,8 @@ export function recoverExpiredDevDeliveryWarrant(queueInput, { now = new Date().
|
|
|
513
367
|
retainedEnqueuedAt: transaction.result.candidate.enqueuedAt,
|
|
514
368
|
expectedOldStateRoot: transaction.expectedOldStateRoot,
|
|
515
369
|
nextStateRoot: transaction.after.stateRoot,
|
|
516
|
-
nextAction:
|
|
370
|
+
nextAction:
|
|
371
|
+
"Re-select through the queue to mint a new lease generation and fencing token.",
|
|
517
372
|
};
|
|
518
373
|
return {
|
|
519
374
|
queue: transaction.after,
|
|
@@ -521,19 +376,54 @@ export function recoverExpiredDevDeliveryWarrant(queueInput, { now = new Date().
|
|
|
521
376
|
receiptRoot: devDeliveryContentRoot(receipt),
|
|
522
377
|
};
|
|
523
378
|
}
|
|
524
|
-
|
|
525
|
-
|
|
379
|
+
export const qualifyDevDeliveryWarrant = createDevDeliveryWarrantQualifier({
|
|
380
|
+
transition,
|
|
381
|
+
assertWarrantMutation,
|
|
382
|
+
});
|
|
383
|
+
export function closeDevDeliveryWarrant(
|
|
384
|
+
queueInput,
|
|
385
|
+
warrant,
|
|
386
|
+
{
|
|
387
|
+
outcome,
|
|
388
|
+
evidenceRoot,
|
|
389
|
+
reason = "",
|
|
390
|
+
providerFailureAuthority = null,
|
|
391
|
+
now = new Date().toISOString(),
|
|
392
|
+
} = {},
|
|
393
|
+
) {
|
|
526
394
|
const currentTime = timestamp(now, "now");
|
|
527
395
|
const normalizedOutcome = text(outcome);
|
|
528
396
|
if (!TERMINAL_STATES.has(normalizedOutcome)) {
|
|
529
|
-
throw new Error(
|
|
397
|
+
throw new Error(
|
|
398
|
+
`outcome must be one of ${[...TERMINAL_STATES].join(", ")}`,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
const failureAuthority = normalizeProviderFailureAuthorityBinding(
|
|
402
|
+
providerFailureAuthority || {},
|
|
403
|
+
);
|
|
404
|
+
if (failureAuthority && normalizedOutcome !== "terminal-failure") {
|
|
405
|
+
throw new Error(
|
|
406
|
+
"provider failure authority is valid only for terminal-failure",
|
|
407
|
+
);
|
|
530
408
|
}
|
|
531
409
|
const transaction = transition(
|
|
532
410
|
queueInput,
|
|
533
411
|
(queue, before) => {
|
|
534
|
-
assertWarrantMutation(before, warrant, currentTime
|
|
412
|
+
assertWarrantMutation(before, warrant, currentTime, {
|
|
413
|
+
allowExpired: true,
|
|
414
|
+
});
|
|
415
|
+
if (
|
|
416
|
+
(before.activeWarrant.phase || "qualified") !== "qualified" &&
|
|
417
|
+
normalizedOutcome === "merged"
|
|
418
|
+
) {
|
|
419
|
+
throw new Error(
|
|
420
|
+
"provisional Warrant cannot settle as merged before native qualification",
|
|
421
|
+
);
|
|
422
|
+
}
|
|
535
423
|
const active = clone(queue.activeWarrant);
|
|
536
|
-
const candidate = queue.candidates.find(
|
|
424
|
+
const candidate = queue.candidates.find(
|
|
425
|
+
(entry) => entry.candidateId === active.candidateId,
|
|
426
|
+
);
|
|
537
427
|
candidate.status = normalizedOutcome;
|
|
538
428
|
candidate.updatedAt = currentTime;
|
|
539
429
|
candidate.terminal = {
|
|
@@ -543,6 +433,7 @@ export function closeDevDeliveryWarrant(queueInput, warrant, { outcome, evidence
|
|
|
543
433
|
closedAt: currentTime,
|
|
544
434
|
fencingToken: active.fencingToken,
|
|
545
435
|
leaseGeneration: active.generation,
|
|
436
|
+
...(failureAuthority || {}),
|
|
546
437
|
};
|
|
547
438
|
queue.activeWarrant = null;
|
|
548
439
|
return { candidate, active };
|
|
@@ -558,8 +449,13 @@ export function closeDevDeliveryWarrant(queueInput, warrant, { outcome, evidence
|
|
|
558
449
|
fencingToken: transaction.result.active.fencingToken,
|
|
559
450
|
leaseGeneration: transaction.result.active.generation,
|
|
560
451
|
evidenceRoot: transaction.result.candidate.terminal.evidenceRoot,
|
|
452
|
+
...(failureAuthority || {}),
|
|
561
453
|
expectedOldStateRoot: transaction.expectedOldStateRoot,
|
|
562
454
|
nextStateRoot: transaction.after.stateRoot,
|
|
455
|
+
successorWake: createDevDeliverySuccessorWake(
|
|
456
|
+
transaction.after,
|
|
457
|
+
currentTime,
|
|
458
|
+
),
|
|
563
459
|
nextAction: "Select the next queued candidate, if any.",
|
|
564
460
|
};
|
|
565
461
|
return {
|
|
@@ -568,16 +464,64 @@ export function closeDevDeliveryWarrant(queueInput, warrant, { outcome, evidence
|
|
|
568
464
|
receiptRoot: devDeliveryContentRoot(receipt),
|
|
569
465
|
};
|
|
570
466
|
}
|
|
571
|
-
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
467
|
+
function createDevDeliverySuccessorWake(queue, now) {
|
|
468
|
+
const next = rankDevDeliveryCandidates(queue, { now })[0]?.candidate;
|
|
469
|
+
if (!next) return null;
|
|
470
|
+
return {
|
|
471
|
+
schema: "kungfu.buildchain.dev-delivery-wake/v1",
|
|
472
|
+
targetBranch: queue.protectedBase,
|
|
473
|
+
...Object.fromEntries(
|
|
474
|
+
[
|
|
475
|
+
"candidateId",
|
|
476
|
+
"pullRequestNumber",
|
|
477
|
+
"sourceHead",
|
|
478
|
+
"assignmentRoot",
|
|
479
|
+
"initiativeRoot",
|
|
480
|
+
"sourceIdentityRoot",
|
|
481
|
+
"sourcePatchRoot",
|
|
482
|
+
"sourceProofRoot",
|
|
483
|
+
"planRoot",
|
|
484
|
+
"closureRoot",
|
|
485
|
+
"dependencyRoot",
|
|
486
|
+
"toolchainRoot",
|
|
487
|
+
"deliveryClass",
|
|
488
|
+
"priority",
|
|
489
|
+
"nativeCommandContract",
|
|
490
|
+
"shardEvidenceRoots",
|
|
491
|
+
]
|
|
492
|
+
.filter((field) => next[field] !== undefined)
|
|
493
|
+
.map((field) => [field, next[field]]),
|
|
494
|
+
),
|
|
495
|
+
...(next.environmentRoot ? { environmentRoot: next.environmentRoot } : {}),
|
|
496
|
+
...(next.affectedPaths ? { affectedPaths: next.affectedPaths } : {}),
|
|
497
|
+
...(Object.hasOwn(next, "sourceWorkflowRunId")
|
|
498
|
+
? { sourceWorkflowRunId: next.sourceWorkflowRunId }
|
|
499
|
+
: {}),
|
|
500
|
+
...(next.releaseBlockerPriority
|
|
501
|
+
? { releaseBlockerPriority: next.releaseBlockerPriority }
|
|
502
|
+
: {}),
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
const cancelQueuedDevDeliveryCandidateTransition =
|
|
506
|
+
createCancelQueuedDevDeliveryCandidate(normalizeDevDeliveryQueue);
|
|
507
|
+
export function cancelQueuedDevDeliveryCandidate(queueInput, input, options) {
|
|
508
|
+
return cancelQueuedDevDeliveryCandidateTransition(queueInput, input, options);
|
|
509
|
+
}
|
|
510
|
+
export const settleDevDeliveryTerminalEvent = createDevDeliveryTerminalSettler({
|
|
511
|
+
normalizeQueue: normalizeDevDeliveryQueue,
|
|
512
|
+
closeWarrant: closeDevDeliveryWarrant,
|
|
513
|
+
cancelQueued: cancelQueuedDevDeliveryCandidate,
|
|
514
|
+
terminalStates: TERMINAL_STATES,
|
|
515
|
+
});
|
|
516
|
+
export function observeDevDeliveryQueue(
|
|
517
|
+
queueInput,
|
|
518
|
+
{ now = new Date().toISOString() } = {},
|
|
519
|
+
) {
|
|
577
520
|
const queue = normalizeDevDeliveryQueue(queueInput);
|
|
578
521
|
const currentTime = timestamp(now, "now");
|
|
579
522
|
const states = {};
|
|
580
|
-
for (const candidate of queue.candidates)
|
|
523
|
+
for (const candidate of queue.candidates)
|
|
524
|
+
states[candidate.status] = (states[candidate.status] || 0) + 1;
|
|
581
525
|
const queued = rankDevDeliveryCandidates(queue, { now: currentTime });
|
|
582
526
|
return {
|
|
583
527
|
schema: "kungfu.buildchain.dev-delivery-queue-observation/v1",
|
|
@@ -586,7 +530,12 @@ export function observeDevDeliveryQueue(queueInput, { now = new Date().toISOStri
|
|
|
586
530
|
stateRoot: queue.stateRoot,
|
|
587
531
|
generation: queue.generation,
|
|
588
532
|
activeWarrant: queue.activeWarrant,
|
|
589
|
-
activeCandidate: queue.activeWarrant
|
|
533
|
+
activeCandidate: queue.activeWarrant
|
|
534
|
+
? queue.candidates.find(
|
|
535
|
+
(candidate) =>
|
|
536
|
+
candidate.candidateId === queue.activeWarrant.candidateId,
|
|
537
|
+
) || null
|
|
538
|
+
: null,
|
|
590
539
|
states,
|
|
591
540
|
queued: queued.map((entry, index) => ({
|
|
592
541
|
position: index + 1,
|
|
@@ -598,7 +547,10 @@ export function observeDevDeliveryQueue(queueInput, { now = new Date().toISOStri
|
|
|
598
547
|
queueAgeSeconds: entry.priority.ageSeconds,
|
|
599
548
|
attempts: entry.candidate.attempts,
|
|
600
549
|
recoveries: entry.candidate.recoveries,
|
|
601
|
-
nextAction:
|
|
550
|
+
nextAction:
|
|
551
|
+
index === 0 && !queue.activeWarrant
|
|
552
|
+
? "eligible-for-selection"
|
|
553
|
+
: "wait-for-active-warrant",
|
|
602
554
|
})),
|
|
603
555
|
observedAt: currentTime,
|
|
604
556
|
};
|