Native Rust Interoperability v1
September 10, 2026 · View on GitHub
Audience: maintainers, host integrators, and compiler contributors.
Status: implemented bounded profile; HOSTED GREEN under the v0.4.0 release baseline. Historical local, authoring-time, ignored, device/simulator, or separately provisioned evidence below retains its narrower scope; public promotion, registry publication and broader product completion remain separately gated.
Native Rust Interoperability v1 is an additive, current-host, scalar bridge. It
does not change callable v2/v3, the native loader or host, Wasm, SPX-B104, or
any existing wire/KAT; the additive semaprax.graph.v25 projection that a
declared import selects leaves every earlier Graph schema's selection and bytes
unchanged. Its admitted round trip is safe generated Rust caller → selected
SEMAPRAX export → selected Rust-import callback → scalar result. It never
detours through Wasm or a dynamic library.
Source and semantic admission
The only new source form is an explicitly identified Rust import:
@id("host.add")
import rust fn add(left: i64, right: i64) -> i64
effects { host.math }
failure status "host.math.v1";
Every native Rust import must end with an explicit failure status "domain";
or failure infallible; clause; omission rejects rather than silently choosing
a failure model. Parameters are 0–8 value-mode i64/bool; results are unit,
i64, or bool. IDs are explicit, effects are sorted and selected, failure
domains are closed, and calls retain the distinct HIR kind
NativeRustImportCall. Selected exports are 1–32 explicit-ID,
non-entry, monomorphic scalar functions whose result is i64 or bool; unit
is admitted only as a Rust-import result. Their acyclic transitive closure is at
most 256 functions and may reach only selected Rust imports. Calls from a
contract, including through a helper, are rejected. The agent context, review,
impact, and target-evidence projections still reject SPX-G218 because each
omits import nodes by construction; the module Graph and the workspace/Project
graph represent these declarations under semaprax.graph.v25. Wasm rejects
SPX-W114; ordinary callable routes remain closed by SPX-B104.
Canonical documents and digests
Spec schema is semaprax.native-rust-interop-spec.v1, compact JSON plus one LF,
with ordered keys schema,module,source_revision,target,exports,imports, capabilities,limits,nonclaims. Descriptor schema is
semaprax.native-rust-interop-descriptor.v1, with ordered keys schema,module, source_revision,hir_digest,target,status_domains,abi,exports,imports,limits, nonclaims. Bundle schema is semaprax.native-rust-interop-bundle.v1, with
ordered keys schema,descriptor,files,toolchain,limits,nonclaims.
Digest domains are:
semaprax.native-rust-interop.source-revision.v1\0semaprax.native-rust-interop.hir-digest.v1\0semaprax.native-rust-interop.spec-digest.v1\0semaprax.native-rust-interop.descriptor-digest.v1\0semaprax.native-rust-interop.call-contract.v1\0semaprax.native-rust-interop.capabilities.v1\0semaprax.native-rust-interop.bundle-digest.v1\0
The target row is triple,pointer_width,endian,panic_strategy,thread_policy and
admits only the exact current host, 64-bit little-endian, unwind, same-thread
profile. Call contracts use u64-BE length framing and bind direction, persistent
ID, source parameter names and scalar types, result, sorted effects and
capabilities, exact status domains/ordinals including semantic 65533, host
65534, and adapter 65535 where required, complete ABI row, and target.
Current-target selection uses the builder's compiled target facts, not merely
its CPU and operating system. The shared selector requires 64-bit pointers,
little endian, an empty disambiguating ABI, and the exact vendor/environment:
unknown/gnu for Linux, apple/empty for macOS, or pc/msvc for Windows.
The private Phase-A target profile retains x86-64 and AArch64 for those three
families. Public scalar and owned-data SDK publication retain the narrower five
targets, excluding AArch64 Windows because its archive tool plan is not frozen.
Musl, GNU Windows, x32, big-endian, and mismatched vendor/ABI configurations do
not inherit another target's identity. They fail through existing unsupported
target errors before staging. This is a pure compile-time selection rule, not
runtime host discovery, cross-compilation support, or toolchain attestation;
the later held-tool checks and existing panic/thread policies remain separate.
Rust cfg facts do not authenticate the raw Cargo TARGET spelling or a custom
target specification. Specifications sharing the admitted facts are
indistinguishable here; no custom-target support is claimed.
Limits are fixed: exports 32, imports 32, parameters 8, closure functions 256, status domains 64, effects 64, identifier bytes 128, source bytes 16,777,216, spec bytes 1,048,576, descriptor bytes 1,048,576, generated C bytes 4,194,304, generated header bytes 1,048,576, combined generated Rust bytes 4,194,304, manifest bytes 1,048,576, cumulative builder bytes 33,554,432, JSON depth 8, semantic expression depth 512, call depth 32, bridge crossings 4,096, and unexpected inventory entries 0.
ABI and status
The generated C ABI is version 1, calling convention C. spxnr_status_v1 is a
u64: code bits 0–31, class 32–39, retry bit 40, reserved zero bits 41–47, and
domain ordinal 48–63. Zero is success. Ordinal 65533 is
semaprax.native-rust-semantics.v1, 65534 is host, and 65535 is adapter;
selected status domains occupy sorted ordinals 1..N. Semantic codes are neg/add/
sub/mul/div/rem = 1..6. Contract pre/post codes are 1/2. Results are caller-owned,
uninitialized, and written only after complete success.
Context SPXNRCTX1 stores ABI version, size, userdata, imports-table pointer,
capability digest, call depth, and zero reserved word. SPXNRIMP1 stores version,
size, and callbacks in descriptor order. C validates pointer alignment,
versions, sizes, bool 0/1, callback presence, capability digest, depth, result
pointer, and status canonicality. The safe Rust wrapper enforces the call budget,
same-thread ownership, and non-reentrant use before effects. The generated bridge never formats, returns, or
stores a caught panic payload and forgets it before the FFI return; no unwind
crosses FFI. Output from a caller-installed process-global panic hook is outside
the bridge's authority and is neither suppressed nor claimed. No allocator
crosses the boundary.
Generated safe Rust defines NativeRustImports, NativeRustImportResult,
NativeRustStatusClass, NativeRustCapabilities, NativeRustBridge, and the
closed call errors. The bridge is opaque, non-Clone/non-Debug, !Send/!Sync,
same-thread, and has no host/raw-context escape. A private sibling FFI module is
the only generated unsafe quarantine.
Build and publication authority
Private A is pure:
prepare_native_rust_interop(&Program,&[u8]) -> PreparedNativeRustInterop.
Its cumulative authority is reserved before phase entry. Pre-resolution HIR,
cleanup inventory/plan, TypeFacts, post-HIR fact construction, the five
renderers, Descriptor replay, and the independent C-expression replay have
named retained-versus-scratch envelopes, iterative depth-bounded traversals,
observed high-water gates, and exact/minus-one entry tests. Persistent facts
and final artifact sinks are charged separately from sequential scratch; the
Spec allocation is transferred rather than charged twice. These are local
bounded-memory facts for this private preparation path, not a general compiler
allocation or no-allocation claim.
Private B calls A once. RUSTC must name an explicit absolute discovery
executable; that executable may only run the frozen bounded sysroot query and
produces no accepted artifact. B independently opens the reported sysroot and
its exact bin/rustc/bin/rustc.exe, rejects path indirection, requires that
direct compiler to reproduce the same held sysroot, validates its version, and
admits Rust artifacts only through the distinct held-direct-rustc authority.
Clang is independently held. One exact pre-effect process arena is consumed by
the four discovery/version operations and eight build/link/run operations. On
Windows its attribute-list size is queried once before effects, capped,
aligned, reserved before allocation, and rechecked on every use. Windows also
requires a frozen absolute SEMAPRAX_VCTOOLS root and the exact verified
SEMAPRAX_LINKER beneath its bin\Hostx64\x64 directory, prepares
-Xmicrosoft-visualc-tools-root <root> -fuse-ld=link, and holds and rechecks
the linker around both Clang links without adding PATH to the child
environment. Private B
exactly replays Descriptor and Manifest bytes, and generates
header/C/safe-Rust/private-FFI artifacts with independent ordered exact-byte
consumers. Prepared invocations bind the admitted current-host target spelling,
including underscore-bearing target components, while rejecting other
punctuation. The four required rustc -vV fields share one preallocated
65,536-byte fixed-capacity store; parsing is no-growth and its retained capacity
is transferred exactly into Phase B rather than reserving four independent
maximum strings. The canonical Spec input and all six outputs reject every-byte
substitution, deletion, insertion, and truncation. One fixed-target fixture pins
the byte length and independently recomputed raw SHA-256 of Descriptor, Manifest,
header, C, safe Rust, and private FFI; it additionally pins the existing protocol
domain digests for Descriptor and Manifest. It compiles strict C and Rust,
statically links the object with the frozen Linux native-static library tail
when applicable, executes the round trip, then publishes a create-new exact
inventory.
There is no dylib, loader, symbol lookup, network, CLI, or public execution
surface.
This direct-image policy closes ordinary rustup-launcher indirection. It does not claim provenance for the selected compiler sysroot, dynamically loaded libraries or backends, or arbitrary descendants. The configured Visual C++ tools root selects Clang's MSVC toolchain and its linker is path-bound and drift-checked, but the current share mode does not prove the exact descendant image under a same-path replacement race. The explicitly configured discovery executable is trusted only to nominate the direct compiler that is then independently held and exercised.
Every owned build stage is continuously represented by the directory authority returned when it was created. Settlement uses only the opaque exact-inventory discard operation. Identity, reparse/symlink, or inventory disagreement stops deletion, preserves any foreign sentinel, and leaves inert residue for external recovery. Exact success/failure-path settlement evidence remains a promotion gate. The safe facade and system quarantine expose no generic or recursive path-delete operation.
The shared Windows directory publisher's legacy rename fallback now explicitly zeros its replacement-policy field when switching from the extended flags layout. Retries, result mapping, prepared capacity and close behavior stay unchanged. Forced-fallback field inspection and native collision fixtures are implemented; this correction is not covered by the historical hosted runs above. See the publication correction contract for the exact API-layout distinction and nonclaims.
Windows promotion additionally requires executable tests for zero and small stdout at normal EOF, silent deadline expiry, descendant-held stdout without overflow, one-character/reserved-DOS/case-folded names, and injected image, Job assignment, resume, terminate, wait/query, pipe-peek, and pipe-read failures. Every ordinary error must retain its sticky code only after proven leader-and-Job quiescence. An unprovable settlement must fail-stop before any later tool or publication action. Source inspection and non-Windows cfg-off compilation do not satisfy this hosted gate.
The six manifest file rows are descriptor.json, module.c,
semaprax_native_rust_interop.h, semaprax_native_rust_interop.rs,
semaprax_native_rust_interop_ffi.rs, and module.o/module.obj, sorted. The
directory additionally contains semaprax.native-rust-interop.json. The
manifest never hashes itself.
Public Native Rust SDK v1 Phase C
The unpublished builder crate exposes the narrow Rust API
build_native_rust_sdk(source, source_path, NativeRustSdkOptions, output).
It constructs the existing canonical private Spec internally, invokes unchanged
private A+B exactly once, and publishes a fresh local generated Cargo package.
The generated package is named semaprax-generated-native-rust-sdk, is not
registry-publishable, and fixes this nine-file inventory. A standalone source
build has no Rust dependencies; a scalar Project build carries the exact
[rust-dependencies] declared by its authenticated Package Manifest as
specified by Project Dependencies v1:
Cargo.tomlbuild.rssrc/lib.rssrc/semaprax_native_rust_interop.rssrc/semaprax_native_rust_interop_ffi.rsnative/libsemaprax_native_rust_sdk.aon Unix ornative/semaprax_native_rust_sdk.libon Windowsnative/descriptor.jsonnative/semaprax.native-rust-interop.jsonsemaprax.native-rust-sdk.json
The outer manifest schema is semaprax.native-rust-sdk.v1. It binds the exact
source revision and current target, private Descriptor and Bundle digests,
every non-manifest file length and raw SHA-256, ordered export/import signatures,
stable public method mappings, selected capabilities, limits, and nonclaims.
An independent byte cursor replays the complete outer grammar before staging
and again from held published bytes. Phase C relies on private B's already
independent exact replay of its own Descriptor/Manifest grammar; it
reauthenticates B's returned digest and all six inner payload rows rather than
claiming a second implementation of the private grammar.
Public method names are injective and order-independent: lowercase letters and
digits pass through after spx_; _, ., and - become _underscore_,
_dot_, and _hyphen_. The generated safe facade exposes
NativeRustSdkImports, NativeRustSdk, and closed admission/import/status/call
types. The public module forbids unsafe Rust; only the unchanged private sibling
FFI module contains reviewed unsafe code. The bridge remains opaque,
same-thread, non-reentrant, and !Send/!Sync, with the private depth, call,
panic, capability, and success-only result-publication rules unchanged.
SEMAPRAX_ARCHIVER must select one explicit absolute held image. Linux uses
the frozen deterministic rcsD invocation; macOS admits only
/usr/bin/libtool -static -D; x86-64 Windows admits only the exact
Hostx64\\x64\\lib.exe below SEMAPRAX_VCTOOLS with /NOLOGO /BREPRO.
The archive is built in a private held nonce stage, admits one exact
byte-identical object member plus the closed platform metadata-member grammar,
and is copied create-new into the outer inventory only after settlement. The
bounded linker-index payloads are archive-digest-bound and exercised by the
real external link, but their platform-specific symbol-table semantics are not
independently reconstructed in Phase C.
AArch64 Windows remains rejected because no matching ARM64 Visual C++ tool plan
is frozen.
Darwin's archive call returns a closed failure phase and explicit settlement
state. Failures before any namespace mutation are Settled; a scratch
mkdirat followed by failed reopen and every failure after process entry are
Uncertain. That state is absorbing through the safe facade and builder: no
speculative pathname deletion, scratch or owned-stage discard, outer-stage
creation, later tool action, or publication follows. The inert inner/archive
stages remain for caller reconciliation. Linux and Windows legacy sys errors do
not yet carry affirmative settlement evidence, so their safe-facade failures
are conservatively uncertain as well. This proves fail-stop handling locally,
not that the real hosted macOS successful path has been accepted.
Every nested Windows Cargo command used by the generated-package evidence
validates the absolute SEMAPRAX_VCTOOLS/SEMAPRAX_LINKER lexical relation,
requires the final linker pathname to be a regular non-symlink file, binds it
through Cargo's x64 MSVC target linker variable, and removes ambient LINK and
_LINK_ option channels. It deliberately leaves LIB and INCLUDE intact and
does not rewrite PATH or RUSTFLAGS. This is pathname configuration, not a
held-linker or ancestor-authority claim, and it does not close a same-path
substitution race. The Windows C compiler plan also fixes
-mno-incremental-linker-compatible so the COFF object TimeDateStamp is zero
before archiving. lib.exe /BREPRO remains required for archive metadata but
cannot normalize an already nondeterministic member. The real Windows archive
gate therefore compiles the production object twice, requires exact byte
equality, and only then performs exact archive-member admission.
The outer package has an explicit one-way publication transition. Phase C performs its final exact root/src/native inventory replay while cleanup authority is still complete, settles every held regular file, closes both descendant directory handles, and retains only the parent plus authenticated root-stage authority for the no-clobber rename. This is required on Windows, where renaming a directory that still contains held open descendants is correctly denied. Once settlement starts, any failure leaves the exact inert stage for caller reconciliation; cleanup is never attempted with incomplete descendant authority. Success still reopens and independently authenticates the published nine-file package.
Phase C caps source, Spec, Descriptor, inner manifest, generated Rust, outer manifest, object, and archive carriers; the archive cap is 8,388,608 bytes and the outer-manifest cap is 1,048,576 bytes. It does not extend private A+B's 33,554,432-byte pre-reserved cumulative-builder proof: Phase-C cumulative high-water, allocation-failure recovery, and OOM recovery remain explicit nonclaims. The external calculator and callback consumers depend only on the generated package, compile locked and offline, and exercise stable-ID exports, the Rust-import callback, status mapping, deterministic double builds, display rename preservation, and one same-source Rust/native-C/Core-Wasm result. The exact-head three-host promotion gate is satisfied at the commit and run cited above.
Project Native Rust SDK v1
The additive build_project_native_rust_sdk(manifest_path, output) entry point
builds the same exact nine-file generated package from one authenticated
Project Manifest v1 snapshot. The Project root retains the manifest and every
declared source handle while lending a non-constructible subject to the
builder. The subject binds the canonical manifest bytes and digest, Project
and workspace revisions, complete Project-graph digest, sorted exact source
path/schema/revision/digest/length facts, entry module, and the manifest's
sorted stable-ID Web exports with their exact module/path origins. The builder
consumes the already linked and validated entry ResolvedProgram directly; it
does not flatten, reparse, or reconstruct cross-file meaning.
The target-neutral subject schema is
semaprax.project-native-rust-subject.v1. Target-specific ABI facts remain in
the distinct semaprax.project-native-rust-interop-descriptor.v1 descriptor,
semaprax.project-native-rust-interop-bundle.v1 private bundle, and
semaprax.project-native-rust-sdk.v1 outer manifest. Their domain-separated
digests and exact replay prevent a direct-source subject, Project subject,
descriptor, bundle, or outer manifest from being substituted for another.
The returned ProjectNativeRustSdkBundle exposes the authenticated Project
revision, workspace revision, and Project-subject digest in addition to the
ordinary SDK bundle.
The same unpublished builder crate exposes the strict workspace binary
semaprax-native-rust-sdk. Its only grammar is project --manifest-path <path> --output <fresh-absolute-path> with each option exactly once. It
rejects missing, repeated, unknown, and trailing arguments and relative output
before delegating exactly once to build_project_native_rust_sdk. Success is
one compact JSON object plus LF with schema
semaprax.project-native-rust-sdk-result.v1 and exact ordered keys
crate_name, manifest_digest, project_revision, schema,
subject_digest, target_triple, and workspace_revision. Failure output is
path-, source-, and tool-output-free. The builder remains the sole owner of
fresh-child admission, held-path authentication, tool execution, and
publication.
Focused local evidence builds and runs the six-export calculator Project as
both Web/Node and generated Rust consumers, applies the opt-in daemon display
rename, shuts the daemon down, then independently rebuilds and reruns both
consumers. It proves stable-ID behavior while requiring the Project,
workspace, subject, and changed-source revisions to change; it deliberately
does not claim whole-package byte equality across a semantic source rename.
Hosted Ubuntu/macOS/Windows promotion is satisfied by the blocking Project
Product Acceptance jobs in the exact run cited above. The unpublished
semaprax-full build --target rust route now admits this scalar Project SDK;
the standalone crates.io CLI remains unchanged. Exact Project-declared Cargo
inputs are a consumer handoff, not registry acquisition or a general ABI,
aggregate, resource, or support-promotion claim. The Project route also carries
an import selection, described next.
The Project route is no longer export-only. The subject's imports are the
sorted native Rust imports declared by the authenticated entry program, and its
capabilities are the sorted union of those imports' declared effects. Both
render as [] when there are none, so an import-free Project's subject bytes
are unchanged. Phase A independently re-derives the reached import set and
still rejects any disagreement. The capabilities row names the effects the
selected imports declare; it grants no host capability and is not a deployment
or authority fact.
Project v1 admission routes a linked entry program that declares a callback
away from the WebAssembly scalar-export emitter and admits it under SPX-J117
without deriving any target bytes or scalar WIT descriptor. Such a Project has
no Web target: Wasm still rejects native Rust imports with SPX-W114, the
ordinary native backend cannot lower a callback call site, and the public
scalar WIT accessor fails closed with the existing SPX-J105. Its only
consumer is the generated C and safe Rust bridge rendered from linked HIR.
semaprax-native-rust-sdk project --manifest-path <path> --output <path> on
such a Project publishes a generated package whose facade carries both the
selected SEMAPRAX export and the Rust callback trait method, and a locally
built Rust consumer completed the round trip Rust caller → SEMAPRAX export →
Rust callback → scalar result, including a declared-status failure surfacing
with its exact domain.
The original bidirectional Project witness was local to one macOS
aarch64-apple-darwin machine. Its exact subject remains historical; the
current implemented release corpus is HOSTED GREEN within its selected host
profiles. The builder crate and generated packages remain unpublished. The
earlier export-only run IDs above retain their original tag and scope.
Diagnostics and nonclaims
The exact owned diagnostics are B106 noncanonical spec; B107 closed declaration reason; B108 descriptor disagreement; B109 limit; B110 target/toolchain; B111 generated replay; B112 SDK admission; I230 Clang; I231 Rust link/run; I232 publication; I233 SDK publication; G218 Graph; and W114 Wasm. Diagnostics never echo source, paths, tool output, secrets, panic payloads, or pointers.
The ordered nonclaims in every document deny resource/aggregate/pointer ABI, cross-boundary allocation, Wasm detours, dynamic loading, public execution, changes to callable/Graph/Agent/Economic/Workspace/Patch wires, sandboxing, same-UID process signaling or task-port isolation, cross-target reuse, unwind, abort/OOM/signal/process recovery, power-loss durability, async/reentrant/cross-thread use, provenance or ambient authority, error text/payload evidence, exactly-once effects, other ecosystem bindings, dynamic dependency identity or filesystem-race isolation, stable Rust ABI, public CLI/registry/network, general interop readiness, and completion of a long-term completion-matrix row.
The focused CLI contract is exercised with:
cargo test --locked -p semaprax-native-rust-interop --test project_sdk_cli
The private A+B promotion gate is satisfied at the exact commit and run cited
above: Ubuntu, macOS, Windows, Rust 1.85, and the required Linux sanitizer lane
are green together, including Windows runtime/capacity settlement. This proves
only the frozen private scalar/static-link profile at that head. Phase C keeps
the three implementation crates and root package unpublished but adds the
narrow builder API and generated local package described above; its exact-head
three-host gate is satisfied by the run cited above. Local runs qualify only when
RUSTC and CLANG explicitly select the admitted absolute tools; an ambient
launcher or proxy is intentionally not equivalent evidence.