diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a7b48ba32..6d9e6bc94 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,7 +2,7 @@ version: 2 updates: # SDK core packages and workspace tooling - package-ecosystem: npm - target-branch: prerelease + target-branch: beta directories: - / - /packages/sdk @@ -25,7 +25,7 @@ updates: # Test apps - package-ecosystem: npm - target-branch: prerelease + target-branch: beta directories: - /test/test-nextjs - /test/test-vite @@ -49,7 +49,7 @@ updates: # Example apps - package-ecosystem: npm - target-branch: prerelease + target-branch: beta directories: - /examples/example-bnb - /examples/example-hoodi @@ -78,7 +78,7 @@ updates: - version-update:semver-patch - package-ecosystem: github-actions - target-branch: prerelease + target-branch: beta directory: / schedule: interval: weekly diff --git a/.github/workflows/abi.yml b/.github/workflows/abi.yml index c1f1a38c3..7356c7c83 100644 --- a/.github/workflows/abi.yml +++ b/.github/workflows/abi.yml @@ -2,7 +2,7 @@ name: ABI Check on: pull_request: - branches: [main, prerelease] + branches: [main, beta, alpha] paths: - "contracts/src/**" - "contracts/foundry.toml" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d3d19c38..d3bd25f48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: pull_request: - branches: [main, prerelease] + branches: [main, beta, alpha] workflow_call: concurrency: diff --git a/.github/workflows/codemod.yml b/.github/workflows/codemod.yml index ef0429ecb..51d477ee9 100644 --- a/.github/workflows/codemod.yml +++ b/.github/workflows/codemod.yml @@ -2,7 +2,7 @@ name: Codemod on: pull_request: - branches: [main, prerelease] + branches: [main, beta, alpha] paths: - "codemods/**" - ".github/workflows/codemod.yml" diff --git a/.github/workflows/contracts-test.yml b/.github/workflows/contracts-test.yml index 1bdcb535a..4c2637db8 100644 --- a/.github/workflows/contracts-test.yml +++ b/.github/workflows/contracts-test.yml @@ -2,7 +2,7 @@ name: Contracts Test on: pull_request: - branches: [main, prerelease] + branches: [main, beta, alpha] paths: - "contracts/src/**" - "contracts/test/**" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 52c95a6e5..04dffe7f0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,7 +2,7 @@ name: Docs on: pull_request: - branches: [main, prerelease] + branches: [main, beta, alpha] paths: &doc-paths - "docs/**" - "examples/**/README.md" @@ -17,11 +17,10 @@ on: - "package.json" - "pnpm-lock.yaml" - ".github/workflows/docs.yml" - # semantic-release forces branch-to-branch merges (prerelease→main) with the - # docs:retarget fix committed directly on top, so doc changes routinely reach - # main/prerelease via direct push, never a PR. Run the checks there too. + # Promotions are branch-to-branch merges (beta→main; alpha→beta→main for breaking work) + # with docs:retarget committed on top, so docs reach main/beta/alpha by direct push, not a PR. push: - branches: [main, prerelease] + branches: [main, beta, alpha] paths: *doc-paths workflow_call: @@ -67,17 +66,15 @@ jobs: - name: Verify LLM corpus artifacts are up to date run: pnpm exec turbo run llm:check - # Doc URLs are branch-specific (raw.githubusercontent .../main|prerelease, and - # docs.zama.org .../stable|alpha). `github.base_ref` is the branch this PR lands - # on — reliable here even though the PR is checked out detached. If the committed - # URLs target the wrong branch (e.g. a promotion that forgot `pnpm docs:retarget`), - # this fails. `ref_name` covers the direct-push and workflow_call (release) paths. + # Doc URLs are branch-specific (raw.githubusercontent .../main|beta, docs.zama.org + # .../stable|beta). `base_ref` is the PR's target branch; `ref_name` covers direct-push + # and workflow_call (release). Fails if committed URLs target the wrong branch. - name: Verify doc URLs target the right branch run: pnpm docs:check-target "${{ github.base_ref || github.ref_name }}" # External link liveness for the published docs + READMEs. Internal links and # anchors are checked above by `pnpm docs:check-links`; this job covers `https://` - # targets. Runs for PRs and direct pushes to main/prerelease, but NOT when this + # targets. Runs for PRs and direct pushes to main/beta/alpha, but NOT when this # workflow is reused by release.yml: there `github.workflow` is the caller's name # ("Release"), so external flakiness still can't block a release. If it flakes on # PRs, move to a scheduled (cron) workflow. diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 0302bb962..a67237c27 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -8,7 +8,7 @@ name: Examples CI # import, a type error — against the SDK release it actually targets. on: pull_request: - branches: [main, prerelease] + branches: [main, beta, alpha] paths: - "examples/**" - ".github/workflows/examples.yml" diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 524dbf778..7d6e23a2b 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -7,7 +7,7 @@ name: Integration # blip should never block an unrelated merge. on: pull_request: - branches: [main, prerelease] + branches: [main, beta, alpha] paths: - "packages/sdk/**" - "pnpm-lock.yaml" diff --git a/.github/workflows/license-compat.yml b/.github/workflows/license-compat.yml index e7643ebdc..d83da657b 100644 --- a/.github/workflows/license-compat.yml +++ b/.github/workflows/license-compat.yml @@ -2,9 +2,9 @@ name: License Compatibility on: pull_request: - branches: [main, prerelease] + branches: [main, beta, alpha] push: - branches: [main, prerelease] + branches: [main, beta, alpha] concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 7ad15a073..5135b3284 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -2,7 +2,7 @@ name: Playwright on: pull_request: - branches: [main, prerelease] + branches: [main, beta, alpha] workflow_call: concurrency: diff --git a/.github/workflows/pr-title-lint.yml b/.github/workflows/pr-title-lint.yml index 0a4d72c7c..ff624ec13 100644 --- a/.github/workflows/pr-title-lint.yml +++ b/.github/workflows/pr-title-lint.yml @@ -2,7 +2,7 @@ name: PR Title Lint on: pull_request: - branches: [main, prerelease] + branches: [main, beta, alpha] types: [opened, edited, synchronize, reopened] concurrency: diff --git a/.github/workflows/release-preview.yml b/.github/workflows/release-preview.yml index a58a05344..636278d40 100644 --- a/.github/workflows/release-preview.yml +++ b/.github/workflows/release-preview.yml @@ -29,8 +29,8 @@ jobs: steps: - name: Validate target branch run: | - if [[ "${GITHUB_REF_NAME}" != "main" && "${GITHUB_REF_NAME}" != "prerelease" ]]; then - echo "Release preview is only allowed from main or prerelease (got: ${GITHUB_REF_NAME})." + if [[ "${GITHUB_REF_NAME}" != "main" && "${GITHUB_REF_NAME}" != "beta" && "${GITHUB_REF_NAME}" != "alpha" ]]; then + echo "Release preview is only allowed from main, beta, or alpha (got: ${GITHUB_REF_NAME})." exit 1 fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f0d995c23..d714f0cf1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Release on: push: - branches: [main, prerelease] + branches: [main, beta, alpha] workflow_dispatch: inputs: publish-tag: @@ -163,8 +163,8 @@ jobs: steps: - name: Validate target branch run: | - if [[ "${GITHUB_REF_NAME}" != "main" && "${GITHUB_REF_NAME}" != "prerelease" ]]; then - echo "::error::Releases are only allowed from main or prerelease (got: ${GITHUB_REF_NAME})." + if [[ "${GITHUB_REF_NAME}" != "main" && "${GITHUB_REF_NAME}" != "beta" && "${GITHUB_REF_NAME}" != "alpha" ]]; then + echo "::error::Releases are only allowed from main, beta, or alpha (got: ${GITHUB_REF_NAME})." exit 1 fi diff --git a/.releaserc.cjs b/.releaserc.cjs index b17c2f8eb..ea18402c5 100644 --- a/.releaserc.cjs +++ b/.releaserc.cjs @@ -26,7 +26,11 @@ const mainTemplate = extract("mainTemplate").replace(/^\* /gm, "- "); const commitPartial = extract("commitPartial").replace(/^\*/, "-"); module.exports = { - branches: ["main", { name: "prerelease", channel: "alpha", prerelease: "alpha" }], + branches: [ + "main", + { name: "beta", channel: "beta", prerelease: "beta" }, + { name: "alpha", channel: "alpha", prerelease: "alpha" }, + ], tagFormat: "v${version}", plugins: [ [ diff --git a/AGENTS.md b/AGENTS.md index 6a313df88..41712a79c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,7 +32,7 @@ Load these on demand during planning — they are not preloaded: - [`docs/agents/architecture.md`](docs/agents/architecture.md) — repo layout, how operations flow through the system (balance, transfer, shield, unshield, routing) - [`docs/agents/conventions.md`](docs/agents/conventions.md) — shared naming rules and design decisions (contracts-vs-tokens, Solidity-mirror params, pure contract call builders, stage-gate docs language, …) - [`docs/agents/gotchas.md`](docs/agents/gotchas.md) — shared footguns: address normalization in query keys, PR base branch, shared-branch safety -- [`docs/agents/changelog.md`](docs/agents/changelog.md) — the GitBook changelog convention: mainline-only pages, the Alpha unreleased tip, per-minor vs. frozen-major pagination, nav wiring, and the `docs:changelog` automation +- [`docs/agents/changelog.md`](docs/agents/changelog.md) — the GitBook changelog convention: mainline-only pages, the Beta unreleased tip, per-minor vs. frozen-major pagination, nav wiring, and the `docs:changelog` automation - [`CONTRIBUTING.md`](CONTRIBUTING.md) — dev commands (build, test, E2E, typecheck, lint, format), LLM artifact generation (`pnpm llm:build` / `llm:check`), API reports (`pnpm api-report*`), and the PR/release workflow Some packages add their own AGENTS.md with package-specific rules — they merge with this root file when you're working in that subtree: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4ca5fe75a..d1d492da0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,7 +63,7 @@ Use descriptive branch names: ### Making Changes -1. Create a feature branch from `prerelease` (the default PR base; `main` is reserved for release/CI-infra PRs) +1. Create a feature branch from `beta` (the default PR base; `main` is reserved for release/CI-infra PRs). `alpha` is not a feature-PR target — it's a protected branch used only for testing changes incompatible with the current protocol on mainnet/testnet (e.g. a new FHEVM SDK major version), synced from `beta` and merged back only once stable enough. 2. Make your changes 3. Ensure all checks pass: @@ -79,7 +79,7 @@ pnpm build # Build output ### GitBook Documentation -The docs in `docs/gitbook/src/` are published to GitBook (both `main` and `prerelease` ship as separate spaces). **Links between doc pages must be relative `.md` paths** — e.g. `[Token](../reference/sdk/Token.md)`, never host-absolute (`/reference/sdk/Token`). GitBook serves each space under a sub-path (`docs.zama.org/protocol/sdk/…`), so a leading-slash link resolves against the site root and breaks (it only surfaces on the live site — use GitBook's per-PR preview to check rendering). Anchors must match a heading in the target page. +The docs in `docs/gitbook/src/` are published to GitBook (both `main` and `beta` ship as separate spaces, labeled "Stable" and "Beta" in the docs site version switcher; `alpha` is a protocol-testing branch only and has no separate docs space). **Links between doc pages must be relative `.md` paths** — e.g. `[Token](../reference/sdk/Token.md)`, never host-absolute (`/reference/sdk/Token`). GitBook serves each space under a sub-path (`docs.zama.org/protocol/sdk/…`), so a leading-slash link resolves against the site root and breaks (it only surfaces on the live site — use GitBook's per-PR preview to check rendering). Anchors must match a heading in the target page. `pnpm docs:check-links` validates this (no host-absolute links, every relative target and `#anchor` resolves) and runs in CI on any docs change. Run it locally before pushing doc edits. @@ -100,10 +100,10 @@ pnpm llm:check The pre-commit hook regenerates and stages the LLM artifacts automatically when relevant staged sources change. If a corpus source is partially staged, stage or discard the remaining changes before committing so the generated files match the committed source state. -Doc URLs are branch-specific: the corpus and the migration-guide prompt link to `raw.githubusercontent.com/zama-ai/sdk//…` and `docs.zama.org/protocol/sdk//…` — `main`/`stable` on the release branch, `prerelease`/`alpha` on the prerelease branch. They can't be derived at build time (CI checks out PRs detached, and the committed artifacts must match the rebuild), so the branch is committed and flipped at promotion with one idempotent command: +Doc URLs are branch-specific: the corpus and the migration-guide prompt link to `raw.githubusercontent.com/zama-ai/sdk//…` and `docs.zama.org/protocol/sdk//…` — `main`/`stable` on the release branch, `beta`/`beta` on the beta branch (`alpha` has no docs space of its own). They can't be derived at build time (CI checks out PRs detached, and the committed artifacts must match the rebuild), so the branch is committed and flipped at promotion with one idempotent command: ```bash -pnpm docs:retarget main # or: prerelease +pnpm docs:retarget main # or: beta ``` CI (`pnpm docs:check-target`, driven by the PR's base branch) fails any PR whose committed URLs target the wrong branch and points you at the command above. @@ -160,28 +160,29 @@ We use [semantic-release](https://semantic-release.gitbook.io/semantic-release/) Release behavior: 1. PR titles are validated against Conventional Commits. -2. Squash-merging into a release branch (`main` or `prerelease`) preserves that title as the release signal. +2. Squash-merging into a release branch (`main`, `beta`, or `alpha`) preserves that title as the release signal. 3. semantic-release computes the next version (`patch`/`minor`/`major`) from merged commits. Breaking changes must be signaled with `!` in the PR title (e.g. `feat!: drop deprecated API`); `BREAKING CHANGE:` text in commit bodies is preserved in the changelog but does not affect the version bump. 4. `@zama-fhe/sdk` and `@zama-fhe/react-sdk` are versioned and published together in lockstep. -5. `main` publishes stable versions to npm `latest`, while `prerelease` publishes prerelease versions to npm `alpha`. +5. `main` publishes stable versions to npm `latest`, `beta` publishes prerelease versions to npm `beta`, and `alpha` publishes prerelease versions to npm `alpha`. 6. GitHub release notes and tags are generated automatically. Release workflows: -- `Release` (`.github/workflows/release.yml`): automatic publish on push to `main` and `prerelease`, gated by `Vitest` and `Playwright`. -- `Release (Manual)` (`.github/workflows/release-manual.yml`): manual publish restricted to `main` and `prerelease`, with the same `Vitest` + `Playwright` gates before publishing. Shares a concurrency group with `Release` to prevent simultaneous publishes on the same ref. -- `Release Preview` (`.github/workflows/release-preview.yml`): manual dry-run restricted to `main` and `prerelease` (`pnpm release:dry-run`), no publish side effects. +- `Release` (`.github/workflows/release.yml`): automatic publish on push to `main`, `beta`, and `alpha`, gated by `Vitest` and `Playwright`. +- Manual publish: the same `Release` workflow (`release.yml`) also runs on `workflow_dispatch` (same `main`/`beta`/`alpha` restriction and `Vitest` + `Playwright` gates). Use it to recover when semantic-release created the tag but the npm publish step failed — a `publish-tag` input republishes from an existing tag, and a `dry-run` input is available. +- `Release Preview` (`.github/workflows/release-preview.yml`): manual dry-run restricted to `main`, `beta`, and `alpha` (`pnpm release:dry-run`), no publish side effects. Install channels: - Stable: `npm i @zama-fhe/sdk` -- Prerelease: `npm i @zama-fhe/sdk@alpha` +- Beta: `npm i @zama-fhe/sdk@beta` +- Alpha: `npm i @zama-fhe/sdk@alpha` Maintainer requirements: - Configure branch protection on `main` to require both `Vitest` and `Playwright` checks before merge. -- Configure branch protection on `prerelease` with the same required checks. -- Configure npm trusted publishers for `@zama-fhe/sdk` and `@zama-fhe/react-sdk` pointing to this repository's `release.yml` and `release-manual.yml` workflows. +- Configure branch protection on `beta` and `alpha` with the same required checks. +- Configure npm trusted publishers for `@zama-fhe/sdk` and `@zama-fhe/react-sdk` pointing to this repository's `release.yml` workflow. ## Architecture Guidelines diff --git a/README.md b/README.md index 8ae42eb86..6e38e24a4 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,9 @@

