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
11 changes: 9 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ permissions:
contents: read

env:
MCPP_VERSION: "2026.7.28.2"
MCPP_VERSION: "2026.8.5.4"
MCPP_RELEASE_BASE: https://github.com/mcpp-community/mcpp/releases/download

jobs:
Expand Down Expand Up @@ -251,4 +251,11 @@ jobs:
- name: Verify release mapping and reproducibility
env:
GITHUB_TOKEN: ${{ github.token }}
run: python3 tools/check_release.py --tag v0.1.0
run: |
# The tag IS the package version — check_release.py enforces it — so
# read it from the manifest rather than hardcoding a second copy here.
# The hardcoded `v0.1.0` is exactly what broke when the version scheme
# moved to upstream's checkpoint (mcpp-index#163).
tag=$(sed -n 's/^version *= *"\(.*\)"/\1/p' mcpp.toml | head -1)
echo "release contract for tag: $tag"
python3 tools/check_release.py --tag "$tag"
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,29 @@ module for mcpp. The public module is:
import llamacpp;
```

Version `0.1.0` maps to llama.cpp `b10069`. CPU is the default backend. Metal
is an additive feature for macOS ARM64.
The package version IS llama.cpp's build number: `b10069`. CPU is the default
backend. Metal is an additive feature for macOS ARM64.

## Add The Package

After `0.1.0` is published in mcpp-index:
After `b10069` is published in mcpp-index:

```bash
mcpp add mcpplibs:llamacpp@0.1.0
mcpp add ggml-org:llamacpp@b10069
```

The equivalent manifest entry is:

```toml
[dependencies.mcpplibs]
llamacpp = "0.1.0"
[dependencies.ggml-org]
llamacpp = "b10069"
```

For Metal on macOS ARM64:

```toml
[dependencies.mcpplibs]
llamacpp = { version = "0.1.0", features = ["backend-metal"] }
[dependencies.ggml-org]
llamacpp = { version = "b10069", features = ["backend-metal"] }
```

## Minimal CPU Program
Expand Down Expand Up @@ -106,18 +106,18 @@ nonzero before reporting success if either Metal buffer is absent.

## Supported Boundary

`0.1.0` includes:
`b10069` includes:

- the public llama.cpp C API exposed through `import llamacpp;`;
- the CPU backend on Linux x86_64, Linux ARM64, Windows x86_64, and macOS ARM64;
- the Metal backend on macOS ARM64;
- architecture-specific x86_64 and ARM64 CPU source selection.

`0.1.0` does not include `mtmd`, CUDA, Vulkan, RPC, or other upstream backends.
`b10069` does not include `mtmd`, CUDA, Vulkan, RPC, or other upstream backends.
It also does not claim that every upstream model architecture has been tested.
Deprecated upstream C APIs remain exported for API completeness; code that
calls them may receive the deprecation warnings defined by upstream.

See [the update policy](docs/upstream-update-policy.md) for versioning and
checkpoint selection, and [the 0.1.0 validation record](docs/validation/0.1.0.md)
checkpoint selection, and [the b10069 validation record](docs/validation/b10069.md)
for exact evidence and remaining release gates.
41 changes: 30 additions & 11 deletions docs/upstream-update-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ checkpoints proven by the complete platform and API gate.

The wrapper and upstream project have separate identities:

- `llama.cpp-m` has its own semantic version.
- `llama.cpp-m` carries **upstream's version verbatim**, not a version of its
own. See "Version Model" below for why that replaced a separate semantic
version.
- Every wrapper release maps to exactly one upstream tag and commit.
- The package coordinate is `mcpplibs:llamacpp`; consumers use
`import llamacpp;`.
- The package coordinate is `ggml-org:llamacpp`; consumers use
`import llamacpp;`. The namespace names whose library this is — llama.cpp is
ggml-org's — rather than who packaged it (see mcpp-index#163).
- mcpp-index points only to immutable `llama.cpp-m` releases, never a moving
upstream branch.

Expand All @@ -25,16 +28,32 @@ verified source under `third_party/llama.cpp/` and does not use a Git submodule.

## Version Model

The initial wrapper release is `0.1.0`, based on upstream `b10069`.
**The wrapper version IS the upstream build number.** The first release is
`b10069`, because that is the llama.cpp it contains.

| Change | Required wrapper version change |
|---|---|
| Wrapper fix on the same upstream checkpoint | Patch |
| New upstream checkpoint or additive API/model/backend support | Minor |
| Module/feature rename or intentional public API removal | Major |
This replaced a separate semantic version (`0.1.0`). The reason is visible in
what the old README had to say next to it: *"Version `0.1.0` maps to llama.cpp
`b10069`."* A version that needs a lookup table to read is not telling the
consumer which library they are getting — which is the one thing a version is
for. The same correction is being applied across the ecosystem
(mcpp-index#163): `imgui@0.0.6` was ImGui 1.92.8, `ffmpeg@0.0.3` was FFmpeg
8.1.2, `opencv@0.0.10` was OpenCV 5.0.0.

This policy applies before `1.0.0`: an upstream update cannot silently break a
published module contract.
| Change | Wrapper version |
|---|---|
| New upstream checkpoint | that checkpoint's build number (`b10420`) |
| Wrapper fix on the same upstream checkpoint | a dotted revision (`b10069.1`) |
| Module/feature rename or intentional public API removal | a new checkpoint release, called out in the release notes |

**What this costs.** `bNNNNN` is not semver, so mcpp cannot express a RANGE over
it — `^b10069` does not parse, and consumers pin exactly. Verified that exact
pinning does work end to end: resolution, version-key matching and wire
addressing all handle a literal non-semver key (mcpp#363 tracks range support).

Since ordering is no longer comparable, the "major/minor/patch" contract above
cannot be carried by the version string. An upstream update still may not
silently break a published module contract — that guarantee now rests on the
API snapshot gate and the release notes rather than on a version component.

Every release records:

Expand Down
12 changes: 6 additions & 6 deletions docs/validation/0.1.0.md → docs/validation/b10069.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# llama.cpp-m 0.1.0 Validation
# llama.cpp-m b10069 Validation

This record separates completed local evidence from GitHub Actions evidence
that requires an authorized push. Version `0.1.0` is not published by this
that requires an authorized push. Version `b10069` is not published by this
branch.

## Release Identity

| Field | Value |
|---|---|
| Wrapper | `llama.cpp-m 0.1.0` |
| Package | `mcpplibs:llamacpp@0.1.0` |
| Wrapper | `llama.cpp-m b10069` |
| Package | `ggml-org:llamacpp@b10069` |
| Module | `import llamacpp;` |
| Upstream tag | `b10069` |
| Upstream commit | `178a6c44937154dc4c4eff0d166f4a044c4fceba` |
Expand All @@ -22,7 +22,7 @@ The final local release contract printed:
Snapshot matches.
All exports match.
Snapshot matches.
llama.cpp-m 0.1.0 -> llama.cpp b10069 (178a6c44937154dc4c4eff0d166f4a044c4fceba)
llama.cpp-m b10069 -> llama.cpp b10069 (178a6c44937154dc4c4eff0d166f4a044c4fceba)
```

