Skip to main content

MEP 57. Mochi module and package system

FieldValue
MEP57
TitleMochi module and package system
AuthorMochi core
StatusDraft
TypeStandards Track
Created2026-05-29 06:25 (GMT+7)
DependsMEP-1 (Grammar), MEP-2 (AST), MEP-4 (Type System), MEP-6 (Type checker), MEP-45 (C transpiler, lowering consumer), MEP-46 (BEAM, Hex), MEP-47 (JVM, Maven Central), MEP-48 (.NET, NuGet), MEP-49 (Swift, Swift Package Index), MEP-50 (Kotlin), MEP-51 (Python, PyPI), MEP-52 (TypeScript, npm + JSR), MEP-53 (Rust, crates.io)
Research~/notes/Spec/0057/01..12
Tracking/docs/implementation/0057/

Abstract

Mochi today (May 2026, immediately after MEP-53's Rust target landed) has nine lowering paths (vm3 plus MEP-45 through MEP-53) but no source-level package management. The language surface defines import "path" as Alias (file-relative) and import go|python|typescript "..." as Alias (FFI). The runtime resolves imports by walking parent directories for go.mod (Go's own module file) and joining the import string against the discovered root. mochi get exists but delegates to go mod tidy, fetching Go FFI dependencies and never Mochi source. There is no Mochi manifest, no Mochi lockfile, no Mochi registry, no version solver, no publish flow, no signing story, no SBOM, no advisory database, no capability declaration, no vendor mode, no workspace primitive. Mochi programs larger than a single file exist only as filesystem trees inside an unmanaged Go module; sharing reusable Mochi code across users requires emailing a tarball.

MEP-57 specifies the first source-level package management system for Mochi. It is the source-language counterpart to the per-target packaging stories already shipped in MEP-45 through MEP-53. Where each transpiler MEP defines how compiled Mochi becomes a publishable artifact in its target ecosystem (Maven Central for JVM, NuGet for .NET, PyPI for Python, npm and JSR for TypeScript, crates.io for Rust, Hex for BEAM, Swift Package Index for Swift), MEP-57 defines the Mochi-native manifest, lockfile, registry, solver, signing pipeline, capability surface, and the polyglot fan-out that lets one source tree publish to all eight ecosystems plus the new central Mochi registry from a single mochi pkg publish invocation.

The proposal is additive at the source-language layer: the existing import "./path" as Alias form keeps file-relative semantics; the new form import "scope/name@^1.2.3" as Alias is resolved through the new manifest plus lockfile plus registry pipeline. FFI imports are unchanged. The eight transpiler MEPs consume MEP-57's resolved module graph as input; their lowering passes are untouched.

The system is anchored on six load-bearing decisions, each justified in §Rationale and surveyed in the companion research notes:

  1. TOML manifest (mochi.toml) shaped like Cargo's Cargo.toml and uv's pyproject.toml plus three Mochi-specific sections. TOML is the modern default for hand-edited manifests across Cargo (since 2014), Pixi (since 2024), uv via pyproject.toml (PEP 518 / 621), pkl-package, dprint, biome, and Bazel-MODULE (TOML-shaped). JSON is rejected for the human-edit surface (no comments, no trailing commas); YAML is rejected (the Norway problem, whitespace traps); a Mochi-DSL manifest is rejected (the manifest must be parseable before any Mochi compiler runs, the bootstrap rule). The three Mochi-only sections are [capabilities] (which capabilities a package needs at the import boundary), [targets] (which of the nine lowering paths the package supports), and [provenance] (publisher OIDC subject expected by the trusted-publish flow). See 01-language-surface §2, 04-manifest-format §1, 02-design-philosophy §1.

  2. PubGrub-derived version solver with incompatibility-driven conflict explanations. PubGrub (Natalie Weizenbaum, Dart team, 2018; refined by the uv team 2024-2025 with universal-platform resolution; Cargo's next-gen resolver RFC #3796 ships PubGrub since edition 2024) is the current best-in-class algorithm for package version resolution. It derives unsolvable conflicts as incompatibilities and produces human-readable "X cannot be selected because Y depends on Z which requires W < N" explanations that classical SAT solvers do not match. Mochi ships its own Go implementation under pkg/pkgsolver/pubgrub/. MVS (Go modules, Bazel bzlmod) is rejected for the user-facing solver because polyglot fan-out crosses ecosystems with semver semantics (npm, crates.io) and MVS does not reconcile incompatible semver branches across mixed downstreams. Plain SAT (Cargo's classic, npm) is rejected because incompatibility explanations are too weak for the level of "why won't this update?" diagnostics modern users expect. Clingo / ASP (Spack) is rejected because the runtime dependency is too heavy for the single-binary mochi CLI. See 02-design-philosophy §2, 05-solver-design §1, 03-prior-art-registries §1.

  3. Text lockfile (mochi.lock) with a per-platform resolved tree, a stable canonical serialisation, and a version envelope. The lockfile is text (TOML-shaped), human-readable, line-diffable, and reproducible. Per-platform sections allow the same lockfile to encode different OS / arch / target combinations, mirroring uv's universal lockfile and Cargo's per-target sections. The serialisation is canonical: keys are sorted, integer fields are decimal, hashes are lowercase hex. A binary lockfile is rejected: Bun introduced bun.lockb in 2023 and reversed to a text lockfile in 2025 after community pushback over diff cost, merge-conflict trauma, and code-review opacity. The lockfile carries version = 1 at the top to allow future format migrations. CI verifies lockfile-manifest consistency via mochi pkg lock --check. See 06-lockfile-format §1, 02-design-philosophy §3.

  4. Sparse HTTPS registry index plus content-addressed BLAKE3 + SHA-256 dual-hashed object store. The registry is served by two endpoints: a sparse index at https://index.mochi.dev/<scope>/<name> returning the per-package version manifest as line-delimited JSON (mirroring Cargo's sparse-index protocol, GA March 2023, demonstrated 5-20x speedup over the git-cloned legacy index), and a content-addressed object store at https://blobs.mochi.dev/<blake3-hex> returning the package tarball keyed by its BLAKE3-256 digest. BLAKE3 is the primary integrity hash because it is parallel, 3-7x faster than SHA-256 on modern hardware, and already adopted at scale by Cargo's checksum migration (2024). SHA-256 is the secondary hash for cross-ecosystem interop with SLSA, Sigstore, and npm. A git-clone-based index is rejected for the primary path (Cargo's 2023 sparse-index migration is the canonical case study). A central registry is the v1 default; a federated mirror replication protocol ships in Phase 11, and a Go-checksum-DB-style transparency log is the v2 candidate. See 07-registry-index §1, 08-content-addressed-store §1, 02-design-philosophy §4.

  5. Sigstore plus GitHub OIDC keyless trusted publishing as the only publish surface; no long-lived API tokens, anywhere. Publishers authenticate via their CI environment's OIDC identity provider (GitHub Actions, GitLab CI, Buildkite, CircleCI, Buildbarn, anything with sigstore-compatible OIDC), the registry verifies the OIDC token, exchanges it with Fulcio for a short-lived signing certificate, and the publisher signs the tarball using that certificate. The signature plus the certificate plus the Rekor log entry form the provenance bundle stored alongside the tarball. Verification (mochi pkg audit signatures) checks the bundle against the Sigstore root of trust. This matches npm Trusted Publishing (GA April 2024), Maven Central Sigstore (GA October 2024), PyPI Trusted Publishing under PEP 740 (GA late 2025), and Cargo's RFC #3724 trusted-publishing path. Long-lived API tokens are rejected: every major supply-chain incident from 2022 through 2026 (event-stream, ua-parser-js, xz-utils, retire.js, the ESLint plugin compromise, and the cascading reflected-string PyPI flood of 2025) traces to a compromised long-lived token or a stale credential left on a dev laptop. See 09-trusted-publishing §1, 02-design-philosophy §5.

  6. Capability declarations at the package boundary, surfaced in the manifest, audited at lock time, enforced at the transpiler boundary. A Mochi package declares the capabilities it requires via [capabilities] in mochi.toml. The capability set is a closed enumeration: fs.read, fs.write, net.dial, net.listen, env, ffi, clock, random, proc.spawn. The consumer acknowledges the surface via a lockfile annotation capabilities-seen = [...] that pins the audited capability set; mochi pkg lock --check fails if a transitive dependency has added a capability the consumer has not re-audited (the monotonicity rule). At runtime the capability set is informational in the vm3 path (logged via the diagnostic pipeline) and enforced at the transpiler boundary: the TypeScript / Deno target maps to Deno's permission flags (--allow-read, --allow-net); the Python target wraps the capabilities as a mochi_runtime.caps manifest the runtime checks at startup; the Wasm component-model target maps capabilities to component-model imports. This direction tracks Deno permissions, Roc platforms, the Wasm Component Model, and Pony's reference capabilities at module boundaries. See 10-capability-model §1, 02-design-philosophy §6.

