Skip to content

S6: Behavior safety and platform qualification

S6 is in progress. This slice implements shared target admission, a production provider safety suite, and recorded native/Node/Chromium evidence. It does not complete the six-platform milestone. Windows, Android and iOS need engine backends; physical-device qualification and broader browser coverage are pending. S4 defines the installed contract/cooker, and S5 defines the Editor and GameHost workflow. S7 remains representative project, performance, designer and release acceptance.

Admission and evidence

config/behavior_profiles.json is the shared interpreter admission policy. The producer and installed LudusConfig.cmake evaluate the same helper before engine or SDK dependencies are loaded. Installed consumers must match the producer's system and architecture. The exported target also asserts the compiler's actual architecture in the public header. LUDUS_SKIP_TOOLCHAIN_CHECK cannot bypass this Behavior gate. Qualification admission permits a build; it does not establish that it has run on a device.

Target Admission Required execution evidence
Linux x86_64 / arm64, Clang 23 Qualification builds Native production-provider suite, ASan/UBSan, relocated SDK and extracted player; CI currently exercises x86_64
macOS x86_64 / arm64, upstream Clang 23 Qualification builds, one architecture per SDK Native production-provider suite, ASan/UBSan, relocated SDK and extracted Mach-O player; macOS CI records its actual CPU
Web wasm32, pinned Emscripten LLVM 22 Qualification builds Development/Release Node and headless Chromium execution of the same hashed Wasm; physical browsers/devices and additional browser engines pending
Windows x86_64 / arm64 Rejected: engine backend pending Native Foundation/backend build and execution, SDK/player, sanitizer and debugger acceptance pending
Android arm64 Rejected: engine backend pending NDK/native engine profile, physical device execution and packaging pending
iOS arm64 Rejected: engine backend pending Apple mobile engine profile, signed physical-device execution and packaging pending

Other systems, compilers, compiler majors, architectures, 32-bit native targets, wasm64, and macOS universal SDKs are rejected. The pinned Web SDK remains separate from reference Clang 23. A policy update requires both implementation review and new execution evidence; adding a name to this table is insufficient.

Execute the safety suite

Use a prepared pinned toolchain and opt-in paired Luau tools:

./scripts/script-provider bootstrap
./scripts/script-provider run --preset linux-clang-development
./scripts/script-provider check --preset linux-clang-development
./scripts/script-provider run --preset linux-clang-asan-ubsan
./scripts/script-provider run --preset web-emscripten-development
./scripts/script-provider run --preset web-emscripten-release
node tools/script-provider/browser.mjs out/build/web-emscripten-release/tools/script-provider

Browser execution requires the existing pinned Playwright installation under tools/web-browser-tests; it does not download tools implicitly. macOS CI uses macos-clang-development and macos-clang-asan-ubsan, with init's upstream Clang shims and paired macOS SDK/libc++ headers. Host compiler/analyzer executables remain separate processes that can use upstream exceptions; they never enter the player link closure. Explicit bootstrap refreshes their CMake cache when paths change.

CTest's ludus_behavior_safety executes the public LuauProvider, not a mock or private test bridge. The twelve named scenarios cover:

  • Exact frozen global inventory; read-only configuration, events and engine API.
  • Fractional, NaN, infinite, negative, overflow and contract-range integers; forged entity values; boolean conversion; every entity identity dimension.
  • Interrupt and stack-fault retirement, expired retained state facades, and a shared safepoint budget across debugger resumes.
  • Bounded package-metadata truncation with active-package retention.
  • Native service resource completion and recursive provider-call rejection.
  • Every allocation point observed in representative load/invoke and debugger runs, and every candidate-load point while an active provider exists.

A separate private allocator contract test forces shrink/equal-size/capacity reuse under persistent failure, checks copied growth, exact physical freeing, header charges and overflow rejection. Its private header is not installed.

The acceptance-only executable replaces FoundationMemory's fallible aligned allocation boundary. Each sweep rejects the selected allocation and every later allocation, then checks the explicit status, unchanged caller state/effects, VM retirement or active-package retention, and successful closure. Counting and injection are enabled only around serial provider operations. There are no public SDK injection hooks. Provider metadata/native allocations outside this boundary are not covered by these VM sweeps; this is not an exhaustive heap fault campaign.