## GitHub Actions Status
Expand Down Expand Up @@ -143,7 +143,7 @@ The `actionlint` image was pulled as
GitHub Actions remains authoritative for actual workflow execution and the
required platform matrix.

## Unsupported In 0.1.0
## Unsupported In b10069

- `mtmd` multimodal API;
- CUDA, Vulkan, RPC, and other upstream backends;
Expand Down
16 changes: 15 additions & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
# The namespace names whose library this is, not who packaged it; the version
# says which upstream you are getting. `mcpplibs` is mcpp's DEFAULT namespace
# (kDefaultNamespace), not an answer to "whose library" — llama.cpp is
# ggml-org's, and this repository only adds the C++23 module layer.
#
# The version is upstream's build number VERBATIM. `0.1.0` needed a lookup
# table to read, and the README carried it: "Version 0.1.0 maps to llama.cpp
# b10069". A version that needs a decoder ring is not a version.
#
# `b10069` is not semver, and mcpp handles it — verified: resolution, version-key
# matching and wire addressing all work on a literal non-semver key. What it
# cannot do is RANGES over it (`^b10069` is not expressible), so consumers pin
# exactly. See mcpp#363; rule and migration table in mcpp-index#163.
[package]
namespace = "ggml-org"
name = "llamacpp"
version = "0.1.0"
version = "b10069"
description = "C++23 module package for llama.cpp b10069"
license = "MIT"
standard = "c++23"
Expand Down
10 changes: 9 additions & 1 deletion tests/unit/test_build_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,15 @@ def target_sources(self, predicate: str) -> list[str]:
def test_identity_and_public_module_are_cxx23(self):
package = self.manifest["package"]
self.assertEqual(package["name"], "llamacpp")
self.assertEqual(package["version"], "0.1.0")
# The namespace names whose library this is (mcpp-index#163); the version
# IS the upstream checkpoint, so it must agree with the build info the
# manifest generates for ggml.
self.assertEqual(package["namespace"], "ggml-org")
self.assertEqual(package["version"], "b10069")
self.assertIn(
f'#define GGML_VERSION "{package["version"]}"',
self.manifest["generated_files"]["generated/ggml_build_info.h"],
)
self.assertEqual(package["standard"], "c++23")
self.assertIn("src/llamacpp.cppm", self.base_sources())
self.assertEqual(self.manifest["targets"], {"llama": {"kind": "lib"}})
Expand Down
67 changes: 35 additions & 32 deletions tests/unit/test_check_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