The gate for each delivery phase is empirical: every fixture under tests/pkgsystem/<phase>/ must produce a mochi.lock that matches the recorded golden lockfile byte-for-byte, the PubGrub solver must produce the same incompatibility-derivation trace under the published Dart-team reference oracle, a mochi pkg lock --check must pass on linux-x86_64, linux-arm64, macos-arm64, and windows-x86_64, the Sigstore round-trip must verify cleanly against a sigstore-mock-fulcio harness, and the emitted CycloneDX 1.6 SBOM must validate against the NIST schema.

Motivation

Mochi today ships nine compile targets (vm3 plus MEP-45 through MEP-53) but does not yet ship the substrate that turns those targets into a usable language ecosystem: a way for one Mochi user to write a library, version it, sign it, publish it, and let another Mochi user depend on it with a single command. Every successful programming language ecosystem of the last two decades made this layer load-bearing:

  1. Cargo (Rust, 2014). Cargo plus crates.io is the single most-cited reason Rust adoption accelerated past every other modern systems language. The manifest is Cargo.toml; the lockfile is Cargo.lock; the solver started as a SAT-derivative and is migrating to PubGrub via RFC #3796 (next-gen resolver, edition 2024 default); the index moved from git-clone to sparse HTTPS in March 2023 (Cargo 1.68); package signing landed via Sigstore RFC #3724 in 2024-2025; cargo install plus cargo install --git plus cargo vendor plus cargo add cover the entire dependency workflow. Mochi must match this surface.

  2. Go modules + workspaces (Go, 2018-2024). Go's MVS (Minimum Version Selection) is the simplest solver in production; go.sum plus the public checksum database (sum.golang.org) provide deterministic verification without long-lived API tokens; go work (Go 1.18) added monorepo support; the retract directive (Go 1.16) added supply-chain remediation. Mochi cannot use MVS as its primary solver because polyglot fan-out crosses ecosystems where MVS does not hold, but Mochi adopts the public-checksum transparency log story for v2.

  3. Deno + JSR (Deno 2024-2026). Deno 2.0 (October 2024) plus JSR (jsr.io, GA September 2024) reset expectations for the JavaScript world: source-only publishing, automatic .d.ts generation, OIDC trusted publishing, capability-aware permissions at the import layer. JSR's source-only model is the cleanest analogue to what Mochi needs (Mochi central is source-only; lowering to per-target artifacts happens at consume time or polyglot-publish time).

  4. Python uv plus PEP 751 (pylock.toml, accepted late 2025). Astral's uv (GA 2024, broad production adoption by 2026) merged the package-manager and resolver roles, brought PubGrub to Python (replacing pip's depth-first resolver), and shipped a universal lockfile (the same uv.lock resolves across linux + macos + windows + multiple Python versions). PEP 751 ratified pylock.toml as the cross-tool lockfile format in late 2025. uv is the explicit model Mochi follows for solver speed, universal lockfile shape, and single-binary distribution.

  5. Bazel bzlmod (Bazel 7.0 December 2023, mandatory in Bazel 8 late 2024). bzlmod replaced WORKSPACE files with MODULE.bazel plus a central registry (bcr.bazel.build) plus a deterministic version selection close to MVS. The Bazel migration is the largest enterprise-scale package-system replacement of the last five years; the lessons (central registry as a distinct service, module extensions for non-Bazel deps, lockfile as ground truth) inform Mochi's choices.

  6. Bun 1.2 + binary lockfile reversal (2023-2025). Bun introduced bun.lockb (binary lockfile) in 2023 citing parser speed; reversed to a text lockfile in 2025 after a year of community pushback about merge conflicts, code review opacity, and audit-trail loss. Mochi takes the reversed decision as the direct lesson: lockfiles are read by humans more often than by machines and must be text.

  7. npm Trusted Publishing (GA April 2024) plus Maven Central Sigstore (GA October 2024) plus PyPI PEP 740 (late 2025) plus Cargo RFC #3724. Four of the top five package ecosystems converged within an 18-month window on Sigstore-keyless OIDC-trusted publishing. By 2026 the industry direction is unambiguous: long-lived API tokens are deprecated, OIDC short-lived attestation is the default. Mochi ships exclusively on this path; legacy API-token publishing is not a Mochi feature.

  8. xz-utils incident (March 2024) and the post-incident industry response. The xz-utils backdoor (CVE-2024-3094) was inserted by a long-running social engineering campaign against a single maintainer; remediation required SBOM-driven dependency auditing across thousands of downstream projects. Mochi ships CycloneDX 1.6 and SPDX 3.0 SBOMs as a phase-15 deliverable; the advisory database query path ships in phase 16. Capability declarations (the load-bearing decision §6) are partly motivated by the observation that xz had no business calling into sshd; a capability-aware import system catches that class of cross-domain mismatches at lock time.

  9. Empirical ecosystem studies (MSR 2024-2026). Recent measurement papers on npm, PyPI, and crates.io show that the median package depends on 70+ transitive packages, that 30% of vulnerabilities reach a project through indirect dependencies, that dependency-tree growth tracks log(time), and that abandonment rates exceed 15% per year on the smallest packages. The implication is that any new package system must (a) make dependency trees inspectable (mochi pkg tree, mochi pkg why), (b) make audit queries cheap (mochi pkg audit against a curated advisory feed), and (c) keep the resolver fast enough that re-solving for a security update is not a hardship.

  10. Recent module-systems theory (POPL / OOPSLA 2024-2026). First-class modules, generative vs applicative functors, modular type checking with separate compilation guarantees, and effect rows at module boundaries are active research topics. Mochi's v1 module system stays simple (one source file per module, one manifest per package, capability declarations at the boundary) but leaves room for v2 to take on richer module types if the language surface warrants it.

