@kungfu-tech/buildchain 2.12.1-alpha.1 → 2.12.1-alpha.11

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 (36) hide show
  1. package/bin/buildchain.mjs +18 -0
  2. package/dist/site/buildchain-contract.json +7 -7
  3. package/dist/site/buildchain-site.json +44 -24
  4. package/dist/site/capability-registry.json +2 -2
  5. package/dist/site/cli-registry.json +12 -0
  6. package/dist/site/kfd-claims.json +108 -12
  7. package/dist/site/kfd-upstream-aggregate.json +1 -1
  8. package/dist/site/manual-registry.json +6 -6
  9. package/dist/site/node-api-registry.json +7 -7
  10. package/dist/site/page-registry.json +34 -14
  11. package/dist/site/public-surface-audit.json +58 -9
  12. package/dist/site/publication-registry.json +4 -4
  13. package/dist/site/site-manifest.json +10 -10
  14. package/dist/site/workflow-registry.json +30 -3
  15. package/docs/binary-distribution.md +20 -1
  16. package/docs/cli.md +17 -0
  17. package/docs/kfd-support.md +44 -1
  18. package/docs/lifecycle-protocol.md +16 -0
  19. package/docs/release-governance.md +7 -0
  20. package/docs/reusable-build-surface.md +69 -0
  21. package/docs/stable-candidate-patrol.md +16 -0
  22. package/package.json +2 -1
  23. package/packages/core/buildchain-kfd-claims.js +51 -2
  24. package/packages/core/buildchain-layout.js +46 -0
  25. package/packages/core/index.js +4 -0
  26. package/packages/core/kfd3-surface-register.js +59 -2
  27. package/scripts/build-standalone-binary.mjs +2 -1
  28. package/scripts/check-inventory.mjs +4 -1
  29. package/scripts/generate-site-bundle.mjs +4 -1
  30. package/scripts/init-repo.mjs +1 -1
  31. package/scripts/locked-source-checkout.mjs +52 -12
  32. package/scripts/npm-publish-dry-run.mjs +1 -1
  33. package/scripts/publication-artifact.mjs +1 -1
  34. package/scripts/publication-package.mjs +1 -1
  35. package/scripts/release-propagation.mjs +1 -1
  36. package/scripts/stable-candidate-qualification.mjs +239 -0
@@ -6,6 +6,7 @@ import {
6
6
  BUILDCHAIN_PUBLIC_SURFACE_AUDIT_CONTRACT,
7
7
  collectPublicSurfaceReverseAudit,
8
8
  } from "./public-surface-audit.js";
9
+ import { normalizeKfd3DistributionDeclaration } from "./kfd3-surface-register.js";
9
10
 
10
11
  export const BUILDCHAIN_KFD_CLAIM_REGISTRY_CONTRACT = "kungfu-buildchain-kfd-claim-registry";
11
12
  export const BUILDCHAIN_KFD_COLLABORATION_INTERFACE_CONTRACT = "kungfu-buildchain-kfd-collaboration-interface";
@@ -307,6 +308,23 @@ export function createBuildchainPublicClaimDefinitions() {
307
308
  "dist/site/public-surface-audit.json",
308
309
  ],
309
310
  },