def write_release_tree(root: Path) -> None:
(root / "mcpp.toml").write_text(
'[package]\nname = "llamacpp"\nversion = "0.1.0"\n',
'[package]\nname = "llamacpp"\nversion = "b10069"\n',
encoding="utf-8",
)
(root / "upstream.lock").write_text(
Expand Down Expand Up @@ -39,9 +39,9 @@ def tearDown(self):
self.tempdir.cleanup()

def test_tag_matches_package_version_and_lock_mapping(self):
identity = check_release.load_identity(self.root, "v0.1.0")
identity = check_release.load_identity(self.root, "b10069")

self.assertEqual(str(identity.version), "0.1.0")
self.assertEqual(str(identity.version), "b10069")
self.assertEqual(identity.upstream_tag, "b10069")
self.assertEqual(
identity.upstream_commit,
Expand All @@ -50,7 +50,7 @@ def test_tag_matches_package_version_and_lock_mapping(self):

def test_rejects_tag_version_mismatch(self):
with self.assertRaisesRegex(ValueError, "tag.*version"):
check_release.load_identity(self.root, "v0.1.1")
check_release.load_identity(self.root, "b10069.1")

def test_rejects_noncanonical_archive_mapping(self):
lock = self.root / "upstream.lock"
Expand All @@ -62,7 +62,7 @@ def test_rejects_noncanonical_archive_mapping(self):
)

with self.assertRaisesRegex(ValueError, "archive URL"):
check_release.load_identity(self.root, "v0.1.0")
check_release.load_identity(self.root, "b10069")

def test_rejects_consistent_mapping_to_upstream_fork(self):
lock = self.root / "upstream.lock"
Expand All @@ -75,10 +75,10 @@ def test_rejects_consistent_mapping_to_upstream_fork(self):
)

with self.assertRaisesRegex(ValueError, "official upstream repository"):
check_release.load_identity(self.root, "v0.1.0")
check_release.load_identity(self.root, "b10069")

def test_changed_existing_tag_mapping_fails(self):
current = check_release.load_identity(self.root, "v0.1.0")
current = check_release.load_identity(self.root, "b10069")
existing = current._replace(upstream_commit="f" * 40)

with self.assertRaisesRegex(ValueError, "immutable"):
Expand All @@ -93,50 +93,53 @@ def git(_root, *arguments):
if arguments[0] == "rev-parse":
return completed(arguments, 0, "d" * 40)
if arguments[-1].endswith(":mcpp.toml"):
return completed(arguments, 0, '[package]\nversion = "0.1.0"\n')
return completed(arguments, 0, '[package]\nversion = "b10069"\n')
return completed(arguments, 1)

with mock.patch.object(check_release, "_git", side_effect=git):
with self.assertRaisesRegex(ValueError, "existing release tag"):
check_release._tag_identity(self.root, "v0.1.0")
check_release._tag_identity(self.root, "b10069")

def test_same_checkpoint_wrapper_fix_requires_patch_bump(self):
previous = check_release.ReleaseIdentity.for_test("0.1.0", "b10069")
patch = check_release.ReleaseIdentity.for_test("0.1.1", "b10069")
minor = check_release.ReleaseIdentity.for_test("0.2.0", "b10069")
def test_same_checkpoint_wrapper_fix_requires_a_revision_bump(self):
# The build number names the checkpoint, so a same-checkpoint release can
# only move the revision. Moving the build number instead would claim a
# checkpoint that is not the one vendored.
previous = check_release.ReleaseIdentity.for_test("b10069", "b10069")
revision = check_release.ReleaseIdentity.for_test("b10069.1", "b10069")
wrong_build = check_release.ReleaseIdentity.for_test("b10123", "b10069")