verify_safety.py requires all scenarios, actual compiled target identity, nonzero sweep counts, a successful exit, and exception-free compile commands for Behavior, Scripting, Luau VM and Common. It writes safety-evidence.json beside the executable with input/artifact/compile-command hashes and measured counts. Missing, duplicated, unknown or contradictory evidence fails the gate. Counts are observed per artifact rather than promised across compiler versions.

The browser runner verifies that downloaded JS/Wasm hashes match Node's evidence, executes the same scenarios and counts in Chromium, and writes out/browser-qa/results/behavior-s6.json with browser version and logs. CI publishes native, sanitized and Wasm evidence. Synthetic CMake policy tests prove rejection logic; they are never recorded as physical-device execution.

Nonlocal-jump and resource audit

The interpreter uses Luau's longjmp recovery, with production translation units compiled without C++ exceptions and no native code generation. Thanks to Roblox / Luau for the exact pinned source reviewed here: revision 1eca9fda. The S0 loader review remains the source of the exact, fail-closed TempBuffer lifetime patch. Its scratch owners, including nilKeys, live outside luaD_rawrunprotected's jump region; their destructors run after recovery. This slice does not change that patch or admit other upstream revisions.

The production boundary was reviewed with these rules:

Boundary Ownership and recovery rule
Runtime protected dispatch and continuations Context/identity/value locals across VM operations are trivially destructible; owning strings, records and provider objects live in the caller or runtime outside the protected region
Behavior service trampoline Copied EntityRef/Value/Record/Services/CommandResult/Transaction representations have static assertions for trivial destruction; the service returns and destroys its own resources before any subsequent VM allocation/error
VM allocator FoundationMemory domain accounting uses explicit failure; retained-capacity headers make shrink/equal-size/reuse infallible; physical accounting includes headers, retained capacity and old+new growth overlap; actual upstream allocation denial and configured quota denial both increment the denial counter
Bytecode load Upstream luau_load can recover OOM internally and return a loader error; the runtime preserves its allocation-denial cause before lua_error translates the outer status, so it reports AllocationFailure rather than ScriptFault
Debug frames, locals and breakpoints Borrowed VM information is copied into bounded native snapshots; no language callback, getter or formatter executes during inspection; native snapshot owners stay outside protected dispatch
Entity and state userdata Payloads are copied POD identities/facades with no native owning destructor; generations and live facade validity are checked at use
Host service calls Synchronous, bounded, noexcept; recursive Load/Invoke/Close/debug operations reject entry while the runtime is active; a service must not invoke raw VM APIs or escape its own native resources

ASan/UBSan and allocation sweeps supplement this lifetime audit. They cannot prove the C++ rules for nonlocal jumps. Any new C callback, upstream update or userdata owner requires another frame/resource review and meaningful failure tests.

The global environment is now an explicit copied allowlist instead of upstream base globals followed by a denylist. It contains assert, error, getmetatable, ipairs, next, pairs, rawequal, rawget, rawlen, select, tonumber, tostring, type, typeof, _VERSION, _G, the frozen generated engine API, and literal-catalog require. The global table and exposed values are sandboxed/frozen. Upstream additions cannot silently grant another facility. Protected calls, coroutines, raw writes, environment mutation, native-pointer exposure, filesystem/network/process access and dynamic source compilation are absent. A future library expansion is a capability and failure-path review.

Remaining qualification limits

Packages are admitted only from the paired, owner-controlled source cooker. Digest checks establish integrity and contract identity, not authenticity or a safe arbitrary-bytecode verifier. Metadata rejection tests do not execute mutated bytecode. This provider is not an untrusted plug-in or process-isolation boundary.

The heap cap covers each VM; candidate replacement can temporarily hold both old and candidate heaps plus native metadata. Safepoints count interpreter interrupt checks, not instructions or elapsed time. Native services are not preemptible and must provide their own bounded work. Debug stops retain the same invocation budget. These constraints must inform project budgets and S7 measurements.

To finish S6, implement the rejected engine target backends and SDK/toolchain profiles; execute and retain equivalent native/player/debugger evidence for each supported architecture; add physical Android/iOS and desktop hardware runs; and qualify the declared browser engines on physical devices. Review allocator, resource and platform-specific ABI/toolchain paths on each new target before changing admission. No six-platform completion claim is made by this slice.