Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .github/workflows/images-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion docs/matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
16 changes: 11 additions & 5 deletions docs/publishing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<version>` 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.
Comment thread
ndizazzo marked this conversation as resolved.

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.

Expand Down
2 changes: 1 addition & 1 deletion packaging/images.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
2 changes: 1 addition & 1 deletion scripts/image-matrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
24 changes: 16 additions & 8 deletions scripts/node-sdk-package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 ||
Expand All @@ -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/") {
Expand Down
4 changes: 2 additions & 2 deletions tests/image-matrix.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand All @@ -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: [],
Expand Down
35 changes: 22 additions & 13 deletions tests/node-sdk-package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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);
});
Expand All @@ -128,26 +130,32 @@ 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",
registry: "https://registry.npmjs.org/",
},
};
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({
Expand All @@ -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/);

Expand Down