Save the current implementation and README, status, and known-issue documentation at the user-requested stop. No completed release or deployment is claimed. Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: GPT-6 Codex <noreply@openai.com>
11 KiB
Separate cold TypeScript compiler endpoint
CompilerMain is a separate optional process/listener from SupervisorMain. It implements only the existing authenticated COMPILE exchange; it has no Wasm engine, execution slots, ready cache, broker, QQQ dependency, application credentials or author-code execution path. The Wasm endpoint continues to refuse COMPILE. The caller configures PersistentWorkerProvider.compileEndpoint separately; cold compilation never consumes interactive execution admission.
This is a staged transport/compiler candidate. Trusted Java child and TLS fixtures prove protocol/cleanup behavior, not Node isolation, Kubernetes qualification or latency. A matching operator evidence string is configuration, not self-certification.
Fixed compiler image
Reuse the existing worker's pinned Node 24.22.2, TypeScript 6.0.3, compiler.ts, protocol.ts and frames.ts. compiler-entry.ts imports only that compiler path; it never imports execute.ts, evaluates the emitted bundle, loads author packages/plugins or accepts compiler flags. The compiler retains its virtual source filesystem, explicit SDK, strict/noEmitOnError policy, approved relative/type-only imports and standard-library allowlist.
Root/operator build recipe (no dependency installation or author script invocation in this recipe):
<absolute-node-24.22.2> <worker>/node_modules/typescript/bin/tsc -p <worker>/tsconfig.json
<absolute-node-24.22.2> <supervisor>/fixtures/build-compiler-image.mjs <absolute-worker-root> <new-absolute-output-directory>
The generator refuses an existing output directory. It packages four emitted compiler JS files, the worker package metadata, TypeScript's package metadata and standalone typescript.js, plus shipped lib.*.d.ts. It records exact SHA256 hashes in compiler/compiler-manifest.json; adjacent compiler-image.json records the manifest hash and the platform-specific Node executable hash. Adjacent notices preserve the worker Apache notice/license and TypeScript license. Ship Node's official distribution license with the separately pinned Node binary. There are no executor, coordinator, test or arbitrary package files in the compiler tree. The generator does not import or run the compiler.
CompilerImage independently checks the executable hash, manifest hash, exact file inventory, every file hash, allowed directories and no symlink files/root. Limits:256 manifest files,16MiB per file,64MiB aggregate compiler files. Mount the binary/compiler tree readonly from an operator-owned immutable image: startup hash checks do not prevent a privileged operator replacing files afterwards. Record the build source, target architecture, Node distribution provenance, manifest and final image digest in external qualification evidence.
Endpoint configuration
Launch separately with ordinary supervisor Java dependencies; no QQQ application JAR is needed:
java -cp '/opt/qqq-compiler/lib/*' com.kingsrook.scripting.supervisor.CompilerMain /run/qqq-compiler/config.json
The absolute configuration file is at most64KiB with exactly these fields. Placeholders are not accepted qualification values:
{
"bind": "127.0.0.1", "port": 18444,
"keyStore": "/run/qqq-compiler/server.p12",
"trustStore": "/run/qqq-compiler/clients.p12",
"certificateNamespaces": {"<client-leaf-DER-sha256>": "<namespace>"},
"profile": "<operator-qualified-profile>",
"toolchain": "typescript-6.0.3-node-24",
"evidence": "<accepted-evidence-id>", "requiredEvidence": "<accepted-evidence-id>",
"nodePath": "/opt/node/bin/node", "nodeSha256": "<actual-target-node-sha256>",
"compilerRoot": "/opt/qqq-compiler/compiler", "manifestSha256": "<actual-manifest-sha256>",
"heapMiB": 256, "slots": 1, "connections": 2,
"cpuMillis": 10000, "wallMillis": 20000
}
PKCS12 passwords use the existing QQQ_SUPERVISOR_KEYSTORE_PASSWORD and QQQ_SUPERVISOR_TRUSTSTORE_PASSWORD process secrets. They never enter the child environment. TLS1.3 requires a trusted client certificate, then exact leaf-DER digest to namespace mapping. No wildcard subjects or client-provided namespace authority. All HELLO profile/toolchain/evidence fields must match. Use a literal bind address: Java's cold DNS resolution is outside an interruptible socket deadline.
The connections setting N (1..16) creates exactly N session workers and at most N waiting socket handoff positions: at most2N admitted sockets. A waiting socket has no parsed source or capability; its existing5s handshake deadline begins at acceptance and is never restarted when a worker becomes available. Excess sockets immediately close. This bounded handoff allows a sequential call to wait through the prior completed task tail without client retry. Child slots are separately bounded1..8; occupied child capacity still closes/refuses immediately, with no queued compiler work. Each accepted connection handles exactly one COMPILE and then closes. A client must not replay an uncertain sent request automatically. Already verified execution artifacts/readiness are unchanged by compiler availability.
Closed wire and actual retirement
The unchanged outer qbit-scripting-supervisor/1 uses four-byte length and strict UTF8 JSON (4MiB frame,8MiB transcript). HELLO/ACCEPT is the same certificate-authorized session handshake. COMPILE has exactly protocol,type,session,sequence,requestId,request, sequence1. request is the unchanged qbit-scripting-worker/1 compile object: protocol,type,requestId,toolchainId,entryPoint,sourceSha256,strict,files. Strict must be Boolean true; source manifest/file counts/lengths are bounded before child creation and the compiler verifies canonical content identity/import/type policy. No capability, context or record values are accepted as extra envelope fields.
The fixed child uses newline JSON, separate private protocol qqq-typescript-child/1:
- READY includes exact
protocol,type,nodeVersion,typescriptVersion,toolchainId. Parent checks the pinned versions before sending any source. - Parent writes exactly one existing compile request. Child runs compile only and returns compileResult, remaining alive.
- Parent samples actual child CPU, sends exact
{protocol,type:"RETIRE"}, closes child stdin. - Child writes exact
{protocol,type:"EXITING"}and exits. Parent requires actual exit0, stdout EOF, no stderr, and successful cleanup before returning. - Server validates exact result identity, strict/toolchain, bounded diagnostics, success bundle hash/map or failure-without-artifact. Only then does it send outer RESULT with
message, followed by RETIRED withretired:true.
No RESULT or RETIRED is issued when cleanup/accounting/identity is uncertain. Stderr is bounded8KiB and discarded without logging content; any nonempty stderr rejects. Oversized/partial/extra output rejects. Each admitted call owns a new child; there is no Node process reuse or author-code execution. Unsuccessful normal type checking may return a validated compileResult with success:false after ordinary retirement.
Limits and remaining platform qualification
Fixed argv contains only the verified executable, heap flag, Node permission mode with the compiler root read allowance, --no-addons, --no-warnings, --disable-proto=throw, and the fixed entrypoint. No shell, PATH lookup, author argv/config, NODE_OPTIONS, application tokens or inherited environment. Child environment is LANG/TZ only, plus SystemRoot on Windows. Node permissions are defense in depth; they are not a hostile-code containment claim. The child never executes submitted JS, but parser/runtime vulnerabilities and resource exhaustion still need the qualified process/pod boundary.
CPU uses the actual child ProcessHandle.Info.totalCpuDuration, sampled every2ms and at protocol boundaries. Missing, negative or decreasing live samples fail closed. Only after retirement has begun, a valid prior live sample exists and a fresh OS ProcessHandle check confirms actual death may missing/decreased post-exit accounting retain that prior sample; it never becomes a fabricated final usage value. Higher observed CPU remains subject to the same limit, and clean exit/EXITING/EOF are still mandatory. CPU settings1..30000ms are distinct from wall100..60000ms. No parent-thread CPU or synthetic fallback is substituted. The child intentionally stays alive after compilation so actual compile CPU can be sampled before retirement. OS accounting granularity, scheduled sampling/kill latency and the small post-sample EXITING/process teardown tail mean this is not exact final OS rusage. No compiler usage object or zero-CPU claim is manufactured. Measure/enforce an accepted overshoot bound on the target Linux image under contention before admission as a qualified compiler profile.
Wall accounting begins before process creation and never resets for partial reads/writes. Once a Process exists, watchdog expiry forcibly terminates that exact owned child; cleanup waits up to2s and retains/refuses the slot if death is unknown. Java cannot interrupt an OS ProcessBuilder.start call that itself hangs before returning a Process; require a supervisor/pod startup/liveness deadline. Confirmed normal process exit is mandatory for RETIRED; a trusted child handshake alone is insufficient.
Node's heap flag is not a process RSS limit. Deploy the separate compiler pod/process with a measured hard cgroup memory/pid/CPU allocation, finite connections/slots, readonly filesystem, no application credentials/preopens, and egress denied except explicitly needed operational infrastructure. Account for parent JSON buffers, pipes, Node native memory, compiler libraries and concurrent children; do not infer a safe aggregate RSS from heapMiB × slots. No compiler child should be able to escape the qualified cgroup. Hard memory enforcement, Node permission behavior, actual pinned compiler success/failure, child CPU support/overshoot, clean drain and the real main-provider cold endpoint still require target-platform tests owned by root. No Kubernetes deployment was performed.
Root-run fixed actual compiler corpus
After the exact Node worker build and packaging, compile the Java test driver without running it:
./mvnw -o -f supervisor/pom.xml -Dmaven.repo.local=<isolated-cache> -DskipTests test-compile
java -cp '<supervisor>/target/classes:<supervisor>/target/test-classes:<org.json-20251224.jar>' com.kingsrook.scripting.supervisor.NativeCompilerQualification <absolute-node> <node-sha256> <compiler-root> <manifest-sha256>
Use the platform's classpath separator. Only the compiled NativeCompilerQualification.class and TlsIdentity.class test helpers are needed besides production classes and org.json. The current JDK's keytool creates an ephemeral loopback certificate; the driver removes its own temporary credentials and listener afterwards. It does not need Extism/JNA, Wasm, QQQ, a shared port or an application credential. Fixed normal sources cover relative modules, SDK typing/TLA, strict type and forbidden ambient import diagnostics, then a later success. This proves actual compilation only after root runs it on the pinned image; it does not execute generated JavaScript, benchmark saving records, qualify a production namespace or claim cgroup isolation. The driver prints fixed counts and public artifact hashes only.