Skip to main content

MEP 70. Mochi and Kotlin package bridge

FieldValue
MEP70
TitleMochi and Kotlin package bridge
AuthorMochi core
StatusDraft
TypeStandards Track
Created2026-05-29 23:12 (GMT+7)
DependsMEP-1 (Grammar), MEP-2 (AST), MEP-4 (Type System), MEP-13 (ADTs and Match), MEP-45 (C transpiler, FFI sidecar pattern), MEP-53 (Rust transpiler, lowering pipeline), MEP-57 (module and package system, mochi.toml, mochi.lock, content-addressed store, Sigstore/trusted publishing)
Research/docs/research/0070/
Tracking/docs/implementation/0070/

Abstract

Mochi today (May 2026) targets nine code-emitting backends and has a growing source-level package story via MEP-57, but has no connection to the Kotlin/JVM ecosystem. The Kotlin language (May 2026) commands the Android platform, has strong penetration in server-side JVM development via Spring Boot and Ktor, and ships over 70,000 artifacts on Maven Central under Kotlin-specific metadata annotations. Mochi authors cannot consume any of this ecosystem, and Kotlin developers cannot add a Mochi library via Gradle.

MEP-70 specifies the bidirectional Kotlin package bridge: a Mochi program can consume any well-formed Maven Central artifact whose JAR carries kotlin.Metadata annotations via import kotlin "<group>:<artifact>@<semver>" as <alias> with no user-written boilerplate, and a Mochi package can publish to Maven Central as a Kotlin library JAR via mochi pkg publish --to=maven-central. The bridge is the fifth source-language interop story Mochi ships (after Go, Python, TypeScript, and Rust from MEP-73), and the first one that targets the JVM platform, which requires a fundamentally different native interop strategy from the C-ABI-grounded bridges of prior MEPs.

The proposal adds a self-contained component under package3/kotlin/, extends MEP-57's manifest/lockfile/capability infrastructure, and adds one new build target (TargetKotlinLibrary) to the MEP-53 emit driver. The Mochi grammar gains the single keyword kotlin as a valid <lang> token in the existing FFI-import production. No existing transpiler MEP needs to change.

1. Motivation

1.1 The Kotlin ecosystem is large and Kotlin-native in metadata

Maven Central hosts over 70,000 artifacts with kotlin.Metadata class-file annotations (May 2026 snapshot). These include the full Jetbrains standard library (kotlin-stdlib, kotlinx-coroutines-core, kotlinx-serialization-json, kotlinx-datetime), all major server frameworks (Ktor, Spring Boot Kotlin extensions, Micronaut Kotlin), the Arrow functional-programming library, Exposed ORM, Koin and Kodein DI, Kotest, MockK, and the full suite of Android Jetpack libraries as AAR. None of these are reachable from Mochi without manual JNI boilerplate that would span dozens of files per artifact.

1.2 Kotlin metadata is stable and machine-readable

Since Kotlin 1.4 (September 2020), every Kotlin .class file carries a @kotlin.Metadata annotation whose data1/data2 binary fields encode the full public Kotlin API surface (function signatures, property types, nullability annotations, sealed class hierarchies, value classes, inline reified type parameters, default argument masks, and coroutine suspend status) in a versioned protobuf schema via the kotlinx-metadata-jvm library. This is the stable, officially-supported machine-readable surface; it supersedes reflection and javap output. The bridge ingests this annotation at lock time, requiring no Kotlin toolchain binary beyond the JAR on disk.

1.3 The JVM interop problem is solved by GraalVM Native Image

The fundamental challenge of calling JVM code from a native binary is JVM startup cost and the need for an embedded JVM. GraalVM Native Image (GraalVM CE 21+, available as a free download) solves both problems: it AOT-compiles a JVM program (including all transitive dependencies) into a platform-native shared library (libwrap.so / libwrap.dylib / libwrap.dll) with a plain C ABI that Mochi's existing FFI machinery can link against. The result has sub-millisecond initialization (no JVM startup), predictable memory usage (no GC pauses visible at the C boundary), and a stable versioned ABI. The bridge uses this path as its primary compilation target; the traditional libjvm.so embedding path is retained as a fallback for artifacts that defeat native-image (dynamic class loading, reflection-heavy frameworks).

