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

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 (103) hide show
  1. package/README.md +4 -2
  2. package/actions/promote-buildchain-ref/README.md +10 -0
  3. package/actions/release-tail/README.md +17 -0
  4. package/bin/buildchain.mjs +11 -0
  5. package/bin/internal/command-registry.mjs +2 -0
  6. package/contracts/auditable-demo-scenario-v1.schema.json +1 -1
  7. package/contracts/engineering-housekeeper-v1.schema.json +143 -0
  8. package/contracts/fixtures/engineering-housekeeper-v1/cases.json +68 -0
  9. package/contracts/fixtures/release-tail-capabilities-v1/kungfu-alpha.json +273 -0
  10. package/contracts/publication-rehearsal-capsule-v1.schema.json +173 -0
  11. package/contracts/release-tail-capabilities-v1.schema.json +199 -0
  12. package/contracts/release-tail-provider-bindings-v1.schema.json +56 -0
  13. package/dist/site/agent-index.json +3 -0
  14. package/dist/site/artifact-schemas.json +6 -0
  15. package/dist/site/buildchain-contract.json +42 -27
  16. package/dist/site/buildchain-site.json +384 -33
  17. package/dist/site/capability-registry.json +15 -12
  18. package/dist/site/cli-registry.json +102 -0
  19. package/dist/site/controller-registry.json +19 -3
  20. package/dist/site/kfd-claims.json +359 -17
  21. package/dist/site/kfd-upstream-aggregate.json +1 -1
  22. package/dist/site/manual-registry.json +53 -7
  23. package/dist/site/node-api-registry.json +5882 -3605
  24. package/dist/site/page-registry.json +348 -21
  25. package/dist/site/public-surface-audit.json +396 -16
  26. package/dist/site/publication-authority-registry.json +106 -1
  27. package/dist/site/publication-registry.json +4 -4
  28. package/dist/site/release-provenance.json +7 -0
  29. package/dist/site/schemas/publication-rehearsal-capsule-v1.schema.json +269 -0
  30. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +353 -0
  31. package/dist/site/schemas/release-tail-provider-bindings-v1.schema.json +94 -0
  32. package/dist/site/site-manifest.json +35 -11
  33. package/dist/site/workflow-registry.json +204 -10
  34. package/docs/MAP.md +6 -2
  35. package/docs/auditable-demo.md +2 -2
  36. package/docs/cli-reference.md +136 -0
  37. package/docs/dev-delivery-warrant.md +49 -4
  38. package/docs/engineering-housekeeper.md +138 -0
  39. package/docs/lifecycle-protocol.md +4 -2
  40. package/docs/node-api-reference.md +495 -281
  41. package/docs/publication-rehearsal.md +94 -0
  42. package/docs/release-governance.md +17 -2
  43. package/docs/release-tail-contract.md +160 -0
  44. package/docs/release-tail-provider-plane.md +120 -0
  45. package/docs/reusable-build-surface.md +11 -0
  46. package/package.json +11 -3
  47. package/packages/core/artifact-signing.js +61 -0
  48. package/packages/core/buildchain-agent-manuals.js +3 -0
  49. package/packages/core/buildchain-config.js +66 -6
  50. package/packages/core/buildchain-kfd-claims.js +1 -1
  51. package/packages/core/buildchain-publication-authority.js +5 -0
  52. package/packages/core/controller-evidence.js +2 -1
  53. package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
  54. package/packages/core/dev-delivery-warrant-shadow.js +502 -0
  55. package/packages/core/dev-delivery-warrant.js +15 -6
  56. package/packages/core/diagnostics.js +8 -3
  57. package/packages/core/engineering-housekeeper-github-client.js +222 -0
  58. package/packages/core/engineering-housekeeper-github.js +501 -0
  59. package/packages/core/engineering-housekeeper.js +259 -0
  60. package/packages/core/index.js +42 -0
  61. package/packages/core/kfd-gate.js +45 -15
  62. package/packages/core/paper-agent-entry.js +11 -5
  63. package/packages/core/paper-repository.js +1 -0
  64. package/packages/core/paper-scaffold-content.js +21 -0
  65. package/packages/core/paper.js +28 -2
  66. package/packages/core/publication-rehearsal-projection.js +173 -0
  67. package/packages/core/publication-rehearsal-runtime.js +921 -0
  68. package/packages/core/release-passport.js +130 -20
  69. package/packages/core/release-tail-compatibility.js +60 -0
  70. package/packages/core/release-tail-provider-adapters.js +461 -0
  71. package/packages/core/release-tail-provider-plane.js +1228 -0
  72. package/scripts/assemble-publication-artifact-admission.mjs +1 -1
  73. package/scripts/assemble-self-publication-admission.mjs +2 -2
  74. package/scripts/audit-publication-control-plane.mjs +1 -1
  75. package/scripts/auditable-demo-bundle-verification.mjs +2 -3
  76. package/scripts/auditable-demo-platform.mjs +2 -2
  77. package/scripts/auditable-demo-renditions.mjs +1 -1
  78. package/scripts/auditable-demo.mjs +2 -2
  79. package/scripts/build-contract-core.mjs +8 -3
  80. package/scripts/build-standalone-binary.mjs +14 -3
  81. package/scripts/buildchain-cli-help.mjs +13 -0
  82. package/scripts/check-core-mechanism-inventory.mjs +347 -0
  83. package/scripts/check-inventory.mjs +10 -8
  84. package/scripts/check-maintainability.mjs +9 -2
  85. package/scripts/check-release-tail-contract.mjs +435 -0
  86. package/scripts/dev-delivery-warrant.mjs +31 -4
  87. package/scripts/dev-pr-auto-merge.mjs +30 -4
  88. package/scripts/dev-pr-delivery-warrant.mjs +50 -0
  89. package/scripts/engineering-housekeeper-workflow.mjs +394 -0
  90. package/scripts/generate-channel-promotion-workflow.mjs +10 -8
  91. package/scripts/generate-site-bundle.mjs +47 -4
  92. package/scripts/init-repo.mjs +26 -2
  93. package/scripts/inspect-artifact-signing-requests.mjs +6 -0
  94. package/scripts/materialize-self-release-candidate-version.mjs +137 -0
  95. package/scripts/publication-commit-evidence.mjs +69 -23
  96. package/scripts/release-candidate-resolver.mjs +16 -10
  97. package/scripts/release-tail.mjs +159 -0
  98. package/scripts/resume-from-candidate-run.mjs +123 -9
  99. package/scripts/seal-artifact-signing-requests.mjs +6 -0
  100. package/scripts/site-capability-metadata.mjs +13 -0
  101. package/scripts/v4-architecture.mjs +600 -0
  102. package/scripts/web-surface-core.mjs +8 -2
  103. package/scripts/workflow-call-contract.mjs +184 -5