311
+ {
312
+ id: "claim:buildchain-shifu-kfd-discovery",
313
+ claim: "Buildchain exposes a versioned repository-layout discovery contract and KFD-3 distribution declarations so Shifu can locate the registry and determine jurisdiction without copying Buildchain layout paths.",
314
+ sourcePaths: [
315
+ "packages/core/buildchain-layout.js",
316
+ "packages/core/buildchain-kfd-claims.js",
317
+ "packages/core/kfd3-surface-register.js",
318
+ "bin/buildchain.mjs",
319
+ "scripts/build-standalone-binary.mjs",
320
+ "docs/kfd-support.md",
321
+ ],
322
+ artifactPaths: [
323
+ "dist/site/kfd-claims.json",
324
+ "dist/site/cli-registry.json",
325
+ "dist/site/node-api-registry.json",
326
+ ],
327
+ },
310
328
  {
311
329
  id: "claim:buildchain-readme-badge-facts",
312
330
  claim: "Buildchain README status badges are generated from repository-owned facts and verified release-passport evidence, not hand-maintained README prose.",
@@ -431,6 +449,37 @@ export function createBuildchainKfdSurfaceRegistry({ root = process.cwd() } = {}
431
449
  ];
432
450
  const siteConsumptionContracts = SITE_CONTRACT_FILES.map((relPath) => surface(`site:${relPath}`, "site-consumption-contract", relPath));
433
451
  const controlSurfaces = WORKFLOW_AND_ACTION_FILES.map((relPath) => surface(`control:${relPath}`, relPath.includes("actions/") ? "action" : "workflow", relPath));
452
+ const standaloneDistribution = surface(
453
+ "distribution:buildchain-standalone",
454
+ "binary-distribution",
455
+ "scripts/build-standalone-binary.mjs",
456
+ {
457
+ name: "Buildchain standalone binary distribution",
458
+ artifactPath: "dist/binary",
459
+ evidencePath: ".github/workflows/binary-distribution.yml",
460
+ distribution: normalizeKfd3DistributionDeclaration({
461
+ registrar: "shifu",
462
+ tasks: ["binary:build"],
463
+ artifacts: [
464
+ {
465
+ kind: "binary",
466
+ platform: "linux",
467
+ pathGlob: "dist/binary/buildchain-x86_64-unknown-linux-gnu.tar.gz",
468
+ },
469
+ {
470
+ kind: "binary",
471
+ platform: "macos",
472
+ pathGlob: "dist/binary/buildchain-aarch64-apple-darwin.tar.gz",
473
+ },
474
+ {
475
+ kind: "binary",
476
+ platform: "windows",
477
+ pathGlob: "dist/binary/buildchain-x86_64-pc-windows-msvc.zip",
478
+ },
479
+ ],
480
+ }, { surfaceId: "distribution:buildchain-standalone" }),
481
+ },
482
+ );
434
483
  const reverseEnumeratedSurfaces = uniqueById([
435
484
  ...reverseAudit.enumerated.cliCommands.map((entry) => surface(`cli:${entry.id}`, "cli-command", "bin/buildchain.mjs", {
436
485
  name: entry.usage,
@@ -463,8 +512,8 @@ export function createBuildchainKfdSurfaceRegistry({ root = process.cwd() } = {}
463
512
  siteConsumptionContracts,
464
513
  reverseEnumeratedSurfaces,
465
514
  },
466
- additionalSurfaces: controlSurfaces,
467
- publicSurfaceCount: docs.length + schemas.length + standardsMetadata.length + exports.length + siteConsumptionContracts.length + reverseEnumeratedSurfaces.length + controlSurfaces.length,
515
+ additionalSurfaces: [...controlSurfaces, standaloneDistribution],
516
+ publicSurfaceCount: docs.length + schemas.length + standardsMetadata.length + exports.length + siteConsumptionContracts.length + reverseEnumeratedSurfaces.length + controlSurfaces.length + 1,
468
517
  };
469
518
  }
470
519
 
@@ -2,6 +2,8 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
4
  export const BUILDCHAIN_DIR = ".buildchain";
5
+ export const BUILDCHAIN_VERSION_PIN_PATH = ".buildchain-version";
6
+ export const BUILDCHAIN_LAYOUT_DISCOVERY_CONTRACT = "kungfu-buildchain-layout-discovery";
5
7
  export const BUILDCHAIN_CONFIG_PATH = ".buildchain/buildchain.toml";
6
8
  export const LEGACY_BUILDCHAIN_CONFIG_PATH = "buildchain.toml";
7
9
  export const BUILDCHAIN_CONTRACT_LOCK_PATH = ".buildchain/contract-lock.json";
@@ -94,6 +96,50 @@ export function resolveReleasePassportPath(cwd = process.cwd()) {
94
96
  ]);
95
97
  }
96
98
 
99
+ export function createBuildchainLayoutDiscovery({
100
+ cwd = process.cwd(),
101
+ buildchainVersion = "",
102
+ } = {}) {
103
+ const resolvedCwd = path.resolve(cwd);
104
+ const configPath = resolveBuildchainConfigPath(resolvedCwd);
105
+ const kfd3RegistryPath = resolveKfd3SurfaceRegistryPath(resolvedCwd);
106
+ return {
107
+ schemaVersion: 1,
108
+ contract: BUILDCHAIN_LAYOUT_DISCOVERY_CONTRACT,
109
+ buildchain: {
110
+ version: String(buildchainVersion || ""),
111
+ versionPinPath: BUILDCHAIN_VERSION_PIN_PATH,
112
+ },
113
+ repository: {
114
+ root: resolvedCwd,
115
+ managed: fs.existsSync(repoPath(resolvedCwd, configPath))
116
+ || fs.existsSync(repoPath(resolvedCwd, BUILDCHAIN_VERSION_PIN_PATH))
117
+ || fs.existsSync(repoPath(resolvedCwd, kfd3RegistryPath)),
118
+ configPath,
119
+ },
120
+ kfd: {
121
+ root: BUILDCHAIN_KFD_ROOT,
122
+ registries: {
123
+ "kfd-3": {
124
+ standard: "kfd-3",
125
+ schemaVersion: 1,
126
+ path: kfd3RegistryPath,
127
+ canonicalPath: BUILDCHAIN_KFD3_SURFACE_REGISTRY_PATH,
128
+ exists: fs.existsSync(repoPath(resolvedCwd, kfd3RegistryPath)),
129
+ },
130
+ },
131
+ },
132
+ shifu: {
133
+ jurisdiction: {
134
+ source: "kfd-3-distribution-declaration",
135
+ registryStandard: "kfd-3",
136
+ field: "surfaces[].distribution.registrar",
137
+ value: "shifu",
138
+ },
139
+ },
140
+ };
141
+ }
142
+
97
143
  export function discoverBuildchainRepoFiles(cwd = process.cwd()) {
98
144
  const files = [
99
145
  {
@@ -292,6 +292,8 @@ export {
292
292
  BUILDCHAIN_CONFIG_PATH,
293
293
  BUILDCHAIN_CONTRACT_LOCK_PATH,
294
294
  BUILDCHAIN_DIR,
295
+ BUILDCHAIN_LAYOUT_DISCOVERY_CONTRACT,
296
+ BUILDCHAIN_VERSION_PIN_PATH,
295
297
  BUILDCHAIN_GENERATED_DIRS,
296
298
  BUILDCHAIN_KFD_ROOT,
297
299
  BUILDCHAIN_KFD1_CONTRACT_WORLD_WITNESS_PATH,
@@ -317,6 +319,7 @@ export {
317
319
  LEGACY_BUILDCHAIN_KFD3_SURFACE_REGISTRY_PATHS,
318
320
  LEGACY_BUILDCHAIN_RELEASE_PASSPORT_PATH,
319
321
  discoverBuildchainRepoFiles,
322
+ createBuildchainLayoutDiscovery,
320
323
  migrateBuildchainLayout,
321
324
  planBuildchainLayoutMigration,
322
325
  resolveBuildchainConfigPath,
@@ -359,6 +362,7 @@ export {
359
362
  auditKfd3Surfaces,
360
363
  createKfd3SurfaceWitness,
361
364
  detectKfd3Surfaces,
365
+ normalizeKfd3DistributionDeclaration,
362
366
  queryKfd3Capabilities,
363
367
  readKfd3SurfaceRegistry,
364
368
  registerKfd3Surfaces,
@@ -67,6 +67,63 @@ function normalizeKinds(kinds = []) {
67
67
  return [...new Set(selected.map(normalizeKind))].sort();
68
68
  }
69
69
 
70
+ export function normalizeKfd3DistributionDeclaration(distribution, { surfaceId = "surface" } = {}) {
71
+ if (!distribution || typeof distribution !== "object" || Array.isArray(distribution)) {
72
+ throw new Error(`KFD-3 surface ${surfaceId} distribution must be an object`);
73
+ }
74
+ const registrar = String(distribution.registrar || "").trim();
75
+ if (!registrar) {
76
+ throw new Error(`KFD-3 surface ${surfaceId} distribution.registrar is required`);
77
+ }
78
+ const tasks = Array.isArray(distribution.tasks)
79
+ ? [...new Set(distribution.tasks.map((entry) => String(entry || "").trim()).filter(Boolean))]
80
+ : [];
81
+ if (tasks.length === 0) {
82
+ throw new Error(`KFD-3 surface ${surfaceId} distribution.tasks must contain at least one task`);
83
+ }
84
+ const sourceArtifacts = Array.isArray(distribution.artifacts) ? distribution.artifacts : [];
85
+ if (sourceArtifacts.length === 0) {
86
+ throw new Error(`KFD-3 surface ${surfaceId} distribution.artifacts must contain at least one artifact`);
87
+ }
88
+ const artifacts = sourceArtifacts.map((artifact, index) => {
89
+ if (!artifact || typeof artifact !== "object" || Array.isArray(artifact)) {
90
+ throw new Error(`KFD-3 surface ${surfaceId} distribution.artifacts[${index}] must be an object`);
91
+ }
92
+ const normalized = {
93
+ ...artifact,
94
+ kind: String(artifact.kind || "").trim(),
95
+ platform: String(artifact.platform || "").trim(),
96
+ pathGlob: String(artifact.pathGlob || "").trim(),
97
+ };
98
+ for (const field of ["kind", "platform", "pathGlob"]) {
99
+ if (!normalized[field]) {
100
+ throw new Error(`KFD-3 surface ${surfaceId} distribution.artifacts[${index}].${field} is required`);
101
+ }
102
+ }
103
+ if (artifact.sha256 !== undefined) {
104
+ normalized.sha256 = String(artifact.sha256 || "").trim().toLowerCase();
105
+ if (!/^[a-f0-9]{64}$/.test(normalized.sha256)) {
106
+ throw new Error(`KFD-3 surface ${surfaceId} distribution.artifacts[${index}].sha256 must be 64 lowercase hex characters`);
107
+ }
108
+ }
109
+ return normalized;
110
+ });
111
+ return {
112
+ ...distribution,
113
+ registrar,
114
+ tasks,
115
+ artifacts,
116
+ };
117
+ }
118
+
119
+ function normalizeRegistrySurface(entry) {
120
+ if (!entry?.distribution) return entry;
121
+ return {
122
+ ...entry,
123
+ distribution: normalizeKfd3DistributionDeclaration(entry.distribution, { surfaceId: entry.id || "surface" }),
124
+ };
125
+ }
126
+
70
127
  function stableId(value) {
71
128
  return String(value || "")
72
129
  .toLowerCase()
@@ -366,7 +423,7 @@ export function readKfd3SurfaceRegistry({ cwd = process.cwd(), registryPath = ""
366
423
  contract: registry.contract || KFD3_SURFACE_REGISTRY_CONTRACT,
367
424
  product: registry.product || {},
368
425
  registryPath: registry.registryPath || effectiveRegistryPath,
369
- surfaces: Array.isArray(registry.surfaces) ? registry.surfaces : [],
426
+ surfaces: Array.isArray(registry.surfaces) ? registry.surfaces.map(normalizeRegistrySurface) : [],
370
427
  policy: registry.policy || {},
371
428
  };
372
429
  }
@@ -379,7 +436,7 @@ export function writeKfd3SurfaceRegistry({ cwd = process.cwd(), registryPath = "
379
436
  product: registry.product || {},
380
437
  registryPath: effectiveRegistryPath,
381
438
  surfaces: uniqueSurfaces(registry.surfaces || []).map((entry) => ({
382
- ...entry,
439
+ ...normalizeRegistrySurface(entry),
383
440
  state: entry.state || "declared",
384
441
  declaration: entry.declaration || {
385
442
  owner: "product",
@@ -176,9 +176,10 @@ function bundleCli({ cwd, tempDir, version, logger }) {
176
176
  sourcemap: false,
177
177
  dts: false,
178
178
  shims: true,
179
- noExternal: ["smol-toml"],
179
+ noExternal: ["smol-toml", /^@kungfu-tech\\/kfd(?:\\/|$)/],
180
180
  define: {
181
181
  "process.env.BUILDCHAIN_EMBEDDED_PACKAGE_VERSION": ${JSON.stringify(JSON.stringify(version || packageVersion(cwd)))},
182
+ "process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT": ${JSON.stringify(JSON.stringify("1"))},
182
183
  },
183
184
  };
184
185
  `);
@@ -683,6 +683,8 @@ for (const requiredSnippet of [
683
683
  "checkout-cache-mode",
684
684
  "BUILDCHAIN_CHECKOUT_CACHE_MIRROR_URL_TEMPLATE",
685
685
  "sourceCheckout",
686
+ "Shifu Cache Profile Passthrough",
687
+ "opaque reference and digest",
686
688
  ]) {
687
689
  if (!reusableBuildSurfaceDoc.includes(requiredSnippet)) {
688
690
  throw new Error(`reusable build surface doc missing contract lock snippet: ${requiredSnippet}`);
@@ -732,7 +734,8 @@ for (const requiredSnippet of [
732
734
  "../packages/core/logging.js",
733
735
  "standalone.cli-bundle.create",
734
736
  "BUILDCHAIN_EMBEDDED_PACKAGE_VERSION",
735
- "noExternal: [\"smol-toml\"]",
737
+ "BUILDCHAIN_EMBEDDED_ENTRYPOINT",
738
+ "noExternal: [\"smol-toml\",",
736
739
  "--macho-segment-name",
737
740
  "mainFormat: \"commonjs\"",
738
741
  "standalone.sea-blob.create",
@@ -395,6 +395,7 @@ function cliCommandMeta(id) {
395
395
  "inspect-artifact": { group: "release-passport-trust", purpose: "Inspect artifact evidence." },
396
396
  "inspect-release": { group: "release-passport-trust", purpose: "Inspect release passport evidence." },
397
397
  kfd: { group: "kfd-trust", purpose: "Inspect KFD standards, schemas, and versioned KFD command families." },
398
+ layout: { group: "kfd-trust", purpose: "Return the versioned repository-layout and KFD registry discovery contract for tools such as Shifu." },
398
399
  "kfd-schema": { group: "kfd-trust", purpose: "Inspect KFD schema command families." },
399
400
  "kfd-schema-list": { group: "kfd-trust", purpose: "List machine-readable schemas exposed by the KFD package standards metadata." },
400
401
  "kfd-schema-show": { group: "kfd-trust", purpose: "Print a machine-readable KFD schema from the KFD package standards metadata." },
@@ -479,7 +480,7 @@ function nodeApiMeta(exportName) {
479
480
  "./buildchain-contract": { group: "governance-versioning", summary: "Runtime contract world and compatibility digest APIs for floating-ref drift checks." },
480
481
  "./surface-manifest": { group: "site-and-propagation", summary: "Surface manifest timestamp and reproducibility policy APIs." },
481
482
  "./issue-reporting": { group: "observability-diagnostics", summary: "Buildchain-owned issue reporting API for workflow friction feedback." },
482
- "./buildchain-layout": { group: "kfd-trust", summary: "Canonical .buildchain repository layout constants, path resolution, and migration APIs." },
483
+ "./buildchain-layout": { group: "kfd-trust", summary: "Versioned Buildchain repository-layout discovery contract plus canonical .buildchain path resolution and migration APIs." },
483
484
  "./kfd": { group: "kfd-trust", summary: "Unified KFD standards, schema discovery, KFD-1/KFD-2/KFD-3 grouped APIs, KFD-4 schema discovery, upstream KFD aggregate facts, and Buildchain KFD claim helpers." },
484
485
  "./public-surface-audit": { group: "kfd-trust", summary: "Reverse audit APIs for CLI, workflow, action, site page, and documentation command surfaces." },
485
486
  "./kfd-gate": { group: "kfd-trust", summary: "KFD-1/KFD-2/KFD-3 release gate evidence and validation APIs." },
@@ -832,6 +833,7 @@ function buildSiteBundle() {
832
833
  ["buildchain-patrol-monthly", "repository-patrol"],
833
834
  ["stable-candidate-patrol", "repository-patrol"],
834
835
  ["buildchain-stable-candidate-patrol", "repository-patrol"],
836
+ ["buildchain-stable-candidate-qualification", "repository-patrol"],
835
837
  ["patrol-daily", "repository-patrol"],
836
838
  ["patrol-weekly", "repository-patrol"],
837
839
  ["patrol-monthly", "repository-patrol"],
@@ -840,6 +842,7 @@ function buildSiteBundle() {
840
842
  ["release-propagation", "preview"],
841
843
  ["candidate-lab", "repository-internal"],
842
844
  ["build-surface-fixture", "repository-internal"],
845
+ ["buildchain-stable-candidate-qualification", "repository-internal"],
843
846
  ["self-hosted-runner-smoke", "compatibility-fixture"],
844
847
  ]);
845
848
  return {
@@ -477,7 +477,7 @@ function readArg(name, fallback = "") {
477
477
  return process.argv[index + 1] || "";
478
478
  }
479
479
 
480
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
480
+ if (!process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT && process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
481
481
  try {
482
482
  const result = initBuildchainRepo({
483
483
  cwd: readArg("cwd", process.cwd()),
@@ -179,7 +179,13 @@ function hasCommit(targetPath, sha, timeoutMs) {
179
179
  }
180
180
 
181
181
  function checkoutFetchedCommit(targetPath, sha, timeoutMs) {
182
- git(["checkout", "--force", "--detach", sha], { cwd: targetPath, timeoutMs });
182
+ // The locked Git tree is also the byte-level source of release evidence.
183
+ // Override runner-global autocrlf only at checkout time so this also works
184
+ // when a container workspace uses an external Git metadata pointer.
185
+ git(["-c", "core.autocrlf=false", "-c", "core.eol=lf", "checkout", "--force", "--detach", sha], {
186
+ cwd: targetPath,
187
+ timeoutMs,
188
+ });
183
189
  }
184
190
 
185
191
  function retryableGitFetchError(error) {
@@ -194,6 +200,7 @@ export function fetchSourceCommit({
194
200
  remoteUrl,
195
201
  sha,
196
202
  fetchRef,
203
+ sourceTreeSha = "",
197
204
  timeoutMs,
198
205
  env = {},
199
206
  runGit = git,
@@ -216,10 +223,22 @@ export function fetchSourceCommit({
216
223
  cwd: targetPath,
217
224
  timeoutMs,
218
225
  env,
219
- stdio: "ignore",
220
226
  });
221
227
  if (containsCommit(targetPath, sha, timeoutMs)) {
222
- return { selector: "ref" };
228
+ return { selector: "ref", checkoutSha: sha };
229
+ }
230
+ if (/^refs\/pull\/\d+\/merge$/.test(fetchRef) && sourceTreeSha) {
231
+ const fetchedSha = runGit(["rev-parse", "refs/buildchain/source-ref^{commit}"], {
232
+ cwd: targetPath,
233
+ timeoutMs,
234
+ });
235
+ const fetchedTree = runGit(["rev-parse", "refs/buildchain/source-ref^{tree}"], {
236
+ cwd: targetPath,
237
+ timeoutMs,
238
+ });
239
+ if (fetchedTree === sourceTreeSha) {
240
+ return { selector: "ref-tree", checkoutSha: fetchedSha };
241
+ }
223
242
  }
224
243
  } catch (error) {
225
244
  // A retryable transport failure belongs to the bounded outer retry. Do
@@ -234,12 +253,11 @@ export function fetchSourceCommit({
234
253
  cwd: targetPath,
235
254
  timeoutMs,
236
255
  env,
237
- stdio: "ignore",
238
256
  });
239
257
  if (!containsCommit(targetPath, sha, timeoutMs)) {
240
258
  throw new Error(`fetched ${fetchRef || sha}, but ${sha} is not available`);
241
259
  }
242
- return { selector: "sha" };
260
+ return { selector: "sha", checkoutSha: sha };
243
261
  }
244
262
 
245
263
  export function runBoundedFetch({ attempts = 1, fetch, onAttempt = () => {}, onRetry = () => {}, shouldRetry = () => true }) {
@@ -266,18 +284,30 @@ function githubRemoteUrl({ repository, serverUrl = "https://github.com" }) {
266
284
  return `${base}/${repository}.git`;
267
285
  }
268
286
 
269
- function verifyCheckout({ targetPath, sourceSha, sourceTreeSha = "" }) {
287
+ function verifyCheckout({ targetPath, sourceSha, sourceTreeSha = "", fetchRef = "" }) {
270
288
  const head = git(["rev-parse", "HEAD"], { cwd: targetPath });
271
289
  const tree = git(["rev-parse", "HEAD^{tree}"], { cwd: targetPath });
272
290
  const headOk = head === sourceSha;
273
291
  const treeOk = !sourceTreeSha || tree === sourceTreeSha;
274
- if (!headOk) {
292
+ const pullMergeTreeEquivalent = !headOk
293
+ && /^refs\/pull\/\d+\/merge$/.test(fetchRef)
294
+ && Boolean(sourceTreeSha)
295
+ && treeOk;
296
+ if (!headOk && !pullMergeTreeEquivalent) {
275
297
  throw new Error(`locked source checkout head mismatch: expected ${sourceSha}, got ${head}`);
276
298
  }
277
299
  if (!treeOk) {
278
300
  throw new Error(`locked source checkout tree mismatch: expected ${sourceTreeSha}, got ${tree}`);
279
301
  }
280
- return { head, tree, headOk, treeOk };
302
+ return {
303
+ head,
304
+ expectedHead: sourceSha,
305
+ tree,
306
+ headOk,
307
+ treeOk,
308
+ identityOk: headOk || pullMergeTreeEquivalent,
309
+ identityMode: pullMergeTreeEquivalent ? "tree-equivalent-pull-merge" : "commit",
310
+ };
281
311
  }
282
312
 
283
313
  function writeEvidence(filePath, evidence) {
@@ -359,6 +389,7 @@ export function lockedSourceCheckout({
359
389
  durationMs: 0,
360
390
  };
361
391
  let checkoutError;
392
+ let checkoutSha = sha;
362
393
  if (normalizedMode !== "off") {
363
394
  try {
364
395
  if (renderedReferenceRepository) {
@@ -374,15 +405,17 @@ export function lockedSourceCheckout({
374
405
  checkoutFetchedCommit(targetPath, sha, timeoutMs);
375
406
  } else if (renderedMirrorUrl) {
376
407
  evidence.cache.transport = "mirror-url";
377
- fetchSourceCommit({
408
+ const fetchResult = fetchSourceCommit({
378
409
  targetPath,
379
410
  remoteName: "buildchain-cache",
380
411
  remoteUrl: renderedMirrorUrl,
381
412
  sha,
382
413
  fetchRef,
414
+ sourceTreeSha: treeSha,
383
415
  timeoutMs,
384
416
  });
385
- checkoutFetchedCommit(targetPath, sha, timeoutMs);
417
+ checkoutSha = fetchResult.checkoutSha || sha;
418
+ checkoutFetchedCommit(targetPath, checkoutSha, timeoutMs);
386
419
  } else {
387
420
  throw new Error("checkout cache is enabled but no mirror URL or reference repository template was provided");
388
421
  }
@@ -416,6 +449,7 @@ export function lockedSourceCheckout({
416
449
  remoteUrl,
417
450
  sha,
418
451
  fetchRef,
452
+ sourceTreeSha: treeSha,
419
453
  timeoutMs: githubTimeoutMs,
420
454
  env: githubAuthEnv(githubToken),
421
455
  }),
@@ -424,14 +458,20 @@ export function lockedSourceCheckout({
424
458
  shouldRetry: retryableGitFetchError,
425
459
  });
426
460
  evidence.cache.githubFetchAttempts = fetchResult.attempts;
461
+ checkoutSha = fetchResult.value.checkoutSha || sha;
427
462
  } catch (error) {
428
463
  evidence.durationMs = Date.now() - startedAt;
429
464
  writeEvidence(path.resolve(workspace, diagnosticsPath), evidence);
430
465
  throw error;
431
466
  }
432
- checkoutFetchedCommit(targetPath, sha, timeoutMs);
467
+ checkoutFetchedCommit(targetPath, checkoutSha, timeoutMs);
433
468
  }
434
- evidence.verification = verifyCheckout({ targetPath, sourceSha: sha, sourceTreeSha: treeSha });
469
+ evidence.verification = verifyCheckout({
470
+ targetPath,
471
+ sourceSha: sha,
472
+ sourceTreeSha: treeSha,
473
+ fetchRef,
474
+ });
435
475
  evidence.durationMs = Date.now() - startedAt;
436
476
  writeEvidence(path.resolve(workspace, diagnosticsPath), evidence);
437
477
  return evidence;
@@ -155,7 +155,7 @@ function usage() {
155
155
  `;
156
156
  }
157
157
 
158
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
158
+ if (!process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT && process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
159
159
  try {
160
160
  const argv = process.argv.slice(2);
161
161
  if (hasFlag(argv, "help") || hasFlag(argv, "h")) {
@@ -57,7 +57,7 @@ export function runPublicationArtifactCli(args = process.argv.slice(2)) {
57
57
  return result;
58
58
  }
59
59
 
60
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
60
+ if (!process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT && process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
61
61
  try {
62
62
  runPublicationArtifactCli();
63
63
  } catch (error) {
@@ -28,7 +28,7 @@ export function runPublicationPackageCli(args = process.argv.slice(2)) {
28
28
  return result;
29
29
  }
30
30
 
31
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
31
+ if (!process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT && process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
32
32
  try {
33
33
  runPublicationPackageCli();
34
34
  } catch (error) {
@@ -93,6 +93,6 @@ export function runReleasePropagationCli(argv = process.argv.slice(2)) {
93
93
  throw new Error(`unsupported release-propagation command: ${mode}`);
94
94
  }
95
95
 
96
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
96
+ if (!process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT && process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
97
97
  runReleasePropagationCli();
98
98
  }