1.4 Mochi as a Kotlin library producer opens the Android/JVM market

A Mochi package that targets TargetKotlinLibrary compiles its public API to a JVM bytecode JAR with the correct kotlin.Metadata annotations, so kotlinc and IntelliJ see it as an idiomatic Kotlin library: functions have their Mochi-declared types reflected as Kotlin types, nullable returns are annotated ?, unit-return functions emit Unit, and KDoc comments are extracted from Mochi doc-comments. The result is publishable to Maven Central and consumable with a plain implementation("group:artifact:version") Gradle line.

2. Design decisions

2.1 kotlinx-metadata-jvm over reflection

Runtime reflection (java.lang.reflect.*) sees only JVM types after type erasure, loses all Kotlin-specific information (nullability, inline classes, sealed hierarchies, suspend status), and requires a live JVM. kotlinx-metadata-jvm reads the @kotlin.Metadata binary annotation from .class bytes on disk, reconstructing the full pre-erasure Kotlin type surface including all extensions. The bridge uses only kotlinx-metadata-jvm; it never spawns a JVM for API surface extraction.

2.2 GraalVM Native Image as the primary runtime bridge

Three options exist for calling JVM code from a native binary: (a) embed a JVM via libjvm.so (JNI classic), (b) AOT-compile with GraalVM Native Image to a native shared library, (c) compile with Kotlin/Native to a platform-native static/dynamic library. Option (a) requires a JVM on the end-user machine and adds 200-400 ms of cold-start latency. Option (c) requires all transitive dependencies to be Kotlin/Native-compatible, which today excludes roughly 80% of Maven Central artifacts. Option (b) (GraalVM) requires only the GraalVM CE toolchain at build time (not at run time), produces a plain extern "C" shared library, adds under 5 ms cold-start, and supports all standard JVM artifacts including reflection with explicit reflect-config.json hints that the bridge synthesises automatically. MEP-70 uses (b) as the primary path and (a) as the [kotlin] runtime = "jvm-embed" override.

2.3 JNI-style C ABI wrapper over Panama FFI

Java 22+ Project Panama (java.lang.foreign) provides a safer, more ergonomic native interop model than JNI. However, GraalVM Native Image's shared-library mode exposes a JNI-compatible C ABI (the graal_create_isolate / graal_tear_down_isolate lifecycle plus standard jni.h function signatures) as its stable public interface. Mochi's existing C-ABI FFI machinery (from MEP-45 and MEP-53) already understands this shape. Using Panama would require a Panama-aware callsite generator entirely separate from the existing FFI pipeline. The bridge uses the JNI-compatible C ABI exposed by GraalVM; this is a private implementation detail hidden behind the synthesised extern fn declarations.

2.4 Maven Central via Sonatype Central Portal for publishing

As of February 2024, Sonatype retired the legacy Nexus-based OSSRH and replaced it with the Central Portal (central.sonatype.com), which exposes a REST API (/api/v1/publisher/upload) for deployment bundles. The bundle format is a ZIP containing the JARs (compiled classes, sources, Javadoc), POM, GPG .asc signatures, and SHA-1/MD5 checksums. The bridge synthesises this bundle and POSTs it via the API using a short-lived portal token obtained via Sonatype's OIDC-compatible CI integration (analogous to how MEP-73 uses Cargo's RFC #3724 trusted publishing). Long-lived API tokens are not accepted by the bridge.

2.5 No boilerplate for the user

The user writes one line: import kotlin "org.jetbrains.kotlinx:[email protected]" as json. The bridge resolves, fetches, compiles the wrapper, and synthesises the Mochi extern declarations. The user never writes @JvmStatic, @JvmName, JNIEnv*, jobject, jstring, or a Gradle build file.

3. Scope and non-goals