check_release.validate_version_transition(previous, patch)
with self.assertRaisesRegex(ValueError, "patch"):
check_release.validate_version_transition(previous, minor)
check_release.validate_version_transition(previous, revision)
with self.assertRaisesRegex(ValueError, "build number"):
check_release.validate_version_transition(previous, wrong_build)

def test_changed_checkpoint_requires_at_least_minor_bump(self):
previous = check_release.ReleaseIdentity.for_test("0.1.0", "b10069")
patch = check_release.ReleaseIdentity.for_test("0.1.1", "b10123")
minor = check_release.ReleaseIdentity.for_test("0.2.0", "b10123")
def test_changed_checkpoint_takes_that_checkpoints_build_number(self):
previous = check_release.ReleaseIdentity.for_test("b10069", "b10069")
forward = check_release.ReleaseIdentity.for_test("b10123", "b10123")
backward = check_release.ReleaseIdentity.for_test("b10069.1", "b10123")

with self.assertRaisesRegex(ValueError, "minor"):
check_release.validate_version_transition(previous, patch)
check_release.validate_version_transition(previous, minor)
check_release.validate_version_transition(previous, forward)
with self.assertRaisesRegex(ValueError, "higher build number"):
check_release.validate_version_transition(previous, backward)

def test_rejects_candidate_older_than_an_existing_release(self):
current = check_release.ReleaseIdentity.for_test("0.1.1", "b10069")
higher = check_release.ReleaseIdentity.for_test("0.2.0", "b10123")
lower = check_release.ReleaseIdentity.for_test("0.1.0", "b10069")
current = check_release.ReleaseIdentity.for_test("b10069.1", "b10069")
higher = check_release.ReleaseIdentity.for_test("b10123", "b10123")
lower = check_release.ReleaseIdentity.for_test("b10069", "b10069")
tags = subprocess.CompletedProcess(
[], 0, stdout="v0.2.0\nv0.1.0\n", stderr=""
[], 0, stdout="b10123\nb10069\n", stderr=""
)

with mock.patch.object(check_release, "_git", return_value=tags):
with mock.patch.object(
check_release,
"_tag_identity",
side_effect=lambda _root, tag: {
"v0.2.0": higher,
"v0.1.0": lower,
"b10123": higher,
"b10069": lower,
}[tag],
):
with self.assertRaisesRegex(ValueError, "newer release already exists"):
check_release._previous_identity(self.root, current, "v0.1.1")
check_release._previous_identity(self.root, current, "b10069.1")

def test_vendored_source_rejects_git_metadata_and_submodules(self):
check_release.validate_vendor_boundary(self.root)
Expand Down Expand Up @@ -167,10 +170,10 @@ def test_repository_checks_invoke_import_export_and_snapshot_check_modes(self):
self.assertEqual(call.kwargs["cwd"], self.root)

def test_mapping_line_is_stable(self):
identity = check_release.load_identity(self.root, "v0.1.0")
identity = check_release.load_identity(self.root, "b10069")
self.assertEqual(
check_release.format_mapping(identity),
"llama.cpp-m 0.1.0 -> llama.cpp b10069 "
"llama.cpp-m b10069 -> llama.cpp b10069 "
"(178a6c44937154dc4c4eff0d166f4a044c4fceba)",
)

Expand Down
3 changes: 2 additions & 1 deletion tests/unit/test_repository_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ def test_no_submodule_or_consumer_time_upstream_fetch(self):
def test_package_and_module_identity(self):
manifest = (ROOT / "mcpp.toml").read_text(encoding="utf-8")
module = (ROOT / "src/llamacpp.cppm").read_text(encoding="utf-8")
self.assertRegex(manifest, r'(?m)^namespace\s*=\s*"ggml-org"$')
self.assertRegex(manifest, r'(?m)^name\s*=\s*"llamacpp"$')
self.assertRegex(manifest, r'(?m)^version\s*=\s*"0\.1\.0"$')
self.assertRegex(manifest, r'(?m)^version\s*=\s*"b10069"$')
self.assertIn("export module llamacpp;", module)

def test_vendored_tree_is_not_locally_patched(self):
Expand Down
2 changes: 1 addition & 1 deletion tools/audit_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ def main() -> int:

root = args.source.resolve()
if not (root / 'include/llama.h').is_file():
ap.error(f'mcpplibs:llamacpp source tree not found: {root}')
ap.error(f'ggml-org:llamacpp source tree not found: {root}')

expected = None
if args.check:
Expand Down
Loading
Loading