@kungfu-tech/buildchain 2.11.14-alpha.0 → 2.11.14-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -412,6 +412,7 @@
412
412
  "buildchain-ref",
413
413
  "buildchain-repository",
414
414
  "checkout-cache-fallback",
415
+ "checkout-cache-fetch-attempts",
415
416
  "checkout-cache-mirror-url-template",
416
417
  "checkout-cache-mode",
417
418
  "checkout-cache-reference-repository-template",
@@ -442,7 +443,7 @@
442
443
  "verify-command",
443
444
  "working-directory"
444
445
  ],
445
- "inputCount": 48
446
+ "inputCount": 49
446
447
  },
447
448
  {
448
449
  "id": ".bump-minor-version",
@@ -2538,8 +2539,8 @@
2538
2539
  "workflowRegistryPath": "dist/site/workflow-registry.json",
2539
2540
  "pageRegistryPath": "dist/site/page-registry.json",
2540
2541
  "cliRegistryDigest": "3c0729f5351eb82fe26625fa5aabf30903f45f68af2431e02860f85ada468ac9",
2541
- "workflowRegistryDigest": "4041321df08640a802a87ff0fbe8584d95dc416945b43778727adb820cab9097",
2542
- "pageRegistryDigest": "083d9e0c63de3ede001c5fd4571050a111567a976176be3cb996e9299ccbeeac"
2542
+ "workflowRegistryDigest": "7052aa984ab49647ce2c9f96669caa32f92da57d8b2e506ac172089bedd793cf",
2543
+ "pageRegistryDigest": "784424e0ecb730449947fc0a3aba0bfcff77c34d097d63d71a9d60d5db1383df"
2543
2544
  },