In scope:

  • Consuming JVM-targeting Kotlin artifacts from Maven Central, JitPack, and Google Maven via import kotlin.
  • Publishing Mochi packages as Kotlin/JVM JARs to Maven Central.
  • Kotlin standard types: Int, Long, Double, Boolean, String, List<T>, Map<K,V>, Set<T>, Pair<A,B>, Triple<A,B,C>, data class, enum class, sealed class (finite hierarchy), object (singleton), companion object functions, nullable T? via Mochi Option<T>.
  • suspend functions via the coroutines bridge (Phase 13).
  • Kotlin Multiplatform libraries that have a JVM target.
  • Android AAR consumer path (Phase 14 subset).

Out of scope:

  • Kotlin/Native artifacts (.klib): these have no JVM bytecode and no Maven Central presence at scale. A future MEP may add direct LLVM-IR interop.
  • Kotlin/JS artifacts.
  • Dynamic class loading (Class.forName, URLClassLoader) inside consumed artifacts: these defeat GraalVM Native Image and are rejected at lock time with a diagnostic.
  • @JvmField with non-final backing fields accessed by reflection: the bridge reads metadata, not runtime state.
  • Android-specific APIs on non-Android hosts: the bridge can consume Android library JARs and stubs them as extern type on non-Android targets; actual Android runtime is only available on Android targets.
  • Incremental compilation of the GraalVM wrapper: each mochi pkg lock rebuilds the full wrapper for changed artifacts.

4. Surface syntax: import kotlin

The Mochi grammar's ImportStmt production gains kotlin as the fifth <lang> token:

ImportStmt := "import" Lang? StringLit "as" Ident ("auto")?
Lang := "go" | "python" | "typescript" | "rust" | "kotlin"

The string literal is one of:

FormExampleResolution
<group>:<artifact>"org.jetbrains.kotlinx:kotlinx-coroutines-core"Bare coordinates. Version resolved from [kotlin-dependencies] + mochi.lock.
<group>:<artifact>@<version>"org.jetbrains.kotlinx:[email protected]"Explicit version constraint.
<group>:<artifact>@<version>@<classifier>"org.jetbrains.kotlin:[email protected]@sources"Explicit classifier (rare; used for sources or test JARs).

Example surface:

import kotlin "org.jetbrains.kotlinx:[email protected]" as json
import kotlin "io.ktor:[email protected]" as ktor
import kotlin "com.squareup.okhttp3:[email protected]" as okhttp

fn fetch_user(url: string): User {
let client = okhttp.OkHttpClient()
let request = okhttp.Request.Builder().url(url).build()
let response = client.newCall(request).execute()
let body = response.body().string()
return json.Json.decodeFromString(body)
}

The auto modifier is supported: import kotlin "..." as k auto binds every public top-level declaration of the artifact at file scope.

5. Manifest tables

5.1 [kotlin-dependencies]

[kotlin-dependencies]
"org.jetbrains.kotlinx:kotlinx-coroutines-core" = "1.7.3"
"org.jetbrains.kotlinx:kotlinx-serialization-json" = { version = "1.6.3" }
"io.ktor:ktor-client-core" = { version = "2.3.9" }
"com.squareup.okhttp3:okhttp" = { version = "4.12.0", exclude = ["com.squareup.okio:okio"] }
"org.jetbrains.kotlin:kotlin-stdlib" = { version = "1.9.23", scope = "provided" }

The grammar mirrors Maven's <dependency> model:

  • A bare version string is shorthand for { version = "..." }.
  • exclude lists transitive dependencies to drop (Maven <exclusion> equivalent).
  • scope is one of "compile" (default), "provided" (not linked into the native wrapper), "test" (excluded from production builds).
  • repository overrides the fetch source: "maven-central" (default), "jitpack", "google", or a custom URL.

5.2 [kotlin]