@@ -0,0 +1,435 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+
7
+ const INVENTORY_PATH = "architecture/release-tail-contract-inventory.json";
8
+ const ROOT_PATTERN = /^sha256:[0-9a-f]{64}$/u;
9
+ const SHA_PATTERN = /^[0-9a-f]{40}$/u;
10
+
11
+ function loadJson(root, file) {
12
+ return JSON.parse(fs.readFileSync(path.join(root, file), "utf8"));
13
+ }
14
+
15
+ function read(root, file) {
16
+ return fs.readFileSync(path.join(root, file), "utf8");
17
+ }
18
+
19
+ function commandInputs(text, indent, stopAt = "") {
20
+ const source = stopAt ? text.split(new RegExp(`^${stopAt}`, "mu"))[0] : text;
21
+ const pattern = new RegExp(`^ {${indent}}([a-z0-9-]+-command):\\s*$`, "gmu");
22
+ return [...source.matchAll(pattern)].map((match) => match[1]);
23
+ }
24
+
25
+ function workflowCoordinates(root, inventory) {
26
+ return (inventory.reverseScan.workflowInputs || []).flatMap((file) =>
27
+ commandInputs(read(root, file), 6, "jobs:").map(
28
+ (name) => `workflow:${file}#${name}`,
29
+ ),
30
+ );
31
+ }
32
+
33
+ function actionCoordinates(root, inventory) {
34
+ return (inventory.reverseScan.actionInputs || []).flatMap((file) =>
35
+ commandInputs(read(root, file).split(/^outputs:/mu)[0], 2).map(
36
+ (name) => `action:${file}#${name}`,
37
+ ),
38
+ );
39
+ }
40
+
41
+ function sortedUnique(values) {
42
+ return [...new Set(values)].sort();
43
+ }
44
+
45
+ function sameSet(left, right) {
46
+ return (
47
+ JSON.stringify(sortedUnique(left)) === JSON.stringify(sortedUnique(right))
48
+ );
49
+ }
50
+
51
+ function validateReverseScan(root, inventory, surfaces, issues) {
52
+ const coordinates = [
53
+ ...workflowCoordinates(root, inventory),
54
+ ...actionCoordinates(root, inventory),
55
+ ...(inventory.reverseScan.configAndCliAliases || []),
56
+ ];
57
+ const owned = new Map();
58
+ for (const surface of surfaces) {
59
+ for (const coordinate of surface.coordinates || []) {
60
+ if (owned.has(coordinate))
61
+ issues.push(
62
+ `${coordinate}: ambiguous surface ownership (${owned.get(coordinate)}, ${surface.id})`,
63
+ );
64
+ owned.set(coordinate, surface.id);
65
+ }
66
+ }
67
+ for (const coordinate of coordinates) {
68
+ if (!owned.has(coordinate))
69
+ issues.push(`unclassified release-tail command surface: ${coordinate}`);
70
+ }
71
+ for (const coordinate of owned.keys()) {
72
+ if (!coordinates.includes(coordinate))
73
+ issues.push(
74
+ `declared release-tail coordinate is not reverse-discovered: ${coordinate}`,
75
+ );
76
+ }
77
+
78
+ const workflowNames = coordinates
79
+ .filter((entry) => entry.startsWith("workflow:"))
80
+ .map((entry) => entry.split("#")[1]);
81
+ const actionNames = coordinates
82
+ .filter((entry) => entry.startsWith("action:"))
83
+ .map((entry) => entry.split("#")[1]);
84
+ if (!sameSet(workflowNames, inventory.reverseScan.workflowCommandNames || []))
85
+ issues.push("reverse scan workflow command-name inventory drifted");
86
+ if (!sameSet(actionNames, inventory.reverseScan.actionCommandNames || []))
87
+ issues.push("reverse scan Action command-name inventory drifted");
88
+
89
+ let executionSites = 0;
90
+ for (const surface of surfaces) {
91
+ for (const site of surface.executionSites || []) {
92
+ const file = path.join(root, site.path || "");
93
+ if (!site.path || !site.marker || !fs.existsSync(file)) {
94
+ issues.push(
95
+ `${surface.id}: execution site is incomplete: ${site.path || "<empty>"}`,
96
+ );
97
+ continue;
98
+ }
99
+ if (!fs.readFileSync(file, "utf8").includes(site.marker))
100
+ issues.push(
101
+ `${surface.id}: execution marker is missing from ${site.path}`,
102
+ );
103
+ executionSites += 1;
104
+ }
105
+ }
106
+ return { coordinates: coordinates.length, executionSites };
107
+ }
108
+
109
+ function validateTransaction(inventory, issues) {
110
+ const transaction = inventory.canonicalTransaction || {};
111
+ const phases = (transaction.phases || []).map((entry) => entry.id);
112
+ if (
113
+ transaction.id !== "buildchain.release-tail/v1" ||
114
+ transaction.singleWriter !== true
115
+ )
116
+ issues.push(
117
+ "canonical release transaction identity or single-writer invariant is invalid",
118
+ );
119
+ if (
120
+ !sameSet(phases, [
121
+ "prepare",
122
+ "publish",
123
+ "commit",
124
+ "activate",
125
+ "readback",
126
+ "settle",
127
+ ])
128
+ )
129
+ issues.push("canonical release transaction must own all six frozen phases");
130
+ if (
131
+ transaction.effectSchema !== "kungfu.buildchain.release-tail.effect/v1" ||
132
+ transaction.observationSchema !==
133
+ "kungfu.buildchain.release-tail.observation/v1" ||
134
+ transaction.receiptSchema !== "kungfu.buildchain.release-tail.receipt/v1"
135
+ )
136
+ issues.push("canonical effect, observation, and receipt schemas drifted");
137
+ if ((transaction.retryClasses || []).some((entry) => entry.localAttempts > 3))
138
+ issues.push("canonical transaction permits unbounded local retry");
139
+ for (const terminal of [
140
+ "complete",
141
+ "blocked",
142
+ "repair-required",
143
+ "terminal-failure",
144
+ ]) {
145
+ if (!(transaction.terminalClasses || []).includes(terminal))
146
+ issues.push(`canonical transaction omits terminal class ${terminal}`);
147
+ }
148
+ for (const forbidden of [
149
+ "choose transaction transitions",
150
+ "execute repository-supplied shell",
151
+ ]) {
152
+ if (!(transaction.adapterBoundary?.mustNot || []).includes(forbidden))
153
+ issues.push(`adapter boundary omits ${forbidden}`);
154
+ }
155
+ }
156
+
157
+ function findForbiddenKey(value, forbidden, at = "$") {
158
+ if (Array.isArray(value)) {
159
+ for (const [index, entry] of value.entries()) {
160
+ const found = findForbiddenKey(entry, forbidden, `${at}[${index}]`);
161
+ if (found) return found;
162
+ }
163
+ return "";
164
+ }
165
+ if (!value || typeof value !== "object") return "";
166
+ for (const [key, entry] of Object.entries(value)) {
167
+ if (forbidden.has(key.toLowerCase())) return `${at}.${key}`;
168
+ const found = findForbiddenKey(entry, forbidden, `${at}.${key}`);
169
+ if (found) return found;
170
+ }
171
+ return "";
172
+ }
173
+
174
+ function validateCapabilityMessages(
175
+ inventory,
176
+ capability,
177
+ fixturePath,
178
+ issues,
179
+ ) {
180
+ if (
181
+ capability.effect?.schema !==
182
+ inventory.canonicalTransaction?.effectSchema ||
183
+ capability.observation?.schema !==
184
+ inventory.canonicalTransaction?.observationSchema ||
185
+ capability.receipt?.schema !== inventory.canonicalTransaction?.receiptSchema
186
+ )
187
+ issues.push(`${fixturePath}: ${capability.id} message schema drifted`);
188
+ }
189
+
190
+ function validateCapabilityOperation(capability, fixturePath, issues) {
191
+ if (
192
+ !ROOT_PATTERN.test(capability.operationIdentity?.transactionRoot || "") ||
193
+ capability.operationIdentity?.capabilityId !== capability.id ||
194
+ !ROOT_PATTERN.test(capability.operationIdentity?.subjectRoot || "") ||
195
+ !ROOT_PATTERN.test(capability.operationIdentity?.targetRoot || "") ||
196
+ !String(capability.operationIdentity?.attemptKey || "").trim()
197
+ )
198
+ issues.push(
199
+ `${fixturePath}: ${capability.id} operation identity is incomplete or inconsistent`,
200
+ );
201
+ if (
202
+ !Number.isInteger(capability.retry?.localAttempts) ||
203
+ capability.retry.localAttempts < 0 ||
204
+ capability.retry.localAttempts > 3
205
+ )
206
+ issues.push(`${fixturePath}: ${capability.id} local retry is invalid`);
207
+ }
208
+
209
+ function validateCapabilityDimensions(
210
+ inventory,
211
+ capability,
212
+ fixturePath,
213
+ issues,
214
+ ) {
215
+ for (const field of inventory.declarativeContract.requiredDimensions || []) {
216
+ if (capability[field] === undefined)
217
+ issues.push(`${fixturePath}: ${capability.id} omits ${field}`);
218
+ }
219
+ if (!(capability.readbackPredicates || []).length)
220
+ issues.push(`${fixturePath}: ${capability.id} has no readback predicate`);
221
+ if (!(capability.evidenceRequirements || []).length)
222
+ issues.push(`${fixturePath}: ${capability.id} has no evidence requirement`);
223
+ }
224
+
225
+ function validateCapabilityFixture(inventory, fixture, fixturePath, issues) {
226
+ const contract = inventory.declarativeContract || {};
227
+ if (
228
+ fixture.contract !== contract.contract ||
229
+ fixture.schemaVersion !== contract.schemaVersion ||
230
+ fixture.transactionPolicy !== inventory.canonicalTransaction?.id
231
+ )
232
+ issues.push(`${fixturePath}: declaration identity is invalid`);
233
+ if (!SHA_PATTERN.test(fixture.subject?.sourceSha || ""))
234
+ issues.push(`${fixturePath}: subject sourceSha is not exact`);
235
+ const forbidden = new Set(contract.forbiddenKeys || []);
236
+ const forbiddenPath = findForbiddenKey(fixture, forbidden);
237
+ if (forbiddenPath)
238
+ issues.push(
239
+ `${fixturePath}: executable key is forbidden at ${forbiddenPath}`,
240
+ );
241
+
242
+ const capabilities = fixture.capabilities || [];
243
+ const ids = capabilities.map((entry) => entry.id);
244
+ if (!sameSet(ids, contract.requiredCapabilityIds || []))
245
+ issues.push(`${fixturePath}: required capability set drifted`);
246
+ if (ids.length !== new Set(ids).size)
247
+ issues.push(`${fixturePath}: capability ids must be unique`);
248
+ for (const capability of capabilities) {
249
+ validateCapabilityDimensions(inventory, capability, fixturePath, issues);
250
+ validateCapabilityMessages(inventory, capability, fixturePath, issues);
251
+ validateCapabilityOperation(capability, fixturePath, issues);
252
+ }
253
+ }
254
+
255
+ function validateContract(root, inventory, issues) {
256
+ const contract = inventory.declarativeContract || {};
257
+ if (!fs.existsSync(path.join(root, contract.schemaPath || "")))
258
+ issues.push("declarative release-tail schema path is missing");
259
+ else {
260
+ const schema = loadJson(root, contract.schemaPath);
261
+ if (
262
+ schema.properties?.contract?.const !== contract.contract ||
263
+ schema.properties?.schemaVersion?.const !== contract.schemaVersion
264
+ )
265
+ issues.push("declarative release-tail JSON Schema identity drifted");
266
+ }
267
+ for (const fixturePath of contract.fixturePaths || []) {
268
+ if (!fs.existsSync(path.join(root, fixturePath))) {
269
+ issues.push(`declarative fixture is missing: ${fixturePath}`);
270
+ continue;
271
+ }
272
+ validateCapabilityFixture(
273
+ inventory,
274
+ loadJson(root, fixturePath),
275
+ fixturePath,
276
+ issues,
277
+ );
278
+ }
279
+ }
280
+
281
+ function validateSurfaceInventory(inventory, issues) {
282
+ const surfaces = inventory.legacyExecutableSurfaces || [];
283
+ const surfaceIds = new Set();
284
+ for (const surface of surfaces) {
285
+ if (!surface.id || surfaceIds.has(surface.id))
286
+ issues.push(
287
+ `release-tail surface id is missing or duplicated: ${surface.id || "<empty>"}`,
288
+ );
289
+ surfaceIds.add(surface.id);
290
+ for (const field of [
291
+ "owner",
292
+ "classification",
293
+ "default",
294
+ "replacement",
295
+ "disposition",
296
+ ]) {
297
+ if (!String(surface[field] || "").trim())
298
+ issues.push(`${surface.id}: ${field} is empty`);
299
+ }
300
+ if (!(surface.publicNames || []).length)
301
+ issues.push(`${surface.id}: publicNames is empty`);
302
+ if (!(surface.coordinates || []).length)
303
+ issues.push(`${surface.id}: coordinates is empty`);
304
+ }
305
+ return { surfaces, surfaceIds };
306
+ }
307
+
308
+ function validateManagedCallers(inventory, surfaceIds, issues) {
309
+ for (const caller of inventory.managedConsumerCallers || []) {
310
+ if (
311
+ !caller.id ||
312
+ !caller.repository ||
313
+ !caller.workflow ||
314
+ !caller.runtimeRef
315
+ )
316
+ issues.push(
317
+ `managed consumer caller is incomplete: ${caller.id || "<empty>"}`,
318
+ );
319
+ if (
320
+ !SHA_PATTERN.test(caller.sourceCommit || "") ||
321
+ !SHA_PATTERN.test(caller.sourceTree || "")
322
+ )
323
+ issues.push(`${caller.id}: managed consumer cut is not exact`);
324
+ for (const surfaceId of caller.executableSurfaceIds || []) {
325
+ if (!surfaceIds.has(surfaceId))
326
+ issues.push(`${caller.id}: unknown executable surface ${surfaceId}`);
327
+ }
328
+ }
329
+ }
330
+
331
+ function validateInventory(inventory, issues) {
332
+ if (
333
+ inventory.schemaVersion !== 1 ||
334
+ inventory.contract !== "kungfu-buildchain-release-tail-contract-inventory"
335
+ )
336
+ issues.push("release-tail inventory identity is invalid");
337
+ if (
338
+ !SHA_PATTERN.test(inventory.baseline?.commit || "") ||
339
+ !SHA_PATTERN.test(inventory.baseline?.tree || "")
340
+ )
341
+ issues.push("release-tail baseline must bind an exact commit and tree");
342
+ const { surfaces, surfaceIds } = validateSurfaceInventory(inventory, issues);
343
+ validateManagedCallers(inventory, surfaceIds, issues);
344
+ return surfaces;
345
+ }
346
+
347
+ function validateMigration(inventory, issues) {
348
+ const migration = inventory.migration || {};
349
+ const window = migration.compatibilityWindow || {};
350
+ if (
351
+ window.maximumDurationDays !== 90 ||
352
+ window.maximumMinorLines !== 2 ||
353
+ window.permanentEscapeHatch !== false
354
+ )
355
+ issues.push(
356
+ "compatibility window is not bounded by time, minor lines, and no-escape policy",
357
+ );
358
+ if ((migration.publishedReleasePreservation || []).length < 3)
359
+ issues.push("published-release preservation rules are incomplete");
360
+ const codes = new Set(
361
+ (migration.rejectionRules || []).map((entry) => entry.code),
362
+ );
363
+ for (const code of [
364
+ "release-tail-command-forbidden",
365
+ "release-tail-alias-collision",
366
+ "release-tail-operation-id-missing",
367
+ "release-tail-readback-missing",
368
+ "release-tail-retry-unbounded",
369
+ ]) {
370
+ if (!codes.has(code)) issues.push(`migration rejection rules omit ${code}`);
371
+ }
372
+ const excepted = new Set(
373
+ (migration.exceptionLedger || []).flatMap(
374
+ (entry) => entry.surfaceIds || [],
375
+ ),
376
+ );
377
+ for (const surface of inventory.legacyExecutableSurfaces || []) {
378
+ if (
379
+ surface.classification !== "adjacent-non-tail" &&
380
+ !excepted.has(surface.id)
381
+ )
382
+ issues.push(
383
+ `${surface.id}: compatibility exception has no owner and sunset test`,
384
+ );
385
+ }
386
+ for (const exception of migration.exceptionLedger || []) {
387
+ if (!exception.owner || !exception.expires || !exception.removalTest)
388
+ issues.push(`${exception.id}: exception ledger entry is incomplete`);
389
+ }
390
+ }
391
+
392
+ function checkReleaseTailContract({
393
+ root = process.cwd(),
394
+ inventory = loadJson(root, INVENTORY_PATH),
395
+ fixtures,
396
+ } = {}) {
397
+ const issues = [];
398
+ const surfaces = validateInventory(inventory, issues);
399
+ validateTransaction(inventory, issues);
400
+ if (fixtures) {
401
+ for (const [fixturePath, fixture] of Object.entries(fixtures))
402
+ validateCapabilityFixture(inventory, fixture, fixturePath, issues);
403
+ } else {
404
+ validateContract(root, inventory, issues);
405
+ }
406
+ const reverseScan = validateReverseScan(root, inventory, surfaces, issues);
407
+ validateMigration(inventory, issues);
408
+ if (issues.length)
409
+ throw new Error(
410
+ `release-tail contract check failed:\n- ${issues.join("\n- ")}`,
411
+ );
412
+ return {
413
+ surfaces: surfaces.length,
414
+ managedCallers: inventory.managedConsumerCallers.length,
415
+ capabilities: inventory.declarativeContract.requiredCapabilityIds.length,
416
+ ...reverseScan,
417
+ };
418
+ }
419
+
420
+ if (
421
+ process.argv[1] &&
422
+ import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href
423
+ ) {
424
+ try {
425
+ const report = checkReleaseTailContract();
426
+ console.log(
427
+ `release-tail contract check passed: ${report.surfaces} classified surfaces, ${report.coordinates} reverse-discovered coordinates, ${report.executionSites} execution sites, ${report.capabilities} declarative capabilities, ${report.managedCallers} managed callers`,
428
+ );
429
+ } catch (error) {
430
+ console.error(error instanceof Error ? error.message : String(error));
431
+ process.exitCode = 1;
432
+ }
433
+ }
434
+
435
+ export { checkReleaseTailContract };
@@ -2,6 +2,7 @@
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { cancelQueuedDevDeliveryCandidate, closeDevDeliveryWarrant, createDevDeliveryQueue, heartbeatDevDeliveryWarrant, observeDevDeliveryQueue, recoverExpiredDevDeliveryWarrant, selectDevDeliveryWarrant, settleDevDeliveryTerminalEvent, submitDevDeliveryCandidate } from "../packages/core/dev-delivery-warrant.js";
5
+ import { planDevDeliveryWarrantShadow, qualifyDevDeliveryWarrantShadow } from "../packages/core/dev-delivery-warrant-shadow.js";
5
6
 
