Public Owned UTF-8 API v1
September 10, 2026 · View on GitHub
Status: implemented bounded Project-v10 API; generated packages remain unpublished and explicit v9/v10 promotion decisions remain open. Release evidence is HOSTED GREEN under the v0.4.0 release baseline.
Audience: compiler contributors, generated-package integrators, and promotion reviewers.
The v0.2.0 tag commit 5f6fb9655fdec92c57ab71615cfd7bfa8cc76051
passed the complete blocking
release run,
including the Windows v10 generated-Rust consumer and the tagged Rust/Project
matrices. Authoring-time “unrun” statements below are superseded only for
nonignored cases selected by that workflow. No package publication, separate
provisioned/ignored gate, or formal v9/v10 promotion is inferred.
Closed identity
- Project schema:
semaprax.project.v10 - Project profile:
owned-utf8-api.v1 - Descriptor schema:
semaprax.public-owned-utf8-api.v1 - npm metadata schema:
semaprax.owned-utf8-api.v1 - Native Rust manifest schema:
semaprax.native-rust-owned-utf8-sdk.v1 - Native Rust manifest file:
semaprax.native-rust-owned-utf8-sdk.json - Descriptor digest domain:
semaprax.public-owned-utf8-api.digest.v1\0
All v8 descriptor, carrier, metadata, generated JavaScript, Wasm, native-provider,
and Rust-package bytes remain selected by their v8 identities. A v10 descriptor
cannot replay as v8, and a v8 descriptor cannot contain owned-utf8.
Descriptor result
owned-utf8 is a distinct result type. It is not an alias for owned-bytes.
The v10 profile admits all v8 result types and adds a SEMAPRAX string result.
Borrowed input mappings are unchanged.
The authored v10 input correction explicitly reuses the existing v8
captured-intrinsic whole-tuple preflight. It checks the cumulative 65,536-byte
borrowed-input bound, including exact UTF-8 lengths, before payload snapshots;
module bytes have a separate 16 MiB pre-copy/hash bound. V10 JavaScript and
dependent artifact bindings intentionally change, not descriptor, Wasm,
TypeScript, or v8 JavaScript bytes. Raw Bytes are still never decoded as text.
Physical boundary
The physical value is (opaque provider handle, exact byte length). Neither
the provider nor a target adapter searches for a NUL terminator. Embedded NUL
bytes are data. The fixed maximum result length is 65,536 bytes.
The native provider uses an allocation representation with an explicit length header, also reused by the later ordinary String contents correction without changing v10 output. Before it attaches an opaque handle or publishes result fields, it validates the exact bytes as Unicode scalar-value UTF-8. An invalid value returns the adapter-failure status and publishes no result.
render_owned_data_c_header now exposes the same descriptor-derived low-level
C11 integration boundary used by the owned-data provider. It declares only
fixed-width values, an opaque context, opaque byte handles, and the closed
status/tag result. A C integrator obtains the exact length, copies the bytes,
and drops the handle before closing the context; no native String layout or
NUL-terminated spelling crosses the boundary. Focused local evidence compiles
the header consumer and actual v10 provider separately, links and executes at
O0/O2, and preserves the embedded NUL plus multibyte UTF-8 by exact length.
The Wasm adapter transports the same length-delimited owned carrier. The npm
facade consumes the carrier once and decodes only an owned-utf8 result with a
fatal UTF-8 decoder. An owned-bytes result remains Uint8Array; it is never
silently decoded. The generated Rust facade copies and settles the opaque
handle before String::from_utf8, so valid and hostile invalid byte sequences
both settle provider ownership exactly once.
The root compiler alone authenticates provider semantics from replayed HIR. The unpublished lower package authenticates the closed descriptor, provider byte integrity, compiler-declared textual binding, held tools, and filesystem publication facts; it verifies the renamed stage through retained stage authority and receives no HIR or independent semantic-proof authority.
Settlement
The v10 Rust package shares the Cargo build-script path boundary.
Missing, non-Unicode, and CR/LF package paths reject before Cargo instructions;
the target guard retains precedence. Only generated build.rs and its manifest
integrity bindings change, not UTF-8 descriptors, provider archives, safe/FFI
Rust, or package schemas. These regressions are implemented.
The existing closure admission remains narrow: a String-returning function must have a literal or direct retained-function call as its body (optionally inside an empty block). A non-String-returning body may not stage String expressions, and compiler-owned String intrinsics remain rejected. A direct call's argument expressions can nevertheless contain String temporaries, bindings, nested calls, and blocks. Unused owned String parameters also require settlement; the narrow outer-body rule does not remove those obligations.
The v10 Wasm correction tracks those physical String owners in deterministic local cells derived from validated HIR and the emitter's exact function plan. This is the existing inline String-cleanup convention, not a reinterpretation of resource CleanupPlan slots or a new cleanup schema. Place reads clone their value, preserving the source binding; temporary handoffs move and clear their source cell. Expression/scope exit settles nonescaping cells before reuse or loop backedges. Calls evaluate all arguments left to right before atomically transferring staged String arguments to the callee. Call-out memory is only transport, not a second cleanup owner.
Success settles non-result owners before result publication. A provisional String result is cleared only after the caller store succeeds; recoverable failure sweeps remaining cells without changing the primary status. Host exceptions, allocation failures, or failing finalizers do not acquire a new recoverable cleanup guarantee: the instance remains poisoned/fail-stop.
For v10 packages selecting an owned UTF-8 result, the npm arena bound is derived from the selected acyclic call closure: one transient handoff slot plus the maximum call-path sum of authenticated Bytes cleanup leaves and String owner cells. Checked arithmetic rejects token space overflow. This is conservative simultaneous-owner accounting, not a heap byte or exact-liveness claim. V8/v9 retain their fixed 16-owner runtime bytes; their separate Bytes-copy admission is unchanged. V10 Wasm/JavaScript and dependent integrity bindings intentionally change; descriptors and public signatures do not. Scalar/Bytes-only v10 selections retain their existing memory layout and fixed arena bound.
Copy does not transfer ownership. Consume/drop settles exactly one live handle. Every successful string result is copied and settled before JavaScript or safe Rust publication. Invalid UTF-8 cannot be returned as a host string, and a conversion failure cannot leave a live provider handle. Stale, foreign, wrong-length, repeated, or exhausted handles retain the v8 fail-closed rules.
The owned npm invocation correction makes a post-consumption JavaScript UTF-8 decoding failure poison the instance even when its arena is empty. Imported UTF-8 validation returns zero only for authenticated malformed text; memory/carrier faults and unexpected host exceptions cannot masquerade as malformed user bytes. The shared v8/v9/v10 failure-state correction changes runtime JavaScript and dependent integrity bindings, not Wasm, descriptors or public signatures. Its regressions are implemented.
The authored shared Rust invocation guard additionally proves the complete provider context settled before any outward value or recoverable error, including a UTF-8 conversion failure. An inner owner guard precedes context closure on Rust unwind; uncertain settlement remains fail-stop. Only a proven-closed context may be reinitialized on a later call. Its private invocation counter resets while the linked provider's handle issuer remains nonreused. Generated safe/private Rust and integrity bindings intentionally change, not provider C/ABI, public signatures, descriptor or manifest schemas. These regressions are implemented.
The later descriptor-selected Rust helper correction omits unused owner-only FFI declarations for admitted scalar-only v10 selections, while keeping the complete context protocol. Any selected Bytes/owned-UTF8 result retains copying, malformed-carrier discard and owner guards byte-for-byte. Only scalar-only private FFI and dependent integrity bindings change, not v10 admission, public signatures, providers or schemas. Warning-denied scalar SDK metadata/consumer checks pass with Rust 1.85.1, 1.88 and 1.98; the documented real minimum-version Cargo handoff covers v8/v9, not an actual v10 provider package.
The native correction is confined to the existing v10 owned-UTF8 provider projection. Its per-function physical String owner cells are declared and initialized before any recoverable failure branch. Emission records the exact cells in bounded staged output; no heap-allocated runtime registry or resource CleanupPlan change is introduced. Temporary-to-binding, branch, call, and provisional-result handoffs transfer ownership rather than creating a second cleanup owner. Every argument is evaluated before the complete String argument group transfers to the callee. Normal scope exit settles nonescaping live owners before loop reuse; the common failure exit settles every remaining owner and preserves the primary status and caller result storage. Success settles non-result owners before publication and relinquishes the result only after its store.
Native generation still emits the supplied program, not only the selected
public closure. The same physical bookkeeping must therefore cover emitted
String intrinsics and contracts in unselected functions; this does not admit
them in the public v10 closure. Allocation failure remains fail-stop. These
rules add no unwind, signal, or longjmp recovery guarantee.
The v10 correction changes only v10 native provider C and its dependent integrity bindings. The subsequent ordinary native String correction reuses the ledger under a separate selection and leaves v10 provider bytes unchanged. The separately amended owned-data provider correction also reuses that ledger and length-header representation for v8/v9 emitted Strings, without changing v10 output or widening selected closure admission. Frozen command/callable projections retain their separate String limitations. Context-handle closure alone is not proof that pre-handle String allocations were freed. The implemented cross-backend settlement and sanitizer regressions use the hosted-green release baseline; broader support and public promotion retain their owning gates.
Authored evidence
Project v10 follows the separate Windows owned npm publication contract: full-toolchain held-handle publication with an existing parent and an admitted output leaf; standalone Windows publication rejects safely. This changes filesystem routing, not UTF-8 semantics, artifacts or Unix behavior. Its admitted publication and consumer-route regressions have hosted-green release evidence.
The shared descriptor cross-replay cases
include authentic v10 Bytes and owned-UTF8 counterparts under the same profile
and synthetic subject facts. Self-replay succeeds; each correctly digested
descriptor must reject against the other's retained HIR. This is signature
binding regression evidence in the v0.4.0 baseline, not cross-schema rejection, source
provenance, behavioral equivalence or target execution.
tests/project/v10_recipe_consumer.rs uses a real four-source Project,
with two imported owned-String helpers sharing a display name but retaining
different stable identities, including source-escaped control characters. It
replays the actual inline npm carrier and reopens all six published artifacts
for exact equality. Its Node consumer uses the published bindings and Wasm,
covering empty text, leading BOM, embedded NUL, multibyte text, raw malformed
UTF-8 remaining Bytes, late-argument arithmetic failure and subsequent
reuse. A helper display-only rename must preserve every descriptor fact except
the three Project revision/graph bindings. Native coverage in this fixture
stops at the compiler-replayed package passed to an intentionally rejecting
publisher; it does not compile, publish, or consume a native SDK. The v0.4.0 release regression corpus is HOSTED GREEN.
cargo test --locked -p semaprax --test project v10_recipe_consumer::
The separate provisioned gate
crates/semaprax-toolchain/tests/project_owned_utf8_sdk_v1.rs consumes the same
four-source fixture through the real private native publisher. It regenerates
the provider from retained checked Project HIR, reopens the exact seven-file
SDK, and compares its canonical manifest against that descriptor/provider
binding and the reopened file hashes. This test-specific consistency check is
not an independent proof of archive provenance or provider semantics.
An unchanged external Rust consumer then builds in an isolated workspace with
--locked --offline and the existing short Cargo target-directory guard. Its
safe API forbids unsafe code and covers the same primary Node corpus: exact
17-byte BOM/NUL/Unicode text, empty text, raw malformed UTF-8, 65,536-byte
Bytes, capacity-plus-one input rejection, and repeated checked failure and
recovery through two SDK objects. Retained String and Vec<u8> values remain
independent after later calls, mutation and SDK destruction. These observations
do not replace physical allocation accounting or prove reuse of one initialized
native context; the maximum-byte case here is not a maximum String result.
The gate also checks exact revision-only descriptor changes after helper rename,
SPX-I234 no-clobber rejection, unchanged consumer inputs, SDK artifacts and
Project source bytes.
This gate is explicitly ignored until selected on a provisioned host with
absolute CLANG and SEMAPRAX_ARCHIVER paths and native Cargo. Windows also
requires the existing SEMAPRAX_VCTOOLS, INCLUDE, LIB and
SEMAPRAX_LINKER configuration. Authoring or skipping it does not count as a
successful SDK gate, and it requires that explicit selection:
cargo test --locked -p semaprax-toolchain --test project_owned_utf8_sdk_v1 -- --ignored
The separate tests/support/owned_utf8_capacity.rs subject isolates the String
result boundary in a two-source Project. A single selected literal contains
65,535 or 65,536 UTF-8 bytes, including repeated BOM, NUL, multibyte and astral
characters. No other String literal consumes the shared 65,536-byte Wasm
literal pool. The real npm and native Rust publication gates compare returned
strings against independently spelled byte oracles, exercise repeated calls
through two instances, and retain earlier host values. Native host values are
also checked after SDK destruction. Package and source inputs remain exact.
The native gate reuses the same test-only manifest consistency oracle as the
multi-module SDK fixture; neither oracle establishes archive provenance.
A separate 65,537-byte source must fail ordinary Project admission with
SPX-W110 (owned UTF-8 literal table exceeds 65536 bytes) before the Project
callback or publication. This is a compile-time literal-pool boundary, not
evidence of a native runtime over-limit rejection. These cases do not replace
physical allocation accounting, failure-path settlement, or maximum input
coverage. Both gates are implemented; the native one is explicitly
ignored and requires the same provisioned tools described above:
cargo test --locked -p semaprax --test project owned_utf8_capacity::
cargo test --locked -p semaprax-toolchain --test project owned_utf8_capacity:: -- --ignored
Retained reference evaluation
The additive retained-Project evaluator replays the exact v10 descriptor and
then independently authenticates the selected HIR closure before evaluating
it without filesystem, process, publication, target, or cache authority. The
public result vocabulary is v10-only: the six v8 scalar/owned-byte shapes plus
one owned UTF-8 String. It does not extend the exhaustive v8 result enum.
The selected export must be an explicit, non-entry stable identity and must
match its descriptor parameter identities, names, types, order, and result.
Automatic-identity monomorphic helpers remain valid inside the exact selected
closure, as they are in descriptor admission. Every reached function is
rechecked as import-, generic-, effect-, and contract-free, the closure is
acyclic and contains at most 256 functions, and the v10-only closure-shape
predicate is applied independently of the broader internal-String interpreter.
Malformed selectors produce the fixed non-echoing SPX-F102 diagnostic.
Each invocation has two fixed, non-caller-widenable logical materialization limits:
- 65,536 cumulative owned UTF-8 payload bytes; and
- 4,096 cumulative owned UTF-8 allocations, including empty strings.
These are logical interpreter payload limits, not claims about allocator
metadata or physical heap size. One atomic precharge occurs before every
interpreter-owned String allocation. Rejection neither mutates the committed
counters nor allocates, and checked-arithmetic overflow fails as the same
closed capacity outcome. There are no refunds. Literal materialization and
the existing source-preserving String place read both charge; authenticated
let/result handoff, staged argument-to-callee transfer, drop, and final carrier
handoff do not. The runtime value carrier has no implicit deep-clone route, so
future String copies must pass through the same accounting seam. Compiler-owned
String intrinsics remain rejected by v10 admission, but their allocation paths
also precharge defensively so admission drift cannot bypass the meter.
Borrowed str and Slice<u8> snapshots retain their separate cumulative
65,536-byte input bound and are admitted completely before any snapshot. The
successful host copy-out retains its separate 65,536-byte output bound. Neither
host boundary is charged again as an interpreter-owned materialization; doing
so would make an otherwise valid maximum-size result impossible. A successful
active Bytes or UTF-8 result emits exactly one distinct copy-out-and-settle
event. Scalars, None, Result::Err, and every failure emit none. Empty UTF-8
is still an active owner and therefore emits one UTF-8 settlement event.
Fuel is charged before a node's materialization precharge, the first selected failure remains sticky, and the closed quota outcome is not a guard error or a language status. Its attempted counters identify the rejecting precharge; ordinary used counters report only committed charges, including when a later language, fuel, or depth failure wins. Counters are created afresh for every evaluation. This reference lane is not target-execution or promotion evidence, and v8, v9, legacy, and internal-String evaluator behavior remains selected by their existing APIs.
The shared lower v8/v10 descriptor reader also rejects repeated parameter identities within one export, even under a freshly computed descriptor digest; see the owned-data descriptor contract. This tightens malformed-input rejection only, without changing emitted package bytes, public signatures, or schema identities.
Focused authored lifetime evidence is in
tests/project/owned_utf8_lifetimes.rs and its raw-arena/real-facade Node
consumer. It derives and replays the real descriptor before target generation;
locals and Copy-only loops occur inside admitted direct-call arguments. The
loop case retains one String before the loop and settles it on exit; it does
not allocate Strings per iteration. Direct String loop storage remains
SPX-T252, and an otherwise legal scalar helper staging Strings remains
outside the selected v10 closure (SPX-J113). Negative cases
preserve the rejected outer-body String forms. The native O0/O2 fixture is
success-value evidence only, not native failure-path allocation evidence.
cargo test --locked -p semaprax --test project owned_utf8_lifetimes::
tests/native/owned_utf8_settlement.rs independently derives/replays the
descriptor and emits the actual v10 length-header native provider. Its fixed
test-only allocation table observes provider allocations and frees at O0/O2:
local, late-argument, nested-call, callee, and loop failures must leave no
allocation live and must preserve poisoned result fields. Subsequent calls
reuse the same context. Success covers clones, branches, mixed Bytes/String,
more than sixteen owners, empty text, embedded NUL, and multibyte UTF-8.
Separately labeled raw semantic calls exercise emitted-but-unselected String
intrinsics, equality, guarded matching, and failed postconditions without
widening admission.
The generated Rust package consumer exercises safe API reuse after failure;
it is not a substitute for the physical allocation counter.
cargo test --locked -p semaprax --test native owned_utf8_settlement::
cargo test --locked -p semaprax --test project_native_rust_owned_utf8_v1
The separate sanitizer gate requires an installed absolute
SEMAPRAX_STRING_SANITIZER_CLANG path with ASan/UBSan support:
cargo test --locked -p semaprax --test native owned_utf8_settlement::provisioned_native_string_provider_asan_ubsan -- --ignored --exact
Allocation counters establish the leak assertion even on hosts without LeakSanitizer; ASan/UBSan are additional memory/undefined-behavior checks, not a claim of LeakSanitizer coverage. None of these new gates was run in this batch.
Non-claims
The implementation's nonignored regression evidence ran in the v0.2.0 tag matrix; separately provisioned and ignored gates remain outside that claim. No generated package is published and formal promotion is not claimed. Project v10 remains blocked on an explicit Project v9 promotion decision; the v0.2.0 tag supplies hosted regression evidence for selected cases, not that decision. The local C11 integration test is not package publication, cross-platform support, or a promotion decision.
This profile adds no command, filesystem, process, network, daemon, recovery,
arbitrary publication, or general text-streaming authority. It does not decode
raw Bytes, expose a public aggregate ABI, or weaken Project v1-v9 behavior.