[kotlin]
kotlin-version = "1.9.23"
jvm-target = "21"
runtime = "graalvm" # or "jvm-embed"
graalvm-version = "21.0.2"
monomorphise = [
{ item = "kotlinx.serialization.json.Json.decodeFromString", T = "User" },
]
coroutines-dispatcher = "blocking" # or "event-loop"
KeyDefaultMeaning
kotlin-version"1.9.23"Kotlin stdlib version pinned in the wrapper compilation.
jvm-target"17"JVM bytecode target for the wrapper JAR.
runtime"graalvm"Bridge runtime: "graalvm" (AOT native image) or "jvm-embed" (libjvm.so).
graalvm-version"21.0.2"GraalVM CE version used to compile the native image. Validated against the installed native-image binary at lock time.
monomorphise[]Explicit generic instantiations for reified inline functions.
coroutines-dispatcher"blocking"How suspend functions are called from the C ABI: "blocking" (runs on a dedicated coroutine thread, blocks the calling thread), "event-loop" (returns a future handle).

5.3 [kotlin.publish]

[kotlin.publish]
group-id = "com.example"
artifact-id = "mylib"
version = "1.0.0"
publish-to = "maven-central"
signing-key-id = "" # empty = OIDC short-lived cert via Sonatype
include-sources = true
include-javadoc = true
android-aar = false
KeyDefaultMeaning
group-id(required)Maven groupId.
artifact-id(required)Maven artifactId.
version(required)Semantic version string.
publish-to"maven-central"Publish target. Also "jitpack" (push to GitHub), "google" (internal; requires approval), "local" (installs to ~/.m2/repository).
signing-key-id""Empty string uses Sonatype OIDC trusted publishing. A fingerprint value uses a pre-uploaded GPG key.
include-sourcestrueWhether to include a -sources.jar.
include-javadoctrueWhether to include a -javadoc.jar (generated from KDoc extraction).
android-aarfalseWhen true, emits an AAR instead of a plain JAR. Requires Android build tools on PATH.

5.4 [kotlin.capabilities]

[kotlin.capabilities]
net = true
fs = false
reflection = false
classloading = false

The bridge walks the transitive dependency graph at lock time and asserts the declared capabilities are a superset of the detected capability union. reflection and classloading are Kotlin-specific additions to MEP-57's [capabilities] model: any artifact that calls Class.forName or URLClassLoader triggers classloading = true; any that calls KClass<*>.memberProperties or java.lang.reflect.* triggers reflection = true. Both defeat the GraalVM native-image path and force runtime = "jvm-embed".

6. Lockfile: [[kotlin-package]]

Each resolved artifact appends one [[kotlin-package]] table to mochi.lock:

[[kotlin-package]]
group-id = "org.jetbrains.kotlinx"
artifact-id = "kotlinx-serialization-json"
version = "1.6.3"
jar-sha256 = "a7c3d5e2..."
jar-blake3 = "f1e2d3c4..."
metadata-schema-version = 9
metadata-sha256 = "b8d4e6f2..."
wrapper-sha256 = "c9e5f7a1..."
capabilities-detected = ["net"]
capabilities-declared = ["net"]
native-image-sha256 = "d0f6a8b2..."

mochi pkg lock --check recomputes every hash and exits non-zero on mismatch. The native-image-sha256 is the content hash of kotlin_wrap/<artifact>/libwrap.so.

7. CLI surface

mochi pkg add kotlin <group>:<artifact>[@<version>]

$ mochi pkg add kotlin org.jetbrains.kotlinx:[email protected]
Added "org.jetbrains.kotlinx:kotlinx-coroutines-core" = "1.7.3" to [kotlin-dependencies]
Running mochi pkg lock ...
Resolved 12 Kotlin packages (kotlinx-coroutines-core + 11 transitive)
Fetched 12 JARs to ~/.cache/mochi/kotlin-deps/
Ingested kotlinx-metadata-jvm v9 API surface: 847 functions, 234 types
Synthesized wrapper: kotlin_wrap/kotlinx-coroutines-core/
Compiled GraalVM native image: kotlin_wrap/kotlinx-coroutines-core/libwrap.so (42 MB)
Wrote mochi.lock (+12 [[kotlin-package]] entries)

mochi pkg lock