The remaining nine Mochi targets (the vm3 plus the eight transpilers) are the right targets for their respective ecosystems. MEP-57 is the source-language layer above them: one mochi.toml, one mochi.lock, one publish flow, eight downstream artifacts.

Specification

This section is normative. Sub-notes under ~/notes/Spec/0057/01..12 are informative.

1. Pipeline overview

MEP-57 introduces a manifest-driven module resolution layer that sits between the parser and every consumer (the vm3, every transpiler MEP):

Mochi source tree
| parser (MEP-1/2/3, reused)
| type checker (MEP-4/6, reused)
v
typed AST per file
| module resolution (MEP-57: manifest + lockfile + index + blob store)
v
resolved module graph
| one of: vm3 evaluator, MEP-45..53 transpiler lowering
v
runtime / target artifact

The driver chain:

mochi.toml -> pkgmanifest.Parse
|
v
pkgmanifest.Manifest + working tree
| pkgsolver.pubgrub.Solve(manifest, index)
v
solution graph -> pkglock.Write -> mochi.lock
| pkgblob.FetchAll(solution) using BLAKE3 + SHA-256 verification
v
populated content-addressed cache
| ResolveImport(path) at parse time
v
parser sees module bodies as if they were local files

mochi run (vm3) and mochi build --target=<X> both consume the resolved module graph identically.

2. Manifest: mochi.toml

Per 04-manifest-format §1 to §10:

mochi-manifest = 1

[package]
name = "@scope/awesome"
version = "0.4.2"
edition = "2026"
min-mochi-version = "0.10"
license = "Apache-2.0 OR MIT"
description = "Awesome demo library for Mochi."
homepage = "https://github.com/example/awesome"
repository = "https://github.com/example/awesome"
authors = ["Tam Nguyen <[email protected]>"]
keywords = ["demo", "example"]
categories = ["data-science"]
readme = "README.md"
include = ["src/**/*.mochi", "LICENSE", "README.md"]

[dependencies]
serde = "^1.0"
"@mochi/runtime" = "^0.6"
sub = { path = "../sub" }
exotic = { git = "https://github.com/example/exotic", tag = "v0.2.0" }

[dev-dependencies]
bench = "^0.1"

[capabilities]
required = ["fs.read", "net.dial"]
optional = ["fs.write"]

[targets]
supports = ["vm3", "typescript", "python", "jvm", "dotnet"]
defaults = ["vm3", "typescript"]

[targets.overrides.python]
dependencies = { numpy = "^2.0" }

[targets.overrides.typescript]
dependencies = { "@types/node" = "^22" }

[provenance]
publisher = "repo:example/awesome:ref:refs/tags/*"
repository = "https://github.com/example/awesome"
workflow = ".github/workflows/release.yml"

The reserved top-level tables are: [package], [dependencies], [dev-dependencies], [build-dependencies], [capabilities], [targets], [provenance], [workspace]. Unknown top-level keys are rejected with a manifest validation error (forward compatibility is opt-in via mochi-manifest = 2 once a v2 lands).

Package names are scoped: @<scope>/<name> or a bare <name> (the bare form maps to the @mochi/<name> scope by default for the central registry; explicit scoping is preferred). Names match [a-z0-9][a-z0-9-]* per scope segment. Reserved scope names: @mochi (the runtime and core libraries), @std (the language standard library, future). Length cap: 64 chars per segment.

