@kungfu-tech/buildchain 4.0.2-alpha.37 → 4.0.2-alpha.39

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 (48) hide show
  1. package/AGENTS.md +1 -1
  2. package/architecture/agent-change-map.md +2 -2
  3. package/architecture/internal-capabilities.json +2 -2
  4. package/architecture/maintainability-debt.json +8 -8
  5. package/architecture/maintainability-policy.json +3 -3
  6. package/architecture/v4-cross-platform-adopter-qualification.json +1 -1
  7. package/architecture/v4-floating-consumer-policy.json +1 -1
  8. package/architecture/v4-next-development-parity.json +2 -2
  9. package/architecture/v4-release-topology.json +0 -1
  10. package/architecture/v4-stage-capsule-qualification.json +1 -1
  11. package/architecture/v4-tail-reseal-parity.json +8 -6
  12. package/architecture/v4-universal-workflow-train-admission.json +1 -1
  13. package/architecture/workflow-taxonomy.json +18 -15
  14. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  15. package/dist/site/buildchain-contract.json +6 -6
  16. package/dist/site/buildchain-site.json +67 -26
  17. package/dist/site/capability-registry.json +2 -2
  18. package/dist/site/kfd-claims.json +19 -73
  19. package/dist/site/kfd-upstream-aggregate.json +1 -1
  20. package/dist/site/manual-registry.json +5 -5
  21. package/dist/site/node-api-registry.json +30 -30
  22. package/dist/site/page-registry.json +58 -17
  23. package/dist/site/public-surface-audit.json +8 -67
  24. package/dist/site/publication-authority-registry.json +1 -16
  25. package/dist/site/publication-registry.json +4 -4
  26. package/dist/site/site-manifest.json +9 -9
  27. package/dist/site/workflow-registry.json +5 -125
  28. package/docs/kfd-support.md +26 -2
  29. package/docs/next-development-transition.md +1 -1
  30. package/docs/node-api-reference.md +32 -32
  31. package/docs/release-passport.md +27 -2
  32. package/docs/v4-adopter-delivery.md +1 -1
  33. package/docs/v4-stage-capsule.md +1 -1
  34. package/docs/v4-tail-reseal.md +1 -1
  35. package/docs/workflow-catalog.md +5 -3
  36. package/docs/workflow-path-migration.md +109 -0
  37. package/package.json +1 -1
  38. package/packages/core/artifact-verification-envelope.js +5 -5
  39. package/packages/core/kfd-adopter-manifest.js +46 -47
  40. package/packages/core/next-development-projection.js +1 -1
  41. package/packages/core/release-passport-contract.js +5 -5
  42. package/packages/core/release-passport.js +5 -4
  43. package/scripts/check-v4-floating-consumer-policy-contract.mjs +1 -1
  44. package/scripts/check-v4-public-dogfood-contract.mjs +4 -4
  45. package/scripts/check-v4-universal-workflow-bootstrap.mjs +3 -3
  46. package/scripts/generate-v4-universal-workflow-facades.mjs +4 -3
  47. package/scripts/v4-universal-workflow-engine.mjs +2 -1
  48. package/scripts/workflow-taxonomy.mjs +50 -11