Resolves [kotlin-dependencies] against Maven Central's metadata index, downloads JARs, ingests Kotlin metadata, synthesises wrapper code, compiles GraalVM native images for each artifact, and writes [[kotlin-package]] entries.

mochi pkg lock --check

Recomputes all hashes and exits non-zero on any mismatch. This is the CI reproducibility gate.

mochi pkg publish --to=maven-central [--dry-run]

  • Builds the package via Driver.Build with target = TargetKotlinLibrary.
  • Generates sources JAR from Mochi source.
  • Generates Javadoc JAR from KDoc extraction.
  • Signs the bundle with GPG or OIDC short-lived cert via Sonatype Central Portal.
  • POSTs the ZIP bundle to https://central.sonatype.com/api/v1/publisher/upload.
  • Polls the deployment status until PUBLISHED or FAILED.

mochi pkg sync kotlin

Re-runs the wrapper synthesiser and GraalVM native-image compilation from the existing mochi.lock without re-resolving versions. Used after a bridge upgrade.

8. Type mapping

The bridge translates Kotlin types to Mochi types following a closed table. Types not in the table cause mochi pkg lock to emit a warning and omit that function from the synthesised shim.

Kotlin typeMochi typeNotes
kotlin.Intint32-bit signed.
kotlin.Longlong64-bit signed.
kotlin.Doubledouble64-bit IEEE 754.
kotlin.Floatfloat32-bit IEEE 754.
kotlin.Booleanbool
kotlin.StringstringUTF-16 to UTF-8 conversion at the JNI boundary.
kotlin.Unit(void)extern fn with no return.
T? (nullable)Option<T>Null maps to Option.None; non-null to Option.Some(v).
kotlin.collections.List<T>List<T>Copied across the boundary; not a live reference.
kotlin.collections.Map<K,V>Map<K,V>Copied across the boundary.
kotlin.collections.Set<T>Set<T>Copied across the boundary.
kotlin.Pair<A,B>Pair<A,B>Two-field struct.
kotlin.Triple<A,B,C>Triple<A,B,C>Three-field struct.
data class Foo(val x: Int, val y: String)extern type Foo + accessor fnsFields exposed as extern fn foo_x(f: Foo): int.
enum class Color { RED, GREEN, BLUE }extern type Color + extern fn color_red(): Color etc.
sealed class Result + subclassesextern type Result + discriminant fnextern fn result_is_ok(r: Result): bool etc.
object Singletonextern fn singleton_instance(): Singleton
companion object functionspromoted to artifact-level fnsFoo.companion.bar()foo_bar() in shim.
suspend fun f(...)extern fn f_async(...): HandleRequires coroutines bridge (Phase 13); not emitted without it.

Refusal set (these items are not emitted into the shim; a diagnostic is printed):

  • Raw generic parameters (fun <T> foo(x: T)) without a monomorphise entry.
  • inline reified functions where the monomorphise table has no matching instantiation.
  • Java-originated types without @kotlin.Metadata (treated as opaque extern type).
  • dynamic types (Kotlin/JS artefact; cannot appear in JVM bytecode).
  • typealias that resolves to a type in the refusal set.

9. Architecture: component map

package3/kotlin/
maven/ Maven Central metadata index client (POM resolution, sparse metadata)
blob/ Content-addressed JAR/AAR store (sha256+blake3, atomic writes)
metadata/ kotlinx-metadata-jvm Go-port: reads @kotlin.Metadata from .class bytes
typemap/ Closed Kotlin→Mochi type translation table
wrapper/ Synthesised Kotlin+Java wrapper source generator
graalvm/ GraalVM native-image compiler driver
extern/ Mochi extern type+fn declaration emitter (shim.mochi generator)
publish/ Maven Central deploy bundle builder + Sonatype Central Portal client
coroutines/ suspend-fn bridge: blocking-call and event-loop dispatch adapters
lock/ mochi.lock [[kotlin-package]] reader/writer
semver/ Maven version range parser (PVP/Semver/Maven range syntax)
errors/ Bridge-specific error types

10. Delivery plan

Each phase is gated by a fixture test against the curated 20-artifact corpus:

