diff --git a/.github/workflows/images-release.yml b/.github/workflows/images-release.yml index e34c544..7fa18d1 100644 --- a/.github/workflows/images-release.yml +++ b/.github/workflows/images-release.yml @@ -491,7 +491,7 @@ jobs: npm init --yes >/dev/null npm install "../$tarball" if jq -e '.targets | index("linux-x64")' <<<"$NPM_PLAN" >/dev/null; then - node -e "const sdk = require('@meshllm/sdk'); if (sdk.currentMeshVersion() !== process.env.MESH_VERSION) process.exit(1)" + node -e "const sdk = require('@mesh-llm/sdk'); if (sdk.currentMeshVersion() !== process.env.MESH_VERSION) process.exit(1)" fi ) - uses: actions/upload-artifact@v7 @@ -524,13 +524,12 @@ jobs: path: artifacts/npm - name: Publish Node SDK package env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} RELEASE_VERSION: ${{ needs.plan.outputs.mesh_version }} shell: bash run: | set -euo pipefail - if npm view "@meshllm/sdk@$RELEASE_VERSION" version >/dev/null 2>&1; then - echo "@meshllm/sdk@$RELEASE_VERSION is already published" + if npm view "@mesh-llm/sdk@$RELEASE_VERSION" version >/dev/null 2>&1; then + echo "@mesh-llm/sdk@$RELEASE_VERSION is already published" exit 0 fi dist_tag=latest diff --git a/README.md b/README.md index 9085a30..f4985e1 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This repository is the packaging and distribution control plane for published [` ```text published upstream tag + immutable tag SHA -> verified release archives -> native packages -> OCI images - -> npm addon lanes -> assembled @meshllm/sdk tarball -> clean install test + -> npm addon lanes -> assembled @mesh-llm/sdk tarball -> clean install test ``` Application packages and images never rebuild `mesh-llm`; they consume verified upstream binaries. The npm lanes are the sole exception: they check out the immutable release SHA to compile the SDK's N-API addons. `native-runtimes.json` and native runtime archives remain upstream assets. @@ -17,7 +17,7 @@ Application packages and images never rebuild `mesh-llm`; they consume verified - Ubuntu 24.04: CPU on amd64/arm64, Vulkan on amd64, CUDA 12 and 13 on amd64/arm64, ROCm 7 on amd64. - Arch: CPU, Vulkan, and CUDA 13 packages/images on amd64 as downstream distribution extensions over the matching upstream glibc archives. - Homebrew: Apple Silicon formula pointing directly at the upstream Metal archive. -- npm: `@meshllm/sdk` addons for macOS arm64/x64, Linux arm64/x64, and Windows x64. +- npm: `@mesh-llm/sdk` addons for macOS arm64/x64, Linux arm64/x64, and Windows x64. - Alpine: declared but disabled. Upstream currently publishes glibc Linux archives, not musl archives, so emitting APKs would be inaccurate. The exact rows live in `packaging/images.json`; `scripts/image-matrix.ts validate` enforces the archive/package relationship. @@ -31,7 +31,7 @@ The exact rows live in `packaging/images.json`; `scripts/image-matrix.ts validat - [GHCR](https://github.com/orgs/Mesh-LLM/packages/container/package/mesh-llm) contains public CPU, Vulkan, CUDA, and ROCm runtime images. See [`docs/tagging.md`](docs/tagging.md) for immutable and moving tag names. -- [npm](https://www.npmjs.com/package/@meshllm/sdk) receives the install-tested +- [npm](https://www.npmjs.com/package/@mesh-llm/sdk) receives the install-tested cross-platform SDK tarball when npm publication is enabled. Package-manager repositories and a public Homebrew tap are not published. The diff --git a/TODO.md b/TODO.md index 3f6b8eb..0bf02f3 100644 --- a/TODO.md +++ b/TODO.md @@ -4,7 +4,7 @@ QA: configuration validation and 100% coverage matrix tests prove enabled, disabled, filtered, and empty npm matrix behavior. -- [x] Assemble, preflight, and publish the canonical `@meshllm/sdk` tarball. +- [x] Assemble, preflight, and publish the canonical `@mesh-llm/sdk` tarball. QA: local fixture dry runs produce the expected cross-platform tarball, install it into a clean consumer project, load the host addon, and pass `npm publish --dry-run`; workflow lint proves CI can schedule every lane. diff --git a/docs/matrix.md b/docs/matrix.md index 78d2ba2..12d2be2 100644 --- a/docs/matrix.md +++ b/docs/matrix.md @@ -28,7 +28,7 @@ Windows upstream archives are not repackaged because this repository has no Wind ## npm lanes The `npm.lanes` entries use the same `matrix_enabled` and `release_enabled` -controls as package rows. They build `@meshllm/sdk` addons for macOS arm64/x64, +controls as package rows. They build `@mesh-llm/sdk` addons for macOS arm64/x64, Linux arm64/x64, and Windows x64. `npm-matrix` expands the enabled lanes; `npm_lane_filter` selects lane IDs or targets for isolated CI dry runs. diff --git a/docs/publishing.md b/docs/publishing.md index 581b9d1..8e1ec25 100644 --- a/docs/publishing.md +++ b/docs/publishing.md @@ -5,18 +5,24 @@ The release workflow has four publication states: 1. `dry_run=true`: all required archive, package, image, and Homebrew validation runs; publication is forcibly disabled. 2. `dry_run=false,publish_images=true`: validated OCI images are pushed to GHCR and receive build-provenance attestations. 3. `dry_run=false,publish_release_assets=true`: native packages, checksums, SPDX SBOMs, and the rendered Homebrew formula are attached to a `packaging-v` release in this repository. -4. `dry_run=false,publish_npm=true`: the install-tested `@meshllm/sdk` +4. `dry_run=false,publish_npm=true`: the install-tested `@mesh-llm/sdk` tarball is published with provenance; stable versions use `latest` and prereleases use `next`. -Publish jobs use the `release` GitHub environment, whose deployment policy accepts only `main`, and have job-local write permissions. All build and validation jobs are read-only. Add required reviewers when the repository plan supports environment reviewers. The upstream tag must already have a non-draft GitHub Release, the repository must be exactly `Mesh-LLM/mesh-llm`, the ref and version must match, and the tag is resolved to an immutable commit SHA for provenance labels. +Non-npm publish jobs use the `release` GitHub environment, whose deployment policy accepts only `main`, while npm publishing uses the separate `npm` environment described below. Publish jobs have job-local write permissions, and all build and validation jobs are read-only. Add required reviewers when the repository plan supports environment reviewers. The upstream tag must already have a non-draft GitHub Release, the repository must be exactly `Mesh-LLM/mesh-llm`, the ref and version must match, and the tag is resolved to an immutable commit SHA for provenance labels. GitHub Release assets, GHCR, and npm are the enabled public channels. Do not create apt, apk, pacman, or Homebrew tap publication until signing/trust-root ownership and rollback procedures exist. Homebrew currently publishes a formula that references the immutable upstream macOS archive and its upstream-verified SHA256; it does not repackage that binary. npm publishing uses the `npm` environment and OIDC trusted publishing for -`Mesh-LLM/mesh-packaging`, workflow `images-release.yml`. `NPM_TOKEN` is only a -first-publish bootstrap fallback and should be removed after trusted publishing -is configured. +`Mesh-LLM/mesh-packaging`, workflow `images-release.yml`. The published package +metadata identifies `Mesh-LLM/mesh-packaging` so npm can verify the repository +claim in GitHub's OIDC identity. The publish job must not depend on a stored npm +credential. + +The assembler accepts the legacy `@meshllm/sdk` name only as immutable upstream +input from the `v0.74.0` bootstrap release and normalizes the assembled package +to `@mesh-llm/sdk`. All other package names are rejected, and subsequent +upstream releases declare the canonical name directly. Moving convenience OCI tags are published alongside immutable version tags. Rollback must never mutate the versioned tag silently: stop the affected row, preserve evidence, and publish a new upstream version or explicit correction record. diff --git a/packaging/images.json b/packaging/images.json index c5ed2dd..7f0e390 100644 --- a/packaging/images.json +++ b/packaging/images.json @@ -11,7 +11,7 @@ "upstream_flavor": "metal" }, "npm": { - "package_name": "@meshllm/sdk", + "package_name": "@mesh-llm/sdk", "source_directory": "sdk/node", "registry": "https://registry.npmjs.org/", "lanes": [ diff --git a/scripts/image-matrix.ts b/scripts/image-matrix.ts index 4546c77..e4eb1e8 100644 --- a/scripts/image-matrix.ts +++ b/scripts/image-matrix.ts @@ -296,7 +296,7 @@ export function validate(config: Config): string[] { } function validateNpmConfig(npm: NpmConfig | undefined, errors: string[]): void { - if (npm?.package_name !== "@meshllm/sdk") errors.push("npm.package_name must be @meshllm/sdk"); + if (npm?.package_name !== "@mesh-llm/sdk") errors.push("npm.package_name must be @mesh-llm/sdk"); if (npm?.source_directory !== "sdk/node") errors.push("npm.source_directory must be sdk/node"); if (npm?.registry !== "https://registry.npmjs.org/") errors.push("npm.registry must be the public npm registry"); if (!Array.isArray(npm?.lanes) || npm.lanes.length === 0) { diff --git a/scripts/node-sdk-package.ts b/scripts/node-sdk-package.ts index 12772b3..89ddf67 100644 --- a/scripts/node-sdk-package.ts +++ b/scripts/node-sdk-package.ts @@ -33,10 +33,12 @@ type PackageMetadata = { }; }; +const CANONICAL_PACKAGE_NAME = "@mesh-llm/sdk"; +const LEGACY_PACKAGE_NAME = "@meshllm/sdk"; + const CANONICAL_REPOSITORY = { type: "git", - url: "git+https://github.com/Mesh-LLM/mesh-llm.git", - directory: "sdk/node", + url: "git+https://github.com/Mesh-LLM/mesh-packaging.git", } as const; const CANONICAL_PUBLISH_CONFIG = { @@ -114,10 +116,15 @@ export function assembleNodeSdk(options: Options): void { } export function stagePackageMetadata(packageJson: PackageMetadata): PackageMetadata { + const isBootstrapPackage = + packageJson.name === LEGACY_PACKAGE_NAME && packageJson.version === "0.74.0"; + if (packageJson.name !== CANONICAL_PACKAGE_NAME && !isBootstrapPackage) { + throw new Error(`unexpected Node SDK package name: ${packageJson.name}`); + } if (packageJson.repository !== undefined && (packageJson.repository?.url !== CANONICAL_REPOSITORY.url || - packageJson.repository?.directory !== CANONICAL_REPOSITORY.directory)) { - throw new Error("Node SDK repository metadata must identify Mesh-LLM/mesh-llm at sdk/node"); + packageJson.repository?.directory !== undefined)) { + throw new Error("Node SDK repository metadata must identify Mesh-LLM/mesh-packaging"); } if (packageJson.publishConfig !== undefined && (packageJson.publishConfig?.access !== CANONICAL_PUBLISH_CONFIG.access || @@ -126,21 +133,22 @@ export function stagePackageMetadata(packageJson: PackageMetadata): PackageMetad } return { ...packageJson, + name: CANONICAL_PACKAGE_NAME, repository: { ...CANONICAL_REPOSITORY }, publishConfig: { ...CANONICAL_PUBLISH_CONFIG }, }; } export function validatePackageMetadata(packageJson: PackageMetadata, expectedVersion: string): void { - if (packageJson.name !== "@meshllm/sdk") { + if (packageJson.name !== CANONICAL_PACKAGE_NAME) { throw new Error(`unexpected Node SDK package name: ${packageJson.name}`); } if (packageJson.version !== expectedVersion || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(packageJson.version)) { throw new Error(`Node SDK version mismatch: expected ${expectedVersion}, got ${packageJson.version}`); } - if (packageJson.repository?.url !== "git+https://github.com/Mesh-LLM/mesh-llm.git" || - packageJson.repository?.directory !== "sdk/node") { - throw new Error("Node SDK repository metadata must identify Mesh-LLM/mesh-llm at sdk/node"); + if (packageJson.repository?.url !== "git+https://github.com/Mesh-LLM/mesh-packaging.git" || + packageJson.repository?.directory !== undefined) { + throw new Error("Node SDK repository metadata must identify Mesh-LLM/mesh-packaging"); } if (packageJson.publishConfig?.access !== "public" || packageJson.publishConfig?.registry !== "https://registry.npmjs.org/") { diff --git a/tests/image-matrix.test.ts b/tests/image-matrix.test.ts index 536aa2c..cdc1861 100644 --- a/tests/image-matrix.test.ts +++ b/tests/image-matrix.test.ts @@ -72,7 +72,7 @@ test("npm lanes expand and toggle independently", () => { assert.equal(rows[4].artifact_name, "mesh-llm-node-sdk-addon-0.73.1-win32-x64"); assert.deepEqual(npmPlan(value, rows), { enabled: true, - package_name: "@meshllm/sdk", + package_name: "@mesh-llm/sdk", registry: "https://registry.npmjs.org/", source_directory: "sdk/node", targets: ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-x64"], @@ -91,7 +91,7 @@ test("npm lanes expand and toggle independently", () => { ]); assert.deepEqual(npmPlan(value, []), { enabled: false, - package_name: "@meshllm/sdk", + package_name: "@mesh-llm/sdk", registry: "https://registry.npmjs.org/", source_directory: "sdk/node", targets: [], diff --git a/tests/node-sdk-package.test.ts b/tests/node-sdk-package.test.ts index 863e0a0..0d21317 100644 --- a/tests/node-sdk-package.test.ts +++ b/tests/node-sdk-package.test.ts @@ -33,14 +33,13 @@ function fixture(t: { after(callback: () => void): void }) { writeFileSync(join(sourceDir, "native", "old", "mesh_llm_nodejs.node"), "old"); writeFileSync(join(sourceDir, "node_modules", "ignored", "index.js"), "ignored"); writeFileSync(join(sourceDir, "package.json"), JSON.stringify({ - name: "@meshllm/sdk", + name: "@mesh-llm/sdk", version: "1.2.3", main: "index.js", files: ["index.js", "native/", "LICENSE"], repository: { type: "git", - url: "git+https://github.com/Mesh-LLM/mesh-llm.git", - directory: "sdk/node", + url: "git+https://github.com/Mesh-LLM/mesh-packaging.git", }, publishConfig: { access: "public", @@ -80,29 +79,32 @@ test("stages canonical publish metadata for immutable upstream packages", (t) => const paths = fixture(t); const sourcePackagePath = join(paths.sourceRoot, "sdk", "node", "package.json"); const sourcePackage = JSON.parse(readFileSync(sourcePackagePath, "utf8")); + sourcePackage.name = "@meshllm/sdk"; + sourcePackage.version = "0.74.0"; delete sourcePackage.repository; delete sourcePackage.publishConfig; writeFileSync(sourcePackagePath, JSON.stringify(sourcePackage)); assembleNodeSdk({ addonRoot: paths.addonRoot, - expectedVersion: "1.2.3", + expectedVersion: "0.74.0", outputDir: paths.outputDir, sourceRoot: paths.sourceRoot, targets: ["linux-x64"], }); const stagedPackage = JSON.parse(readFileSync(join(paths.outputDir, "package.json"), "utf8")); + assert.equal(stagedPackage.name, "@mesh-llm/sdk"); assert.deepEqual(stagedPackage.repository, { type: "git", - url: "git+https://github.com/Mesh-LLM/mesh-llm.git", - directory: "sdk/node", + url: "git+https://github.com/Mesh-LLM/mesh-packaging.git", }); assert.deepEqual(stagedPackage.publishConfig, { access: "public", registry: "https://registry.npmjs.org/", }); const unchangedSourcePackage = JSON.parse(readFileSync(sourcePackagePath, "utf8")); + assert.equal(unchangedSourcePackage.name, "@meshllm/sdk"); assert.equal(unchangedSourcePackage.repository, undefined); assert.equal(unchangedSourcePackage.publishConfig, undefined); }); @@ -128,14 +130,13 @@ test("argument and artifact validation rejects incomplete packages", (t) => { test("assembly validates output, targets, addons, and package metadata", (t) => { const paths = fixture(t); const valid = { - name: "@meshllm/sdk", + name: "@mesh-llm/sdk", version: "1.2.3", main: "index.js", files: ["index.js", "native/", "LICENSE"], repository: { type: "git", - url: "git+https://github.com/Mesh-LLM/mesh-llm.git", - directory: "sdk/node", + url: "git+https://github.com/Mesh-LLM/mesh-packaging.git", }, publishConfig: { access: "public", @@ -143,11 +144,18 @@ test("assembly validates output, targets, addons, and package metadata", (t) => }, }; assert.deepEqual(stagePackageMetadata({ ...valid, repository: undefined }).repository, valid.repository); + assert.equal(stagePackageMetadata({ + ...valid, + name: "@meshllm/sdk", + version: "0.74.0", + }).name, "@mesh-llm/sdk"); + assert.throws(() => stagePackageMetadata({ ...valid, name: "@meshllm/sdk" }), /unexpected/); + assert.throws(() => stagePackageMetadata({ ...valid, name: "@other/sdk" }), /unexpected/); assert.throws(() => stagePackageMetadata({ ...valid, repository: { ...valid.repository, - url: "git+https://github.com/Mesh-LLM/mesh-packaging.git", + url: "git+https://github.com/Mesh-LLM/mesh-llm.git", }, }), /repository metadata/); assert.throws(() => stagePackageMetadata({ @@ -162,21 +170,22 @@ test("assembly validates output, targets, addons, and package metadata", (t) => ...valid, repository: { ...valid.repository, - url: "git+https://github.com/Mesh-LLM/mesh-packaging.git", + url: "git+https://github.com/Mesh-LLM/mesh-llm.git", }, }, "1.2.3"), /repository metadata/); assert.throws(() => validatePackageMetadata({ ...valid, repository: { type: "git", - url: "git+https://github.com/Mesh-LLM/mesh-llm.git", + url: "git+https://github.com/Mesh-LLM/mesh-packaging.git", + directory: "sdk/node", }, }, "1.2.3"), /repository metadata/); assert.throws(() => validatePackageMetadata({ ...valid, repository: { ...valid.repository, - directory: "sdk/python", + directory: "sdk/node", }, }, "1.2.3"), /repository metadata/);