@@ -0,0 +1,109 @@
1
+ ---
2
+ status: draft
3
+ period: ongoing
4
+ theme: workflow-path-migration
5
+ doc_type: migration-guide
6
+ source_level: local-files
7
+ confidence: high
8
+ sensitivity: public
9
+ evidence_grade: B
10
+ review_state: unreviewed
11
+ last_reviewed: 2026-09-06
12
+ ai_provenance:
13
+ model_family: GPT-6
14
+ product: Codex
15
+ generated_at: 2026-09-05
16
+ visible_context: Exact GitHub channel refs and workflow files on 23 organization default branches
17
+ invisible_context_boundary: Other branches, private or unlisted consumers and future floating ref updates were not established by this audit
18
+ ---
19
+
20
+ # Version-prefixed workflow path migration
21
+
22
+ This source change retires three filenames. Their reusable capabilities, inputs,
23
+ outputs, jobs and permission envelopes remain at the canonical paths below.
24
+ Keep the existing channel selector and dual contract-lock policy when migrating.
25
+
26
+ | Retired path | Canonical path |
27
+ | --- | --- |
28
+ | `v4-adopter-delivery.yml` | `public-build-adopter-qualification.yml` |
29
+ | `v4-stage-capsule-canary.yml` | `public-build-stage-capsule-canary.yml` |
30
+ | `v4-tail-reseal.yml` | `public-ops-tail-reseal.yml` |
31
+
32
+ All paths are beneath `.github/workflows/`. The taxonomy gate rejects version
33
+ segments in filenames and calls to version-prefixed Buildchain workflows. An
34
+ explicit retirement record keeps the original migration identity for historical
35
+ metrics and frozen facade source; generators resolve that identity to the
36
+ canonical implementation and cannot recreate the removed file.
37
+
38
+ ## Development merge and channel rollout
39
+
40
+ The development change retires these filenames on `dev/v4/v4.0`, updates the
41
+ Buildchain-owned callers, and enforces the canonical paths through generators
42
+ and required checks. Its delivery boundary is the protected development merge.
43
+ External-repository migration and stable publication are separate follow-up
44
+ work and do not block this development merge.
45
+
46
+ The published channel boundary still requires the following rollout order:
47
+
48
+ 1. Publish and verify the canonical entries on every affected channel while the
49
+ old entries remain available. The admission identity must match the actual
50
+ invoked canonical workflow, rather than its previous filename.
51
+ 2. Migrate consumers on their existing floating channels, refresh locks through
52
+ the supported contract acceptance path if required, and verify real consumer
53
+ runs. Stable callers remain on stable; alpha callers remain on alpha.
54
+ 3. Before publishing the retirement into a channel, recheck its live consumers
55
+ and resolve any remaining old-path calls. A protected development merge does
56
+ not by itself establish this channel readiness.
57
+
58
+ ## Initial consumer readback
59
+
60
+ On 2026-09-05, all workflow files from 23 accessible `kungfu-systems` repository
61
+ _default branches_ were read at their returned Git revisions. Five calls were
62
+ found in three repositories. This is a bounded inventory, not evidence that
63
+ unknown or non-default-branch consumers do not exist.
64
+
65
+ | Repository and observed revision | Caller | Existing selector |
66
+ | --- | --- | --- |
67
+ | Buildchain `d434a73d2d4fb010ee80fede2bf3ce872b1d04c8` | `self-build-adopter-dogfood.yml` | `v4-alpha` |
68
+ | Buildchain `d434a73d2d4fb010ee80fede2bf3ce872b1d04c8` | `self-build-public-consumer-dogfood.yml` | `v4-alpha` |
69
+ | agent-hub-demo `3f5de882b63464d70c50df0fd7ca418f487b794b` | `v4-adopter-delivery-qualification.yml` | `v4-alpha` |
70
+ | agent-hub-demo `3f5de882b63464d70c50df0fd7ca418f487b794b` | `v4-stage-capsule-canary.yml` | `v4` |
71
+ | taolu `6dcccdc3bd2c8b1801c7f6decea08ae7877aac2b` | `verify.yml` | `v4-alpha` |
72
+
73
+ The initial published channel readback was `v4` at
74
+ `da5e2db8384313e899eace87aea0a1a8e28c0aa8` and `v4-alpha` at
75
+ `dd87f43f7122b41c4ce7f1f6d1888521cc7f892f`. Neither contained the three canonical
76
+ filenames at that initial readback. The later evidence below records progress
77
+ without changing the scope or revisions of that initial inventory.
78
+
79
+ ## Rollout evidence as of 2026-09-06
80
+
81
+ The canonical invocation-identity preparation was merged in
82
+ [Buildchain PR 3508](https://github.com/kungfu-systems/buildchain/pull/3508).
83
+ All three canonical paths are available in the immutable `v4.0.2-alpha.35`
84
+ release (`feab078f5d7c9927a3525154003e321700e8ca30`) and the later
85
+ `v4.0.2-alpha.37` release (`f5fa1a4a3e8d83eccee79c077e5f48417760c9df`).
86
+ The floating `v4-alpha` readback selects alpha.37.
87
+
88
+ [Taolu PR 30](https://github.com/kungfu-systems/taolu/pull/30) migrated its
89
+ Stage Capsule call on `@v4-alpha`, preserving the existing dual locks.
90
+ Its exact source `b3f4c12578d733ff44165ff1acafe131251b8907` passed
91
+ [consumer Verify](https://github.com/kungfu-systems/taolu/actions/runs/33975786413)
92
+ on all declared platforms and
93
+ [native delivery](https://github.com/kungfu-systems/taolu/actions/runs/34005576362).
94
+ The protected dev landing is `7e15123733110ad8ce484241934bbf0ef99fbb45`.
95
+
96
+ The stable `v4` readback remains
97
+ `da5e2db8384313e899eace87aea0a1a8e28c0aa8`, which does not contain the canonical
98
+ paths. Stable publication and the agent-hub-demo migration remain **pending**
99
+ outside this development change's completion boundary. The alpha.35
100
+ [Build Surface Fixture](https://github.com/kungfu-systems/buildchain/actions/runs/34005360217)
101
+ passed, but this individual result does not establish complete stable
102
+ qualification or authorize moving the stable channel.
103
+
104
+ ## Recovery
105
+
106
+ Before channel retirement, revert consumer changes to the still-available old
107
+ paths if verification fails. After retirement, restore the exact previous
108
+ provider implementation through a reviewed corrective commit and normal
109
+ publication; do not rewrite immutable release tags or bypass admission checks.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kungfu-tech/buildchain",
3
- "version": "4.0.2-alpha.37",
3
+ "version": "4.0.2-alpha.39",
4
4
  "private": false,
5
5
  "description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
6
6
  "repository": "https://github.com/kungfu-systems/buildchain",
@@ -417,7 +417,7 @@ function kfdBindingBody({ manifest, manifestGate, legacyProjection, paths }) {
417
417
  gatePath: paths.gate,
418
418
  gateRoot: manifestGate.gateRoot,
419
419
  },
420
- source: jsonClone(manifestGate.source),
420
+ adopter: jsonClone(manifestGate.adopter), source: jsonClone(manifestGate.source),
421
421
  standardPackage: jsonClone(manifestGate.standardPackage),
422
422
  witness: {
423
423
  decisionRoot: manifestGate.decisionWitness.root,
@@ -445,10 +445,10 @@ export function createKfdAdopterReleaseBinding({
445
445
  manifestPath = "kfd-adopter-manifest.json",
446
446
  gatePath = "kfd-adopter-manifest-gate.json",
447
447
  legacyProjectionPath = "kfd-support.json",
448
- expectedSourceSha = "",
448
+ expectedAdopterId = "kungfu-systems/buildchain", expectedSourceRepository = "", expectedSourceSha = "",
449
449
  } = {}) {
450
450
  const gate = validateKfdAdopterManifestGate(manifestGate, {
451
- expectedSourceSha,
451
+ expectedAdopterId, expectedSourceRepository, expectedSourceSha,
452
452
  checkedAt: manifestGate?.checkedAt,
453
453
  });
454
454
  const legacy = validateKfdLegacySupportMatrixProjection(legacyProjection, { manifest, manifestGate });
@@ -468,7 +468,7 @@ export function validateKfdAdopterReleaseBinding(binding, {
468
468
  manifest,
469
469
  manifestGate,
470
470
  legacyProjection,
471
- expectedSourceSha = "",
471
+ expectedAdopterId = "kungfu-systems/buildchain", expectedSourceRepository = "", expectedSourceSha = "",
472
472
  } = {}) {
473
473
  const issues = [];
474
474
  if (!binding || binding.schemaVersion !== 1 || binding.contract !== KFD_ADOPTER_RELEASE_BINDING_CONTRACT) {
@@ -488,7 +488,7 @@ export function validateKfdAdopterReleaseBinding(binding, {
488
488
  manifestPath: binding.authority?.manifestPath,
489
489
  gatePath: binding.authority?.gatePath,
490
490
  legacyProjectionPath: binding.legacyProjection?.path,
491
- expectedSourceSha,
491
+ expectedAdopterId, expectedSourceRepository, expectedSourceSha,
492
492
  });
493
493
  if (stableJson(binding) !== stableJson(expected)) {
494
494
  issues.push({ code: "adopter-release-binding-drift", path: "", message: "release binding differs from the exact manifest, gate, or legacy projection closure" });
@@ -21,7 +21,7 @@ export const KFD_LEGACY_SUPPORT_MATRIX_CONTRACT = "kungfu-kfd-support-matrix";
21
21
 
22
22
  const BUILDCHAIN_ADOPTER_ID = "kungfu-systems/buildchain";
23
23
  const ROOT_PATTERN = /^sha256:[0-9a-f]{64}$/;
24
- const SHA_PATTERN = /^[0-9a-f]{40}$/;
24
+ const SHA_PATTERN = /^[0-9a-f]{40}$/, SOURCE_REPOSITORY_PATTERN = /^[^@\s]+$/;
25
25
  const REQUIRED_DECISIONS = Object.freeze(["KFD-1", "KFD-2", "KFD-3", "KFD-4", "KFD-5", "KFD-7"]);
26
26
  const PRODUCT_GATE_STANDARDS = Object.freeze(["kfd-4", "kfd-5", "kfd-7"]);
27
27
  const PRODUCT_GATE_SET = new Set(PRODUCT_GATE_STANDARDS);
@@ -77,29 +77,27 @@ function verifyPublishedManifest(manifest, packageOptions, issues) {
77
77
  }
78
78
  }
79
79
 
80
- function manifestSourceSha(manifest) {
81
- const match = /^kungfu-systems\/buildchain@([0-9a-f]{40})$/.exec(text(manifest?.adopter?.artifact?.coordinate));
82
- return match?.[1] || "";
83
- }
80
+ function adopterExpectation({ expectedAdopterId = BUILDCHAIN_ADOPTER_ID, expectedSourceRepository = "" } = {}) { const adopterId = text(expectedAdopterId) || BUILDCHAIN_ADOPTER_ID; return { adopterId, sourceRepository: text(expectedSourceRepository) || adopterId }; }
84
81
 
85
- function validateManifestIdentity(manifest, packageArtifactRoot, expectedSourceSha, issues) {
86
- if (manifest?.adopter?.id !== BUILDCHAIN_ADOPTER_ID) {
87
- issues.push(issue("adopter-identity", "manifest.adopter.id", `manifest adopter must be ${BUILDCHAIN_ADOPTER_ID}`));
88
- }
82
+ function manifestSource(manifest) { const coordinate = text(manifest?.adopter?.artifact?.coordinate), match = /^([^@\s]+)@([0-9a-f]{40})$/.exec(coordinate); return { coordinate, repository: match?.[1] || "", sha: match?.[2] || "" }; }
83
+
84
+ function validateManifestIdentity(manifest, packageArtifactRoot, expectation, expectedSourceSha, issues) {
85
+ if (manifest?.adopter?.id !== expectation.adopterId) issues.push(issue("adopter-identity", "manifest.adopter.id", `manifest adopter must be ${expectation.adopterId}`));
89
86
  const pinned = manifest?.kfdCut?.package;
90
87
  if (pinned?.name !== kfdPackageJson.name
91
88
  || pinned?.version !== kfdPackageJson.version
92
89
  || pinned?.artifactRoot !== packageArtifactRoot) {
93
90
  issues.push(issue("adopter-package-cut", "manifest.kfdCut.package", "manifest must bind the exact installed KFD package cut"));
94
91
  }
95
- const sourceSha = manifestSourceSha(manifest);
96
- if (manifest?.adopter?.artifact?.kind !== "git-commit" || !sourceSha
97
- || !ROOT_PATTERN.test(text(manifest?.adopter?.artifact?.root))) {
98
- issues.push(issue("adopter-source", "manifest.adopter.artifact", "Buildchain adopter authority must bind one exact git commit and artifact root"));
99
- } else if (expectedSourceSha && sourceSha !== expectedSourceSha) {
92
+ const source = manifestSource(manifest);
93
+ if (!SOURCE_REPOSITORY_PATTERN.test(expectation.sourceRepository) || manifest?.adopter?.artifact?.kind !== "git-commit" || !source.sha || !ROOT_PATTERN.test(text(manifest?.adopter?.artifact?.root))) {
94
+ issues.push(issue("adopter-source", "manifest.adopter.artifact", "adopter authority must bind one exact repository, git commit, and artifact root"));
95
+ } else if (source.repository !== expectation.sourceRepository) {
96
+ issues.push(issue("adopter-source", "manifest.adopter.artifact.coordinate", `manifest source repository must be ${expectation.sourceRepository}`));
97
+ } else if (expectedSourceSha && source.sha !== expectedSourceSha) {
100
98
  issues.push(issue("adopter-source", "manifest.adopter.artifact.coordinate", "manifest source does not match the requested release source"));
101
99
  }
102
- return sourceSha;
100
+ return source;
103
101
  }
104
102
 
105
103
  function validateRequiredDecisions(decisions, issues) {
@@ -131,7 +129,7 @@ function collectProductGates(gateResults, decisions, verificationCut, issues) {
131
129
  for (const [index, gate] of gateResults.entries()) {
132
130
  const validation = validateKfdProductGateResult(gate, verificationCut);
133
131
  if (!validation.valid) {
134
- issues.push(issue("adopter-gate-invalid", `gateResults[${index}]`, "Buildchain product gate is invalid", {
132
+ issues.push(issue("adopter-gate-invalid", `gateResults[${index}]`, "adopter product gate is invalid", {
135
133
  gateIssues: validation.issues,
136
134
  }));
137
135
  }
@@ -141,15 +139,16 @@ function collectProductGates(gateResults, decisions, verificationCut, issues) {
141
139
  if (gates.has(gate?.standard)) {
142
140
  issues.push(issue("adopter-gate-duplicate", `gateResults[${index}].standard`, `${gate.standard} gate is duplicated`));
143
141
  }
142
+ if (gate?.source?.repository !== verificationCut.expectedSourceRepository) issues.push(issue("adopter-gate-source", `gateResults[${index}].source.repository`, "product gate repository must match the adopter source repository"));
144
143
  gates.set(gate?.standard, gate);
145
144
  }
146
145
  for (const standard of PRODUCT_GATE_STANDARDS) {
147
146
  const gate = gates.get(standard);
148
147
  const id = standard.toUpperCase();
149
148
  if (!gate) {
150
- issues.push(issue("adopter-gate-missing", `gateResults.${standard}`, `${id} requires its existing Buildchain product gate`));
149
+ issues.push(issue("adopter-gate-missing", `gateResults.${standard}`, `${id} requires its exact adopter product gate`));
151
150
  } else if (!(decisions.get(id)?.verificationEvidence || []).some((entry) => entry?.root === gate.gateRoot)) {
152
- issues.push(issue("adopter-gate-unbound", `manifest.decisions.${id}.verificationEvidence`, `${id} must bind the exact Buildchain gate root`));
151
+ issues.push(issue("adopter-gate-unbound", `manifest.decisions.${id}.verificationEvidence`, `${id} must bind the exact adopter gate root`));
153
152
  }
154
153
  }
155
154
  return gates;
@@ -159,12 +158,12 @@ function projectedProductGates(gates) {
159
158
  return PRODUCT_GATE_STANDARDS.map((standard) => {
160
159
  const gate = gates.get(standard);
161
160
  return gate
162
- ? { standard, sourceSha: gate.source.sha, gateRoot: gate.gateRoot, status: gate.status }
163
- : { standard, sourceSha: "", gateRoot: "", status: "missing" };
161
+ ? { standard, sourceRepository: gate.source.repository, sourceSha: gate.source.sha, gateRoot: gate.gateRoot, status: gate.status }
162
+ : { standard, sourceRepository: "", sourceSha: "", gateRoot: "", status: "missing" };
164
163
  });
165
164
  }
166
165
 
167
- function createGateDocument({ manifest, bundle, gates, authorityPath, packageArtifactRoot, sourceSha, checkedAt, maxAgeSeconds, issues }) {
166
+ function createGateDocument({ manifest, bundle, gates, authorityPath, packageArtifactRoot, source, checkedAt, maxAgeSeconds, issues }) {
168
167
  const gate = {
169
168
  schemaVersion: 1,
170
169
  contract: KFD_ADOPTER_MANIFEST_GATE_CONTRACT,
@@ -174,10 +173,8 @@ function createGateDocument({ manifest, bundle, gates, authorityPath, packageArt
174
173
  contract: text(manifest?.contract),
175
174
  manifestRoot: bundle?.roots?.manifestRoot || "",
176
175
  },
177
- source: {
178
- sha: sourceSha,
179
- artifactRoot: text(manifest?.adopter?.artifact?.root),
180
- },
176
+ adopter: { id: text(manifest?.adopter?.id) },
177
+ source: { repository: source.repository, coordinate: source.coordinate, sha: source.sha, artifactRoot: text(manifest?.adopter?.artifact?.root) },
181
178
  verificationCut: { checkedAt, maxAgeSeconds },
182
179
  standardPackage: {
183
180
  name: kfdPackageJson.name,
@@ -211,28 +208,28 @@ export function createKfdAdopterManifestGate({
211
208
  packageArtifactRoot = "",
212
209
  gateResults = [],
213
210
  authorityPath = ".buildchain/kfd/adopter-manifest.json",
214
- expectedSourceSha = "",
211
+ expectedAdopterId = BUILDCHAIN_ADOPTER_ID, expectedSourceRepository = "", expectedSourceSha = "",
215
212
  checkedAt = new Date().toISOString(),
216
213
  maxAgeSeconds = 86400,
217
214
  } = {}) {
218
- const issues = [];
215
+ const issues = [], expectation = adopterExpectation({ expectedAdopterId, expectedSourceRepository });
219
216
  const bundle = verifyPublishedManifest(manifest, {
220
217
  packageArtifactRoot,
221
218
  verifiedAt: checkedAt,
222
219
  maxAgeSeconds,
223
220
  }, issues);
224
221
  const decisions = new Map((manifest?.decisions || []).map((row) => [row.id, row]));
225
- let sourceSha = manifestSourceSha(manifest);
222
+ let source = manifestSource(manifest);
226
223
  if (bundle) {
227
- sourceSha = validateManifestIdentity(manifest, packageArtifactRoot, expectedSourceSha, issues);
224
+ source = validateManifestIdentity(manifest, packageArtifactRoot, expectation, expectedSourceSha, issues);
228
225
  validateRequiredDecisions(decisions, issues);
229
226
  validateWarrantWitness(decisions, issues);
230
227
  }
231
- const gates = collectProductGates(gateResults, decisions, { expectedSourceSha: sourceSha || expectedSourceSha, checkedAt }, issues);
232
- return createGateDocument({ manifest, bundle, gates, authorityPath, packageArtifactRoot, sourceSha, checkedAt, maxAgeSeconds, issues });
228
+ const gates = collectProductGates(gateResults, decisions, { expectedSourceRepository: expectation.sourceRepository, expectedSourceSha: source.sha || expectedSourceSha, checkedAt }, issues);
229
+ return createGateDocument({ manifest, bundle, gates, authorityPath, packageArtifactRoot, source, checkedAt, maxAgeSeconds, issues });
233
230
  }
234
231
 
235
- function validateGateDocument(gate, issues) {
232
+ function validateGateDocument(gate, expectation, issues) {
236
233
  const copy = structuredClone(gate);
237
234
  const root = copy.gateRoot;
238
235
  delete copy.gateRoot;
@@ -244,12 +241,13 @@ function validateGateDocument(gate, issues) {
244
241
  || !ROOT_PATTERN.test(text(gate?.authority?.manifestRoot))) {
245
242
  issues.push(issue("adopter-gate-authority", "authority", "adopter gate must bind the standard manifest contract and root"));
246
243
  }
244
+ if (gate?.adopter?.id !== expectation.adopterId) issues.push(issue("adopter-gate-identity", "adopter.id", `adopter gate must bind ${expectation.adopterId}`));
247
245
  if (gate?.standardPackage?.name !== kfdPackageJson.name || gate?.standardPackage?.version !== kfdPackageJson.version
248
246
  || ![gate?.standardPackage?.artifactRoot, gate?.standardPackage?.registryRoot, gate?.standardPackage?.verifierSetRoot].every((value) => ROOT_PATTERN.test(text(value)))) {
249
247
  issues.push(issue("adopter-gate-package", "standardPackage", "adopter gate uses stale KFD package metadata"));
250
248
  }
251
- if (!SHA_PATTERN.test(text(gate?.source?.sha)) || !ROOT_PATTERN.test(text(gate?.source?.artifactRoot))) {
252
- issues.push(issue("adopter-gate-source", "source", "adopter gate must bind one exact Buildchain source commit and artifact root"));
249
+ if (gate?.source?.repository !== expectation.sourceRepository || gate?.source?.coordinate !== `${expectation.sourceRepository}@${gate?.source?.sha || ""}` || !SHA_PATTERN.test(text(gate?.source?.sha)) || !ROOT_PATTERN.test(text(gate?.source?.artifactRoot))) {
250
+ issues.push(issue("adopter-gate-source", "source", "adopter gate must bind the expected repository, exact source commit, coordinate, and artifact root"));
253
251
  }
254
252
  if (gate?.decisionWitness?.rootAlgorithm !== "sha256-buildchain-stable-json-v1"
255
253
  || !ROOT_PATTERN.test(text(gate?.decisionWitness?.root))
@@ -259,7 +257,7 @@ function validateGateDocument(gate, issues) {
259
257
  }
260
258
  }
261
259
 
262
- function validateProjectedGates(gate, expectedSourceSha, issues) {
260
+ function validateProjectedGates(gate, expectation, expectedSourceSha, issues) {
263
261
  const seenStandards = new Set();
264
262
  for (const [index, productGate] of (gate.gateResults || []).entries()) {
265
263
  if (!PRODUCT_GATE_SET.has(productGate?.standard) || productGate?.status !== "passed" || !ROOT_PATTERN.test(text(productGate?.gateRoot))) {
@@ -267,7 +265,7 @@ function validateProjectedGates(gate, expectedSourceSha, issues) {
267
265
  }
268
266
  if (seenStandards.has(productGate?.standard)) issues.push(issue("adopter-gate-result-set", `gateResults[${index}]`, "product gate standards must be unique"));
269
267
  seenStandards.add(productGate?.standard);
270
- if (productGate?.sourceSha !== gate?.source?.sha || (expectedSourceSha && productGate?.sourceSha !== expectedSourceSha)) {
268
+ if (productGate?.sourceRepository !== gate?.source?.repository || productGate?.sourceRepository !== expectation.sourceRepository || productGate?.sourceSha !== gate?.source?.sha || (expectedSourceSha && productGate?.sourceSha !== expectedSourceSha)) {
271
269
  issues.push(issue("adopter-gate-source", `gateResults[${index}].sourceSha`, "product gate source must match the release source"));
272
270
  }
273
271
  }
@@ -277,15 +275,15 @@ function validateProjectedGates(gate, expectedSourceSha, issues) {
277
275
  }
278
276
 
279
277
  export function validateKfdAdopterManifestGate(gate, {
280
- expectedSourceSha = "",
278
+ expectedAdopterId = BUILDCHAIN_ADOPTER_ID, expectedSourceRepository = "", expectedSourceSha = "",
281
279
  checkedAt = gate?.checkedAt || new Date().toISOString(),
282
280
  } = {}) {
283
- const issues = [];
281
+ const issues = [], expectation = adopterExpectation({ expectedAdopterId, expectedSourceRepository });
284
282
  if (!gate || gate.schemaVersion !== 1 || gate.contract !== KFD_ADOPTER_MANIFEST_GATE_CONTRACT) {
285
283
  return { valid: false, issues: [issue("adopter-gate-contract", "", `gate must use ${KFD_ADOPTER_MANIFEST_GATE_CONTRACT} v1`)] };
286
284
  }
287
- validateGateDocument(gate, issues);
288
- validateProjectedGates(gate, expectedSourceSha, issues);
285
+ validateGateDocument(gate, expectation, issues);
286
+ validateProjectedGates(gate, expectation, expectedSourceSha, issues);
289
287
  if (gate.checkedAt !== checkedAt || gate?.verificationCut?.checkedAt !== checkedAt || !Number.isFinite(Date.parse(checkedAt))
290
288
  || !Number.isSafeInteger(gate?.verificationCut?.maxAgeSeconds) || gate.verificationCut.maxAgeSeconds < 0) {
291
289
  issues.push(issue("adopter-gate-time", "verificationCut", "adopter gate verification cut does not match the requested cut"));
@@ -300,7 +298,7 @@ function legacyStatus(row) {
300
298
  return row.state;
301
299
  }
302
300
 
303
- function legacyRow(row, gates) {
301
+ function legacyRow(row, gates, adopterId) {
304
302
  const key = row.id.toLowerCase();
305
303
  const productGate = gates.get(key);
306
304
  return {
@@ -318,7 +316,7 @@ function legacyRow(row, gates) {
318
316
  releaseQualification: { shippedSupport: false },
319
317
  claimClass: "standard-adopter-manifest-projection",
320
318
  knownLimitations: [...row.gaps],
321
- owner: BUILDCHAIN_ADOPTER_ID,
319
+ owner: adopterId,
322
320
  nextGate: "Release Passport artifact binding and independent release decision",
323
321
  declaration: { state: row.state, usage: row.usage, root: kfdProductGateDigest(decisionWitnessRow(row)) },
324
322
  };
@@ -326,7 +324,7 @@ function legacyRow(row, gates) {
326
324
 
327
325
  export function createKfdLegacySupportMatrixProjection({ manifest, manifestGate } = {}) {
328
326
  const validation = validateKfdAdopterManifestGate(manifestGate, {
329
- expectedSourceSha: manifestGate?.source?.sha,
327
+ expectedAdopterId: manifestGate?.adopter?.id, expectedSourceRepository: manifestGate?.source?.repository, expectedSourceSha: manifestGate?.source?.sha,
330
328
  checkedAt: manifestGate?.checkedAt,
331
329
  });
332
330
  if (!validation.valid || manifest?.contract !== "kfd.adopter-conformance-manifest/v1") {
@@ -338,9 +336,9 @@ export function createKfdLegacySupportMatrixProjection({ manifest, manifestGate
338
336
  verifiedAt: manifestGate.verificationCut.checkedAt,
339
337
  maxAgeSeconds: manifestGate.verificationCut.maxAgeSeconds,
340
338
  }, authorityIssues);
341
- const sourceSha = validateManifestIdentity(manifest, manifestGate.standardPackage.artifactRoot, manifestGate.source.sha, authorityIssues);
339
+ const source = validateManifestIdentity(manifest, manifestGate.standardPackage.artifactRoot, { adopterId: manifestGate.adopter.id, sourceRepository: manifestGate.source.repository }, manifestGate.source.sha, authorityIssues);
342
340
  const decisionRoot = kfdProductGateDigest(manifest.decisions.map(decisionWitnessRow));
343
- if (!bundle || sourceSha !== manifestGate.source.sha || manifest.adopter.artifact.root !== manifestGate.source.artifactRoot
341
+ if (!bundle || source.sha !== manifestGate.source.sha || source.repository !== manifestGate.source.repository || source.coordinate !== manifestGate.source.coordinate || manifest.adopter.artifact.root !== manifestGate.source.artifactRoot
344
342
  || bundle.roots.manifestRoot !== manifestGate.authority.manifestRoot
345
343
  || bundle.roots.verificationReportRoot !== manifestGate.manifestVerificationReportRoot
346
344
  || bundle.bundleRoot !== manifestGate.manifestBundleRoot
@@ -364,7 +362,8 @@ export function createKfdLegacySupportMatrixProjection({ manifest, manifestGate
364
362
  path: manifestGate.authority.path,
365
363
  contract: manifest.contract,
366
364
  root: manifestGate.authority.manifestRoot,
367
- gateRoot: manifestGate.gateRoot,
365
+ gateRoot: manifestGate.gateRoot, adopterId: manifestGate.adopter.id,
366
+ sourceRepository: manifestGate.source.repository, sourceSha: manifestGate.source.sha,
368
367
  },
369
368
  upstream: {
370
369
  package: manifestGate.standardPackage.name,
@@ -374,7 +373,7 @@ export function createKfdLegacySupportMatrixProjection({ manifest, manifestGate
374
373
  verifierSetRoot: manifestGate.standardPackage.verifierSetRoot,
375
374
  standardsSha256: standardsFileDigest(),
376
375
  },
377
- rows: manifest.decisions.map((row) => legacyRow(row, gates)),
376
+ rows: manifest.decisions.map((row) => legacyRow(row, gates, manifestGate.adopter.id)),
378
377
  };
379
378
  }
380
379
 
@@ -244,7 +244,7 @@ invalid.
244
244
  ## Public self-dogfood and adoption
245
245
 
246
246
  \`.github/workflows/self-build-public-consumer-dogfood.yml\` remains the exact thin
247
- consumer of the public \`v4-stage-capsule-canary.yml@v4-alpha\` contract. Its
247
+ consumer of the public \`public-build-stage-capsule-canary.yml@v4-alpha\` contract. Its
248
248
  declared \`lifecycle.verify\` runs \`pnpm run check\`, so the same public path
249
249
  executes pure recovery tests that inject a transient durable-state write
250
250
  failure, restore the adapter operation on a fresh runner, supersede stale work
@@ -405,7 +405,7 @@ export function prepareReleasePassportKfdSections({ kfd1, kfd2Claims, kfd3, kfdA
405
405
  };
406
406
  }
407
407
 
408
- export function collectKfdAdopterReleaseEvidence({ manifest, gateResults = [], comparisonMatrix, sourceSha = "", checkedAt } = {}) {
408
+ export function collectKfdAdopterReleaseEvidence({ manifest, gateResults = [], comparisonMatrix, expectedAdopterId = "kungfu-systems/buildchain", expectedSourceRepository = "", sourceSha = "", checkedAt } = {}) {
409
409
  if (!manifest) {
410
410
  if (comparisonMatrix || gateResults.length > 0) {
411
411
  throw new Error("KFD support and product-gate inputs require --kfd-adopter-manifest-json; --kfd-support-matrix-json is comparison-only");
@@ -414,7 +414,7 @@ export function collectKfdAdopterReleaseEvidence({ manifest, gateResults = [], c
414
414
  }
415
415
  const manifestGate = createKfdAdopterManifestGate({
416
416
  manifest, packageArtifactRoot: installedKfdPackageArtifactRoot(), gateResults,
417
- expectedSourceSha: sourceSha, checkedAt,
417
+ expectedAdopterId, expectedSourceRepository, expectedSourceSha: sourceSha, checkedAt,
418
418
  });
419
419
  const legacyProjection = createKfdLegacySupportMatrixProjection({ manifest, manifestGate });
420
420
  if (comparisonMatrix) {
@@ -425,18 +425,18 @@ export function collectKfdAdopterReleaseEvidence({ manifest, gateResults = [], c
425
425
  }
426
426
  return {
427
427
  manifest, manifestGate, legacyProjection,
428
- binding: createKfdAdopterReleaseBinding({ manifest, manifestGate, legacyProjection, expectedSourceSha: sourceSha }),
428
+ binding: createKfdAdopterReleaseBinding({ manifest, manifestGate, legacyProjection, expectedAdopterId, expectedSourceRepository, expectedSourceSha: sourceSha }),
429
429
  };
430
430
  }
431
431
 
432
- export function validateKfdAdopterReleaseEvidence({ binding, artifactBinding, manifest, manifestGate, legacyProjection, passportLegacyProjection, expectedSourceSha = "" } = {}) {
432
+ export function validateKfdAdopterReleaseEvidence({ binding, artifactBinding, manifest, manifestGate, legacyProjection, passportLegacyProjection, expectedAdopterId = "kungfu-systems/buildchain", expectedSourceRepository = "", expectedSourceSha = "" } = {}) {
433
433
  if (!binding) {
434
434
  return passportLegacyProjection
435
435
  ? [{ code: "kfdSupport.authority", message: "legacy KFD support projection requires the standard adopter manifest binding" }]
436
436
  : [];
437
437
  }
438
438
  const issues = validateKfdAdopterReleaseBinding(binding, {
439
- manifest, manifestGate, legacyProjection, expectedSourceSha,
439
+ manifest, manifestGate, legacyProjection, expectedAdopterId, expectedSourceRepository, expectedSourceSha,
440
440
  }).issues.map((entry) => ({ code: `kfdAdopter.${entry.path || entry.code}`, message: entry.message, details: entry }));
441
441
  if (!artifactBinding || passportStableJson(artifactBinding) !== passportStableJson(binding)) {
442
442
  issues.push({ code: "kfdAdopter.artifactEvidence", message: "release passport and artifact evidence must bind the same exact KFD adopter closure" });
@@ -1431,7 +1431,7 @@ export function createReleasePassport(options = {}) {
1431
1431
  };
1432
1432
  }
1433
1433
 
1434
- function collectKfdAdopterReleaseInputs({ cwd, manifestJson, supportMatrixJson, productGateJsons, sourceSha, checkedAt }) {
1434
+ function collectKfdAdopterReleaseInputs({ cwd, manifestJson, supportMatrixJson, productGateJsons, repository, sourceSha, checkedAt }) {
1435
1435
  const manifestMeta = parseJsonInputWithMeta(manifestJson, undefined, { cwd, label: "kfdAdopterManifestJson" });
1436
1436
  const supportMatrixMeta = parseJsonInputWithMeta(supportMatrixJson, undefined, { cwd, label: "kfdSupportMatrixJson" });
1437
1437
  const productGateMetas = (productGateJsons || [])
@@ -1442,7 +1442,7 @@ function collectKfdAdopterReleaseInputs({ cwd, manifestJson, supportMatrixJson,
1442
1442
  manifest: manifestMeta.value,
1443
1443
  gateResults: productGateMetas.map((meta) => meta.value),
1444
1444
  comparisonMatrix: supportMatrixMeta.value,
1445
- sourceSha,
1445
+ expectedAdopterId: repository || "kungfu-systems/buildchain", expectedSourceRepository: repository, sourceSha,
1446
1446
  checkedAt,
1447
1447
  });
1448
1448
  }
@@ -1620,7 +1620,7 @@ export function collectGitHubReleasePassport({
1620
1620
  const productMechanism = defaultProductMechanism({ repository, productName });
1621
1621
  const kfdAdopterEvidence = collectKfdAdopterReleaseInputs({
1622
1622
  cwd, manifestJson: kfdAdopterManifestJson, supportMatrixJson: kfdSupportMatrixJson,
1623
- productGateJsons: kfdProductGateJsons, sourceSha, checkedAt: resolvedCheckedAt,
1623
+ productGateJsons: kfdProductGateJsons, repository, sourceSha, checkedAt: resolvedCheckedAt,
1624
1624
  });
1625
1625
  const {
1626
1626
  manifest: kfdAdopterManifest,
@@ -2060,7 +2060,8 @@ function validateReleaseEvidenceContracts({
2060
2060
  manifestGate: kfdAdopterManifestGate,
2061
2061
  legacyProjection: kfdSupportEvidence,
2062
2062
  passportLegacyProjection: passport?.kfdSupport,
2063
- expectedSourceSha: optionalString(passport?.release?.sourceSha),
2063
+ expectedAdopterId: optionalString(passport?.product?.repository) || "kungfu-systems/buildchain",
2064
+ expectedSourceRepository: optionalString(passport?.product?.repository), expectedSourceSha: optionalString(passport?.release?.sourceSha),
2064
2065
  })) {
2065
2066
  issues.push(issue("error", entry.code, entry.message, entry.details));
2066
2067
  }
@@ -148,7 +148,7 @@ export function checkV4FloatingConsumerPolicyContract() {
148
148
  "Resolve controller identities",
149
149
  "Install Buildchain runtime dependencies",
150
150
  ]);
151
- const stageCanary = read(".github/workflows/v4-stage-capsule-canary.yml");
151
+ const stageCanary = read(".github/workflows/public-build-stage-capsule-canary.yml");
152
152
  if (
153
153
  !stageCanary.includes("consumer-admission:") ||
154
154
  !stageCanary.includes("needs: consumer-admission")
@@ -10,7 +10,7 @@ const DEFAULT_ROOT = path.resolve(
10
10
  "..",
11
11
  );
12
12
  const CALLER_PATH = ".github/workflows/self-build-public-consumer-dogfood.yml";
13
- const REUSABLE_PATH = ".github/workflows/v4-stage-capsule-canary.yml";
13
+ const REUSABLE_PATH = ".github/workflows/public-build-stage-capsule-canary.yml";
14
14
  export const V4_PUBLIC_DOGFOOD_ALPHA_REF = "v4-alpha";
15
15
  const PRIVATE_CONSUMER = ["buildchain", "self", "dogfood"].join("-");
16
16
  const PRIVATE_SHADOW = ["kungfu", "shadow"].join("-");
@@ -27,7 +27,7 @@ permissions:
27
27
 
28
28
  jobs:
29
29
  dogfood:
30
- uses: kungfu-systems/buildchain/.github/workflows/v4-stage-capsule-canary.yml@${validationRef}
30
+ uses: kungfu-systems/buildchain/.github/workflows/public-build-stage-capsule-canary.yml@${validationRef}
31
31
  with:
32
32
  consumer: buildchain
33
33
  node-version: "24"
@@ -114,7 +114,7 @@ function assertWorkflowInventory(root) {
114
114
  fail(`${relative} invokes the private qualification script directly`);
115
115
  if (
116
116
  relative !== CALLER_PATH &&
117
- text.includes(".github/workflows/v4-stage-capsule-canary.yml@")
117
+ text.includes(".github/workflows/public-build-stage-capsule-canary.yml@")
118
118
  )
119
119
  fail(`${relative} creates a second v4 dogfood caller`);
120
120
  if (relative !== REUSABLE_PATH && relative !== CALLER_PATH)
@@ -162,7 +162,7 @@ function assertArchitecture(root) {
162
162
  !dogfood ||
163
163
  dogfood.callerWorkflow !== CALLER_PATH ||
164
164
  dogfood.reusableWorkflow !==
165
- "kungfu-systems/buildchain/.github/workflows/v4-stage-capsule-canary.yml" ||
165
+ "kungfu-systems/buildchain/.github/workflows/public-build-stage-capsule-canary.yml" ||
166
166
  dogfood.runtimeBinding !== "job.workflow_sha" ||
167
167
  dogfood.consumerSourceBinding !== "github.sha" ||
168
168
  JSON.stringify(dogfood.executableStages) !==
@@ -1,5 +1,5 @@
1
1
  import assert from "node:assert/strict";
2
- import { projectWorkflowIdentities } from "./workflow-taxonomy.mjs";
2
+ import { currentWorkflowPath, projectWorkflowIdentities } from "./workflow-taxonomy.mjs";
3
3
  import crypto from "node:crypto";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
@@ -48,7 +48,7 @@ assert.deepEqual(
48
48
  );
49
49
  for (const relative of contract.inventoryWorkflows)
50
50
  assert.ok(
51
- fs.statSync(path.join(root, relative)).isFile(),
51
+ fs.statSync(path.join(root, currentWorkflowPath(root, relative))).isFile(),
52
52
  `inventoried workflow is unavailable: ${relative}`,
53
53
  );
54
54
  assert.deepEqual(
@@ -87,7 +87,7 @@ for (const relative of contract.bootstrapGovernedWorkflows) {
87
87
  contract.inventoryWorkflows.includes(relative),
88
88
  `Bootstrap-governed workflow is outside the inventory: ${relative}`,
89
89
  );
90
- const source = fs.readFileSync(path.join(root, relative), "utf8");
90
+ const source = fs.readFileSync(path.join(root, currentWorkflowPath(root, relative)), "utf8");
91
91
  assert.ok(
92
92
  relative === contract.bootstrap.publicWorkflow ||
93
93
  /uses:\s+(?:\.\/)?\.github\/workflows\/bootstrap\.yml/u.test(source) ||
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs";
3
3
  import crypto from "node:crypto";
4
- import { writeWorkflowSource, rewriteRepositoryWorkflowPaths } from "./workflow-taxonomy.mjs";
4
+ import { currentWorkflowPath, writeWorkflowSource, rewriteRepositoryWorkflowPaths } from "./workflow-taxonomy.mjs";
5
5
  import path from "node:path";
6
6
  import { execFileSync } from "node:child_process";
7
7
  import { fileURLToPath } from "node:url";
@@ -426,14 +426,15 @@ function main() {
426
426
  );
427
427
  }
428
428
  for (const relative of activeFacadePaths()) {
429
- const target = path.join(root, relative);
429
+ const current = currentWorkflowPath(root, relative);
430
+ const target = path.join(root, current);
430
431
  const source = fresh
431
432
  ? frozenFacadeSource(relative)
432
433
  : fs.readFileSync(target, "utf8");
433
434
  if (check) verify(source, relative);
434
435
  else
435
436
  writeWorkflowSource(
436
- root, relative,
437
+ root, current,
437
438
  migrateV4UniversalWorkflowFacade(source, relative),
438
439
  );
439
440
  }
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import crypto from "node:crypto";
3
3
  import fs from "node:fs";
4
+ import { currentWorkflowPath } from "./workflow-taxonomy.mjs";
4
5
  import path from "node:path";
5
6
  import { execFileSync } from "node:child_process";
6
7
  import {
@@ -192,7 +193,7 @@ function executeBootstrapConformance(request, admission) {
192
193
  fail("candidate Bootstrap conformance does not close execution semantics");
193
194
  for (const relative of architecture.bootstrapGovernedWorkflows) {
194
195
  const source = fs.readFileSync(
195
- new URL(`../${relative}`, import.meta.url),
196
+ path.join(path.resolve(import.meta.dirname, ".."), currentWorkflowPath(path.resolve(import.meta.dirname, ".."), relative)),
196
197
  "utf8",
197
198
  );
198
199
  if (