- Dev release + Dev release - Dev release last updated + Dev release last updated CI diff --git a/docs/agents/changelog.md b/docs/agents/changelog.md index bc6bf786c..2ae868381 100644 --- a/docs/agents/changelog.md +++ b/docs/agents/changelog.md @@ -7,16 +7,17 @@ The repo has **two** changelog surfaces — keep them distinct: ## Conventions -- **Mainline releases only** — no per-prerelease pages. The unreleased tip accumulates on a single **Alpha page** under an "Unreleased" hint; on release its content is promoted into a version section and the Alpha page resets. +- **Mainline releases only** — no per-prerelease pages. The unreleased tip accumulates on a single **Beta page** under an "Unreleased" hint; on release its content is promoted into a version section and the Beta page resets. +- **`alpha` is off the changelog** — the protocol-testing `alpha` branch has no GitBook space or doc URLs, so its `-alpha.N` builds never get a page or section here; only `beta`/stable work is written up. - **Active major line → one page per minor**, behind a collapsible parent index. Page title and H1 are both `{minor}.x`. Inside, one `## {version}` section per release, **newest-first**, each opening with a `_Released YYYY-MM-DD._` dateline and grouping changes under `###` topic headings (not raw commit types). - **Superseded majors → one page per major**, grouped under a collapsible "Legacy versions" parent, same newest-first sections. - **Legacy pages are historical record** — write them in the API vocabulary of their era (don't modernize names) and don't link into the current-API `guides/`/`reference/` trees, the migration guide excepted. -- **Nav is newest-to-oldest** (Alpha → active line → Legacy) and nests with **2-space indent** — the SUMMARY-driven LLM corpus derives page depth from indentation, so an off-indent entry miscategorizes the page. +- **Nav is newest-to-oldest** (Beta → active line → Legacy) and nests with **2-space indent** — the SUMMARY-driven LLM corpus derives page depth from indentation, so an off-indent entry miscategorizes the page. - **Release datelines are real dates by design.** The no-calendar-dates rule is about forward-looking promises, not the historical record of when a version shipped. ## Automation -- **`pnpm docs:changelog`** does the mechanical half — scaffolds missing `## {version}` stubs, new `{minor}.x` pages and their nav entries, and the Alpha "raw material" list. Deterministic, idempotent, no-op-safe; it writes stubs, never prose. +- **`pnpm docs:changelog`** does the mechanical half — scaffolds missing `## {version}` stubs, new `{minor}.x` pages and their nav entries, and the Beta "raw material" list. Deterministic, idempotent, no-op-safe; it writes stubs, never prose. - **The `sdk-changelog` skill** does the editorial half — drafting the plain-language prose from those stubs. - **Promotion is automatic**, inside `pnpm docs:retarget main` at the release merge, with the version derived from the newest mainline entry in `CHANGELOG.md` — so pull the release commit before retargeting, or promotion silently no-ops. - **Enforcement:** `pnpm docs:check-links` (links, anchors, orphans) and the SUMMARY-driven corpus (`pnpm llm:build`). diff --git a/docs/agents/fhevm-sdk-1x-migration.md b/docs/agents/fhevm-sdk-1x-migration.md index 1cf05fb4b..4fc691481 100644 --- a/docs/agents/fhevm-sdk-1x-migration.md +++ b/docs/agents/fhevm-sdk-1x-migration.md @@ -16,7 +16,7 @@ > **This doc is now a historical record** of two migrations that have both landed. Keep it for the > rationale and the rename mapping (still an accurate reference for the current API); it no longer > describes pending work. For the user-facing description of the current backend, see -> [`changelog/alpha.md`](../gitbook/src/changelog/alpha.md) and +> [`changelog/beta.md`](../gitbook/src/changelog/beta.md) and > [`concepts/architecture.md`](../gitbook/src/concepts/architecture.md). Internal feedback that originally set this split (track 1 was deferred first, then completed): diff --git a/docs/agents/gotchas.md b/docs/agents/gotchas.md index 5eb1a52d1..ddee8b773 100644 --- a/docs/agents/gotchas.md +++ b/docs/agents/gotchas.md @@ -3,8 +3,8 @@ Non-obvious things that have caused real bugs or friction. - **Address normalization in query keys.** All addresses in query keys must use `getAddress()` for checksumming. Inconsistent casing causes cache misses. -- **Never delete or rename `main`, `prerelease`, or `release/*` branches locally** — they're shared across worktrees and tooling. If a tool needs a specific branch name for verification, use a throwaway name or a `/tmp` clone. -- **PR base defaults to `prerelease`**, not `main`. `main` is reserved for release/CI infrastructure PRs (`.releaserc*`, `scripts/release/`, release workflows). Product and feature work targets `prerelease`. +- **Never delete or rename `main`, `beta`, `alpha`, or `release/*` branches locally** — they're shared across worktrees and tooling. If a tool needs a specific branch name for verification, use a throwaway name or a `/tmp` clone. +- **PR base defaults to `beta`**, not `main`. `main` is reserved for release/CI infrastructure PRs (`.releaserc*`, `scripts/release/`, release workflows). Product and feature work targets `beta`; `alpha` is a protected branch used only to test protocol-breaking changes (synced from `beta`, merged back once stable), not a feature-PR target. - **`storage` and `permitStorage` can safely share one backing store.** Transport-key-pair and permit keys are namespaced internally (`keypair:*` vs `permits:*`), so one shared `IndexedDBStorage` instance never collides — and it's the SDK's own default when `permitStorage` is omitted. Don't add a "must use separate stores" warning; it isn't true. - **Chain reads live on `GenericProvider`, wallet writes on `GenericSigner`.** Custom adapters must split read- vs write-side responsibilities across the two interfaces or the SDK's routing breaks. - **`signTypedData`: strip `EIP712Domain` from `types` before calling the wallet client.** Viem injects it automatically from the `domain` field — passing it in `types` causes a signature mismatch that surfaces as a credential rejection at decrypt time. diff --git a/docs/gitbook/src/SUMMARY.md b/docs/gitbook/src/SUMMARY.md index 54175b026..7cbf297eb 100644 --- a/docs/gitbook/src/SUMMARY.md +++ b/docs/gitbook/src/SUMMARY.md @@ -16,7 +16,7 @@ ## Changelog -- [Alpha (unreleased)](changelog/alpha.md) +- [Beta (unreleased)](changelog/beta.md) - [3.x (current)](changelog/v3.md) - [3.5.x](changelog/v3-5.md) - [3.4.x](changelog/v3-4.md) diff --git a/docs/gitbook/src/changelog/alpha.md b/docs/gitbook/src/changelog/alpha.md deleted file mode 100644 index c395ef88b..000000000 --- a/docs/gitbook/src/changelog/alpha.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: Alpha -description: Unreleased changes on the prerelease (alpha) line — not yet in a stable release. ---- - -# Alpha - -{% hint style="warning" %} -**Unreleased.** The changes on this page are on the prerelease (`alpha`) line and are **not yet available in a stable release**. They ship with the next stable release, at which point this page is retitled to that version and folded into the version list above. Treat everything here as a preview — details may still change before release. -{% endhint %} - -_Nothing is staged for the next release yet. As alpha builds land, run `pnpm docs:changelog` to refresh this page._ diff --git a/docs/gitbook/src/changelog/beta.md b/docs/gitbook/src/changelog/beta.md new file mode 100644 index 000000000..37d8f2494 --- /dev/null +++ b/docs/gitbook/src/changelog/beta.md @@ -0,0 +1,12 @@ +--- +title: Beta +description: Unreleased changes on the prerelease (beta) line — not yet in a stable release. +--- + +# Beta + +{% hint style="warning" %} +**Unreleased.** The changes on this page are on the prerelease (`beta`) line and are **not yet available in a stable release**. They ship with the next stable release, at which point this page is retitled to that version and folded into the version list above. Treat everything here as a preview — details may still change before release. +{% endhint %} + +_Nothing is staged for the next release yet. As beta builds land, run `pnpm docs:changelog` to refresh this page._ diff --git a/docs/gitbook/src/concepts/security-model.md b/docs/gitbook/src/concepts/security-model.md index cc77fd265..ecae2ca6f 100644 --- a/docs/gitbook/src/concepts/security-model.md +++ b/docs/gitbook/src/concepts/security-model.md @@ -161,7 +161,7 @@ Use `sdk.permits.warmTransportKeyPairScope(scopeId)`, not `sdk.permits.warmTrans The TFHE WASM binaries ship **inside the `@fhevm/sdk` npm package** and load from your own bundle — there is no runtime CDN fetch. Integrity is guaranteed the same way as any other dependency: by your package manager's lockfile hashes and your build pipeline. -For advanced deployments that host the WASM assets on a URL instead (via the `runtime` option's `wasmAssetLoadMode` / `locateFile`), the integrity guarantee depends on the mode: `verified-blob` SHA-verifies the fetched bytes against hashes pinned in the library and fails initialization on mismatch, `precheck-direct-url` runs a precheck request before loading directly from the URL, and `trusted-direct-url` loads directly with no verification (fastest, least defensive). The `embedded-base64` mode inlines the WASM as base64 with no network fetch at all. See the [`wasmAssetLoadMode` mode table](../changelog/alpha.md) for the full list. +For advanced deployments that host the WASM assets on a URL instead (via the `runtime` option's `wasmAssetLoadMode` / `locateFile`), the integrity guarantee depends on the mode: `verified-blob` SHA-verifies the fetched bytes against hashes pinned in the library and fails initialization on mismatch, `precheck-direct-url` runs a precheck request before loading directly from the URL, and `trusted-direct-url` loads directly with no verification (fastest, least defensive). The `embedded-base64` mode inlines the WASM as base64 with no network fetch at all. See the [`wasmAssetLoadMode` mode table](../changelog/beta.md) for the full list. ![WASM loading and integrity](../images/security-wasm-integrity.svg) diff --git a/docs/llm/corpus-manifest.json b/docs/llm/corpus-manifest.json index e9408df69..53b90d2a3 100644 --- a/docs/llm/corpus-manifest.json +++ b/docs/llm/corpus-manifest.json @@ -97,14 +97,14 @@ "include_in_llms_full": true }, { - "id": "doc:changelog/alpha", - "title": "Alpha", - "source_path": "docs/gitbook/src/changelog/alpha.md", - "source_url": "https://raw.githubusercontent.com/zama-ai/sdk/main/docs/gitbook/src/changelog/alpha.md", + "id": "doc:changelog/beta", + "title": "Beta", + "source_path": "docs/gitbook/src/changelog/beta.md", + "source_url": "https://raw.githubusercontent.com/zama-ai/sdk/main/docs/gitbook/src/changelog/beta.md", "source_type": "official-doc", "category": "changelog", - "logical_path": "changelog/alpha", - "description": "Unreleased changes on the prerelease (alpha) line — not yet in a stable release.", + "logical_path": "changelog/beta", + "description": "Unreleased changes on the prerelease (beta) line — not yet in a stable release.", "include_in_llms_txt": true, "include_in_llms_full": true }, diff --git a/llms-full.txt b/llms-full.txt index 3535845fb..937df8597 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -2124,21 +2124,21 @@ Stuck on the migration, or spotted a step or rename this guide is missing? **Ope [s6]: #step-6-token-wrappedtoken-upgraded-contracts [s7]: #step-7-removed-with-no-replacement -## Alpha +## Beta - source_type: official-doc -- source_path: docs/gitbook/src/changelog/alpha.md -- source_url: https://raw.githubusercontent.com/zama-ai/sdk/main/docs/gitbook/src/changelog/alpha.md -- logical_path: changelog/alpha +- source_path: docs/gitbook/src/changelog/beta.md +- source_url: https://raw.githubusercontent.com/zama-ai/sdk/main/docs/gitbook/src/changelog/beta.md +- logical_path: changelog/beta -# Alpha +# Beta > [!WARNING] -> **Unreleased.** The changes on this page are on the prerelease (`alpha`) line and are **not yet available in a stable release**. They ship with the next stable release, at which point this page is retitled to that version and folded into the version list above. Treat everything here as a preview — details may still change before release. +> **Unreleased.** The changes on this page are on the prerelease (`beta`) line and are **not yet available in a stable release**. They ship with the next stable release, at which point this page is retitled to that version and folded into the version list above. Treat everything here as a preview — details may still change before release. -_Nothing is staged for the next release yet. As alpha builds land, run `pnpm docs:changelog` to refresh this page._ +_Nothing is staged for the next release yet. As beta builds land, run `pnpm docs:changelog` to refresh this page._ ## 3.x @@ -19204,7 +19204,7 @@ A fresh key pair invalidates every permit granted for the old one, so the signer The TFHE WASM binaries ship **inside the `@fhevm/sdk` npm package** and load from your own bundle — there is no runtime CDN fetch. Integrity is guaranteed the same way as any other dependency: by your package manager's lockfile hashes and your build pipeline. -For advanced deployments that host the WASM assets on a URL instead (via the `runtime` option's `wasmAssetLoadMode` / `locateFile`), the integrity guarantee depends on the mode: `verified-blob` SHA-verifies the fetched bytes against hashes pinned in the library and fails initialization on mismatch, `precheck-direct-url` runs a precheck request before loading directly from the URL, and `trusted-direct-url` loads directly with no verification (fastest, least defensive). The `embedded-base64` mode inlines the WASM as base64 with no network fetch at all. See the [`wasmAssetLoadMode` mode table](https://raw.githubusercontent.com/zama-ai/sdk/main/docs/gitbook/src/changelog/alpha.md) for the full list. +For advanced deployments that host the WASM assets on a URL instead (via the `runtime` option's `wasmAssetLoadMode` / `locateFile`), the integrity guarantee depends on the mode: `verified-blob` SHA-verifies the fetched bytes against hashes pinned in the library and fails initialization on mismatch, `precheck-direct-url` runs a precheck request before loading directly from the URL, and `trusted-direct-url` loads directly with no verification (fastest, least defensive). The `embedded-base64` mode inlines the WASM as base64 with no network fetch at all. See the [`wasmAssetLoadMode` mode table](https://raw.githubusercontent.com/zama-ai/sdk/main/docs/gitbook/src/changelog/beta.md) for the full list. ![WASM loading and integrity](../images/security-wasm-integrity.svg) @@ -22254,9 +22254,9 @@ npm run test:e2e # Playwright e2e tests (starts dev server a