kotlinx-coroutines-core, kotlinx-serialization-json, kotlinx-datetime,
ktor-client-core, ktor-server-core, kotlin-stdlib, kotlin-reflect,
arrow-core, exposed-core, koin-core, kotest-framework-engine,
mockk, okhttp, retrofit, gson, jackson-module-kotlin,
spring-context, micrometer-core, grpc-kotlin-stub, protobuf-kotlin
PhaseDeliverableGate
00package3/kotlin/ skeleton: Go module, error types, semver parsergo test ./... green
01Maven Central metadata client: POM fetch, version resolution, transitive graphResolve [email protected] + 11 transitive
02Blob cache: JAR/POM fetch + SHA-256/BLAKE3 verify + content-addressed storeCache round-trip for all 20 artifacts
03Kotlin metadata ingest: @kotlin.Metadata binary decode via kotlinx-metadata-jvm v9Extract 847 fns from kotlinx-coroutines-core
04Type-mapping table: Kotlin→Mochi closed table + refusal setType-map all public fns of all 20 artifacts
05Wrapper synthesiser: emit Kotlin/Java JNI wrapper source for each artifactCompile wrapper source with kotlinc
06GraalVM native-image driver: compile wrapper to libwrap.so, emit graal_create_isolate headerlibwrap.so loads in a Mochi test binary
07Mochi extern emitter: synthesise shim.mochi from the type-mapped surfaceParser accepts all shim files
08Grammar: add kotlin to Lang token; resolve import kotlin to shim.mochi pathimport kotlin "..." as k parses and resolves
09MEP-53 build orchestration: Driver.Build triggers lock check + wrapper compile + linkEnd-to-end: Mochi source → binary with Kotlin library linked
10mochi.lock integration: [[kotlin-package]] read/write + mochi pkg lock --checklock --check detects tampered hashes
11TargetKotlinLibrary: lower Mochi public API to JVM bytecode JAR with @kotlin.MetadataJAR accepted by kotlinc as a dependency
12Maven Central publish: bundle builder + Sonatype Central Portal client + GPG/OIDC signing--dry-run exercises signing and bundle validation
13Coroutines bridge: suspend fn AOT-compiled with blocking and event-loop dispatchers[email protected] suspend fns usable
14Generic monomorphisation + KMP JVM subset + Android AAR consumer pathAll 20 corpus artifacts have green fixture tests

11. Rationale

11.1 Why kotlinx-metadata-jvm over dokka