2544
2545
  "comparison": {
2545
2546
  "missingCliRegistry": [],
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-publication-release-registry",
4
- "generatedAt": "2026-07-10T10:14:07.465Z",
5
- "publishedAt": "2026-07-10T10:14:07.465Z",
4
+ "generatedAt": "2026-07-10T13:03:42.866Z",
5
+ "publishedAt": "2026-07-10T13:03:42.866Z",
6
6
  "reproducible": true,
7
7
  "timestampPolicy": "ci-injected",
8
8
  "deterministicInputs": [
@@ -19,7 +19,7 @@
19
19
  "declared Buildchain surface manifest contract"
20
20
  ],
21
21
  "sourceDateEpoch": "0",
22
- "sourceRevision": "06c518d7c00527f0964e6c6eefd91532783565fe",
22
+ "sourceRevision": "d0be2b26d092ef5fa411d30888ea0ee3fa044c85",
23
23
  "timestampPolicyDetails": {
24
24
  "contract": "kungfu-buildchain-surface-timestamp-policy",
25
25
  "timestampFields": [
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "package": {
34
34
  "name": "@kungfu-tech/buildchain",
35
- "version": "2.11.14-alpha.0",
35
+ "version": "2.11.14-alpha.2",
36
36
  "versionSource": "package.json#version"
37
37
  },
38
38
  "sourceKind": "package-site-bundle",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "contract": "kungfu-buildchain-site-manifest",
4
- "generatedAt": "2026-07-10T10:14:07.465Z",
5
- "publishedAt": "2026-07-10T10:14:07.465Z",
4
+ "generatedAt": "2026-07-10T13:03:42.866Z",
5
+ "publishedAt": "2026-07-10T13:03:42.866Z",
6
6
  "reproducible": true,
7
7
  "timestampPolicy": "ci-injected",
8
8
  "deterministicInputs": [
@@ -19,7 +19,7 @@
19
19
  "declared Buildchain surface manifest contract"
20
20
  ],
21
21
  "sourceDateEpoch": "0",
22
- "sourceRevision": "06c518d7c00527f0964e6c6eefd91532783565fe",
22
+ "sourceRevision": "d0be2b26d092ef5fa411d30888ea0ee3fa044c85",
23
23
  "timestampPolicyDetails": {
24
24
  "contract": "kungfu-buildchain-surface-timestamp-policy",
25
25
  "timestampFields": [
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "package": {
39
39
  "name": "@kungfu-tech/buildchain",
40
- "version": "2.11.14-alpha.0",
40
+ "version": "2.11.14-alpha.2",
41
41
  "versionSource": "package.json#version"
42
42
  },
43
43
  "entrypoint": "buildchain-site.json",
@@ -197,7 +197,7 @@
197
197
  "path": "docs/lifecycle-protocol.md",
198
198
  "plane": "use",
199
199
  "exists": true,
200
- "digest": "sha256:16c8e4994276142e6b57a61a18a88998727dce5911cb2da96d3900c049fb76dc"
200
+ "digest": "sha256:60c39693878258d275e9d47983b306faed35d6416297c14570429de73284604c"
201
201
  },
202
202
  {
203
203
  "id": "reusable-build-surface",
@@ -205,7 +205,7 @@
205
205
  "path": "docs/reusable-build-surface.md",
206
206
  "plane": "use",
207
207
  "exists": true,
208
- "digest": "sha256:5f56d66ad2798bc46acde3f8091c2e5ccee80412c1b26b70c8197ff9c7f6a406"
208
+ "digest": "sha256:4f3699841c5a189d199db045d526b11f76ad0dff1464d6a63ca98ceadf1bb215"
209
209
  },
210
210
  {
211
211
  "id": "publish-transaction",
@@ -221,7 +221,7 @@
221
221
  "path": "docs/release-governance.md",
222
222
  "plane": "why",
223
223
  "exists": true,
224
- "digest": "sha256:9c8e3f096d314b2d0ea9a6badfdf263006e190aaa6e861489bdd05c93d3eaf04"
224
+ "digest": "sha256:59858b6e02e42377b8a1f3d6d33ba50c5ffba476f7e1443fd61e03919046eff2"
225
225
  },
226
226
  {
227
227
  "id": "release-flow",
@@ -229,7 +229,7 @@
229
229
  "path": "docs/release-flow.md",
230
230
  "plane": "verify",
231
231
  "exists": true,
232
- "digest": "sha256:feb1e056975a4ffb714d8fc0583a0f6fd8ed774c30ca168c27d5e6baf02f1cde"
232
+ "digest": "sha256:5b855ab3863e197b2ab257536b1f3ff85c79bca1fe962c45089b3f4c5ab81189"
233
233
  },
234
234
  {
235
235
  "id": "runtime-train-validation",
@@ -253,7 +253,7 @@
253
253
  "path": "docs/web-surface-deployments.md",
254
254
  "plane": "use",
255
255
  "exists": true,
256
- "digest": "sha256:4c88b27af8aeb3f72fc8b979529b68d5fbc910dce70fe4219192c09aeb404e5c"
256
+ "digest": "sha256:f1e92ba87417dc9aac4f8cf080565f7f2c114b5007bb68b90f33692c73fdad88"
257
257
  }
258
258
  ],
259
259
  "facts": [
@@ -26,6 +26,7 @@
26
26
  "buildchain-ref",
27
27
  "buildchain-repository",
28
28
  "checkout-cache-fallback",
29
+ "checkout-cache-fetch-attempts",
29
30
  "checkout-cache-mirror-url-template",
30
31
  "checkout-cache-mode",
31
32
  "checkout-cache-reference-repository-template",
@@ -56,7 +57,7 @@
56
57
  "verify-command",
57
58
  "working-directory"
58
59
  ],
59
- "inputCount": 48,
60
+ "inputCount": 49,
60
61
  "surface": "reusable-workflow",
61
62
  "capabilityGroup": "api-cli-reference",
62
63
  "status": "active"
@@ -293,7 +293,10 @@ checked-out release material. Any code or undeclared file change still fails the
293
293
  release tree gate.
294
294
 
295
295
  The action input `verification-command` remains supported. When it is provided,
296
- it overrides `lifecycle.verify` for that invocation.
296
+ it overrides `lifecycle.verify` for that invocation. The override inherits
297
+ `[lifecycle.env]` and `lifecycle.verify.env`; when the configured stage declares
298
+ `shell`, it also inherits that shell. If the stage has no explicit shell,
299
+ Buildchain preserves the platform-default shell for compatibility.
297
300
 
298
301
  ## Migration Preflight
299
302
 
@@ -150,6 +150,15 @@ The build-only workflow:
150
150
 
151
151
  It does not publish npm packages, deploy web pages, or create GitHub Releases.
152
152
 
153
+ The paper release preset additionally hydrates every prior published package
154
+ registry from the npm registry before generating the current manifest. npm
155
+ package integrity authenticates each downloaded source; Buildchain verifies the
156
+ registry self-digest, merges immutable records, and fails if a cumulative
157
+ registry drops an accepted version or changes immutable route/artifact facts.
158
+ The synthesized package therefore carries complete history even on a clean
159
+ runner. Its cumulative registry and file SHA-256 values are bound into the paper
160
+ release build summary and release passport evidence.
161
+
153
162
  ## Paper Release Preset
154
163
 
155
164
  Paper repositories that publish a versioned npm package should use the
@@ -12,6 +12,8 @@ flowchart TD
12
12
  Review["Protected branch review"]
13
13
  Merge["Merge PR into alpha or release"]
14
14
  Promotion["Buildchain Ref Promotion"]
15
+ StableDecision{"Release channel?"}
16
+ StableGate["Stable only: exact-alpha canaries + soak + cooldown"]
15
17
  Action["promote-buildchain-ref action"]
16
18
  VersionState["Version-state commit"]
17
19
  ExactTag["Exact tag"]
@@ -22,7 +24,10 @@ flowchart TD
22
24
  Verify --> Review
23
25
  Review --> Merge
24
26
  Merge --> Promotion
25
- Promotion --> Action
27
+ Promotion --> StableDecision
28
+ StableDecision -->|yes| StableGate
29
+ StableDecision -->|no: alpha| Action
30
+ StableGate --> Action
26
31
  Action --> VersionState
27
32
  Action --> ExactTag
28
33
  Action --> FloatingRefs
@@ -33,6 +38,10 @@ flowchart TD
33
38
  Buildchain treats the PR merge as release intent and the promotion action as the
34
39
  only component allowed to turn that intent into release refs.
35
40
 
41
+ `StableGate` applies only to Buildchain's release channel. Alpha and train
42
+ iteration bypass it. See [Stable Release Throttle And Canary Gate](release-governance.md#stable-release-throttle-and-canary-gate)
43
+ for the versioned policy and evidence contract.
44
+
36
45
  ## Ref State
37
46
 
38
47
  | Ref kind | Example | Mutability | Purpose |
@@ -206,6 +206,41 @@ The production channel and the test channel therefore intentionally diverge
206
206
  after release: production stays on the release commit, while alpha/dev continue
207
207
  at the next prerelease commit.
208
208
 
209
+ ### Stable Release Throttle And Canary Gate
210
+
211
+ Buildchain's own stable channel has an additional pre-publication gate. It is
212
+ evaluated after the PR-stage release candidate has been resolved and before the
213
+ publish-gate ref, package registry, exact stable tag, or floating stable refs
214
+ are mutated. Train refs and alpha promotion do not execute this gate.
215
+
216
+ The policy is versioned in `.buildchain/stable-release-policy.json`. A stable
217
+ candidate is allowed only when all of these facts are true:
218
+
219
+ - the candidate resolves to an immutable exact alpha tag and its GitHub
220
+ prerelease timestamp;
221
+ - at least 24 hours have passed since the preceding stable patch on the same
222
+ minor line;
223
+ - the preceding stable-to-alpha comparison contains a product or public
224
+ contract path, not only version, test, evidence, or retrospective changes;
225
+ - the version-bound impact record has a non-empty summary and at least one
226
+ surface impact;
227
+ - the `Build Surface Fixture` release-candidate run succeeded;
228
+ - `site-libkungfu-dev` completed its no-apply `Buildchain Stable Canary` and an allowed
229
+ maintainer attested that successful run on the exact alpha SHA through the
230
+ `buildchain-canary/site-libkungfu-dev` commit-status context;
231
+ - at least one hour has elapsed after the last required canary completed.
232
+
233
+ The machine report is written to
234
+ `.buildchain/release-passport/stable-release-gate.json`. A passing report is
235
+ uploaded with the stable release passport. A blocked run writes the same report
236
+ before failing, so the missing or stale condition is inspectable without
237
+ opening a publication transaction.
238
+
239
+ The cooldown is a minimum interval, not an instruction to release every day.
240
+ Compatible work should still be batched until a stable release has a concrete
241
+ consumer need. Changing the interval, canary set, attestors, product path
242
+ boundary, or soak time is a reviewed policy change.
243
+
209
244
  If release finalization is resumed after generated version-state bookkeeping was
210
245
  partially applied, Buildchain applies the same recovery rule: the current
211
246
  release head may be the generated commit, or a historical merge commit that
@@ -213,6 +248,13 @@ contains the recorded release material, existing exact tags and alpha/dev refs
213
248
  are accepted when they match the transaction, and missing floating `vX.Y` or
214
249
  `vX` tags are retried idempotently before completion.
215
250
 
251
+ Once the durable release transaction is `complete` and the exact/floating
252
+ stable refs have moved, next-alpha preparation is post-release bookkeeping. A
253
+ failure there records `deferred-post-release-bookkeeping` and
254
+ `next-anchor-required` instead of retroactively reporting the stable release as
255
+ failed. Generated next-alpha merges use the current dev tree as their base and
256
+ overlay only declared version-state paths, preserving concurrent dev changes.
257
+
216
258
  ## Major Gate Semantics
217
259
 
218
260
  A major gate merge is:
@@ -276,8 +318,14 @@ After each merge, the next PR is re-evaluated before it can move the protected
276
318
  dev branch. This prevents one merge from silently making the next candidate
277
319
  stale or conflicting.
278
320
 
279
- The required check should be the `check` job. Repositories can keep that job
280
- name stable while changing the actual verification command declaratively in
321
+ The canonical consumer required check context is `check / check`, matching the
322
+ reusable workflow call plus its `check` job. Buildchain's own `Verify` workflow
323
+ emits the repository-local context `check`, so Buildchain self-promotion,
324
+ release-line bootstrap, and dogfood patrol wrappers pass `check` explicitly.
325
+ Release governance preserves the exact context emitted by the repository and
326
+ records branch-protection policy before/after facts; it does not rewrite one
327
+ context shape into the other. Consumer repositories can keep their context
328
+ stable while changing the actual verification command declaratively in
281
329
  `buildchain.toml`:
282
330
 
283
331
  ```toml
@@ -317,7 +365,7 @@ jobs:
317
365
  statuses: read
318
366
  with:
319
367
  target-branch: dev/v2/v2.6
320
- required-status-checks: check
368
+ required-status-checks: check / check
321
369
  ready-label: ready
322
370
  block-labels: blocked,do-not-merge
323
371
  max-merges: 1
@@ -233,6 +233,7 @@ jobs:
233
233
  checkout-cache-reference-repository-template: ${{ vars.BUILDCHAIN_CHECKOUT_CACHE_REFERENCE_REPOSITORY_TEMPLATE }}
234
234
  checkout-cache-fallback: github
235
235
  checkout-cache-timeout-seconds: 60
236
+ checkout-cache-fetch-attempts: 3
236
237
  ```
237
238
 
238
239
  `checkout-cache-mode` accepts:
@@ -255,7 +256,10 @@ Do not read cache URLs or reference paths from PR-controlled files such as
255
256
  `.buildchain/buildchain.toml`. These values are trusted workflow inputs or repo/org
256
257
  variables. Buildchain does not pass GitHub credentials to cache mirrors or
257
258
  reference repositories. If it must fall back to GitHub, the workflow token is
258
- used only for the GitHub fetch path.
259
+ used only for the GitHub fetch path. Retryable timeout and transient network
260
+ failures use the bounded `checkout-cache-fetch-attempts` budget; permanent
261
+ failures stop immediately. Diagnostics record the configured budget and actual
262
+ GitHub fetch attempts before exact HEAD/tree verification.
259
263
 
260
264
  Each platform diagnostics artifact includes `source-checkout.json` and embeds a
261
265
  compact `sourceCheckout` summary in `diagnostics.json`: mode, transport,
@@ -592,7 +596,7 @@ jobs:
592
596
  runner-preset: github-hosted
593
597
  trusted-publishing: true
594
598
  github-release: true
595
- required-status-check: check
599
+ required-status-check: check / check
596
600
  required-artifact-count: 3
597
601
  publish-dist-tag: alpha
598
602
  publish-package-set-order: platforms-first-main-last
@@ -351,6 +351,16 @@ The CLI emits GitHub outputs when `GITHUB_OUTPUT` is present:
351
351
  - `web-surface-artifact-hash`
352
352
  - `web-surface-manifest-json`
353
353
 
354
+ The reusable workflow resolves that same effective channel before running the
355
+ caller build or verify command. Both steps receive
356
+ `BUILDCHAIN_WEB_SURFACE_CHANNEL` (`preview`, `staging`, or `production`) and
357
+ `BUILDCHAIN_PREVIEW_ALIAS` for previews. Compatibility aliases
358
+ `BUILDCHAIN_SURFACE_CHANNEL` and `BUILDCHAIN_WEB_SURFACE_ALIAS` are also
359
+ provided. Callers should consume these variables instead of reconstructing the
360
+ release-intent state machine from raw GitHub events.
361
+ An unapproved manual canary resolves to `staging`; a trusted manual dispatch
362
+ with `production-approved=true` resolves to `production` through the same path.
363
+
354
364
  ## Explicit Apply
355
365
 
356
366
  `deploy-apply` and `cleanup-apply` are explicit execution modes for the
@@ -452,7 +462,11 @@ HTTP check is visible from the consumer run.
452
462
 
453
463
  It can also execute a previously saved deploy plan. In that mode Buildchain
454
464
  recomputes the local artifact hash before running AWS commands and fails closed
455
- if the artifact no longer matches the saved plan:
465
+ if the artifact no longer matches the saved plan. Before any adapter operation,
466
+ it also checks channel-aware JSON manifests in the artifact: top-level
467
+ `canonicalHost` and declared `pages[].host` facts must belong to the plan's
468
+ surface hosts. A production plan therefore rejects staging/preview host facts
469
+ before AWS apply:
456
470
 
457
471
  ```bash
458
472
  node scripts/web-surface.mjs \
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kungfu-tech/buildchain",
3
- "version": "2.11.14-alpha.0",
3
+ "version": "2.11.14-alpha.2",
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",
@@ -96,6 +96,10 @@ export function createBuildchainContractWorld({ root = process.cwd(), packageJso
96
96
  "platforms-json",
97
97
  "release-candidate",
98
98
  "artifact-transfer-mode",
99
+ "checkout-cache-mode",
100
+ "checkout-cache-fallback",
101
+ "checkout-cache-timeout-seconds",
102
+ "checkout-cache-fetch-attempts",
99
103
  "buildchain-contract-lock-path",
100
104
  "buildchain-contract-drift-issue-mode",
101
105
  ],
@@ -104,6 +108,7 @@ export function createBuildchainContractWorld({ root = process.cwd(), packageJso
104
108
  "publish source locks are verified before heavy build jobs",
105
109
  "release-candidate builds do not publish registry artifacts",
106
110
  "contract drift is checked before heavy build jobs for stable floating refs",
111
+ "retryable GitHub fallback fetches use a bounded attempt budget before exact source SHA and tree verification",
107
112
  ],
108
113
  }),
109
114
  surface(root, {
@@ -115,7 +120,7 @@ export function createBuildchainContractWorld({ root = process.cwd(), packageJso
115
120
  requiredOutputs: ["promoted-sha", "built-source-sha", "release-candidate-artifact"],
116
121
  breakingDefaults: {
117
122
  promoteOnlyReleaseCandidate: true,
118
- requiredStatusCheck: "check",
123
+ requiredStatusCheck: "check / check",
119
124
  },
120
125
  optionalInputs: [
121
126
  "buildchain-ref",
@@ -177,6 +182,8 @@ export function createBuildchainContractWorld({ root = process.cwd(), packageJso
177
182
  "contract drift is checked before caller build, web-surface render, deploy planning, and apply side effects",
178
183
  "compatible contract drift continues and produces a consumer issue or summary",
179
184
  "breaking contract drift fails closed before rendering, deployment, or release publication",
185
+ "effective channel and preview alias are injected before caller build and verify commands",
186
+ "channel-aware artifact host facts are checked against the deploy plan before adapter side effects",
180
187
  ],
181
188
  }),
182
189
  surface(root, {
@@ -189,6 +196,7 @@ export function createBuildchainContractWorld({ root = process.cwd(), packageJso
189
196
  breakingDefaults: {
190
197
  requireGovernance: false,
191
198
  releasePassport: true,
199
+ requiredStatusCheck: "check / check",
192
200
  },
193
201
  optionalInputs: [
194
202
  "publish-transaction",
@@ -477,7 +477,54 @@ function publicationArchiveRecord({ collected, manifestPath, passportPath, regis
477
477
  };
478
478
  }
479
479
 
480
- function updatePublicationArchiveRegistry({ cwd, collected, manifestPath, passportPath, registryOutput, generatedAt }) {
480
+ function immutablePublicationRecord(record = {}) {
481
+ const { publishedAt: _publishedAt, latestObservedAt: _latestObservedAt, recordSha256: _recordSha256, ...immutable } = record;
482
+ return immutable;
483
+ }
484
+
485
+ function readVerifiedPublicationRegistry(registryPath) {
486
+ const registry = JSON.parse(fs.readFileSync(registryPath, "utf8"));
487
+ if (registry.contract !== PUBLICATION_ARTIFACT_REGISTRY_CONTRACT) {
488
+ throw new Error(`publication archive registry has unsupported contract: ${registry.contract || "(missing)"}`);
489
+ }
490
+ const expectedDigest = sha256Json({ ...registry, registrySha256: undefined });
491
+ if (registry.registrySha256 !== expectedDigest) {
492
+ throw new Error(`publication archive registry digest mismatch for ${registryPath}: expected ${expectedDigest}, got ${registry.registrySha256 || "(missing)"}`);
493
+ }
494
+ if (!Array.isArray(registry.versions)) throw new Error(`publication archive registry versions must be an array: ${registryPath}`);
495
+ return registry;
496
+ }
497
+
498
+ function mergePublicationRegistryVersions({ registries = [], publicationId }) {
499
+ const versions = new Map();
500
+ for (const registry of registries) {
501
+ if (registry.publication?.id !== publicationId) {
502
+ throw new Error(`publication archive registry id mismatch: expected ${publicationId}, got ${registry.publication?.id || "(missing)"}`);
503
+ }
504
+ if (registry.historyPolicy === "cumulative-authenticated-hydration") {
505
+ const declaredVersions = new Set(registry.versions.map((record) => String(record?.version || "")));
506
+ const missingVersions = [...versions.keys()].filter((version) => !declaredVersions.has(version));
507
+ if (missingVersions.length > 0) throw new Error(`cumulative publication archive registry dropped accepted versions: ${missingVersions.join(", ")}`);
508
+ }
509
+ for (const record of registry.versions) {
510
+ const version = String(record?.version || "").trim();
511
+ if (!version) throw new Error("publication archive registry record is missing version");
512
+ const existing = versions.get(version);
513
+ if (!existing) {
514
+ versions.set(version, record);
515
+ continue;
516
+ }
517
+ if (existing.immutableDigest !== record.immutableDigest || stableJson(immutablePublicationRecord(existing)) !== stableJson(immutablePublicationRecord(record))) {
518
+ throw new Error(`publication archive version ${version} changed across accepted registries: ${existing.immutableDigest || "(missing)"} != ${record.immutableDigest || "(missing)"}`);
519
+ }
520
+ const latestObservedAt = [existing.latestObservedAt, record.latestObservedAt].filter(Boolean).sort().at(-1);
521
+ versions.set(version, latestObservedAt ? { ...existing, latestObservedAt } : existing);
522
+ }
523
+ }
524
+ return [...versions.values()];
525
+ }
526
+
527
+ function updatePublicationArchiveRegistry({ cwd, collected, manifestPath, passportPath, registryOutput, registryInputs = [], generatedAt }) {
481
528
  const archive = collected.manifest.publication.archive;
482
529
  if (!archive) {
483
530
  return undefined;
@@ -503,17 +550,16 @@ function updatePublicationArchiveRegistry({ cwd, collected, manifestPath, passpo
503
550
  siteConsumers: collected.manifest.publication.siteConsumers,
504
551
  },
505
552
  appendOnly: true,
553
+ historyPolicy: "cumulative-authenticated-hydration",
506
554
  sameVersionRepublish: "fail-on-digest-change",
507
555
  immutablePathPolicy: archive.immutablePathPolicy,
508
556
  versions: [],
509
557
  };
510
- if (fs.existsSync(registryPath)) {
511
- registry = JSON.parse(fs.readFileSync(registryPath, "utf8"));
512
- if (registry.contract !== PUBLICATION_ARTIFACT_REGISTRY_CONTRACT) {
513
- throw new Error(`publication archive registry has unsupported contract: ${registry.contract || "(missing)"}`);
514
- }
515
- }
516
- const versions = Array.isArray(registry.versions) ? registry.versions : [];
558
+ const inputPaths = [...registryInputs.map((entry) => path.resolve(cwd, entry)), ...(fs.existsSync(registryPath) ? [registryPath] : [])];
559
+ const versions = mergePublicationRegistryVersions({
560
+ registries: inputPaths.map(readVerifiedPublicationRegistry),
561
+ publicationId: archive.id,
562
+ });
517
563
  const existingIndex = versions.findIndex((entry) => entry.version === record.version);
518
564
  if (existingIndex !== -1) {
519
565
  const existing = versions[existingIndex];
@@ -559,6 +605,7 @@ export function writePublicationArtifact({
559
605
  output = "",
560
606
  passportOutput = "",
561
607
  registryOutput = "",
608
+ registryInputs = [],
562
609
  sourceSha = "",
563
610
  sourceBundle = true,
564
611
  sourceBundlePath = "",
@@ -586,6 +633,7 @@ export function writePublicationArtifact({
586
633
  manifestPath: path.resolve(cwd, manifestOutput),
587
634
  passportPath: path.resolve(cwd, passportPath),
588
635
  registryOutput,
636
+ registryInputs,
589
637
  generatedAt,
590
638
  });
591
639
  return {
@@ -106,6 +106,7 @@ export function collectPublicationPackageFacts({
106
106
  manifestPath: publication.manifestPath,
107
107
  passportPath: ".buildchain/publication/publication-artifact-passport.json",
108
108
  registryPath: publication.archive?.registryPath || "",
109
+ registryHydrationPath: publication.archive ? ".buildchain/publication/registry-hydration.json" : "",
109
110
  sourceBundlePath: publication.sourceBundlePath,
110
111
  artifactPaths: [publication.primaryArtifact, ...publication.artifactPaths].filter(Boolean),
111
112
  metadataPaths: publication.metadataPaths,
@@ -131,6 +132,7 @@ export function preparePublicationNpmPackage({
131
132
  facts.publication.manifestPath,
132
133
  facts.publication.passportPath,
133
134
  facts.publication.registryPath,
135
+ facts.publication.registryHydrationPath,
134
136
  facts.publication.sourceBundlePath,
135
137
  ...facts.publication.artifactPaths,
136
138
  ...facts.publication.metadataPaths,
@@ -197,6 +199,9 @@ export function preparePublicationNpmPackage({
197
199
  publicationManifest: facts.publication.manifestPath,
198
200
  publicationPassport: facts.publication.passportPath,
199
201
  publicationRegistry: facts.publication.registryPath || undefined,
202
+ publicationRegistryHydration: files.some((file) => file.path === facts.publication.registryHydrationPath)
203
+ ? facts.publication.registryHydrationPath
204
+ : undefined,
200
205
  primaryArtifact: facts.publication.primaryArtifact,
201
206
  sourceBundle: facts.publication.sourceBundlePath,
202
207
  siteConsumers: facts.publication.siteConsumers,