Versions follow semver 2.0.0 plus the Cargo ^X.Y.Z, ~X.Y.Z, >=X.Y, <X+1.0, =X.Y.Z requirement syntax. Pre-release identifiers (1.0.0-alpha.1) are admitted; build-metadata identifiers (1.0.0+linux) are admitted in version strings but ignored in compatibility decisions.

Edition is the language-surface dialect ("2026" is the v1 default, matching the May 2026 spec).

3. Lockfile: mochi.lock

Per 06-lockfile-format §1 to §8:

version = 1
mochi = "0.10.2"
platforms = ["linux-x86_64", "linux-arm64", "macos-arm64", "windows-x86_64"]
capabilities-seen = ["fs.read", "fs.write", "net.dial"]

[[package]]
name = "@scope/awesome"
version = "0.4.2"
source = { kind = "path", path = "." }
dependencies = ["[email protected]", "@mochi/[email protected]"]
capabilities = ["fs.read", "net.dial"]

[[package]]
name = "serde"
version = "1.0.193"
source = { kind = "registry", registry = "https://index.mochi.dev" }
blake3 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
sha256 = "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
dependencies = []
capabilities = []

[[package]]
name = "@mochi/runtime"
version = "0.6.4"
source = { kind = "registry", registry = "https://index.mochi.dev" }
blake3 = "..."
sha256 = "..."
dependencies = []
capabilities = ["fs.read", "fs.write", "net.dial", "env"]

The lockfile is a text file; the canonical serialiser emits keys in sorted order, integer fields in decimal, and hex hashes in lowercase. Two mochi pkg lock runs over the same manifest plus the same index snapshot produce byte-identical lockfiles. The version = 1 envelope reserves the right to migrate the format in v2 with a clean cut-over.

Per-platform sections sit under [target."<platform>"] for platform-specific resolution divergence (a dependency that has a different version on Windows than on Linux, for instance because of an OS-specific FFI dependency):

[target."windows-x86_64".dependencies]
winapi = "0.3.9"

4. PubGrub-derived version solver

Per 05-solver-design §1 to §16:

The Mochi solver is a Go implementation of PubGrub. The algorithm:

  1. Start with the root package's dependency set as a unit incompatibility.
  2. Choose an unresolved package. Pick the highest version not yet refuted by an existing incompatibility.
  3. Propagate: for the choice, derive consequences via unit propagation through known incompatibilities. If a propagation fires, record a decision and continue.
  4. On a conflict (an incompatibility becomes satisfied by current decisions), resolve the conflict via conflict-driven backtracking: derive a new incompatibility from the conflict-induced terms, undo decisions back to the level where the new incompatibility becomes a unit clause, then continue.
  5. Terminate when every package required by the root has a decision (a solution) or when the root itself becomes incompatible with itself (no solution exists, with an explanation derived from the final incompatibility chain).

Explanations: mochi pkg add foo@^2 && mochi pkg add bar that produces a conflict emits:

error: dependencies cannot be solved
because bar (>= 1.4) depends on baz (= 0.9)
and foo (^2) depends on baz (>= 1.0)
no version of baz satisfies both = 0.9 and >= 1.0

The reference oracle: every PubGrub fixture from the Dart team's published reference plus a curated set of uv issue-tracker regression fixtures (uv tracks PubGrub-related issues in its solver label).

Performance target: solver on a manifest with 100 direct deps and 1000 transitive deps completes in under 200ms on a 2024-vintage M2 laptop, single-threaded. Index fetches dominate wall-clock time; the solver itself is in-memory after the first index touch.

5. Sparse HTTPS registry index

Per 07-registry-index §1 to §9:

The index endpoint serves https://index.mochi.dev/<scope>/<name> returning line-delimited JSON (LDJSON), one line per published version, mirroring Cargo's sparse-index format:

{"name":"@mochi/runtime","vers":"0.6.0","deps":[...],"cksum":"...","yanked":false,"features":{...},"capabilities":["fs.read"]}
{"name":"@mochi/runtime","vers":"0.6.1","deps":[...],"cksum":"...","yanked":false,"features":{...},"capabilities":["fs.read"]}
...

HTTP caching is canonical: the client sends If-None-Match with the previously-received ETag; the server returns 304 Not Modified if unchanged. Cache miss falls back to If-Modified-Since. A directory-prefix layout (@mochi/run/runtime) prevents single-directory hot spots at scale.

Index responses are unsigned (the integrity story rides on the per-blob BLAKE3 + SHA-256 + Sigstore bundle, not on the index itself). The index host is HTTPS-only; the CA root set is the system root set; no custom CA pinning at v1.

Yanking: a yanked: true entry marks a version as unrecommended (still resolvable for existing lockfiles, but rejected for new resolutions unless the manifest pins it). Yanking does not delete; the published artifact remains downloadable for reproducibility.

6. Content-addressed object store

Per 08-content-addressed-store §1 to §10:

Package tarballs are stored at https://blobs.mochi.dev/<blake3-prefix>/<blake3-full>.tar.zst. The path layout:

blobs.mochi.dev/
ab/ # first 2 hex chars of BLAKE3
abcdef.../ # next 6 hex chars
abcdef0123456... # full BLAKE3-256 hex, 64 chars
.tar.zst
.sigstore.bundle

On fetch, the client computes both BLAKE3-256 and SHA-256 over the received bytes; both must match the lockfile-recorded hashes; either mismatch is a fatal error.

