# External script supervisor This optional Java21 process implements `qbit-scripting-supervisor/1` for the existing persistent TLS WorkerProvider. It has no QQQ, QContext, storage, record permission or other QBit dependencies. Application authorization remains in the QQQ caller and its per-invocation broker. It is a transport/runtime candidate, **not a qualified Kubernetes deployment or a latency result**. The execution entrypoint `SupervisorMain` only constructs the pinned Extism/Javy binding. `GuestRuntime` permits trusted lifecycle fixtures; it is not selectable from operator JSON. That execution entrypoint has no Node process, arbitrary guest Wasm, runtime download or permissive fallback. The separate [cold compiler endpoint](COLD-COMPILER.md) uses a disposable pinned Node process solely for strict TypeScript compilation; emitted author JavaScript is never executed by it. ## Build and run From the qbit-scripting root, using the existing wrapper and isolated dependency cache: ```powershell .\mvnw.cmd -o -f supervisor/pom.xml verify ``` Runtime Java dependencies are exactly JNA5.18.1 and org.json20251224. Keep their ordinary separate JARs with the supervisor JAR; do not replace the application QBit JAR or install this service into QQQ. With those three files in an operator-owned `lib` directory, the Linux launch is: ```text java -cp '/opt/qqq-supervisor/lib/*' com.kingsrook.scripting.supervisor.SupervisorMain /run/qqq-supervisor/config.json ``` For a nonroot process with a read-only root filesystem, explicitly set `HOME=/tmp` and `XDG_CACHE_HOME=/tmp/cache` to directories within the process's bounded, writable private temporary mount. Keep the runtime, native library, configuration and TLS stores read-only. These are supervisor startup directories, not guest filesystem grants; the guest manifest still has no allowed paths. Do not mount an operator's real home or cache. The October 31 local demo refused startup with `RUNTIME_COMPILE` and then started after those two environment settings were supplied. That observation establishes the working launch configuration; it does not identify a particular internal Extism cache operation or qualify Kubernetes containment. No guest execution or successful demo journey is inferred from startup alone. The configuration file must be absolute, at most64KiB, and contain exactly these fields (the angle-bracket strings are placeholders, not runnable qualification): ```json { "bind": "127.0.0.1", "port": 18443, "keyStore": "/run/qqq-supervisor/server.p12", "trustStore": "/run/qqq-supervisor/clients.p12", "certificateNamespaces": {"": ""}, "profile": "", "toolchain": "", "evidence": "", "requiredEvidence": "", "runtimePath": "/opt/qqq-supervisor/runtime/qqq_javy_runtime.wasm", "runtimeSha256": "", "nativePath": "/opt/qqq-supervisor/native/libextism.so", "nativeSha256": "", "slots": 1, "connections": 2, "coldOperations": 1, "entries": 8, "codeBytes": 8388608, "memoryBytes": 67108864, "idleMillis": 30000, "fuel": 100000000 } ``` PKCS12 passwords are required process secrets `QQQ_SUPERVISOR_KEYSTORE_PASSWORD` and `QQQ_SUPERVISOR_TRUSTSTORE_PASSWORD`; neither belongs in configuration, logs or guest input. TLS1.3 requires a client certificate, then an exact leaf SHA256 mapping authorizes its one namespace. The client independently verifies server trust and hostname. Certificate rotation needs explicit updated fingerprints and reconnection/reprepare; there are no subject-name wildcards. Configuration, trust stores, binaries and signing/evidence policy remain trusted operator inputs. Equal evidence labels do not independently prove containment. ## Wire, cache and lifecycle The closed outer wire is the existing [persistent transport contract](../docs/PERSISTENT-WORKER-TRANSPORT.md): four-byte big-endian length plus strict UTF8 JSON, at most4MiB per frame,8MiB across both directions per operation. Duplicate keys, coercion, unsupported fields, wrong session/sequence/request and mismatched capability tokens refuse the connection. Broker frames are separately limited to64KiB and50 calls. The server never retries a sent operation. ### Additive cold heartbeat Failed or expired sessions abort socket input before closing TLS. A graceful TLS 1.3 close can otherwise drain unread peer input for the full idle timeout, retaining the sole clean engine while the client waits for a heartbeat. Teardown also uses zero linger; it does not wait for a peer close-notify before releasing an already-destroyed guest's compiled slot. This changes neither guest retirement proof nor quarantine rules. Startup failure attempts cleanup of every acquired engine, listener and executor, preserving the primary failure and suppressing distinct cleanup failures. The unpublished supervisor/1 contract also accepts `PING` on an authenticated idle single-flight connection. Its exact keys are `protocol,type,session,sequence,requestId`; all existing identity/order/deadline checks apply. It returns `PONG` with exactly those five envelope keys, then the ordinary `RETIRED` envelope with `retired:true`. No artifact identifier, inventory, source, record, token or other payload is accepted or returned. The handler creates no guest, compiles no code and neither reads nor modifies prepared entries. PONG plus RETIRED confirms that this same connection/session cache epoch remains intact. The implementation never silently clears/replaces a session cache; any future cache-epoch loss must close the session, not return PONG. This is liveness of that authenticated session, not a new authorization or proof that a requested publication is prepared. The caller maintains its existing per-session ready identities and sends heartbeat only when it has exclusively acquired an idle connection. Server processing is serial, so no heartbeat is processed concurrently with a guest. Normal successful PREPARE/INVOKE retirement may also refresh the caller's confirmed-session age. This cold maintenance feature does not add preparation, reconnection or retries to hot execute. After certificate/HELLO admission, one warm TLS session owns one serial compiled-runtime slot. The slot's common Wasm is compiled at cold service startup, never in INVOKE. A bounded session cache holds only prepared bytecode and immutable digest/entry/profile identity. PREPARE accepts the existing `qqq-esm/1` bundle (at most1Mi UTF16 code units AND2MiB UTF8), verifies its digest and invokes cold Javy bytecode preparation. This is **not strict TypeScript compilation**. Outer COMPILE is intentionally refused; a separately qualified compiler endpoint must supply actual strict-TypeScript builds. Compilation must not consume interactive execution slots. PREPARE/READY retain the source `.ts` entrypoint. The private prepared result must contain its exact `.js` conversion, sorted unique portable module paths, at most200 modules/2MiB decoded bytecode, canonical base64 and the peer runtime's domain-separated digest. Only the compiler-produced cached object can enter RUN; client-supplied bytecode is never accepted. Entry-count and serialized-byte caps refuse additional preparation without evicting existing code. Cache fullness closes the session; subsequent cold rewarming is explicit. INVOKE requires an already prepared readyId and matching artifact/entrypoint. It neither connects, compiles, uploads the bundle nor fetches missing code. Each prepare/run creates a fresh Extism Plugin/Store, runs the fixed `run` export once, and actually frees that instance before READY/RESULT and RETIRED. Prepared bytes survive in trusted Java memory; guest globals, context, tokens and pending jobs do not. Any failed operation discards its connection and prepared-code cache; it is never replayed. A recoverable I/O or malformed-JSON failure may return only the common compiled engine to admission after the fresh guest was actually destroyed (or before any guest construction began). This includes bounded invocation refusal and failure to deliver a response after cleanup. A new session must authenticate and prepare its own cache again; initialized guests are never reused. Cleanup failure, fatal Error, unexpected runtime failure or uncertain construction quarantines that engine until service restart. Suppressed cleanup failures also prevent reuse. When every configured engine is quarantined, the listener closes and the service loop fails with ENGINE_CAPACITY_EXHAUSTED; SupervisorMain exits nonzero through its existing failure path instead of remaining live with zero capacity. No reset, replacement engine allocation or speculative reuse occurs. Current QQQ authority is rechecked on each caller invocation; readiness grants no permission. The sole application import is `extism:host/user::qqq_broker(i64)->i64`. Java validates the current invocation thread and Extism allocation length, relays a query through the current TLS session, and returns a fresh allocation. The fixed Wasm plugin frees both request and response blocks. Java never returns the borrowed request block or retains broker authority in compiled code. Callback failures cannot be caught by author code and transformed into a successful supervisor result. ## Budgets and retirement limits Admission has no unbounded queue:1–64 engine slots, at most128 connection threads,1–8 concurrent cold preparations. Each session cache has at most256 entries/64MiB serialized code. These are ceilings, not deployment recommendations; native engine/Java heap and concurrent frame copies require host capacity measurements. Invocation memory must exactly match the configured64–256MiB profile. Arbitrary lower per-request memory is refused rather than silently rounded up. CPU usage is actual supported/enabled `ThreadMXBean` time for the calling invocation thread, including native Wasm execution on that thread, host callback CPU and construction/free. Unsupported/disabled/unavailable accounting refuses startup/execution; there is no fabricated zero or wall-as-CPU substitution. CPU and wall are sampled every1ms, checked at broker/return boundaries and after actual cleanup. Guest wall hints are ignored. Broker waiting counts toward wall, not CPU. A finite Extism fuel budget and10s native timeout supplement these checks. **One-millisecond scheduling is not a proven one-millisecond enforcement bound.** OS scheduling, JNA/native cancellation and cleanup can overshoot. Native destruction runs under the cancellation lock to prevent use-after-free; a hung native free cannot be safely interrupted by Java. Exact Linux tests must establish cancellation/free behavior, target-thread native CPU accounting, fuel and overshoot under contention. A containing pod/process budget and termination policy remain required; this implementation does not attest hostile-runtime safety from a successful timer callback. TLS handshake/HELLO has a5s whole-exchange deadline. Each complete session operation, including partial input and final writes, has an idleMillis+15s outer socket deadline; requested guest wall remains at most10s. Per-operation deadline objects serialize expiry with retirement, so a dequeued old timer cannot close a subsequent operation. Startup bind-address DNS resolution and native common-runtime compilation are cold startup work without a Java hard deadline; use a literal bind address and an operator startup timeout. Shutdown stops new admission, closes owned sockets, and waits up to15s for invocation cleanup. It frees common engines only after session threads have terminated. Incomplete drain refuses rather than issuing retirement or freeing active native state; the orchestrator must then terminate the process. This is bounded shutdown waiting, not a promise of graceful completion of every in-flight call. ## Evidence and remaining qualification `trusted-lifecycle-full.log` records the initial10-test standalone verify: real ephemeral mutual TLS, exact namespace refusal, sequential fresh-instance calls, bounded cache/slot admission, broker authority refusal, measured thread-CPU cancellation, cleanup/error retirement, strict framing and stale timer isolation. The runtime fixture never evaluates source; these are transport/lifecycle tests, not Javy or Kubernetes qualification. Historical evidence files were not carried into this standalone source tree. Required next evidence: exact fixed-Wasm+Extism native execution through this binding; actual prepared-module/Promise/broker semantics; one-call/fresh-state proof; bounded host callback allocation/free; CPU/fuel/memory/timeout/drain and overshoot; complete WASI import/environment/filesystem/network inventory; actual production PersistentWorkerProvider interoperability; qualified cold strict-TypeScript endpoint; deployment certificate/namespace/NetworkPolicy/cgroup capacity and full record-save latency. No production image, cluster installation, author-code run or latency benchmark is implied by this source package.