Dokka (Kotlin's documentation engine) can emit a JSON description of the public API. However, Dokka requires a full Kotlin compiler analysis pass, which needs the Kotlin compiler binary and a Gradle-compatible project structure. kotlinx-metadata-jvm reads @kotlin.Metadata directly from .class bytes, requires no toolchain, and is three orders of magnitude faster at lock time (milliseconds vs. tens of seconds). The dokka JSON format is also less stable than the metadata schema, which has been versioned and backwards-compatible since Kotlin 1.4.

11.2 Why GraalVM over Kotlin/Native

Kotlin/Native is the official cross-platform compilation target and produces true native binaries. However, only a small fraction of Maven Central artifacts are Kotlin/Native-compatible: the entire Android Jetpack, most server-framework dependencies, and any artifact that uses Java standard library types (java.io, java.net, etc.) are JVM-only. GraalVM Native Image supports the full JVM standard library and all standard Kotlin artifacts, making it applicable to the entire Maven Central corpus. The tradeoff is a larger native image build time (30-120 seconds per artifact) vs. Kotlin/Native (5-30 seconds), but this cost is paid once at mochi pkg lock time and cached.

11.3 Why not JNI with libjvm.so

The libjvm.so embedding path (JNI classic) requires a JRE on the end-user machine, adds 200-400 ms of JVM startup to the first Kotlin call, and exposes the full GC pauses and thread management complexity of a JVM to the Mochi runtime. GraalVM Native Image eliminates all three problems for the common case. The runtime = "jvm-embed" override is provided for the rare case of artifacts with dynamic class loading that cannot be analysed at build time.

11.4 Why Maven Central + Sonatype Central Portal

Maven Central is the canonical Kotlin/JVM artifact registry (70,000+ Kotlin artifacts, May 2026). JitPack and Google Maven are secondary registries that the bridge supports as additional sources. The Sonatype Central Portal replaced the legacy OSSRH endpoint in February 2024 and is the only officially-supported publishing path for new namespaces. Using its API ensures the bridge works with the current toolchain without maintaining a legacy OSSRH compatibility shim.

12. Open questions

  1. GraalVM version coupling: the bridge records the GraalVM version in mochi.lock. If a developer builds on GraalVM 21 and another on GraalVM 22, lock --check will differ. Should the bridge normalise the native image hash to the GraalVM version slot (major.minor only)?

  2. AAR vs JAR for Android: Android libraries ship as AARs (a ZIP containing classes.jar + res/ + AndroidManifest.xml). The consumer path needs to unpack the AAR and extract classes.jar. Phase 14 handles this; should AAR be a separate [android-dependencies] table for clarity?

  3. Coroutines bridge dispatcher ownership: when coroutines-dispatcher = "event-loop", the bridge creates a kotlinx.coroutines.EventLoop that runs on a dedicated OS thread inside the native image. Should this thread be shared across all Kotlin artifacts in a process, or per-artifact?

  4. Maven Central trusted publishing GA timeline: Sonatype's OIDC trusted-publishing feature (analogous to PyPI's and crates.io's) is currently in beta (May 2026). The bridge should detect the beta endpoint and fall back to long-lived token + GPG if OIDC is unavailable. Should the long-lived token path be permanent or sunset on OIDC GA?

  5. Kotlin Multiplatform commonMain API surface: KMP artifacts publish both a JVM jar and a .klib for non-JVM targets. Should the bridge expose the commonMain API surface (which is the intersection) or the full JVM API surface (which is a superset)?

13. Security considerations

  • All JARs are verified against SHA-256 and BLAKE3 before compilation. A tampered JAR is rejected at lock time.
  • The GraalVM native image is compiled at lock time; its hash is recorded. A tampered libwrap.so is detected by lock --check on the next build.
  • classloading capability explicitly opts in to dynamic class loading, which disables the GraalVM path and enables the jvm-embed path with full JVM access. This is a deliberately high-friction opt-in.
  • Maven Central signing verification: the bridge verifies the artifact's GPG signature (from the Central Portal's signature endpoint) before adding it to the blob cache. Unsigned artifacts from non-Central sources trigger a [kotlin.capabilities] unverified-source = true requirement.
  • The mochi pkg publish --to=maven-central flow never writes a long-lived token to disk. OIDC token exchange happens in-process and the short-lived token is used once.

14. Reference implementation

The reference implementation is in package3/kotlin/. The Go package path is github.com/mochilang/mochi/package3/kotlin. The entry points are:

  • maven.NewClient(config) - Maven Central metadata + JAR fetcher
  • metadata.IngestJAR(jarPath) - Extract @kotlin.Metadata from all .class files in a JAR
  • typemap.Translate(kotlinType) - Kotlin→Mochi type translation
  • wrapper.Synthesize(apiSurface, outputDir) - Generate Kotlin/Java JNI wrapper source
  • graalvm.CompileNativeImage(wrapperDir, outputDir) - Run native-image to produce libwrap.so
  • extern.Emit(apiSurface, wrapperDir) - Generate shim.mochi extern declarations
  • publish.BuildBundle(config, jarPath) - Assemble Maven Central deployment ZIP
  • publish.Upload(bundle, token) - POST to Sonatype Central Portal API

Cross-references

  • Research bundle — twelve informative notes covering the design space.
  • Implementation tracking — per-phase delivery status.
  • MEP-57 — manifest, lockfile, and capability infrastructure this extends.
  • MEP-53 — the emit pipeline that gains TargetKotlinLibrary.
  • MEP-73 — the Rust bridge, whose architecture MEP-70 mirrors.
  • MEP-45 — C transpiler and FFI sidecar pattern.