6
7
  const STATE_PATH = "queue.json";
7
8
  const STATE_REF_PREFIX = "buildchain/dev-delivery-warrant/";
@@ -189,6 +190,7 @@ function transitionFor(command, queue, options) {
189
190
  closureRoot: exactRoot(options.closureRoot, "closureRoot"),
190
191
  dependencyRoot: exactRoot(options.dependencyRoot, "dependencyRoot"),
191
192
  toolchainRoot: exactRoot(options.toolchainRoot, "toolchainRoot"),
193
+ sourceWorkflowRunId: options.sourceWorkflowRunId ? positiveInteger(options.sourceWorkflowRunId, "sourceWorkflowRunId") : 0,
192
194
  deliveryClass: options.deliveryClass,
193
195
  priority: options.priority || "ordinary",
194
196
  },
@@ -345,6 +347,23 @@ export async function runDevDeliveryCommand(optionsInput = {}, clientInput) {
345
347
  };
346
348
  }
347
349
 
350
+ export function runDevDeliveryShadowCommand(options = {}) {
351
+ if (bool(options.execute, false)) {
352
+ throw new Error("shadow qualification is effect-disabled and rejects --execute");
353
+ }
354
+ if (!options.inputPath) throw new Error("shadow qualification requires --input FILE");
355
+ const input = JSON.parse(fs.readFileSync(options.inputPath, "utf8"));
356
+ if (options.command === "shadow-plan") {
357
+ return planDevDeliveryWarrantShadow(input, {
358
+ maxConcurrency: positiveInteger(options.maxConcurrency, "maxConcurrency", 2),
359
+ });
360
+ }
361
+ if (options.command === "shadow-qualify") {
362
+ return qualifyDevDeliveryWarrantShadow(input);
363
+ }
364
+ throw new Error(`unsupported shadow command ${options.command || "<empty>"}`);
365
+ }
366
+
348
367
  function flag(args, name, fallback = "") {
349
368
  const index = args.indexOf(`--${name}`);
350
369
  return index === -1 ? fallback : args[index + 1] || "";
@@ -386,6 +405,8 @@ export function devDeliveryCliOptions(args = [], environment = process.env) {
386
405
  evidenceRoot: flag(rest, "evidence-root", environment.BUILDCHAIN_DEV_DELIVERY_EVIDENCE_ROOT),
387
406
  reason: flag(rest, "reason", environment.BUILDCHAIN_DEV_DELIVERY_REASON),
388
407
  now: flag(rest, "now", environment.BUILDCHAIN_DEV_DELIVERY_NOW),
408
+ inputPath: flag(rest, "input", environment.BUILDCHAIN_DEV_DELIVERY_SHADOW_INPUT),
409
+ maxConcurrency: flag(rest, "max-concurrency", environment.BUILDCHAIN_DEV_DELIVERY_SHADOW_MAX_CONCURRENCY || "2"),
389
410
  outputPath: flag(rest, "output", environment.BUILDCHAIN_DEV_DELIVERY_OUTPUT || ".buildchain/dev-delivery/result.json"),
390
411
  execute: hasFlag(rest, "execute"),
391
412
  json: hasFlag(rest, "json"),
@@ -393,7 +414,7 @@ export function devDeliveryCliOptions(args = [], environment = process.env) {
393
414
  }
394
415
 
395
416
  function usage() {
396
- return "Usage:\n buildchain dev warrant <submit|select|heartbeat|recover|close|settle|cancel-queued|observe> --repository owner/repo --branch dev/vN/vN.M [--execute] [--output FILE] [--json]\n";
417
+ return "Usage:\n buildchain dev warrant <submit|select|heartbeat|recover|close|settle|cancel-queued|observe> --repository owner/repo --branch dev/vN/vN.M [--execute] [--output FILE] [--json]\n buildchain dev warrant <shadow-plan|shadow-qualify> --input FILE [--max-concurrency 1|2] [--output FILE] [--json]\n";
397
418
  }
398
419
 
399
420
  async function main() {
@@ -403,16 +424,22 @@ async function main() {
403
424
  return;
404
425
  }
405
426
  const options = devDeliveryCliOptions(args);
406
- if (!["submit", "select", "heartbeat", "recover", "close", "settle", "cancel-queued", "observe"].includes(options.command)) {
427
+ if (!["submit", "select", "heartbeat", "recover", "close", "settle", "cancel-queued", "observe", "shadow-plan", "shadow-qualify"].includes(options.command)) {
407
428
  throw new Error(usage().trim());
408
429
  }
409
- const result = await runDevDeliveryCommand(options);
430
+ const shadow = options.command.startsWith("shadow-");
431
+ const result = shadow ? runDevDeliveryShadowCommand(options) : await runDevDeliveryCommand(options);
410
432
  fs.mkdirSync(path.dirname(options.outputPath), { recursive: true });
411
433
  fs.writeFileSync(options.outputPath, `${JSON.stringify(result, null, 2)}\n`);
412
434
  if (options.json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
413
435
  else {
414
436
  process.stdout.write(`Buildchain dev delivery ${options.command}: ${result.receipt?.reason || result.mode}\n`);
415
- process.stdout.write(`State root: ${result.after?.stateRoot || result.observation.stateRoot}\n`);
437
+ if (shadow) {
438
+ process.stdout.write(`Decision: ${result.decision}\n`);
439
+ process.stdout.write(`Evidence root: ${result.planRoot || result.qualificationRoot}\n`);
440
+ } else {
441
+ process.stdout.write(`State root: ${result.after?.stateRoot || result.observation.stateRoot}\n`);
442
+ }
416
443
  if (result.receiptRoot) process.stdout.write(`Receipt root: ${result.receiptRoot}\n`);
417
444
  process.stdout.write(`Result: ${options.outputPath}\n`);
418
445
  }
@@ -4,7 +4,7 @@ import { spawnSync } from "node:child_process";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
6
  import { verifyProjectCutReplayProof } from "../packages/core/dev-delivery-warrant.js";
7
- import { admitExistingQueueEntry, createDevPrAdmissionReceipt, readDeliveryWarrantResult, runSourceQualification, runTargetedQueueAdmission } from "./dev-pr-delivery-warrant.mjs";
7
+ import { admitExistingQueueEntry, createDevPrAdmissionReceipt, enqueueAfterStatusPropagation, readDeliveryWarrantResult, runSourceQualification, runTargetedQueueAdmission, verifyCurrentDeliveryWarrant } from "./dev-pr-delivery-warrant.mjs";
8
8
  const DEFAULT_BLOCK_LABELS = ["blocked", "do-not-merge", "work-in-progress"];
9
9
  const DEFAULT_ALLOWED_HEAD_PREFIXES = ["feature/", "fix/", "chore/", "docs/", "ci/", "refactor/"];
10
10
  const DEFAULT_REQUIRED_CHECKS = ["check"];
@@ -70,6 +70,7 @@ function normalizeOptions(options = {}) {
70
70
  allowedHeadPrefixes: splitList(options.allowedHeadPrefixes, DEFAULT_ALLOWED_HEAD_PREFIXES),
71
71
  requiredChecks: splitList(options.requiredChecks, DEFAULT_REQUIRED_CHECKS),
72
72
  queueAdmissionContext: String(options.queueAdmissionContext || "").trim(),
73
+ activeLeaseContext: String(options.activeLeaseContext || "").trim(),
73
74
  requireApproval: boolOption(options.requireApproval, true),
74
75
  sameRepositoryOnly: boolOption(options.sameRepositoryOnly, true),
75
76
  maxMerges: intOption(options.maxMerges, 1),
@@ -218,6 +219,20 @@ async function setQueueAdmissionStatus(client, repository, sha, context, state)
218
219
  return { context, state, sha };
219
220
  }
220
221
 
222
+ async function setActiveLeaseStatus(client, repository, sha, context, state) {
223
+ if (!context) return null;
224
+ await client.request("POST", `/repos/${repository.owner}/${repository.repo}/statuses/${sha}`, {
225
+ body: {
226
+ state,
227
+ context,
228
+ description: state === "pending"
229
+ ? "Buildchain reactivated this exact lease for merge-group qualification"
230
+ : "Buildchain released this exact lease after queue admission failed",
231
+ },
232
+ });
233
+ return { context, state, sha };
234
+ }
235
+
221
236
  function skip(reason, details = {}) {
222
237
  return { action: "skip", reason, ...details };
223
238
  }
@@ -853,11 +868,15 @@ export async function runDevPrAdmission(optionsInput = {}, clientInput) {
853
868
  let warrant = null;
854
869
  try {
855
870
  warrant = readDeliveryWarrantResult(options, pr);
871
+ await verifyCurrentDeliveryWarrant(client, options, pr, warrant);
856
872
  } catch (error) {
857
873
  return reject("blocked", error.code || "invalid-delivery-warrant");
858
874
  }
859
875
  const matchingEntry = initialQueue.entries.find((entry) =>
860
876
  entry.pullRequestNumber === pr.number && entry.pullRequestHeadSha === options.expectedHeadSha);
877
+ if (matchingEntry && !options.dryRun) {
878
+ await setActiveLeaseStatus(client, options.repository, options.expectedHeadSha, options.activeLeaseContext, "pending");
879
+ }
861
880
  const existing = await admitExistingQueueEntry({
862
881
  options, pullRequest: pr, readiness, client, entry: matchingEntry, warrant,
863
882
  createReceipt: createAdmissionReceipt,
@@ -893,6 +912,7 @@ async function reconcileEnqueueError({ client, options, pr, expectedHeadSha, ent
893
912
  const exactEntry = queueReadback?.entries?.find((candidate) =>
894
913
  candidate.pullRequestNumber === pr.number && candidate.pullRequestHeadSha === expectedHeadSha);
895
914
  if (exactEntry) {
915
+ entry.activeLeaseStatus = await setActiveLeaseStatus(client, options.repository, expectedHeadSha, options.activeLeaseContext, "pending");
896
916
  entry.action = "enqueued";
897
917
  entry.reason = "already-enqueued-exact-head";
898
918
  entry.queueEntry = exactEntry;
@@ -902,6 +922,7 @@ async function reconcileEnqueueError({ client, options, pr, expectedHeadSha, ent
902
922
  return;
903
923
  }
904
924
  entry.queueAdmissionStatus = await setQueueAdmissionStatus(client, options.repository, expectedHeadSha, options.queueAdmissionContext, "failure");
925
+ entry.activeLeaseStatus = await setActiveLeaseStatus(client, options.repository, expectedHeadSha, options.activeLeaseContext, "failure");
905
926
  entry.action = "skip";
906
927
  entry.reason = "enqueue-rejected";
907
928
  entry.enqueueError = {
@@ -1067,10 +1088,14 @@ export async function runDevPrAutoMerge(optionsInput = {}, clientInput) {
1067
1088
  result.skipped.push(entry);
1068
1089
  } else {
1069
1090
  try {
1091
+ entry.activeLeaseStatus = await setActiveLeaseStatus(client, options.repository, expectedHeadSha, options.activeLeaseContext, "pending");
1070
1092
  entry.queueAdmissionStatus = await setQueueAdmissionStatus(client, options.repository, expectedHeadSha, options.queueAdmissionContext, "success");
1071
- const queueEntry = await client.enqueuePullRequest({
1072
- pullRequestId: decision.pullRequestId,
1073
- expectedHeadOid: expectedHeadSha,
1093
+ const queueEntry = await enqueueAfterStatusPropagation({
1094
+ enqueue: (input) => client.enqueuePullRequest(input),
1095
+ input: { pullRequestId: decision.pullRequestId, expectedHeadOid: expectedHeadSha },
1096
+ attempts: options.pollMergeableAttempts,
1097
+ delayMs: options.pollMergeableDelayMs,
1098
+ sleep: delay,
1074
1099
  });
1075
1100
  entry.action = "enqueued";
1076
1101
  entry.reason = "enqueued-with-expected-head";
@@ -1155,6 +1180,7 @@ export function cliOptions(args = [], environment = process.env) {
1155
1180
  allowedHeadPrefixes: cliValue(args, "allowed-head-prefixes", environment.BUILDCHAIN_DEV_PR_ALLOWED_HEAD_PREFIXES),
1156
1181
  requiredChecks: cliValue(args, "required-checks", environment.BUILDCHAIN_DEV_PR_REQUIRED_CHECKS),
1157
1182
  queueAdmissionContext: cliValue(args, "queue-admission-context", environment.BUILDCHAIN_DEV_PR_QUEUE_ADMISSION_CONTEXT),
1183
+ activeLeaseContext: cliValue(args, "active-lease-context", environment.BUILDCHAIN_DEV_PR_ACTIVE_LEASE_CONTEXT),
1158
1184
  diagnosticContext: cliValue(args, "diagnostic-context", environment.BUILDCHAIN_DEV_PR_DIAGNOSTIC_CONTEXT),
1159
1185
  warrantMode: cliValue(args, "warrant-mode", environment.BUILDCHAIN_DEV_PR_WARRANT_MODE),
1160
1186
  warrantResultPath: cliValue(args, "warrant-result", environment.BUILDCHAIN_DEV_PR_WARRANT_RESULT_PATH),
@@ -116,6 +116,56 @@ export function readDeliveryWarrantResult(options, pullRequest) {
116
116
  };
117
117
  }
118
118
 
119
+ export async function readCurrentDeliveryQueueState(client, repository, targetBranch) {
120
+ if (typeof client.getDevDeliveryQueueState === "function") {
121
+ return client.getDevDeliveryQueueState(targetBranch);
122
+ }
123
+ const stateRef = `buildchain/dev-delivery-warrant/${targetBranch.replaceAll("/", "-")}`;
124
+ const query = new URLSearchParams({ ref: stateRef });
125
+ const { data } = await client.request(
126
+ "GET",
127
+ `/repos/${repository.owner}/${repository.repo}/contents/queue.json?${query}`,
128
+ );
129
+ if (data?.type !== "file" || data?.encoding !== "base64" || !data?.content) {
130
+ mismatch("delivery-warrant-current-readback-invalid");
131
+ }
132
+ return JSON.parse(Buffer.from(String(data.content).replace(/\s+/g, ""), "base64").toString("utf8"));
133
+ }
134
+
135
+ export async function verifyCurrentDeliveryWarrant(client, options, pullRequest, warrant) {
136
+ if (!warrant) return;
137
+ let queue;
138
+ try {
139
+ queue = await readCurrentDeliveryQueueState(client, options.repository, options.targetBranch);
140
+ } catch {
141
+ mismatch("delivery-warrant-current-readback-failed");
142
+ }
143
+ const active = queue?.activeWarrant;
144
+ const candidate = queue?.candidates?.find((entry) => entry.candidateId === active?.candidateId);
145
+ requireMatch(active?.candidateId === warrant.candidateId, "delivery-warrant-no-longer-active");
146
+ requireMatch(active?.fencingToken === warrant.fencingToken, "delivery-warrant-current-fencing-mismatch");
147
+ requireMatch(Number(active?.generation) === Number(warrant.generation), "delivery-warrant-current-generation-mismatch");
148
+ requireMatch(Number(active?.pullRequestNumber) === Number(pullRequest.number), "delivery-warrant-current-pr-mismatch");
149
+ requireMatch(String(active?.sourceHead || "").toLowerCase() === options.expectedHeadSha, "delivery-warrant-current-head-mismatch");
150
+ requireMatch(["selected", "proving", "waiting", "blocked"].includes(candidate?.status), "delivery-warrant-current-candidate-not-selected");
151
+ requireMatch(candidate?.sourceHead === active.sourceHead, "delivery-warrant-current-candidate-head-mismatch");
152
+ }
153
+
154
+ export async function enqueueAfterStatusPropagation({ enqueue, input, attempts, delayMs, sleep }) {
155
+ let lastError;
156
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
157
+ try {
158
+ return await enqueue(input);
159
+ } catch (error) {
160
+ lastError = error;
161
+ const propagation = /required status(?:es| check).*failing|failing required status|cannot change this locked branch/i.test(String(error?.message || ""));
162
+ if (!propagation || attempt === attempts) throw error;
163
+ await sleep(delayMs);
164
+ }
165
+ }
166
+ throw lastError;
167
+ }
168
+
119
169
  export async function runSourceQualification({ options, pullRequest, readiness, client, evaluate, admissionState, createReceipt, root, publishDiagnostic, reject }) {
120
170
  const decision = await evaluate(pullRequest, { ...options, landingMode: options.landingMode, dryRun: true }, client);
121
171
  if (decision.observedHeadSha && String(decision.observedHeadSha).toLowerCase() !== options.expectedHeadSha) {