- Dev release + Dev release - Dev release last updated + Dev release last updated CI diff --git a/llms.txt b/llms.txt index 6c0bfc226..2eae81879 100644 --- a/llms.txt +++ b/llms.txt @@ -131,7 +131,7 @@ Use this file for discovery. Follow the links to fetch the smallest source that ### Changelog -- [Alpha](https://raw.githubusercontent.com/zama-ai/sdk/main/docs/gitbook/src/changelog/alpha.md): Unreleased changes on the prerelease (alpha) line — not yet in a stable release. +- [Beta](https://raw.githubusercontent.com/zama-ai/sdk/main/docs/gitbook/src/changelog/beta.md): Unreleased changes on the prerelease (beta) line — not yet in a stable release. - [3.x](https://raw.githubusercontent.com/zama-ai/sdk/main/docs/gitbook/src/changelog/v3.md): Human-readable release notes for the 3.x line — what changed in each minor version of @zama-fhe/sdk and @zama-fhe/react-sdk, in plain language. - [3.5.x](https://raw.githubusercontent.com/zama-ai/sdk/main/docs/gitbook/src/changelog/v3-5.md): Release notes for the 3.5.x line. - [3.4.x](https://raw.githubusercontent.com/zama-ai/sdk/main/docs/gitbook/src/changelog/v3-4.md): Release notes for the 3.4.x line. diff --git a/scripts/docs/__tests__/changelog.test.mjs b/scripts/docs/__tests__/changelog.test.mjs index cbe9dc1db..55f1eff16 100644 --- a/scripts/docs/__tests__/changelog.test.mjs +++ b/scripts/docs/__tests__/changelog.test.mjs @@ -12,12 +12,12 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, test } from "vitest"; import { - alphaHasSubstance, - alphaReleasesSinceLastMainline, + betaHasSubstance, + betaReleasesSinceLastMainline, compareVersions, - emptyAlphaTemplate, + emptyBetaTemplate, hasVersionSection, - isAlphaVersion, + isBetaVersion, newestMainline, parseChangelog, parseVersion, @@ -28,14 +28,14 @@ import { // ─────────────────────────────────────────────────────────────── parser ── -// Mirrors real semantic-release output: newest-first, an alpha tip above the +// Mirrors real semantic-release output: newest-first, a beta tip above the // last mainline release, multiple typed H3 sections, a BREAKING CHANGES block // with real squash noise (Co-Authored-By lines + a `*`-prefixed stray bullet // that must NOT be picked up as a real bullet), and both hash-link shapes // (`([hash]())` empty and a full commit URL). const FIXTURE_CHANGELOG = `# Changelog -## [3.4.0-alpha.6](https://github.com/zama-ai/sdk/compare/v3.4.0-alpha.5...v3.4.0-alpha.6) (2026-07-16) +## [3.4.0-beta.6](https://github.com/zama-ai/sdk/compare/v3.4.0-beta.5...v3.4.0-beta.6) (2026-07-16) ### Code Refactoring @@ -73,24 +73,24 @@ Co-Authored-By: Claude Opus 4.6 (1M context) describe("parseChangelog", () => { const releases = parseChangelog(FIXTURE_CHANGELOG); - test("parses newest-first: alpha tip then mainline release", () => { + test("parses newest-first: beta tip then mainline release", () => { expect(releases).toHaveLength(2); - expect(releases[0].version).toBe("3.4.0-alpha.6"); + expect(releases[0].version).toBe("3.4.0-beta.6"); expect(releases[1].version).toBe("3.3.0"); }); - test("extracts isAlpha + major/minor/patch + date for the alpha release", () => { - const alpha = releases[0]; - expect(alpha.isAlpha).toBe(true); - expect(alpha.major).toBe(3); - expect(alpha.minor).toBe(4); - expect(alpha.patch).toBe(0); - expect(alpha.date).toBe("2026-07-16"); + test("extracts isBeta + major/minor/patch + date for the beta release", () => { + const beta = releases[0]; + expect(beta.isBeta).toBe(true); + expect(beta.major).toBe(3); + expect(beta.minor).toBe(4); + expect(beta.patch).toBe(0); + expect(beta.date).toBe("2026-07-16"); }); - test("extracts isAlpha + major/minor/patch + date for the mainline release", () => { + test("extracts isBeta + major/minor/patch + date for the mainline release", () => { const mainline = releases[1]; - expect(mainline.isAlpha).toBe(false); + expect(mainline.isBeta).toBe(false); expect(mainline.major).toBe(3); expect(mainline.minor).toBe(3); expect(mainline.patch).toBe(0); @@ -156,13 +156,13 @@ describe("parseChangelog", () => { }); }); -describe("parseVersion / isAlphaVersion", () => { +describe("parseVersion / isBetaVersion", () => { test("parses core + prerelease", () => { - expect(parseVersion("3.4.0-alpha.6")).toEqual({ + expect(parseVersion("3.4.0-beta.6")).toEqual({ major: 3, minor: 4, patch: 0, - prerelease: "alpha.6", + prerelease: "beta.6", }); expect(parseVersion("3.3.0")).toEqual({ major: 3, minor: 3, patch: 0, prerelease: null }); }); @@ -171,10 +171,10 @@ describe("parseVersion / isAlphaVersion", () => { expect(parseVersion("not-a-version")).toBeNull(); }); - test("isAlphaVersion only matches the -alpha.N shape, not other prerelease tags", () => { - expect(isAlphaVersion("3.4.0-alpha.6")).toBe(true); - expect(isAlphaVersion("3.4.0")).toBe(false); - expect(isAlphaVersion("3.4.0-beta.1")).toBe(false); + test("isBetaVersion only matches the -beta.N shape, not other prerelease tags", () => { + expect(isBetaVersion("3.4.0-beta.6")).toBe(true); + expect(isBetaVersion("3.4.0")).toBe(false); + expect(isBetaVersion("3.4.0-alpha.1")).toBe(false); }); }); @@ -231,42 +231,68 @@ describe("compareVersions", () => { expect(compareVersions("3.4.0", "3.3.0")).toBeGreaterThan(0); }); - test("a mainline release outranks its own alpha prereleases (same core version)", () => { - expect(compareVersions("3.4.0", "3.4.0-alpha.6")).toBeGreaterThan(0); - expect(compareVersions("3.4.0-alpha.6", "3.4.0")).toBeLessThan(0); + test("a mainline release outranks its own beta prereleases (same core version)", () => { + expect(compareVersions("3.4.0", "3.4.0-beta.6")).toBeGreaterThan(0); + expect(compareVersions("3.4.0-beta.6", "3.4.0")).toBeLessThan(0); }); - test("orders alpha builds numerically, not lexicographically", () => { - expect(compareVersions("3.4.0-alpha.2", "3.4.0-alpha.10")).toBeLessThan(0); + test("orders beta builds numerically, not lexicographically", () => { + expect(compareVersions("3.4.0-beta.2", "3.4.0-beta.10")).toBeLessThan(0); }); }); -describe("newestMainline / alphaReleasesSinceLastMainline", () => { - test("newestMainline picks the first non-alpha release from a newest-first list", () => { +describe("newestMainline / betaReleasesSinceLastMainline", () => { + test("newestMainline picks the first non-beta release from a newest-first list", () => { const releases = [ - { version: "3.4.0-alpha.6", isAlpha: true }, - { version: "3.3.0", isAlpha: false }, + { version: "3.4.0-beta.6", isBeta: true, isPrerelease: true }, + { version: "3.3.0", isBeta: false, isPrerelease: false }, ]; expect(newestMainline(releases).version).toBe("3.3.0"); }); test("newestMainline returns null when there is no mainline release yet", () => { - expect(newestMainline([{ version: "3.4.0-alpha.1", isAlpha: true }])).toBeNull(); + expect( + newestMainline([{ version: "3.4.0-beta.1", isBeta: true, isPrerelease: true }]), + ).toBeNull(); + }); + + // Regression: an `-alpha.N` entry has isBeta=false but is NOT mainline. Before the + // isPrerelease fix, `!isBeta` was read as "is mainline", so the alpha tip was taken as + // the shipped version and `docs:retarget main` promoted a bogus prerelease section. + test("alpha entries are not mainline and are excluded from the beta tip", () => { + const releases = parseChangelog( + [ + "# Changelog", + "", + "## [3.6.0-beta.2](url) (2026-09-02)", + "## [3.6.0-alpha.1](url) (2026-09-01)", + "## [3.6.0-beta.1](url) (2026-08-31)", + "## [3.5.0](url) (2026-08-26)", + "", + ].join("\n"), + ); + expect(newestMainline(releases).version).toBe("3.5.0"); + expect(betaReleasesSinceLastMainline(releases).map((r) => r.version)).toEqual([ + "3.6.0-beta.2", + "3.6.0-beta.1", + ]); }); - test("alphaReleasesSinceLastMainline stops at the first mainline release, ignoring older alphas beyond it", () => { + test("betaReleasesSinceLastMainline stops at the first mainline release, ignoring older betas beyond it", () => { const releases = [ - { version: "3.4.0-alpha.6", isAlpha: true }, - { version: "3.4.0-alpha.5", isAlpha: true }, - { version: "3.3.0", isAlpha: false }, - { version: "3.3.0-alpha.1", isAlpha: true }, // older alpha, past the mainline boundary + { version: "3.4.0-beta.6", isBeta: true, isPrerelease: true }, + { version: "3.4.0-beta.5", isBeta: true, isPrerelease: true }, + { version: "3.3.0", isBeta: false, isPrerelease: false }, + { version: "3.3.0-beta.1", isBeta: true, isPrerelease: true }, // older beta, past the mainline boundary ]; - const alphas = alphaReleasesSinceLastMainline(releases); - expect(alphas.map((r) => r.version)).toEqual(["3.4.0-alpha.6", "3.4.0-alpha.5"]); + const betas = betaReleasesSinceLastMainline(releases); + expect(betas.map((r) => r.version)).toEqual(["3.4.0-beta.6", "3.4.0-beta.5"]); }); test("returns [] when the changelog opens on a mainline release (no unreleased tip)", () => { - expect(alphaReleasesSinceLastMainline([{ version: "3.3.0", isAlpha: false }])).toEqual([]); + expect( + betaReleasesSinceLastMainline([{ version: "3.3.0", isBeta: false, isPrerelease: false }]), + ).toEqual([]); }); }); @@ -280,22 +306,22 @@ describe("hasVersionSection", () => { }); }); -describe("alphaHasSubstance", () => { +describe("betaHasSubstance", () => { test("the freshly-reset template has no substance", () => { - expect(alphaHasSubstance(emptyAlphaTemplate())).toBe(false); + expect(betaHasSubstance(emptyBetaTemplate())).toBe(false); }); test("real staged prose counts as substance", () => { - const staged = `${emptyAlphaTemplate()}\n## New feature\n\nSome real notes.\n`; - expect(alphaHasSubstance(staged)).toBe(true); + const staged = `${emptyBetaTemplate()}\n## New feature\n\nSome real notes.\n`; + expect(betaHasSubstance(staged)).toBe(true); }); test("comment noise that survives a single strip pass is not substance", () => { // A single `` removal on this leaves `` behind (the // outer ``); only stripping until stable empties // it. Regression for the incomplete-multi-character-sanitization fix. - const noise = `${emptyAlphaTemplate()}\n- R -->\n`; - expect(alphaHasSubstance(noise)).toBe(false); + const noise = `${emptyBetaTemplate()}\n- R -->\n`; + expect(betaHasSubstance(noise)).toBe(false); }); }); @@ -351,15 +377,15 @@ const CHANGELOG_MD_FIXTURE = `# Changelog - **sdk:** add confidentialTransferAndCall to Token methods ([#423](https://github.com/zama-ai/sdk/issues/423)) ([7b2b916]()) `; -const STAGED_ALPHA_FIXTURE = `--- -title: Alpha -description: Unreleased changes on the prerelease (alpha) line — not yet in a stable release. +const STAGED_BETA_FIXTURE = `--- +title: Beta +description: Unreleased changes on the prerelease (beta) line — not yet in a stable release. --- -# Alpha +# Beta {% hint style="warning" %} -**Unreleased.** The changes on this page are on the prerelease (\`alpha\`) line and are **not yet available in a stable release**. They ship with the next stable release, at which point this page is retitled to that version and folded into the version list above. Treat everything here as a preview — details may still change before release. +**Unreleased.** The changes on this page are on the prerelease (\`beta\`) line and are **not yet available in a stable release**. They ship with the next stable release, at which point this page is retitled to that version and folded into the version list above. Treat everything here as a preview — details may still change before release. {% endhint %} ## wrap() for two-signature shield @@ -368,18 +394,18 @@ description: Unreleased changes on the prerelease (alpha) line — not yet in a `; /** A minimal, valid GitBook + CHANGELOG.md tree, not yet promoted: a mainline - * 3.4.0 release exists in CHANGELOG.md, no v3-4.md page yet, and the Alpha + * 3.4.0 release exists in CHANGELOG.md, no v3-4.md page yet, and the Beta * page carries genuine staged prose. */ function writeFreshFixture(root) { mkdirSync(join(root, CHANGELOG_DIR), { recursive: true }); writeFileSync(join(root, "CHANGELOG.md"), CHANGELOG_MD_FIXTURE); writeFileSync(join(root, "docs/gitbook/src/SUMMARY.md"), SUMMARY_FIXTURE); writeFileSync(join(root, `${CHANGELOG_DIR}/v3.md`), V3_INDEX_FIXTURE); - writeFileSync(join(root, `${CHANGELOG_DIR}/alpha.md`), STAGED_ALPHA_FIXTURE); + writeFileSync(join(root, `${CHANGELOG_DIR}/beta.md`), STAGED_BETA_FIXTURE); } describe("promoteChangelog", () => { - test("fresh-alpha fixture: promotes, creates the version page, resets alpha", () => { + test("fresh-beta fixture: promotes, creates the version page, resets beta", () => { const root = makeRoot(); writeFreshFixture(root); @@ -392,7 +418,7 @@ describe("promoteChangelog", () => { const versionPage = readFileSync(join(root, `${CHANGELOG_DIR}/v3-4.md`), "utf8"); expect(hasVersionSection(versionPage, "3.4.0")).toBe(true); expect(versionPage).toContain("_Released 2026-07-16._"); - // Alpha prose was carried over, demoted one heading level (## → ###). + // Beta prose was carried over, demoted one heading level (## → ###). expect(versionPage).toContain("### wrap() for two-signature shield"); const summary = readFileSync(join(root, "docs/gitbook/src/SUMMARY.md"), "utf8"); @@ -401,8 +427,8 @@ describe("promoteChangelog", () => { const index = readFileSync(join(root, `${CHANGELOG_DIR}/v3.md`), "utf8"); expect(index).toContain("(v3-4.md)"); - const alpha = readFileSync(join(root, `${CHANGELOG_DIR}/alpha.md`), "utf8"); - expect(alphaHasSubstance(alpha)).toBe(false); + const beta = readFileSync(join(root, `${CHANGELOG_DIR}/beta.md`), "utf8"); + expect(betaHasSubstance(beta)).toBe(false); }); test("already-promoted fixture (target `## {version}` already present): no-op, zero writes", () => { @@ -434,16 +460,16 @@ Already-written plain-language notes. expect(result.changed).toEqual([]); }); - test("already-promoted fixture (alpha already at the reset template): no-op, zero writes", () => { + test("already-promoted fixture (beta already at the reset template): no-op, zero writes", () => { const root = makeRoot(); writeFreshFixture(root); - // No version page yet, but nothing is staged on Alpha (secondary guard). - writeFileSync(join(root, `${CHANGELOG_DIR}/alpha.md`), emptyAlphaTemplate()); + // No version page yet, but nothing is staged on Beta (secondary guard). + writeFileSync(join(root, `${CHANGELOG_DIR}/beta.md`), emptyBetaTemplate()); const result = promoteChangelog({ root }); expect(result.promoted).toBe(false); - expect(result.reason).toBe("alpha page is empty"); + expect(result.reason).toBe("beta page is empty"); expect(result.changed).toEqual([]); }); @@ -455,7 +481,7 @@ Already-written plain-language notes. expect(first.promoted).toBe(true); const versionPageAfterFirst = readFileSync(join(root, `${CHANGELOG_DIR}/v3-4.md`), "utf8"); - const alphaAfterFirst = readFileSync(join(root, `${CHANGELOG_DIR}/alpha.md`), "utf8"); + const betaAfterFirst = readFileSync(join(root, `${CHANGELOG_DIR}/beta.md`), "utf8"); const second = promoteChangelog({ root }); @@ -467,7 +493,7 @@ Already-written plain-language notes. expect(readFileSync(join(root, `${CHANGELOG_DIR}/v3-4.md`), "utf8")).toBe( versionPageAfterFirst, ); - expect(readFileSync(join(root, `${CHANGELOG_DIR}/alpha.md`), "utf8")).toBe(alphaAfterFirst); + expect(readFileSync(join(root, `${CHANGELOG_DIR}/beta.md`), "utf8")).toBe(betaAfterFirst); }); }); @@ -505,9 +531,9 @@ Already-written plain-language notes. // ─────────────────────────────────────── stale-CHANGELOG warning (Fix #1) ── describe("promoteChangelog — stale-CHANGELOG.md warning", () => { - test("warns (but stays a no-op, zero writes) when already-promoted AND alpha still has substance", () => { + test("warns (but stays a no-op, zero writes) when already-promoted AND beta still has substance", () => { const root = makeRoot(); - writeFreshFixture(root); // alpha carries genuine staged prose + writeFreshFixture(root); // beta carries genuine staged prose // The newest mainline entry (3.4.0) is already on its page — the state the guard // reaches when CHANGELOG.md is stale (the release commit adding a NEWER `## [X.Y.Z]` // wasn't pulled), so `target` derived to the prior, already-promoted version. @@ -525,12 +551,12 @@ describe("promoteChangelog — stale-CHANGELOG.md warning", () => { expect(snapshotTree(root)).toEqual(before); }); - test("does NOT warn on a legitimate idempotent re-run (alpha already reset to empty)", () => { + test("does NOT warn on a legitimate idempotent re-run (beta already reset to empty)", () => { const root = makeRoot(); writeFreshFixture(root); - // Genuine already-promoted state: version page present AND alpha reset — a true no-op. + // Genuine already-promoted state: version page present AND beta reset — a true no-op. writeFileSync(join(root, `${CHANGELOG_DIR}/v3-4.md`), PRE_PROMOTED_V3_4); - writeFileSync(join(root, `${CHANGELOG_DIR}/alpha.md`), emptyAlphaTemplate()); + writeFileSync(join(root, `${CHANGELOG_DIR}/beta.md`), emptyBetaTemplate()); const result = promoteChangelog({ root }); @@ -543,12 +569,12 @@ describe("promoteChangelog — stale-CHANGELOG.md warning", () => { // ────────────────────── self-referential + sibling anchor repointing (Fix #2/#6) ── describe("promoteChangelog — anchor repointing in the moved prose", () => { - const ALPHA_WITH_SELF_REFS = `--- -title: Alpha -description: Unreleased changes on the prerelease (alpha) line — not yet in a stable release. + const BETA_WITH_SELF_REFS = `--- +title: Beta +description: Unreleased changes on the prerelease (beta) line — not yet in a stable release. --- -# Alpha +# Beta {% hint style="warning" %} **Unreleased.** Preview only. @@ -556,41 +582,41 @@ description: Unreleased changes on the prerelease (alpha) line — not yet in a ## Runtime tuning -See [error handling](alpha.md#error-handling) and the [runtime notes](./alpha.md#runtime-tuning) for caveats. Not the sibling [myalpha](myalpha.md#sibling) doc. +See [error handling](beta.md#error-handling) and the [runtime notes](./beta.md#runtime-tuning) for caveats. Not the sibling [mybeta](mybeta.md#sibling) doc. ## Error handling Errors now carry typed causes. `; - test("self-referential `](alpha.md#…)` / `](./alpha.md#…)` links are repointed to the version page", () => { + test("self-referential `](beta.md#…)` / `](./beta.md#…)` links are repointed to the version page", () => { const root = makeRoot(); writeFreshFixture(root); - writeFileSync(join(root, `${CHANGELOG_DIR}/alpha.md`), ALPHA_WITH_SELF_REFS); + writeFileSync(join(root, `${CHANGELOG_DIR}/beta.md`), BETA_WITH_SELF_REFS); const result = promoteChangelog({ root }); expect(result.promoted).toBe(true); const versionPage = readFileSync(join(root, `${CHANGELOG_DIR}/v3-4.md`), "utf8"); - // Both self-refs now point at the version page (same changelog/ dir), not alpha.md + // Both self-refs now point at the version page (same changelog/ dir), not beta.md // (which just reset — those links would dangle without the fix). The `./` prefix is // preserved on the one that carried it. expect(versionPage).toContain("(v3-4.md#error-handling)"); expect(versionPage).toContain("(./v3-4.md#runtime-tuning)"); - expect(versionPage).not.toContain("(alpha.md#"); // no un-repointed self-ref remains - // Fix #6 boundary: the sibling `myalpha.md#…` is NOT a match and stays put. - expect(versionPage).toContain("(myalpha.md#sibling)"); + expect(versionPage).not.toContain("(beta.md#"); // no un-repointed self-ref remains + // Fix #6 boundary: the sibling `mybeta.md#…` is NOT a match and stays put. + expect(versionPage).toContain("(mybeta.md#sibling)"); }); - test("inbound `](myalpha.md#slug)` on another page is left alone (path-boundary)", () => { + test("inbound `](mybeta.md#slug)` on another page is left alone (path-boundary)", () => { const root = makeRoot(); writeFreshFixture(root); - // A page whose inbound links include BOTH the real alpha.md anchor and a sibling - // `myalpha.md` that merely ends in `alpha.md#…`. The moved prose owns `#wrap-…`. + // A page whose inbound links include BOTH the real beta.md anchor and a sibling + // `mybeta.md` that merely ends in `beta.md#…`. The moved prose owns `#wrap-…`. const sibling = `# Sibling -Real: [wrap notes](alpha.md#wrap-for-two-signature-shield). -Decoy: [other](myalpha.md#wrap-for-two-signature-shield). +Real: [wrap notes](beta.md#wrap-for-two-signature-shield). +Decoy: [other](mybeta.md#wrap-for-two-signature-shield). `; writeFileSync(join(root, `${CHANGELOG_DIR}/v3-2.md`), sibling); @@ -599,7 +625,7 @@ Decoy: [other](myalpha.md#wrap-for-two-signature-shield). const page = readFileSync(join(root, `${CHANGELOG_DIR}/v3-2.md`), "utf8"); expect(page).toContain("(v3-4.md#wrap-for-two-signature-shield)"); // real link repointed - expect(page).toContain("(myalpha.md#wrap-for-two-signature-shield)"); // decoy untouched + expect(page).toContain("(mybeta.md#wrap-for-two-signature-shield)"); // decoy untouched expect(page).not.toContain("(myv3-4.md#"); // old bug would have corrupted it to this }); }); @@ -609,9 +635,9 @@ Decoy: [other](myalpha.md#wrap-for-two-signature-shield). describe("promoteChangelog — atomic failure isolation", () => { test("a throw AFTER edits are staged (during the anchor sweep) writes NOTHING to disk", () => { const root = makeRoot(); - writeFreshFixture(root); // alpha has a heading ⇒ repointAlphaAnchors runs (no early return) + writeFreshFixture(root); // beta has a heading ⇒ repointBetaAnchors runs (no early return) // Booby-trap: a DIRECTORY named `*.md` under the docs tree. The anchor sweep runs - // AFTER the version page / reset alpha / SUMMARY / index edits are staged in memory + // AFTER the version page / reset beta / SUMMARY / index edits are staged in memory // but BEFORE flush(); readFileSync on a directory throws EISDIR mid-sweep. Because // flush() is not atomic across files, "tree byte-identical after a throw" can only // hold if the throw lands in the compute phase — which is exactly what this asserts @@ -622,7 +648,7 @@ describe("promoteChangelog — atomic failure isolation", () => { expect(() => promoteChangelog({ root })).toThrow(/EISDIR/); - // No version page created, alpha not reset, SUMMARY/index untouched: fully atomic. + // No version page created, beta not reset, SUMMARY/index untouched: fully atomic. expect(existsSync(join(root, `${CHANGELOG_DIR}/v3-4.md`))).toBe(false); expect(snapshotTree(root)).toEqual(before); }); diff --git a/scripts/docs/__tests__/check-target.test.mjs b/scripts/docs/__tests__/check-target.test.mjs index d7ca3b272..b264d276a 100644 --- a/scripts/docs/__tests__/check-target.test.mjs +++ b/scripts/docs/__tests__/check-target.test.mjs @@ -35,7 +35,7 @@ function write(root, rel, content) { const SPACE_LINK = { stable: "https://docs.zama.org/protocol/sdk/guides/x.md", - alpha: "https://docs.zama.org/protocol/sdk/alpha/guides/x.md", + beta: "https://docs.zama.org/protocol/sdk/beta/guides/x.md", }; /** A minimal fixture: check-target only needs one doc page under docs/gitbook/src and @@ -71,27 +71,34 @@ describe("check-target main() — end to end", () => { expect(res.stdout).toContain("stable"); }); - test("clean prerelease fixture passes", () => { + test("clean beta fixture passes", () => { const dir = freshDir(); - makeFixture(dir, { branch: "prerelease", link: "alpha" }); - const res = runCheckTarget(dir, "prerelease"); + makeFixture(dir, { branch: "beta", link: "beta" }); + const res = runCheckTarget(dir, "beta"); expect(res.status).toBe(0); }); - test("fails when a doc links to the wrong (alpha) GitBook space for main", () => { + test("clean alpha fixture passes — raw self-targets alpha, docs use the beta space", () => { const dir = freshDir(); - makeFixture(dir, { branch: "main", link: "alpha" }); + makeFixture(dir, { branch: "alpha", link: "beta" }); + const res = runCheckTarget(dir, "alpha"); + expect(res.status).toBe(0); + }); + + test("fails when a doc links to the wrong (beta) GitBook space for main", () => { + const dir = freshDir(); + makeFixture(dir, { branch: "main", link: "beta" }); const res = runCheckTarget(dir, "main"); expect(res.status).toBe(1); - expect(res.stderr).toContain("alpha"); + expect(res.stderr).toContain("beta"); expect(res.stderr).toContain("overview.md"); }); test("fails on the corpus.config.json rawGithubBaseUrl assertion when it targets the other branch", () => { const dir = freshDir(); - // Docs are clean-stable (good for main) but the config still points at prerelease — + // Docs are clean-stable (good for main) but the config still points at beta — // isolates the rawGithubBaseUrl check from the per-file space check. - makeFixture(dir, { branch: "prerelease", link: "stable" }); + makeFixture(dir, { branch: "beta", link: "stable" }); const res = runCheckTarget(dir, "main"); expect(res.status).toBe(1); expect(res.stderr).toContain("corpus.config.json"); diff --git a/scripts/docs/__tests__/retarget.test.mjs b/scripts/docs/__tests__/retarget.test.mjs index 30b9a0e51..bf5815a59 100644 --- a/scripts/docs/__tests__/retarget.test.mjs +++ b/scripts/docs/__tests__/retarget.test.mjs @@ -16,8 +16,8 @@ import { fileURLToPath } from "node:url"; import { afterEach, describe, expect, test } from "vitest"; import { findUrlProblems } from "../check-target.mjs"; import { - alphaHasSubstance, - emptyAlphaTemplate, + betaHasSubstance, + emptyBetaTemplate, RAW_END, RAW_START, TODO_MARKER, @@ -27,77 +27,89 @@ import { retargetUrls } from "../retarget.mjs"; // ─────────────────────────────────────────────── function-level regressions ── describe("retargetUrls — raw.githubusercontent branch segment", () => { - test("main → prerelease flips the branch", () => { - expect(retargetUrls("raw.githubusercontent.com/zama-ai/sdk/main/llms.txt", "prerelease")).toBe( - "raw.githubusercontent.com/zama-ai/sdk/prerelease/llms.txt", + test("main → beta flips the branch", () => { + expect(retargetUrls("raw.githubusercontent.com/zama-ai/sdk/main/llms.txt", "beta")).toBe( + "raw.githubusercontent.com/zama-ai/sdk/beta/llms.txt", ); }); - test("prerelease → main flips back", () => { - expect( - retargetUrls("raw.githubusercontent.com/zama-ai/sdk/prerelease/llms-full.txt", "main"), - ).toBe("raw.githubusercontent.com/zama-ai/sdk/main/llms-full.txt"); + test("beta → main flips back", () => { + expect(retargetUrls("raw.githubusercontent.com/zama-ai/sdk/beta/llms-full.txt", "main")).toBe( + "raw.githubusercontent.com/zama-ai/sdk/main/llms-full.txt", + ); }); }); describe("retargetUrls — docs.zama.org space segment (stable is implicit)", () => { const stableUrl = "docs.zama.org/protocol/sdk/guides/handle-errors.md"; - const alphaUrl = "docs.zama.org/protocol/sdk/alpha/guides/handle-errors.md"; + const betaUrl = "docs.zama.org/protocol/sdk/beta/guides/handle-errors.md"; - test("main → prerelease INSERTS the alpha segment", () => { - expect(retargetUrls(stableUrl, "prerelease")).toBe(alphaUrl); + test("main → beta INSERTS the beta segment", () => { + expect(retargetUrls(stableUrl, "beta")).toBe(betaUrl); }); - test("prerelease → main REMOVES the alpha segment", () => { - expect(retargetUrls(alphaUrl, "main")).toBe(stableUrl); + test("beta → main REMOVES the beta segment", () => { + expect(retargetUrls(betaUrl, "main")).toBe(stableUrl); }); test("retargeting to the current branch is a no-op (idempotent)", () => { - expect(retargetUrls(alphaUrl, "prerelease")).toBe(alphaUrl); + expect(retargetUrls(betaUrl, "beta")).toBe(betaUrl); expect(retargetUrls(stableUrl, "main")).toBe(stableUrl); }); test("rewrites every occurrence on a line", () => { const both = `see ${stableUrl} and ${stableUrl}`; - expect(retargetUrls(both, "prerelease")).toBe(`see ${alphaUrl} and ${alphaUrl}`); + expect(retargetUrls(both, "beta")).toBe(`see ${betaUrl} and ${betaUrl}`); }); test("does not treat a real path segment as a space segment", () => { - // `overview` is a page, not a space — alpha must be inserted before it. - expect(retargetUrls("docs.zama.org/protocol/sdk/overview.md", "prerelease")).toBe( - "docs.zama.org/protocol/sdk/alpha/overview.md", + // `overview` is a page, not a space — beta must be inserted before it. + expect(retargetUrls("docs.zama.org/protocol/sdk/overview.md", "beta")).toBe( + "docs.zama.org/protocol/sdk/beta/overview.md", ); }); }); describe("retargetUrls — round-trip byte-identity", () => { - // A promotion (main→prerelease) followed by its inverse (prerelease→main) must + // A promotion (main→beta) followed by its inverse (beta→main) must // return the exact original bytes, and vice-versa. Any asymmetry in the - // insert/remove-`alpha/` logic would surface as a mismatch here. - test("main → prerelease → main is byte-identical", () => { + // insert/remove-`beta/` logic would surface as a mismatch here. + test("main → beta → main is byte-identical", () => { const stable = [ "raw.githubusercontent.com/zama-ai/sdk/main/llms.txt", "docs.zama.org/protocol/sdk/guides/x.md", "docs.zama.org/protocol/sdk/reference/sdk/Token.md", "github.com/zama-ai/sdk/blob/main/CHANGELOG.md", ].join("\n"); - expect(retargetUrls(retargetUrls(stable, "prerelease"), "main")).toBe(stable); + expect(retargetUrls(retargetUrls(stable, "beta"), "main")).toBe(stable); }); - test("prerelease → main → prerelease is byte-identical", () => { - const alpha = [ - "raw.githubusercontent.com/zama-ai/sdk/prerelease/llms-full.txt", - "docs.zama.org/protocol/sdk/alpha/guides/x.md", - "docs.zama.org/protocol/sdk/alpha/overview.md", + test("beta → main → beta is byte-identical", () => { + const beta = [ + "raw.githubusercontent.com/zama-ai/sdk/beta/llms-full.txt", + "docs.zama.org/protocol/sdk/beta/guides/x.md", + "docs.zama.org/protocol/sdk/beta/overview.md", + ].join("\n"); + expect(retargetUrls(retargetUrls(beta, "main"), "beta")).toBe(beta); + }); + + // beta↔alpha is a primary path (alpha syncs from beta). Both use the beta space, so the + // flip touches only the raw branch — the round-trip must leave the beta/ space untouched + // (no drift, no double-insert). + test("beta → alpha → beta is byte-identical", () => { + const beta = [ + "raw.githubusercontent.com/zama-ai/sdk/beta/llms-full.txt", + "docs.zama.org/protocol/sdk/beta/guides/x.md", + "docs.zama.org/protocol/sdk/beta/overview.md", ].join("\n"); - expect(retargetUrls(retargetUrls(alpha, "main"), "prerelease")).toBe(alpha); + expect(retargetUrls(retargetUrls(beta, "alpha"), "beta")).toBe(beta); }); }); describe("retargetUrls — mixed content", () => { test("only the wrong URL flips; correct and unrelated URLs stay put", () => { const content = [ - "docs.zama.org/protocol/sdk/alpha/wrong.md", // wrong for main → flips to stable + "docs.zama.org/protocol/sdk/beta/wrong.md", // wrong for main → flips to stable "docs.zama.org/protocol/sdk/right.md", // already stable → unchanged "raw.githubusercontent.com/zama-ai/sdk/main/ok.txt", // already main → unchanged "docs.zama.org/other/path.md", // not the /protocol/sdk/ space → unchanged @@ -119,7 +131,7 @@ describe("retargetUrls — malformed / out-of-scope links are left alone", () => // `.../sdk/main` (end of string, no following `/path`) has no trailing slash and // must be left untouched — flipping it would corrupt a non-file reference. const bare = "see raw.githubusercontent.com/zama-ai/sdk/main"; - expect(retargetUrls(bare, "prerelease")).toBe(bare); + expect(retargetUrls(bare, "beta")).toBe(bare); }); test("a github `blob/` link is never retargeted", () => { @@ -127,33 +139,113 @@ describe("retargetUrls — malformed / out-of-scope links are left alone", () => // point at main regardless of target — it is neither a raw.github corpus link nor // a docs.zama.org space link, so retargetUrls must not touch it. const blob = "github.com/zama-ai/sdk/blob/main/CHANGELOG.md"; - expect(retargetUrls(blob, "prerelease")).toBe(blob); + expect(retargetUrls(blob, "beta")).toBe(blob); expect(retargetUrls(blob, "main")).toBe(blob); }); }); describe("findUrlProblems — catches the implicit-stable blind spot", () => { - test("space-less (stable) URL on prerelease is flagged", () => { - const problems = findUrlProblems("docs.zama.org/protocol/sdk/guides/x.md", "prerelease"); - expect(problems).toEqual([`links to the "stable" GitBook space (expected "alpha")`]); + test("space-less (stable) URL on beta is flagged", () => { + const problems = findUrlProblems("docs.zama.org/protocol/sdk/guides/x.md", "beta"); + expect(problems).toEqual([`links to the "stable" GitBook space (expected "beta")`]); }); - test("alpha URL on main is flagged", () => { - const problems = findUrlProblems("docs.zama.org/protocol/sdk/alpha/guides/x.md", "main"); - expect(problems).toEqual([`links to the "alpha" GitBook space (expected "stable")`]); + test("beta URL on main is flagged", () => { + const problems = findUrlProblems("docs.zama.org/protocol/sdk/beta/guides/x.md", "main"); + expect(problems).toEqual([`links to the "beta" GitBook space (expected "stable")`]); }); test("correctly-targeted URLs are clean in both directions", () => { - expect(findUrlProblems("docs.zama.org/protocol/sdk/alpha/x.md", "prerelease")).toEqual([]); + expect(findUrlProblems("docs.zama.org/protocol/sdk/beta/x.md", "beta")).toEqual([]); expect(findUrlProblems("docs.zama.org/protocol/sdk/x.md", "main")).toEqual([]); }); test("wrong raw.githubusercontent branch is flagged", () => { - const problems = findUrlProblems( - "raw.githubusercontent.com/zama-ai/sdk/main/llms.txt", - "prerelease", + const problems = findUrlProblems("raw.githubusercontent.com/zama-ai/sdk/main/llms.txt", "beta"); + expect(problems).toEqual([`links to the "main" branch (expected "beta")`]); + }); +}); + +// The `prerelease`→`beta` rename leaves the renamed branch carrying the old +// `.../sdk/prerelease/...` raw URLs and `.../sdk/alpha/...` space URLs. `docs:retarget beta` +// must migrate them (not corrupt/dead-link them), and `check-target` must flag them. +describe("legacy prerelease/alpha migration (one-time prerelease→beta rename)", () => { + test("retargetUrls rewrites a legacy prerelease raw branch to the target", () => { + expect(retargetUrls("raw.githubusercontent.com/zama-ai/sdk/prerelease/llms.txt", "beta")).toBe( + "raw.githubusercontent.com/zama-ai/sdk/beta/llms.txt", + ); + }); + + test("retargetUrls rewrites a legacy alpha space to the target space (and removes it for main)", () => { + expect(retargetUrls("docs.zama.org/protocol/sdk/alpha/guides/x.md", "beta")).toBe( + "docs.zama.org/protocol/sdk/beta/guides/x.md", + ); + expect(retargetUrls("docs.zama.org/protocol/sdk/alpha/guides/x.md", "main")).toBe( + "docs.zama.org/protocol/sdk/guides/x.md", + ); + }); + + test("findUrlProblems flags a legacy prerelease raw branch and alpha space", () => { + expect( + findUrlProblems("raw.githubusercontent.com/zama-ai/sdk/prerelease/llms.txt", "beta"), + ).toEqual([`links to the "prerelease" branch (expected "beta")`]); + expect(findUrlProblems("docs.zama.org/protocol/sdk/alpha/x.md", "beta")).toEqual([ + `links to the "alpha" GitBook space (expected "beta")`, + ]); + }); +}); + +// `alpha` is a valid target: its raw/LLM URLs self-target `alpha` (so alpha testers' agents +// fetch alpha content), while its docs.zama.org links use the `beta` space (alpha has no space). +describe("alpha target — raw self-targets alpha, docs.zama.org uses the beta space", () => { + test("retargetUrls → alpha rewrites raw branches to alpha", () => { + expect(retargetUrls("raw.githubusercontent.com/zama-ai/sdk/beta/llms.txt", "alpha")).toBe( + "raw.githubusercontent.com/zama-ai/sdk/alpha/llms.txt", + ); + expect(retargetUrls("raw.githubusercontent.com/zama-ai/sdk/prerelease/x", "alpha")).toBe( + "raw.githubusercontent.com/zama-ai/sdk/alpha/x", + ); + }); + + test("retargetUrls → alpha maps the docs.zama.org space to beta", () => { + expect(retargetUrls("docs.zama.org/protocol/sdk/alpha/guides/x.md", "alpha")).toBe( + "docs.zama.org/protocol/sdk/beta/guides/x.md", + ); + expect(retargetUrls("docs.zama.org/protocol/sdk/overview.md", "alpha")).toBe( + "docs.zama.org/protocol/sdk/beta/overview.md", ); - expect(problems).toEqual([`links to the "main" branch (expected "prerelease")`]); + }); + + test("retargetUrls rewrites alpha raw AWAY when targeting beta/main", () => { + expect(retargetUrls("raw.githubusercontent.com/zama-ai/sdk/alpha/x", "beta")).toBe( + "raw.githubusercontent.com/zama-ai/sdk/beta/x", + ); + expect(retargetUrls("raw.githubusercontent.com/zama-ai/sdk/alpha/x", "main")).toBe( + "raw.githubusercontent.com/zama-ai/sdk/main/x", + ); + }); + + test("retargetUrls → alpha is idempotent", () => { + const alpha = [ + "raw.githubusercontent.com/zama-ai/sdk/alpha/llms-full.txt", + "docs.zama.org/protocol/sdk/beta/guides/x.md", + ].join("\n"); + expect(retargetUrls(alpha, "alpha")).toBe(alpha); + }); + + test("findUrlProblems for alpha: raw must be alpha, space must be beta", () => { + expect( + findUrlProblems( + "raw.githubusercontent.com/zama-ai/sdk/alpha/x\ndocs.zama.org/protocol/sdk/beta/y.md", + "alpha", + ), + ).toEqual([]); + expect(findUrlProblems("raw.githubusercontent.com/zama-ai/sdk/beta/x", "alpha")).toEqual([ + `links to the "beta" branch (expected "alpha")`, + ]); + expect(findUrlProblems("docs.zama.org/protocol/sdk/guides/x.md", "alpha")).toEqual([ + `links to the "stable" GitBook space (expected "beta")`, + ]); }); }); @@ -214,7 +306,7 @@ const SUMMARY = `# Table of contents ## Changelog -- [Alpha](changelog/alpha.md) +- [Beta](changelog/beta.md) - [3.x](changelog/v3.md) - [3.1.x](changelog/v3-1.md) - [3.0.x](changelog/v3-0.md) @@ -265,7 +357,7 @@ _Released 2026-01-01._ The first stable v3 line. `; -// Carries an inbound anchor into the Alpha page's "FHE runtime" heading — promotion +// Carries an inbound anchor into the Beta page's "FHE runtime" heading — promotion // must repoint it to the new version page (else it dangles). Exercises A1's // anchor-repoint folded into retarget's changed[]. const V3_1 = `--- @@ -281,37 +373,37 @@ This page covers the \`3.1.x\` line. _Released 2026-02-01._ -Per-chain tuning is documented under [FHE runtime](alpha.md#fhe-runtime). +Per-chain tuning is documented under [FHE runtime](beta.md#fhe-runtime). `; -// A staged Alpha page: editorial prose (with a flippable alpha-space URL and a -// prerelease raw.github URL, so promotion carries them into the version page and +// A staged Beta page: editorial prose (with a flippable beta-space URL and a +// beta raw.github URL, so promotion carries them into the version page and // retarget's URL-rewrite must flip them), plus the machine-owned raw-material block. -const STAGED_ALPHA = [ +const STAGED_BETA = [ "---", - "title: Alpha", - "description: Unreleased changes on the prerelease (alpha) line.", + "title: Beta", + "description: Unreleased changes on the beta (beta) line.", "---", "", - "# Alpha", + "# Beta", "", '{% hint style="warning" %}', - "**Unreleased.** These changes are on the prerelease (`alpha`) line and are not yet in a stable release.", + "**Unreleased.** These changes are on the beta (`beta`) line and are not yet in a stable release.", "{% endhint %}", "", - "The internal FHE backend moved to `@fhevm/sdk`. See the [alpha runtime reference](https://docs.zama.org/protocol/sdk/alpha/guides/runtime.md) and the [raw corpus](https://raw.githubusercontent.com/zama-ai/sdk/prerelease/llms.txt).", + "The internal FHE backend moved to `@fhevm/sdk`. See the [beta runtime reference](https://docs.zama.org/protocol/sdk/beta/guides/runtime.md) and the [raw corpus](https://raw.githubusercontent.com/zama-ai/sdk/beta/llms.txt).", "", "## FHE runtime", "", "`createConfig` accepts a process-wide `runtime` object that configures the FHE engine.", "", - // A table whose column width is driven by an alpha-space URL: flipping it to - // stable (dropping the `alpha/` segment, 6 chars) forces oxfmt to reflow the + // A table whose column width is driven by a beta-space URL: flipping it to + // stable (dropping the `beta/` segment, 6 chars) forces oxfmt to reflow the // table. This is the #538 shape — if llm:build ran BEFORE oxfmt, the corpus would // capture the pre-reflow widths and a fresh build would diverge. "| Setting | Reference |", "| --- | --- |", - "| runtime | https://docs.zama.org/protocol/sdk/alpha/guides/runtime.md |", + "| runtime | https://docs.zama.org/protocol/sdk/beta/guides/runtime.md |", "", "## Bug fixes", "", @@ -323,7 +415,7 @@ const STAGED_ALPHA = [ "", TODO_MARKER, "", - "### 3.4.0-alpha.1 — 2026-07-15", + "### 3.4.0-beta.1 — 2026-07-15", "", "#### Features", "", @@ -372,8 +464,8 @@ function corpusConfig(branch) { } /** Build a corpus-buildable fixture tree. `branch` sets corpus.config's rawGithubBaseUrl; - * `alpha` is a staged tip ("staged") or the empty reset template ("empty"). */ -function makeFixture(root, { branch = "prerelease", alpha = "staged" } = {}) { + * `beta` is a staged tip ("staged") or the empty reset template ("empty"). */ +function makeFixture(root, { branch = "beta", beta = "staged" } = {}) { write(root, "CHANGELOG.md", CHANGELOG); write(root, "README.md", README); write(root, "docs/llm/corpus.config.json", corpusConfig(branch)); @@ -384,8 +476,8 @@ function makeFixture(root, { branch = "prerelease", alpha = "staged" } = {}) { write(root, "docs/gitbook/src/changelog/v3-1.md", V3_1); write( root, - "docs/gitbook/src/changelog/alpha.md", - alpha === "staged" ? STAGED_ALPHA : emptyAlphaTemplate(), + "docs/gitbook/src/changelog/beta.md", + beta === "staged" ? STAGED_BETA : emptyBetaTemplate(), ); } @@ -405,12 +497,12 @@ function snapshotTree(root) { } const VERSION_PAGE = "docs/gitbook/src/changelog/v3-4.md"; -const ALPHA_PAGE = "docs/gitbook/src/changelog/alpha.md"; +const BETA_PAGE = "docs/gitbook/src/changelog/beta.md"; const read = (root, rel) => readFileSync(join(root, rel), "utf8"); describe("docs:retarget main — promotion integration", () => { test( - "promote-once: version page created, alpha reset, SUMMARY + index wired, anchors repointed", + "promote-once: version page created, beta reset, SUMMARY + index wired, anchors repointed", { timeout: 60_000 }, () => { const dir = freshDir(); @@ -425,29 +517,29 @@ describe("docs:retarget main — promotion integration", () => { expect(versionPage).toContain("_Released 2026-07-17._"); expect(versionPage).toContain("### FHE runtime"); expect(versionPage).toContain("object that configures the FHE engine"); - // Machine-owned scaffolding + the Alpha page's own H1 must NOT carry over. + // Machine-owned scaffolding + the Beta page's own H1 must NOT carry over. expect(versionPage).not.toContain(RAW_START); expect(versionPage).not.toContain(TODO_MARKER); - expect(versionPage).not.toMatch(/^#\s+Alpha\s*$/m); + expect(versionPage).not.toMatch(/^#\s+Beta\s*$/m); // The moved URLs are flipped to the stable space (the #450 guard). expect(versionPage).toContain("docs.zama.org/protocol/sdk/guides/runtime.md"); - expect(versionPage).not.toContain("docs.zama.org/protocol/sdk/alpha/"); + expect(versionPage).not.toContain("docs.zama.org/protocol/sdk/beta/"); expect(versionPage).toContain("raw.githubusercontent.com/zama-ai/sdk/main/llms.txt"); - expect(versionPage).not.toContain("/sdk/prerelease/"); + expect(versionPage).not.toContain("/sdk/beta/"); - // Alpha page reset to the empty template. - expect(alphaHasSubstance(read(dir, ALPHA_PAGE))).toBe(false); - expect(read(dir, ALPHA_PAGE)).not.toContain("object that configures the FHE engine"); + // Beta page reset to the empty template. + expect(betaHasSubstance(read(dir, BETA_PAGE))).toBe(false); + expect(read(dir, BETA_PAGE)).not.toContain("object that configures the FHE engine"); // Nav wired: SUMMARY child + major-index bullet. expect(read(dir, "docs/gitbook/src/SUMMARY.md")).toContain("changelog/v3-4.md"); expect(read(dir, "docs/gitbook/src/changelog/v3.md")).toMatch(/v3-4\.md/); - // Inbound anchor repointed off the (now emptied) alpha page. + // Inbound anchor repointed off the (now emptied) beta page. const v31 = read(dir, "docs/gitbook/src/changelog/v3-1.md"); expect(v31).toContain("v3-4.md#fhe-runtime"); - expect(v31).not.toContain("alpha.md#fhe-runtime"); + expect(v31).not.toContain("beta.md#fhe-runtime"); }, ); @@ -464,7 +556,7 @@ describe("docs:retarget main — promotion integration", () => { expectOk(runNode(RETARGET, ["main"], dir)); const afterSecond = snapshotTree(dir); - // No second version section, alpha/SUMMARY unchanged, corpus byte-identical. + // No second version section, beta/SUMMARY unchanged, corpus byte-identical. expect(afterSecond).toEqual(afterFirst); expect(afterSecond[VERSION_PAGE].match(/^## 3\.4\.0\s*$/gm)?.length).toBe(1); }, @@ -494,7 +586,7 @@ describe("docs:retarget main — promotion integration", () => { VERSION_PAGE, "docs/gitbook/src/changelog/v3-1.md", "docs/gitbook/src/changelog/v3.md", - ALPHA_PAGE, + BETA_PAGE, "docs/gitbook/src/SUMMARY.md", "docs/llm/corpus.config.json", ]; @@ -512,14 +604,14 @@ describe("docs:retarget main — promotion integration", () => { ); test( - "check-target main passes on the promoted version page (no residual alpha/ URLs)", + "check-target main passes on the promoted version page (no residual beta/ URLs)", { timeout: 60_000 }, () => { const dir = freshDir(); makeFixture(dir); expectOk(runNode(RETARGET, ["main"], dir)); - // Meaningful only because the promoted page carried alpha URLs that retarget + // Meaningful only because the promoted page carried beta URLs that retarget // flipped: had it not, check-target main would fail here. const check = runNode(CHECK_TARGET, ["main"], dir); expect(check.status, `check-target main failed:\n${check.stderr}`).toBe(0); @@ -539,15 +631,15 @@ describe("docs:retarget main — promotion integration", () => { expect(res.status).not.toBe(0); expect(snapshotTree(dir)).toEqual(before); // nothing half-written - expect(read(dir, "docs/llm/corpus.config.json")).toContain("/sdk/prerelease"); + expect(read(dir, "docs/llm/corpus.config.json")).toContain("/sdk/beta"); expect(existsSync(join(dir, "llms.txt"))).toBe(false); // build never ran }); test("no-op-safe: nothing to promote and no URLs to flip ⇒ exit 0, zero writes", () => { const dir = freshDir(); - // Already on main + an empty Alpha page ⇒ promoteChangelog no-ops ("alpha page is + // Already on main + an empty Beta page ⇒ promoteChangelog no-ops ("beta page is // empty") and there are no URLs to flip. - makeFixture(dir, { branch: "main", alpha: "empty" }); + makeFixture(dir, { branch: "main", beta: "empty" }); const before = snapshotTree(dir); const res = runNode(RETARGET, ["main"], dir); @@ -564,22 +656,22 @@ describe("docs:retarget main — promotion integration", () => { () => { const dir = freshDir(); // The stale-CHANGELOG shape: already on main, the newest mainline entry (3.4.0) - // is already on its version page, yet the Alpha page STILL carries staged content - // (the release commit that would add a NEWER `## [X.Y.Z]` wasn't pulled). Alpha + // is already on its version page, yet the Beta page STILL carries staged content + // (the release commit that would add a NEWER `## [X.Y.Z]` wasn't pulled). Beta // here carries NO branch/space URLs, so the URL pass has nothing to flip — the // only thing that could write is promotion, and the guard suppresses it, letting // us assert BOTH exit 0 and zero writes. - makeFixture(dir, { branch: "main", alpha: "staged" }); + makeFixture(dir, { branch: "main", beta: "staged" }); write( dir, - ALPHA_PAGE, + BETA_PAGE, [ "---", - "title: Alpha", - "description: Unreleased changes on the prerelease (alpha) line.", + "title: Beta", + "description: Unreleased changes on the beta (beta) line.", "---", "", - "# Alpha", + "# Beta", "", '{% hint style="warning" %}', "**Unreleased.** Preview only.", @@ -623,15 +715,15 @@ describe("docs:retarget main — promotion integration", () => { ); }); -describe("docs:retarget prerelease — never promotes", () => { - test("staged alpha is left intact; no version page, no SUMMARY entry", () => { +describe("docs:retarget beta — never promotes", () => { + test("staged beta is left intact; no version page, no SUMMARY entry", () => { const dir = freshDir(); - makeFixture(dir, { branch: "prerelease", alpha: "staged" }); + makeFixture(dir, { branch: "beta", beta: "staged" }); - expectOk(runNode(RETARGET, ["prerelease"], dir)); + expectOk(runNode(RETARGET, ["beta"], dir)); expect(existsSync(join(dir, VERSION_PAGE))).toBe(false); - expect(alphaHasSubstance(read(dir, ALPHA_PAGE))).toBe(true); + expect(betaHasSubstance(read(dir, BETA_PAGE))).toBe(true); expect(read(dir, "docs/gitbook/src/SUMMARY.md")).not.toContain("v3-4.md"); }); }); diff --git a/scripts/docs/changelog.mjs b/scripts/docs/changelog.mjs index aea8c6cd0..c9bf38b29 100644 --- a/scripts/docs/changelog.mjs +++ b/scripts/docs/changelog.mjs @@ -1,38 +1,23 @@ -// Scaffold the human-readable GitBook changelog from the semantic-release -// CHANGELOG.md, and promote the Alpha page into a shipped version page. +// Scaffold the human-readable GitBook changelog from semantic-release's CHANGELOG.md, +// and promote the Beta page into a shipped version page. // -// pnpm docs:changelog scaffold missing sections + refresh Alpha raw material -// pnpm docs:changelog --check dry-run: report what is missing, write nothing +// pnpm docs:changelog scaffold missing sections + refresh Beta raw material +// pnpm docs:changelog --check dry-run: report what's missing, write nothing // pnpm docs:changelog --version X.Y.Z scope scaffolding to one version (escape hatch) // -// The repo publishes two changelog surfaces: -// - CHANGELOG.md (repo root) — auto-generated by semantic-release, commit-level. -// - docs/gitbook/src/changelog/*.md — hand-written, plain-language, per-minor-line pages -// plus an Alpha page holding the unreleased prerelease tip. +// Two surfaces: CHANGELOG.md (repo root, semantic-release-generated, commit-level) and +// docs/gitbook/src/changelog/*.md (hand-written per-minor pages + a Beta page for the +// unreleased tip). This does the MECHANICAL half: parse CHANGELOG.md, scaffold missing +// `## {version}` sections as STUBs (raw bullets + TODO, never prose), add `{minor}.x` +// pages + nav for a new minor line, and refresh the machine-owned "raw material" block on +// the Beta page. The `sdk-changelog` skill writes the prose; promotion (beta → version at +// release) is exported as promoteChangelog(), driven by docs:retarget main. // -// This script does the MECHANICAL half of keeping the GitBook pages in sync: it -// parses CHANGELOG.md, scaffolds any missing `## {version}` section as a STUB -// (raw bullets + a TODO marker — never prose), creates a new `{minor}.x` page + -// nav entries when a new minor line appears, and refreshes the machine-owned -// "raw material" block on the Alpha page listing the alpha builds since the last -// stable release. The `sdk-changelog` skill supplies the plain-language judgment -// on top; promotion (alpha → version, at release time) is exported as -// `promoteChangelog()` and driven by `docs:retarget main`. -// -// Two hard invariants (mirroring retarget.mjs): -// - NO-OP-SAFE: every path exits 0 when there is nothing to do. It runs at merge -// time, so a spurious failure would block the merge. When nothing changed we -// write nothing and run NEITHER oxfmt, llm:build, NOR check-links (running the -// link check on the no-op path could fail on a pre-existing, unrelated broken -// link and wrongly block the merge). -// - IDEMPOTENT: re-running yields zero diff. Presence is detected SEMANTICALLY -// (heading/file/nav-entry exists, version-set matches) — never by byte-comparing -// generated markdown, because oxfmt reflows whatever we write. Only genuinely -// missing/stale material is (re)generated. -// -// oxfmt runs BEFORE llm:build, always: llm:build inlines these sources into the -// corpus, so it must read the formatted version or a fresh CI `llm:check` diverges -// (the bug that gated the 3.3.0 release). +// Two invariants (mirroring retarget.mjs): +// - NO-OP-SAFE: exit 0 with no writes when nothing changed (runs at merge time; a spurious +// failure blocks the merge — so no oxfmt/llm:build/check-links on the no-op path either). +// - IDEMPOTENT: presence detected SEMANTICALLY (heading/file/nav exists), never by byte-diffing +// generated markdown (oxfmt reflows it). oxfmt runs BEFORE llm:build, always (the #538/3.3.0 gate). import { spawnSync } from "node:child_process"; import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; @@ -50,7 +35,7 @@ export const ACTIVE_MAJOR = 3; export const CHANGELOG_PATH = "CHANGELOG.md"; export const CHANGELOG_DIR = "docs/gitbook/src/changelog"; export const SUMMARY_PATH = "docs/gitbook/src/SUMMARY.md"; -export const ALPHA_PAGE_PATH = `${CHANGELOG_DIR}/alpha.md`; +export const BETA_PAGE_PATH = `${CHANGELOG_DIR}/beta.md`; export const MAJOR_INDEX_PATH = `${CHANGELOG_DIR}/v${ACTIVE_MAJOR}.md`; export const TODO_MARKER = ""; @@ -58,10 +43,10 @@ export const RAW_START = ""; export const RAW_END = ""; -/** The reset state of the Alpha page after promotion — a stable template so the - * "already reset" check is semantic (see alphaHasSubstance), not byte-exact. */ -const ALPHA_EMPTY_PLACEHOLDER = - "_Nothing is staged for the next release yet. As alpha builds land, run `pnpm docs:changelog` to refresh this page._"; +/** The reset state of the Beta page after promotion — a stable template so the + * "already reset" check is semantic (see betaHasSubstance), not byte-exact. */ +const BETA_EMPTY_PLACEHOLDER = + "_Nothing is staged for the next release yet. As beta builds land, run `pnpm docs:changelog` to refresh this page._"; // ───────────────────────────────────────────────────────────── parsing ── @@ -70,11 +55,11 @@ const VERSION_HEADING_RE = /^## \[([^\]]+)\]\(([^)]+)\)\s+\((\d{4}-\d{2}-\d{2})\ const SECTION_HEADING_RE = /^###\s+(.+?)\s*$/; const BULLET_RE = /^-\s+(.+)$/; -export function isAlphaVersion(version) { - return /-alpha\.\d+$/.test(version); +export function isBetaVersion(version) { + return /-beta\.\d+$/.test(version); } -/** `3.4.0-alpha.6` → { major, minor, patch, prerelease }. Returns null if unparseable. */ +/** `3.4.0-beta.6` → { major, minor, patch, prerelease }. Returns null if unparseable. */ export function parseVersion(version) { const match = /^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/.exec(version); if (!match) { @@ -88,7 +73,7 @@ export function parseVersion(version) { }; } -/** Newest-first ordering: mainline sorts AFTER its own prereleases (3.4.0 > 3.4.0-alpha.6). */ +/** Newest-first ordering: mainline sorts AFTER its own prereleases (3.4.0 > 3.4.0-beta.6). */ export function compareVersions(a, b) { const va = typeof a === "string" ? parseVersion(a) : a; const vb = typeof b === "string" ? parseVersion(b) : b; @@ -172,7 +157,11 @@ export function parseChangelog(text) { version, compareUrl, date, - isAlpha: isAlphaVersion(version), + isBeta: isBetaVersion(version), + // Any suffix (`-alpha.N` or `-beta.N`) is a prerelease; only bare `X.Y.Z` is a + // shipped mainline. `isBeta` alone must NOT be read as "is mainline" — with the + // alpha channel added, an `-alpha.N` entry has isBeta=false but is not mainline. + isPrerelease: (parsed?.prerelease ?? null) !== null, major: parsed?.major ?? null, minor: parsed?.minor ?? null, patch: parsed?.patch ?? null, @@ -211,7 +200,7 @@ export function parseChangelog(text) { // ─────────────────────────────────────────────────── release selection ── export function newestMainline(releases) { - return releases.find((rel) => !rel.isAlpha) ?? null; + return releases.find((rel) => !rel.isPrerelease) ?? null; } /** Read CHANGELOG.md from the edit set, failing with a descriptive error (rather @@ -228,19 +217,23 @@ function readChangelog(edits, root) { /** Mainline releases on the active major, newest-first. */ export function mainlineReleasesForActiveMajor(releases) { - return releases.filter((rel) => !rel.isAlpha && rel.major === ACTIVE_MAJOR); + return releases.filter((rel) => !rel.isPrerelease && rel.major === ACTIVE_MAJOR); } -/** Alpha builds newer than the newest mainline release (the unreleased tip), - * newest-first. CHANGELOG is newest-first, so these are the alpha entries above - * the first mainline heading. */ -export function alphaReleasesSinceLastMainline(releases) { +/** Beta builds newer than the newest mainline release (the unreleased tip), + * newest-first. CHANGELOG is newest-first, so these are the prerelease entries above + * the first mainline heading. `alpha` builds are off the changelog, so they're skipped + * from the collection — but an `-alpha.N` entry is NOT the mainline boundary (only a + * suffix-less version is), so break on `!isPrerelease`, not `!isBeta`. */ +export function betaReleasesSinceLastMainline(releases) { const out = []; for (const rel of releases) { - if (!rel.isAlpha) { + if (!rel.isPrerelease) { break; } - out.push(rel); + if (rel.isBeta) { + out.push(rel); + } } return out; } @@ -346,9 +339,9 @@ export function renderMinorPage(version, sectionsMarkdown, { description } = {}) ].join("\n"); } -/** The machine-owned Alpha raw-material block: every alpha build since the last +/** The machine-owned Beta raw-material block: every beta build since the last * stable release, grouped by section, for the skill to rewrite. */ -export function renderRawMaterialBlock(alphaReleases, lastMainlineVersion) { +export function renderRawMaterialBlock(betaReleases, lastMainlineVersion) { const parts = [ RAW_START, "", @@ -356,10 +349,10 @@ export function renderRawMaterialBlock(alphaReleases, lastMainlineVersion) { "", TODO_MARKER, "", - `_Skeleton of the alpha builds since \`${lastMainlineVersion}\`. The \`sdk-changelog\` skill rewrites these into the plain-language notes above; this block is regenerated by \`pnpm docs:changelog\` and dropped when the release is promoted._`, + `_Skeleton of the beta builds since \`${lastMainlineVersion}\`. The \`sdk-changelog\` skill rewrites these into the plain-language notes above; this block is regenerated by \`pnpm docs:changelog\` and dropped when the release is promoted._`, "", ]; - for (const release of alphaReleases) { + for (const release of betaReleases) { parts.push(`### ${release.version} — ${release.date}`, ""); if (release.sections.length === 0) { parts.push("_No release-note-worthy changes._", ""); @@ -371,15 +364,15 @@ export function renderRawMaterialBlock(alphaReleases, lastMainlineVersion) { return parts.join("\n"); } -/** The alpha versions a page's raw-material block currently documents, or null if +/** The beta versions a page's raw-material block currently documents, or null if * the block is absent. Used for SEMANTIC idempotency (version-set match), so the - * block is only regenerated when the set of alpha builds actually changes. */ -export function alphaBlockVersions(alphaContent) { - const block = extractBetween(alphaContent, RAW_START, RAW_END); + * block is only regenerated when the set of beta builds actually changes. */ +export function betaBlockVersions(betaContent) { + const block = extractBetween(betaContent, RAW_START, RAW_END); if (block === null) { return null; } - return [...block.matchAll(/^###\s+(\d+\.\d+\.\d+-alpha\.\d+)\b/gmu)].map((match) => match[1]); + return [...block.matchAll(/^###\s+(\d+\.\d+\.\d+-beta\.\d+)\b/gmu)].map((match) => match[1]); } // ────────────────────────────────────────────────── nav / page wiring ── @@ -452,7 +445,7 @@ export function insertMinorIntoIndex(indexContent, version) { return lines.join("\n"); } -// ─────────────────────────────────────────────────── alpha page prose ── +// ─────────────────────────────────────────────────── beta page prose ── /** Return the substring between `start` and `end` markers (exclusive), or null. */ function extractBetween(content, start, end) { @@ -471,7 +464,7 @@ function stripFrontmatter(content) { return content.replace(/^---\n[\s\S]*?\n---\n?/u, ""); } -/** Drop the leading `# Title` H1 (the Alpha page's own title) — the version page +/** Drop the leading `# Title` H1 (the Beta page's own title) — the version page * supplies its own H1 and `## {version}` header, so it must not carry over. */ function stripPageTitle(body) { return body.replace(/^\s*#\s+.*(?:\n|$)/u, ""); @@ -500,8 +493,8 @@ function stripRawMaterial(body) { } /** Lower every markdown heading by `by` levels (fence-aware: `#` inside fenced code - * blocks is left alone). The Alpha page authors release topics as `##` under its - * `# Alpha` title, but a version page nests topics as `###` under `## {version}`, + * blocks is left alone). The Beta page authors release topics as `##` under its + * `# Beta` title, but a version page nests topics as `###` under `## {version}`, * so promotion demotes the moved prose by one level to keep it inside the section. */ export function demoteHeadings(markdown, by = 1) { let fence = null; @@ -531,25 +524,25 @@ export function demoteHeadings(markdown, by = 1) { .join("\n"); } -/** The editorial prose on the Alpha page — everything a release promotion should +/** The editorial prose on the Beta page — everything a release promotion should * carry into the version page: frontmatter, the Unreleased banner, and the * raw-material block are stripped; the rest (including any other hints) is kept. * Headings are demoted one level so the page's `##` topics nest as `###` under the * version's `##` heading. */ -export function extractAlphaProse(alphaContent) { +export function extractBetaProse(betaContent) { const body = stripRawMaterial( - stripUnreleasedBanner(stripPageTitle(stripFrontmatter(alphaContent))), + stripUnreleasedBanner(stripPageTitle(stripFrontmatter(betaContent))), ).trim(); return demoteHeadings(body, 1); } -/** Does the Alpha page carry genuine staged content (vs. the empty reset template)? +/** Does the Beta page carry genuine staged content (vs. the empty reset template)? * Semantic check: after stripping frontmatter/banner/raw-material/comments and the * known placeholder, is there any substance left? Robust to oxfmt reflow. */ -export function alphaHasSubstance(alphaContent) { - const remainder = stripHtmlComments(extractAlphaProse(alphaContent)) +export function betaHasSubstance(betaContent) { + const remainder = stripHtmlComments(extractBetaProse(betaContent)) .split("\n") - .filter((line) => normalizeWhitespace(line) !== normalizeWhitespace(ALPHA_EMPTY_PLACEHOLDER)) + .filter((line) => normalizeWhitespace(line) !== normalizeWhitespace(BETA_EMPTY_PLACEHOLDER)) .join("\n") .trim(); return remainder.length > 0; @@ -571,35 +564,35 @@ function normalizeWhitespace(text) { return text.replace(/\s+/gu, " ").trim(); } -/** The Alpha page reset to its empty template (post-promotion state). */ -export function emptyAlphaTemplate() { +/** The Beta page reset to its empty template (post-promotion state). */ +export function emptyBetaTemplate() { return [ "---", - "title: Alpha", - "description: Unreleased changes on the prerelease (alpha) line — not yet in a stable release.", + "title: Beta", + "description: Unreleased changes on the prerelease (beta) line — not yet in a stable release.", "---", "", - "# Alpha", + "# Beta", "", '{% hint style="warning" %}', - "**Unreleased.** The changes on this page are on the prerelease (`alpha`) line and are **not yet available in a stable release**. They ship with the next stable release, at which point this page is retitled to that version and folded into the version list above. Treat everything here as a preview — details may still change before release.", + "**Unreleased.** The changes on this page are on the prerelease (`beta`) line and are **not yet available in a stable release**. They ship with the next stable release, at which point this page is retitled to that version and folded into the version list above. Treat everything here as a preview — details may still change before release.", "{% endhint %}", "", - ALPHA_EMPTY_PLACEHOLDER, + BETA_EMPTY_PLACEHOLDER, "", ].join("\n"); } -/** Splice the raw-material block into the Alpha page (replacing an existing block, +/** Splice the raw-material block into the Beta page (replacing an existing block, * or appending one). */ -function withRawMaterialBlock(alphaContent, block) { - const from = alphaContent.indexOf(RAW_START); +function withRawMaterialBlock(betaContent, block) { + const from = betaContent.indexOf(RAW_START); if (from === -1) { - return `${alphaContent.replace(/\s*$/u, "")}\n\n${block}\n`; + return `${betaContent.replace(/\s*$/u, "")}\n\n${block}\n`; } - const to = alphaContent.indexOf(RAW_END, from); - const end = to === -1 ? alphaContent.length : to + RAW_END.length; - return alphaContent.slice(0, from) + block + alphaContent.slice(end); + const to = betaContent.indexOf(RAW_END, from); + const end = to === -1 ? betaContent.length : to + RAW_END.length; + return betaContent.slice(0, from) + block + betaContent.slice(end); } // ────────────────────────────────────────────────── in-memory edit set ── @@ -658,9 +651,9 @@ class EditSet { // ───────────────────────────────────────────────────────────── modes ── /** - * Scaffold missing mainline `## {version}` sections and refresh the Alpha raw + * Scaffold missing mainline `## {version}` sections and refresh the Beta raw * material. Idempotent + no-op-safe: only genuinely missing sections/pages/nav - * entries and a stale alpha version-set trigger writes. + * entries and a stale beta version-set trigger writes. * * @param {{ root?: string, version?: string, dryRun?: boolean }} [opts] * @returns {{ changed: string[], missing: string[] }} absolute changed paths + a @@ -698,19 +691,19 @@ export function scaffoldChangelog({ root = process.cwd(), version, dryRun = fals } } - // Alpha raw material — only in full (un-scoped) scaffold mode. + // Beta raw material — only in full (un-scoped) scaffold mode. if (!version) { - const alphas = alphaReleasesSinceLastMainline(releases); + const betas = betaReleasesSinceLastMainline(releases); const lastMainline = newestMainline(releases); - if (alphas.length > 0 && lastMainline) { - const alphaContent = edits.read(ALPHA_PAGE_PATH); - const targetVersions = alphas.map((rel) => rel.version); - const current = alphaBlockVersions(alphaContent); + if (betas.length > 0 && lastMainline) { + const betaContent = edits.read(BETA_PAGE_PATH); + const targetVersions = betas.map((rel) => rel.version); + const current = betaBlockVersions(betaContent); const upToDate = current !== null && arraysEqual(current, targetVersions); if (!upToDate) { - const block = renderRawMaterialBlock(alphas, lastMainline.version); - edits.write(ALPHA_PAGE_PATH, withRawMaterialBlock(alphaContent, block)); - missing.push(`Alpha raw material (${targetVersions.join(", ")})`); + const block = renderRawMaterialBlock(betas, lastMainline.version); + edits.write(BETA_PAGE_PATH, withRawMaterialBlock(betaContent, block)); + missing.push(`Beta raw material (${targetVersions.join(", ")})`); } } } @@ -720,7 +713,7 @@ export function scaffoldChangelog({ root = process.cwd(), version, dryRun = fals } /** - * Promote the Alpha page into the shipped version's page/section, reset Alpha to + * Promote the Beta page into the shipped version's page/section, reset Beta to * the empty template, and wire nav for a new minor line. Version is auto-derived * from the newest mainline entry in CHANGELOG.md; `opts.version` overrides. * @@ -729,14 +722,14 @@ export function scaffoldChangelog({ root = process.cwd(), version, dryRun = fals * * Own state detection (retarget's "idempotent" is output-stable, not "does * nothing"): the version page already having `## {version}` is the workhorse - * no-op guard (makes `retarget main` twice a zero-diff); an already-empty Alpha + * no-op guard (makes `retarget main` twice a zero-diff); an already-empty Beta * page is the secondary "nothing staged" guard. All file contents are computed in * memory and flushed only if every step succeeds (failure isolation). * * @param {{ root?: string, version?: string, dryRun?: boolean }} [opts] * @returns {{ changed: string[], version: string|null, promoted: boolean, reason?: string, * warning?: string }} - * `changed` = absolute paths (version page, reset alpha.md, and SUMMARY.md when a + * `changed` = absolute paths (version page, reset beta.md, and SUMMARY.md when a * new minor page is created). `warning` is set (no-op path only) when the state * looks like a stale CHANGELOG.md — see the "already promoted" guard. */ @@ -754,35 +747,35 @@ export function promoteChangelog({ root = process.cwd(), version, dryRun = false const pageContent = edits.exists(pagePath) ? edits.read(pagePath) : null; // Workhorse guard: already promoted. Normally that means retarget ran twice — a - // true no-op. But if the Alpha page STILL carries staged content while the newest + // true no-op. But if the Beta page STILL carries staged content while the newest // mainline entry is already on its page, CHANGELOG.md is very likely STALE: the // release commit (`chore(release): X.Y.Z [skip ci]`) that adds the NEW `## [X.Y.Z]` // hasn't been pulled, so `target` derived to the PRIOR, already-promoted version. - // That would silently ship a release with no version page and no alpha reset — and + // That would silently ship a release with no version page and no beta reset — and // a MISSING page is not a broken link, so nothing downstream (incl. check-links) // catches it. Warn loudly, but stay a no-op (exit 0 preserved): retarget runs at // merge time, so failing here would block the merge. if (pageContent !== null && hasVersionSection(pageContent, target)) { const result = { changed: [], version: target, promoted: false, reason: "already promoted" }; - if (alphaHasSubstance(edits.read(ALPHA_PAGE_PATH))) { + if (betaHasSubstance(edits.read(BETA_PAGE_PATH))) { result.warning = - `⚠ Alpha page has unreleased content but the newest mainline entry (${target}) is ` + + `⚠ Beta page has unreleased content but the newest mainline entry (${target}) is ` + "already promoted — CHANGELOG.md may be stale; did you pull the release commit " + "before running docs:retarget main?"; } return result; } - // Secondary guard: nothing staged on the Alpha page. - const alphaContent = edits.read(ALPHA_PAGE_PATH); - if (!alphaHasSubstance(alphaContent)) { - return { changed: [], version: target, promoted: false, reason: "alpha page is empty" }; + // Secondary guard: nothing staged on the Beta page. + const betaContent = edits.read(BETA_PAGE_PATH); + if (!betaHasSubstance(betaContent)) { + return { changed: [], version: target, promoted: false, reason: "beta page is empty" }; } - const date = releases.find((rel) => rel.version === target && !rel.isAlpha)?.date; - // Repoint self-referential alpha anchors in the prose BEFORE it lands on the version + const date = releases.find((rel) => rel.version === target && !rel.isPrerelease)?.date; + // Repoint self-referential beta anchors in the prose BEFORE it lands on the version // page — the inbound sweep below can't fix them (the version page isn't on disk yet). - const prose = repointSelfAnchors(extractAlphaProse(alphaContent), targetBasename); + const prose = repointSelfAnchors(extractBetaProse(betaContent), targetBasename); const section = [`## ${target}`, "", `_Released ${date ?? "unreleased"}._`, "", prose].join("\n"); if (pageContent === null) { @@ -792,10 +785,10 @@ export function promoteChangelog({ root = process.cwd(), version, dryRun = false } else { edits.write(pagePath, insertSection(pageContent, target, section)); } - edits.write(ALPHA_PAGE_PATH, emptyAlphaTemplate()); + edits.write(BETA_PAGE_PATH, emptyBetaTemplate()); - // Repoint inbound anchors before the moved sections vanish from the Alpha page. - repointAlphaAnchors(edits, root, extractHeadingSlugs(prose), targetBasename); + // Repoint inbound anchors before the moved sections vanish from the Beta page. + repointBetaAnchors(edits, root, extractHeadingSlugs(prose), targetBasename); const changed = dryRun ? edits.changedPaths() : edits.flush(); return { changed, version: target, promoted: true }; @@ -821,27 +814,27 @@ function docPagesRelative(root) { } /** - * Repoint inbound anchor links from the Alpha page to the version page the content - * moved to. Promotion moves a section (e.g. `## FHE runtime…`) out of `alpha.md` - * and resets the page, so any `](…alpha.md#that-slug)` elsewhere would dangle and + * Repoint inbound anchor links from the Beta page to the version page the content + * moved to. Promotion moves a section (e.g. `## FHE runtime…`) out of `beta.md` + * and resets the page, so any `](…beta.md#that-slug)` elsewhere would dangle and * fail check-links at the release merge. Only the file part of the link changes — - * `alpha.md` → `vX-Y.md` (both sit in `changelog/`, so relative prefixes stay + * `beta.md` → `vX-Y.md` (both sit in `changelog/`, so relative prefixes stay * valid) — and only for slugs that actually moved. Edits go through the shared set * so they land in the returned `changed[]` (and get oxfmt'd + rebuilt by retarget). */ -function repointAlphaAnchors(edits, root, movedSlugs, targetBasename) { +function repointBetaAnchors(edits, root, movedSlugs, targetBasename) { if (movedSlugs.size === 0) { return; } // Path-boundary the prefix: an optional group that must END in `/` (a directory // separator) or be absent (start-of-path, right after `](`). Otherwise a sibling - // like `myalpha.md#slug` would spuriously match (`my` as the prefix). + // like `mybeta.md#slug` would spuriously match (`my` as the prefix). const patterns = [...movedSlugs].map((movedSlug) => - RE2JS.compile(`\\]\\(([^)#]*\\/)?alpha\\.md#(${escapeRegExp(movedSlug)})\\)`), + RE2JS.compile(`\\]\\(([^)#]*\\/)?beta\\.md#(${escapeRegExp(movedSlug)})\\)`), ); for (const relPath of docPagesRelative(root)) { const content = edits.read(relPath); - if (content === null || !content.includes("alpha.md#")) { + if (content === null || !content.includes("beta.md#")) { continue; } let updated = content; @@ -857,18 +850,18 @@ function repointAlphaAnchors(edits, root, movedSlugs, targetBasename) { } /** - * Rewrite SELF-referential alpha anchors INSIDE the moved prose. A link like - * `](alpha.md#other-section)` (or `](./alpha.md#…)`) within the Alpha prose points at + * Rewrite SELF-referential beta anchors INSIDE the moved prose. A link like + * `](beta.md#other-section)` (or `](./beta.md#…)`) within the Beta prose points at * another section on the SAME page; once that prose moves to the version page and - * alpha.md resets, it would dangle. The inbound sweep (`repointAlphaAnchors`) can't + * beta.md resets, it would dangle. The inbound sweep (`repointBetaAnchors`) can't * fix it — the version page isn't on disk when that sweep reads the tree — so rewrite * it here, swapping only the file part to the version page basename (both live in * `changelog/`, so a relative link stays valid). Path-boundary anchored (the `./` - * prefix or start-of-path) so a sibling like `myalpha.md` is left alone. + * prefix or start-of-path) so a sibling like `mybeta.md` is left alone. */ function repointSelfAnchors(prose, targetBasename) { return prose.replace( - /\]\((\.\/)?alpha\.md#([^)]+)\)/gu, + /\]\((\.\/)?beta\.md#([^)]+)\)/gu, (_match, prefix, anchor) => `](${prefix ?? ""}${targetBasename}#${anchor})`, ); } diff --git a/scripts/docs/check-target.mjs b/scripts/docs/check-target.mjs index 46e1c8b57..2078e2dbb 100644 --- a/scripts/docs/check-target.mjs +++ b/scripts/docs/check-target.mjs @@ -1,37 +1,51 @@ // Assert the committed doc URLs target the expected publish branch. // -// pnpm docs:check-target +// pnpm docs:check-target // // Runs in CI with the PR's base branch (github.base_ref) — which, unlike a local // git branch name, is reliable in CI's detached-HEAD PR checkout and is exactly // where the PR will land. If the committed URLs point at the other branch/space -// (e.g. a prerelease→main promotion that forgot to flip them), this fails and +// (e.g. a beta→main promotion that forgot to flip them), this fails and // tells you to run `pnpm docs:retarget `. Idempotent partner to retarget.mjs. +// `alpha` IS a valid target: its raw/LLM URLs must self-target `alpha`, while its +// docs.zama.org links use the `beta` space (alpha has no GitBook space of its own). import { readFileSync, readdirSync, statSync } from "node:fs"; import { join } from "node:path"; import { pathToFileURL } from "node:url"; -export const BRANCH_TO_SPACE = { main: "stable", prerelease: "alpha" }; +/** Publish branch → expected GitBook space (`alpha`→`beta`; alpha has no space of its own). */ +export const BRANCH_TO_SPACE = { main: "stable", beta: "beta", alpha: "beta" }; // `stable` is GitBook's default space and is omitted from docs.zama.org URLs, so a -// stale stable-form URL on the prerelease branch has *no* space segment at all — we +// stale stable-form URL on the beta branch has *no* space segment at all — we // can't catch it by looking for a literal wrong segment (the old approach silently // missed it). Instead read the actual space — the segment after /protocol/sdk/, or // the implicit `stable` when none is present — and compare it to what's expected. -const SPACE_URL_RE = /docs\.zama\.org\/protocol\/sdk\/(?:(alpha|stable)\/)?/g; +// `alpha` is the legacy (pre-rename) space, kept in the alternation so a stale +// `.../sdk/alpha/...` URL on the renamed branch is caught and fixed by docs:retarget, +// not silently read as the implicit `stable` space. Drop once no `alpha` URLs remain. +const SPACE_URL_RE = /docs\.zama\.org\/protocol\/sdk\/(?:(alpha|beta|stable)\/)?/g; /** * Human-readable problems with the branch/space URLs in `content` for a PR - * landing on `expected` (`main` or `prerelease`). Empty array means clean. + * landing on `expected` (`main`, `beta`, or `alpha`). Empty array means clean. */ export function findUrlProblems(content, expected) { const space = BRANCH_TO_SPACE[expected]; - const otherBranch = expected === "main" ? "prerelease" : "main"; const problems = []; - if (content.includes(`raw.githubusercontent.com/zama-ai/sdk/${otherBranch}/`)) { - problems.push(`links to the "${otherBranch}" branch (expected "${expected}")`); + // Flag any raw.githubusercontent branch segment that isn't `expected` — covers the + // other publish branches (main/beta/alpha) and the legacy `prerelease`. One flag per + // distinct wrong branch; docs:retarget fixes them. (Raw URLs self-target the branch, + // so alpha's must be `alpha`, not the `beta` space its docs.zama.org links use.) + const seenBranches = new Set(); + for (const m of content.matchAll(/raw\.githubusercontent\.com\/zama-ai\/sdk\/([\w.-]+)\//g)) { + const branch = m[1]; + if (branch !== expected && !seenBranches.has(branch)) { + seenBranches.add(branch); + problems.push(`links to the "${branch}" branch (expected "${expected}")`); + } } for (const match of content.matchAll(SPACE_URL_RE)) { @@ -64,7 +78,7 @@ function main() { if (!Object.hasOwn(BRANCH_TO_SPACE, expected)) { // Not a publish branch (e.g. a feature→feature PR) — nothing to assert. console.log( - `docs:check-target: "${expected ?? "(nothing)"}" is not main/prerelease; skipping.`, + `docs:check-target: "${expected ?? "(nothing)"}" is not main/beta/alpha; skipping.`, ); process.exit(0); } diff --git a/scripts/docs/retarget.mjs b/scripts/docs/retarget.mjs index a7e2879c3..ae4d9ebfc 100644 --- a/scripts/docs/retarget.mjs +++ b/scripts/docs/retarget.mjs @@ -1,45 +1,26 @@ // Retarget the doc URLs to a publish branch and rebuild the LLM corpus. // -// pnpm docs:retarget +// pnpm docs:retarget // -// Two kinds of branch-specific URL live in the docs (see docs/llm/corpus.config.json -// and the migration guide): -// - raw.githubusercontent.com/zama-ai/sdk//... — main on the release -// branch, prerelease on the prerelease branch (the files only exist on `main` -// after a release). -// - docs.zama.org/protocol/sdk//....md — the GitBook space the -// branch publishes to: `main` → `stable`, `prerelease` → `alpha`. `stable` is -// GitBook's default space and is omitted from the URL, so only `alpha` ever -// appears as a path segment: promotion *inserts* `alpha/` (main→prerelease) or -// *removes* it (prerelease→main), it isn't a fixed-segment swap. +// Two branch-specific URL kinds live in the docs (see corpus.config.json + the migration guide): +// - raw.githubusercontent.com/zama-ai/sdk//... — `main`, `beta`, or `alpha`, self-targeted. +// This is the LLM corpus, so alpha's must point at `alpha` for alpha testers' agents to get alpha +// content (alpha IS meant to be consumed) — not beta/main. +// - docs.zama.org/protocol/sdk//....md — GitBook space: `main`→`stable`, `beta`/`alpha`→`beta`. +// alpha has no GitBook space of its own, so its human doc links use the `beta` preview site. +// `stable` is the default (omitted) space, so promotion inserts/removes the `beta/` segment. +// Branches can't be derived at build time (CI PRs are detached HEAD), so they're committed and +// flipped here; idempotent (same branch = no-op). Only `main` promotes the changelog (below). // -// They can't be derived at build time: CI checks out PRs as a detached HEAD, so -// there's no branch to read, and the committed artifacts must match the rebuild. -// So the branch is committed (in corpus.config.json + the hand-authored links) and -// flipped at promotion with this one command, which is idempotent — running it for -// the branch you're already on is a no-op. +// At release (beta→main) this ALSO promotes the GitBook changelog — the Beta page's staged tip +// becomes the shipped version page — via promoteChangelog() in changelog.mjs. `docs:retarget beta` +// never promotes. // -// At release (merge prerelease→main) this command ALSO promotes the human-readable -// GitBook changelog: the Alpha page's staged tip becomes the shipped version page. -// Promotion rides on `docs:retarget main` (not a separate command) because a release -// shipping is exactly the moment the alpha tip becomes a version and doc URLs flip to -// the stable space. It is delegated to the idempotent, auto-versioned -// `promoteChangelog()` in changelog.mjs; `docs:retarget prerelease` never promotes. -// -// STRICT ordering (each step re-merges a past production failure if wrong): -// promote (flush to disk) → URL-rewrite (over all files, incl. the just-flushed -// version page) → oxfmt(every changed + promoted file) → llm:build. -// - The version page written by promotion carries `alpha/`-space URLs from the -// Alpha prose; the URL-rewrite pass must flip them to stable before check-target -// inspects it, or we re-trigger the #450 hand-fix. -// - Every promoted file (new version page, reset alpha.md, SUMMARY.md, any -// anchor-repointed page) must be oxfmt'd before llm:build inlines it, or a fresh -// CI `llm:check` diverges — the #538 / 3.3.0 corpus-diff failure that gated the -// release job. -// - Promotion runs first and flushes only on full success, so a promotion throw -// leaves the URL/corpus outputs untouched (failure isolation). -// - No-op-safe: nothing to promote AND no URLs to flip ⇒ exit 0 with zero writes. -// retarget runs at merge time, so a spurious failure would block the merge. +// STRICT ordering (each guards a past prod failure): +// promote → URL-rewrite (incl. the flushed version page) → oxfmt(changed+promoted) → llm:build. +// - URL-rewrite must flip the promoted page's `beta/` URLs to stable before check-target (#450). +// - oxfmt must precede llm:build or a fresh CI llm:check diverges (#538 / 3.3.0 release gate). +// - Promotion flushes only on full success (failure isolation); no-op-safe (exit 0, zero writes). import { spawnSync } from "node:child_process"; import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; @@ -49,28 +30,39 @@ import { promoteChangelog } from "./changelog.mjs"; const scriptDir = dirname(fileURLToPath(import.meta.url)); -/** Publish branch → GitBook space. */ -export const BRANCH_TO_SPACE = { main: "stable", prerelease: "alpha" }; +/** Publish branch → GitBook space. `alpha` has no space of its own; its human doc links + * point at the `beta` preview site, while its raw/LLM URLs self-target the `alpha` branch. */ +export const BRANCH_TO_SPACE = { main: "stable", beta: "beta", alpha: "beta" }; /** - * Rewrite the branch/space URLs in `content` to target `target` (`main` or - * `prerelease`). `stable` is the default (omitted) space, so the docs.zama.org - * rewrite inserts or removes the `alpha/` segment rather than swapping a fixed - * one. Idempotent: retargeting to the branch the content already targets is a + * Rewrite the branch/space URLs in `content` to target `target` (`main`, `beta`, + * or `alpha`). The raw branch self-targets `target`; the docs.zama.org space is + * `BRANCH_TO_SPACE[target]` (`main`→stable, `beta`/`alpha`→beta). `stable` is the + * default (omitted) space, so the docs.zama.org rewrite inserts or removes the + * `beta/` segment rather than swapping a fixed one. Idempotent: retargeting to the + * branch the content already targets is a * no-op. Only the raw.githubusercontent `/` and docs.zama.org space * forms are touched — a bare `.../sdk/main` (no trailing slash) or a GitHub * `blob/` link (e.g. the canonical CHANGELOG.md link, which always points * at `main`) is intentionally left alone. + * + * The legacy `prerelease` branch and `alpha` space forms are also recognized, so the + * one-time `prerelease`→`beta` rename migrates with a plain `pnpm docs:retarget beta` + * (the renamed branch still carries the old `.../sdk/prerelease/...` and `.../sdk/alpha/...` + * URLs). Safe to drop once no `prerelease`/`alpha` URLs remain in the tree. */ export function retargetUrls(content, target) { const space = BRANCH_TO_SPACE[target]; const spaceSegment = space === "stable" ? "" : `${space}/`; return content .replace( - /(raw\.githubusercontent\.com\/zama-ai\/sdk\/)(?:main|prerelease)(\/)/g, + /(raw\.githubusercontent\.com\/zama-ai\/sdk\/)(?:main|beta|alpha|prerelease)(\/)/g, `$1${target}$2`, ) - .replace(/(docs\.zama\.org\/protocol\/sdk\/)(?:(?:alpha|stable)\/)?/g, `$1${spaceSegment}`); + .replace( + /(docs\.zama\.org\/protocol\/sdk\/)(?:(?:alpha|beta|stable)\/)?/g, + `$1${spaceSegment}`, + ); } function walkMarkdown(dir, acc = []) { @@ -91,7 +83,7 @@ function walkMarkdown(dir, acc = []) { /** * Format the changed sources with oxfmt, then rebuild the LLM corpus — oxfmt BEFORE * llm:build, always. oxfmt reflows Markdown tables to the content width, and changing - * a URL's length (inserting/removing the `alpha/` segment) changes the width of any + * a URL's length (inserting/removing the `beta/` segment) changes the width of any * table whose cells hold that URL. llm:build inlines these sources into llms-full.txt, * so it must read the *formatted* version — otherwise the corpus captures the * pre-reflow widths while the committed source has the post-reflow ones, and a fresh @@ -126,16 +118,16 @@ function main() { const target = process.argv[2]; if (!Object.hasOwn(BRANCH_TO_SPACE, target)) { - console.error(`Usage: pnpm docs:retarget \nGot: ${target ?? "(nothing)"}`); + console.error(`Usage: pnpm docs:retarget \nGot: ${target ?? "(nothing)"}`); process.exit(1); } const space = BRANCH_TO_SPACE[target]; // ── Promotion (main only, FIRST, with failure isolation) ────────────────── - // The alpha changelog tip becomes the shipped version page. promoteChangelog + // The beta changelog tip becomes the shipped version page. promoteChangelog // computes every edit in memory and flushes only on full success, so a throw // leaves the tree untouched; running it BEFORE any URL/corpus write means a - // promotion failure can't leave those half-written. `prerelease` never promotes. + // promotion failure can't leave those half-written. `beta` never promotes. let promotedPaths = []; if (target === "main") { let result; @@ -150,10 +142,10 @@ function main() { promotedPaths = result.changed; console.log( result.promoted - ? `Promoted the Alpha changelog page → version ${result.version}.` + ? `Promoted the Beta changelog page → version ${result.version}.` : `No changelog promotion (${result.reason}).`, // a no-op reason is success ); - // A no-op that nonetheless looks like a stale CHANGELOG.md (staged alpha content + // A no-op that nonetheless looks like a stale CHANGELOG.md (staged beta content // with the newest mainline entry already promoted) is surfaced loudly on stderr — // it stays a warning, never a failure (exit 0 preserved), so it can't block the merge. if (result.warning) { @@ -165,7 +157,7 @@ function main() { // ── URL rewrite ──────────────────────────────────────────────────────────── // Hand-authored sources that may carry branch/space URLs: the GitBook docs plus // the package READMEs that feed the corpus. Promotion has already flushed any new - // version page to disk, so walkMarkdown sees it here and its `alpha/`-space URLs + // version page to disk, so walkMarkdown sees it here and its `beta/`-space URLs // get flipped before check-target inspects them. const docFiles = [ ...walkMarkdown(join(root, "docs/gitbook/src")), @@ -175,7 +167,7 @@ function main() { ]; // Absolute paths of every file to oxfmt before the rebuild. Seed with the promoted - // files: some (the reset alpha.md, SUMMARY.md) carry no branch/space URL and so + // files: some (the reset beta.md, SUMMARY.md) carry no branch/space URL and so // never enter via the rewrite below, but must still be formatted before llm:build // inlines them. A Set dedupes a version page that both moved and had a URL flipped. const changed = new Set(promotedPaths); @@ -199,7 +191,7 @@ function main() { const configPath = join(root, "docs/llm/corpus.config.json"); const config = readFileSync(configPath, "utf8"); const updatedConfig = config.replace( - /(https:\/\/raw\.githubusercontent\.com\/zama-ai\/sdk\/)(?:main|prerelease)/, + /(https:\/\/raw\.githubusercontent\.com\/zama-ai\/sdk\/)(?:main|beta|alpha|prerelease)/, `$1${target}`, ); if (updatedConfig !== config) {