Compression: zstd level 19 (compress side) with --long=27 for cross-blob deduplication potential; decompression is single-threaded zstd. The tarball format is POSIX ustar with file mtimes set to SOURCE_DATE_EPOCH (the build's pinned timestamp), uid / gid normalised to zero, and entries sorted lexicographically. Reproducibility gate: byte-identical .tar.zst across two CI hosts on the same source tree with the same SOURCE_DATE_EPOCH.

Local cache: ~/.cache/mochi/blobs/<blake3-prefix>/<blake3-full>.tar.zst. Cache eviction is LRU with a 4GB default cap (configurable via [mochi.cache] limit-gb = N in the user profile).

7. Sigstore + OIDC trusted publishing

Per 09-trusted-publishing §1 to §12:

The publish flow:

  1. CI environment (GitHub Actions, GitLab CI, anything with sigstore-compatible OIDC) issues an OIDC token to the mochi pkg publish step. The token's sub claim encodes the workflow identity (repo:example/awesome:ref:refs/tags/v0.4.2:workflow:release.yml).
  2. mochi pkg publish exchanges the OIDC token with Fulcio (fulcio.sigstore.dev) for a short-lived X.509 signing certificate (~10 minutes lifetime) bound to the OIDC subject.
  3. mochi pkg publish signs the package tarball using the Fulcio certificate; produces a Sigstore bundle (DSSE envelope plus certificate plus signing-time timestamp plus Rekor log entry).
  4. The signature plus the bundle are uploaded to the central registry alongside the tarball. The registry verifies the OIDC subject against the manifest's [provenance].publisher pattern (e.g. repo:example/awesome:ref:refs/tags/* accepts any signed tag from example/awesome); mismatch rejects the publish.
  5. Rekor (rekor.sigstore.dev) appends the bundle to the public transparency log. Every later consumer can fetch the Rekor entry and verify inclusion.

Verification (mochi pkg audit signatures): for each locked dependency, fetch the bundle, verify against Sigstore's root of trust, check Rekor inclusion, and check that the publisher OIDC subject matches the registered package owner.

Legacy API tokens are not a publish surface. Test mode (mochi pkg publish --dry-run) prints the proposed bundle but does not push.

8. Capability declarations at the package boundary

Per 10-capability-model §1 to §14:

The closed capability set:

NameMeaning
fs.readread any file the process can open
fs.writewrite or create any file
net.dialopen outbound TCP / UDP / QUIC connections
net.listenbind to a port and accept connections
envread or set environment variables
ffiload a native shared object (.so, .dylib, .dll) and call into it
clockobserve wall-clock time (non-monotonic)
randomconsume entropy from the OS RNG
proc.spawnstart a child process

A package's [capabilities].required lists capabilities the package's code uses. optional lists capabilities enabled by feature flags. Both are surfaced in the lockfile per package.

Monotonicity: mochi pkg lock --check fails if any transitive dependency's required set has grown since the last mochi pkg lock of the same manifest (a non-major bump that adds fs.write is treated as a breaking change for audit purposes; the consumer must explicitly re-acknowledge by re-running mochi pkg lock without --check).

Enforcement at the transpiler boundary (the runtime layer in the corresponding MEP):

  • vm3: informational; the diagnostic pipeline logs every cross-capability call. v2 may enforce.
  • TypeScript / Deno (MEP-52): maps to Deno's --allow-* permission flags. mochi build --target=typescript --emit-perms writes a deno.json permission preset matching the resolved capability set.
  • Python (MEP-51): mochi_runtime.caps runtime module enforces by intercepting the relevant standard-library calls (open, socket.connect, os.environ, ctypes.CDLL, time.time, random.SystemRandom, subprocess.Popen).
  • JVM (MEP-47): java.security.Policy plus AccessController.doPrivileged enforcement (the legacy SecurityManager removal in JDK 17 is worked around via a custom permission manager in the runtime library).
  • .NET (MEP-48): System.Security.CodeAccessSecurity plus AppDomain isolation; for NativeAOT, capability checks compile to runtime guards.
  • Wasm component-model targets (future MEP): capabilities map directly to component-model imports.

9. Polyglot fan-out

Per 11-polyglot-fanout §1 to §11:

mochi pkg publish --target=<eco> builds an ecosystem-specific artifact from the same mochi.toml:

mochi pkg publish # central Mochi registry
mochi pkg publish --target=npm # npm package via MEP-52
mochi pkg publish --target=jsr # JSR scope via MEP-52
mochi pkg publish --target=pypi # PyPI wheel + sdist via MEP-51
mochi pkg publish --target=maven # Maven Central jar via MEP-47
mochi pkg publish --target=nuget # NuGet nupkg via MEP-48
mochi pkg publish --target=crates # crates.io crate via MEP-53
mochi pkg publish --target=hex # Hex.pm app via MEP-46
mochi pkg publish --target=swiftpm # Swift Package Index via MEP-49
mochi pkg publish --target=all # all targets the manifest opts into

Per-target overrides under [targets.overrides.<name>] let the manifest add ecosystem-specific dependencies (@types/node for the npm artifact, numpy for the PyPI artifact) without polluting the source-level manifest.

The fan-out reuses the per-target build driver (MEP-47's Maven publish, MEP-51's uv publish, MEP-52's npm publish --provenance, etc.) and threads Mochi's Sigstore bundle through to each ecosystem's signing infrastructure where one exists.

10. SBOM and provenance

Per 09-trusted-publishing §10 to §12, 11-polyglot-fanout §8:

Every publish emits:

  1. A CycloneDX 1.6 SBOM (<pkg>.cdx.json) listing every package in the resolved tree, its hashes, its licence, and its capability surface.
  2. An SPDX 3.0 document (<pkg>.spdx.json) with the equivalent dependency graph plus licence resolution.
  3. An in-toto attestation (<pkg>.intoto.jsonl) carrying the SLSA Build L3 predicate (builder identity, source identity, build invocation, materials, and the resulting artifact hash).
  4. A Rekor log entry index for every Sigstore bundle in the dependency tree.

Validation: emitted CycloneDX validates against the NIST CycloneDX 1.6 schema; SPDX validates under spdx-tools 3.0; in-toto attestations validate under the in-toto Go library.

11. Advisory database

Per 12-risks-and-alternatives §3:

The advisory feed is a RustSec-style YAML repository at https://advisories.mochi.dev/ mirroring the schema used by RustSec/advisory-db and pypa/advisory-database. Each advisory has:

id: MOCHISEC-2026-0001
package: "@scope/vuln"
versions: ">= 0.5.0, < 0.6.2"
severity: high
cve: CVE-2026-XXXX
description: |
...
remediation: ">= 0.6.2"
introduced-by: "..."
withdrawn-after: "2026-09-01"

mochi pkg audit walks the locked dependency tree, queries the feed, and prints a remediation plan. CI integration via mochi pkg audit --json for machine consumption.

12. Reproducible package build

Per 02-design-philosophy §7, 06-lockfile-format §6:

  • SOURCE_DATE_EPOCH is honoured by the tarball builder for all timestamp fields.
  • Tar entries are sorted lexicographically; uid / gid are normalised to zero; permission bits are normalised to canonical 0644 / 0755.
  • zstd is invoked with deterministic flags (no embedded build host, no embedded build time).
  • BLAKE3 + SHA-256 are computed after compression; the same source tree at the same SOURCE_DATE_EPOCH produces byte-identical hashes.
  • Gate: byte-identical .mochi.tar.zst SHA-256 across two CI hosts (linux-x86_64 plus macos-arm64) with the same SOURCE_DATE_EPOCH pin.

13. Vendor mode

Per 02-design-philosophy §9:

mochi pkg vendor [<dir>] (default <dir> is vendor/) copies every locked package's source tree under vendor/ and rewrites the lockfile sources to kind = "path". A subsequent mochi pkg fetch --offline succeeds without network access.

14. Workspace

Per 01-language-surface §4, 04-manifest-format §6:

A workspace umbrella manifest sits in the repo root:

[workspace]
members = ["crates/*", "apps/*"]
exclude = ["legacy"]
resolver = "pubgrub"

Member packages share the workspace lockfile (mochi.lock at the workspace root) and the workspace cache. Member-to-member deps resolve through path automatically.

15. CLI surface

Per 02-design-philosophy §10:

Primary surface (verbs every user touches):

mochi pkg new <name> [--lib | --bin]
mochi pkg init
mochi pkg add <pkg>[@<req>] [--features ...] [--dev]
mochi pkg remove <pkg>
mochi pkg update [<pkg>]
mochi pkg tree
mochi pkg why <pkg>
mochi pkg lock [--check]
mochi pkg fetch [--offline]
mochi pkg vendor [<dir>]
mochi pkg publish [--target=<eco>] [--dry-run]
mochi pkg audit [signatures]
mochi pkg yank <ver>
mochi pkg search <term>
mochi pkg info <pkg>
mochi pkg mirror serve|sync
mochi pkg workspace ls|add|remove

Phase-specific surface (verbs introduced by a single phase; same mochi pkg <verb> namespace):

mochi pkg pack [--verify-reproducible] # Phase 17
mochi pkg rebuild --from-source <repo>@<tag> # Phase 17
mochi pkg sbom show|verify|convert <pkg> # Phase 15
mochi pkg bench [--baseline=<file>] # Phase 19
mochi pkg cache verify|prune|gc # Phase 19
mochi pkg config registry default <url> # Phase 0 / Phase 11
mochi pkg publish register # Phase 13 (publisher binding)
mochi pkg audit mirror <name> # Phase 11
mochi pkg audit fix # Phase 16
mochi pkg registry init <root> # Phase 7 (filesystem registry seed)
mochi pkg registry serve --local=<root> # Phase 7 (HTTPS debug front-end)

mochi get keeps its current meaning (Go-module FFI fetch) for backward compatibility; mochi pkg fetch is the new pure-Mochi command.

16. Identifier and import rewriting

Per 01-language-surface §3, §6:

The existing language surface

import "./local/path" as L
import "github.com/foo/bar" as G # legacy form, deprecated
import go "strings" as gs # FFI; unchanged
import python "polars" as pl # FFI; unchanged
import typescript "https://..." as t # FFI; unchanged

is extended with the new package form:

import "@scope/name@^1.2" as N # registry dependency

The legacy bare-string form (import "github.com/foo/bar") is deprecated; a manifest-driven equivalent (mochi.toml lists github-bar = { git = "..." } and code writes import "github-bar" as B) is the preferred path. The deprecated form still resolves in v1 with a warning; it is removed in v2.

17. Mirror protocol

Per 07-registry-index §7:

A mirror is a read-only copy of the central registry. It serves the sparse index and the blob store at alternative hosts (index.mirror.example.com, blobs.mirror.example.com). The mirror sync protocol is a periodic pull from the canonical index plus blob backfill on demand. Consumers point at a mirror via [registry] in the user profile:

[registry]
default = "https://index.mirror.example.com"
fallback = "https://index.mochi.dev"

18. Gates

Per 12-risks-and-alternatives §1:

  1. Goal-alignment audit before each phase open (per the umbrella-phase coverage rule from MEP-50 / 51 / 52).
  2. Per-phase fixture corpus (tests/pkgsystem/<phase>/).
  3. PubGrub solver fixture corpus from the Dart team plus regression fixtures from real-world conflict reports.
  4. Byte-identical mochi.lock for the same manifest + same index snapshot.
  5. Four-platform CI: linux-x86_64, linux-arm64, macos-arm64, windows-x86_64.
  6. Network resilience: simulated 503s, timeouts, partial responses; exponential backoff and cache warmth.
  7. Sigstore round-trip via sigstore-mock-fulcio.
  8. CycloneDX 1.6 schema validation; SPDX 3.0 validation; in-toto attestation validation.
  9. Capability monotonicity: forbidden additions across non-major bumps trigger audit warnings.
  10. Byte-identical .mochi.tar.zst reproducibility across two CI hosts.

Phase plan

20 phases:

PhaseNameSurface
0Skeletonpkg/ package stubs and CLI wiring
1Manifest formatmochi.toml parser, schema validation, round-trip
2Local resolutionmanifest activates resolver; path-form imports preserved
3Workspacesmulti-package monorepo; workspace umbrella
4Lockfile formatmochi.lock writer / reader; sorted keys; version envelope
5PubGrub solver coreconflict-driven backtracking, incompatibility derivation
6Solver explanationsmochi pkg why; conflict explanations; MSRV-style version pin
7Local registryfilesystem-backed sparse index for tests and offline
8Network sparse indexHTTPS sparse index over index.mochi.dev; ETag; retries
9Content-addressed object storeBLAKE3 + SHA-256 dual hash; blob fetch; cache layout
10Capability declarations[capabilities] schema; lockfile pinning; audit warning surface
11Registry mirror protocolmirror discovery; replication; integrity
12Publish pipelinetarball build; manifest pin; dry-run
13Sigstore + OIDC trusted publishingFulcio cert request; signature attach; bundle verify
14Polyglot fan-outone mochi.toml to npm / PyPI / Maven / NuGet / JSR / crates / Hex / SwiftPM
15SBOM + provenanceCycloneDX 1.6; SPDX 3.0; in-toto attestation per target
16Advisory database + mochi pkg auditRustSec-style YAML feed; severity; EOL flags
17Reproducible package buildSOURCE_DATE_EPOCH; sorted tar; byte-identical .mochi.tar.zst
18Offline + vendormochi pkg vendor; offline cache hits-only mode
19Workspace cache + perfparallel fetch; content-deduped global cache; integrity checks

A phase lands when its gate is green per 12-risks-and-alternatives §1. The goal-alignment audit rule (memory: feedback_goal_alignment_audit) applies: before opening a phase, confirm its gate moves the user-facing goal (working multi-package Mochi programs with versioned, signed, audited dependencies) and not just spec-internal scaffolding.

Rationale

The six load-bearing decisions (§Abstract) flow from a single observation: every successful modern package system between 2014 (Cargo) and 2026 (uv plus PEP 751 plus npm Trusted Publishing plus Cargo's PubGrub migration) converged on the same architecture. TOML manifest, PubGrub-derived solver, text lockfile, sparse HTTPS index, content-addressed objects, OIDC-keyless trusted publishing, capability or permission declarations at the boundary. The differences across Cargo, uv, Bun, Deno, JSR, npm, NuGet, Bazel bzlmod, Pixi, Spack, and Pkl are second-order. MEP-57 picks the convergence point and writes the spec.

Why TOML and not JSON or YAML. TOML is the only widely-deployed manifest format that is unambiguous, comment-friendly, line-diffable, and not whitespace-sensitive. JSON loses comments and trailing commas, both of which matter for human editors of a long-lived manifest. YAML's Norway problem (country: NO parses as boolean false) and whitespace traps are well documented; the ecosystem has moved away (Bazel's MODULE.bazel is TOML-shaped, Pixi uses TOML, Pkl uses Pkl-DSL which is closer to TOML in feel than YAML, uv uses TOML via pyproject.toml). The Mochi-DSL option is rejected because the manifest must be parseable before any Mochi binary runs, the bootstrap rule.

Why PubGrub and not MVS, SAT, or Clingo. MVS (Go modules, Bazel bzlmod) is the simplest solver: it always picks the minimum version satisfying all constraints. MVS works for Go because Go's semver compatibility rule is enforced by import path versioning (module/v2); it does not work for Mochi because Mochi's polyglot fan-out crosses ecosystems with classical semver (npm, crates.io, PyPI), and MVS does not reconcile incompatible semver branches under the classical rule. Classical SAT (Cargo's legacy, npm v6, pip's pre-2024 resolver) works but produces unhelpful "unsolvable" errors; PubGrub's incompatibility derivation produces explanations that uv has demonstrated reduce user support load by a measurable margin. Clingo / ASP (Spack) is more expressive than needed and requires bundling Clingo (a Prolog-derivative solver) into the Mochi CLI, which inflates the binary by ~5MB. PubGrub is the inflection point on the explanation-quality / implementation-cost curve, and the consensus algorithm of the 2024-2026 cohort (uv, Rye, Cargo's RFC #3796, Dart pub).

Why text lockfile. Bun shipped bun.lockb (binary) in 2023 citing parser speed; reversed to a text format in 2025 after sustained community feedback about merge conflicts, code-review opacity, and the trauma of every Mochi-equivalent migration being a binary diff. The lesson is direct: lockfiles are read by humans (during code review, during incident response, during dependency-tree exploration) more often than by machines on a hot path. Optimising for machine parse speed over human readability is the wrong trade. Mochi's lockfile is TOML-shaped for consistency with the manifest format; canonical serialisation guarantees reproducibility without going binary.

Why sparse HTTPS index plus content-addressed blob store. Cargo's git-clone-based index reached its scalability limit in 2022-2023 (the index repo grew past 800MB, cold clones took minutes, hot sync still touched thousands of small files). The March 2023 sparse-index migration moved to a per-package HTTPS endpoint and demonstrated 5-20x cold-cache speedup. Mochi adopts the sparse protocol directly. The content-addressed object store separates immutable artifact storage from the mutable index, lets mirrors deduplicate across packages by hash, and aligns with the SLSA Build L3 requirement that artifacts be content-identifiable. BLAKE3-256 (parallel, hardware-accelerated on modern CPUs) is the primary hash; SHA-256 is carried alongside for SLSA / Sigstore / npm cross-ecosystem interop.

Why Sigstore + OIDC and not long-lived tokens. Four of the five top package ecosystems (npm, Maven Central, PyPI, Cargo via RFC #3724) converged on Sigstore-keyless OIDC publishing within the 18-month window April 2024 through late 2025. The convergence is not coincidence: every major supply-chain incident (event-stream 2018, ua-parser-js 2021, xz-utils 2024, the cascading PyPI flood of 2025) traces to a compromised long-lived token. Short-lived OIDC tokens with workflow-bound identity (the sub claim encodes "this workflow on this branch of this repo") raise the attacker's cost: they cannot exfiltrate a token from a developer laptop and use it months later. Mochi ships exclusively on this path; the alternative (legacy API tokens) is not a Mochi feature.

Why capability declarations. Wasm Component Model imports, Deno permissions, Pony reference capabilities at module boundaries, Roc platforms, and the post-xz industry response all point in the same direction: declaring what a package does at the import boundary is the right defence-in-depth against a class of supply-chain attacks. A capability-aware system catches the "xz had no business calling into sshd" mismatch at lock time, not at runtime. The closed enumeration (nine capabilities) is intentionally small: the v1 set covers the surfaces that have actually been weaponised in real incidents; v2 can extend the set if new categories emerge. Enforcement at the transpiler boundary (vs at parse time) gives the Mochi runtime / target ecosystems the choice of where to enforce: vm3 logs (no enforcement), Deno enforces via OS permissions, Wasm components enforce via the component-model import surface.

The choice of one central registry plus a mirror protocol rather than federated discovery follows from observation: every successful package system has a single canonical authority (crates.io, PyPI, npmjs.org, Maven Central) augmented by mirrors. Federation has been tried (OPAM, the various Lisp package networks) and the operational cost of consistency dwarfs the benefit. The mirror protocol gives organisations an air-gapped or geo-local cache without splitting the canonical name space; this is the right balance for v1.

The choice of manifest-once, publish-many polyglot fan-out (one mochi.toml -> eight ecosystem artifacts) follows from the observation that Mochi targets eight downstream ecosystems via the existing transpiler MEPs and that asking package authors to maintain eight separate manifests for the same source is a non-starter. The polyglot fan-out reuses each transpiler MEP's existing publish driver (MEP-52's npm publish --provenance, MEP-51's uv publish, MEP-47's Maven Central deploy) and threads Mochi's Sigstore bundle through to each ecosystem's signing infrastructure where one exists.

Backwards Compatibility

MEP-57 is purely additive at the source-language layer. The existing import "./path" as Alias form keeps file-relative semantics. The existing import go|python|typescript "..." as Alias FFI forms are unchanged. The new form import "@scope/name@^1.2" as Alias is gated behind the presence of a mochi.toml manifest: a Mochi program without a mochi.toml works exactly as it did pre-MEP-57.

mochi get keeps its current meaning (Go-module FFI fetch via go mod tidy) for backward compatibility with the existing import go "..." workflow. mochi pkg fetch is the new pure-Mochi download command.

The legacy import "github.com/foo/bar" bare-Go-import form is deprecated with a warning in v1; the manifest-driven equivalent (mochi.toml lists github-bar = { git = "..." } and code writes import "github-bar" as B) is the preferred path. The deprecated form is removed in v2 (no earlier than 12 months after MEP-57 v1 ships).

Existing transpiler MEPs (MEP-45 through MEP-53) consume the resolved module graph through their existing aotir IR input; no changes to their lowering passes. Per-target publishing flows (Maven, npm, PyPI, NuGet, JSR, crates.io, Hex, Swift Package Index) are preserved; the polyglot fan-out wraps them.

Legacy projects (no mochi.toml). Existing Mochi codebases that predate MEP-57 do not need a manifest to keep working: path imports, FFI imports, and the deprecated bare-Go-import form continue to resolve. There is no automatic conversion tool in v1. Users opt in by running mochi pkg init in the project root and authoring [dependencies] entries by hand; the manifest then unlocks scoped imports, lockfile, audit, and publish. A mochi pkg init --convert tool that scans import go "..." calls and seeds the manifest is tracked for v1.1.

Lockfile format evolution. The version = 1 envelope (see §3 Lockfile) reserves the right to extend the hash set in v2 (for example, adding BLAKE3+SHA3-256 as the dual hash if BLAKE3 or SHA-256 is deprecated by NIST). v1 clients reject any lockfile with version > 1 rather than partially parse it; v2 clients are expected to read v1 lockfiles transparently and rewrite on the next mochi pkg lock. The dual-hash family used by Phase 9's content store and Phase 11's mirror tampering check is paired with the lockfile envelope, so a hash rotation is a coordinated v2 cut.

Reference Implementation

Implementation lives under:

  • pkg/pkgmanifest/: mochi.toml parse, validate, write, round-trip. Uses github.com/pelletier/go-toml/v2.
  • pkg/pkglock/: mochi.lock parse, write, diff. Canonical TOML serialiser.
  • pkg/pkgsolver/pubgrub/: PubGrub solver in Go. ~3000 LOC. Includes incompatibility derivation, conflict-driven backtracking, and explanation rendering.
  • pkg/pkgindex/: sparse HTTPS index client plus filesystem fixture index plus mirror discovery. HTTP cache layer with ETag and If-Modified-Since.
  • pkg/pkgblob/: content-addressed blob store client; BLAKE3 + SHA-256 verification on read.
  • pkg/pkgsign/: Sigstore bundle build and verify; Fulcio OIDC exchange; Rekor log entry submit and verify.
  • pkg/pkgcap/: capability whitelist, audit, enforcement hooks per target.
  • pkg/pkgfanout/: polyglot publish; per-ecosystem drivers delegate to MEP-45 through MEP-53.
  • pkg/pkgsbom/: CycloneDX 1.6 plus SPDX 3.0 plus in-toto emission and validation.
  • pkg/pkgaudit/: advisory feed query plus remediation plan.
  • cmd/mochi/pkg.go: mochi pkg ... subcommand tree.
  • tests/pkgsystem/: per-phase fixture corpus.

Initial implementation total: approximately 14,000 LOC of Go across the eight pkg/ packages plus 2,500 LOC of test fixtures plus 1,200 LOC of CLI wiring plus the central registry service (approximately 4,000 LOC of Go, deployed as a separate repository mochilang/registry). CI runs on linux-x86_64, linux-arm64, macos-arm64, and windows-x86_64 across Go 1.24 plus Go 1.25 plus the latest Go tip. Twelve research notes (~/notes/Spec/0057/01..12) cover prior art across Cargo, Go modules, Deno, JSR, Bun, npm, uv, Bazel bzlmod, Nix flakes, Swift PM, Hex, NuGet, Pixi, Spack, Pkl plus recent (2024-2026) literature on PubGrub, supply-chain provenance (Sigstore, SLSA, in-toto), reproducible builds, content-addressed code (Unison), capability-safe imports (Deno permissions, Wasm Component Model, Pony, Roc), and empirical package-ecosystem studies.