QBit Scripting
Version 1.0. Published to the KOF22 Gitea Maven registry (
com.kingsrook.qbits). It needs a qualified external worker deployment to compile or run scripts; with none configured every build and execution fails closed (see below).
Start with current status and build prerequisites and known issues. These documents supersede older release-readiness statements below.
base/, rdbms/, middleware/ and github/ are the Java reactor modules. worker/, wasm-runtime/, supervisor/ and deploy/ contain separate runtime components and deployment material. A Java package build does not build or qualify every runtime image.
Shared, reusable scripting for QQQ: script projects, immutable strict-TypeScript builds with provenance, capability-limited execution and typed record lifecycle hooks. Other QBits consume published, pinned scripts; this one knows nothing about documents, retained files, mobile or printing.
Status: foundation, not a production runtime. This repository implements and tests the control plane. Running or compiling script code requires an external hardened worker deployment that has not yet been qualified. With no worker configured, every build and execution fails closed. See docs/STATUS.md for the standalone release status and remaining production gates.
What it gives you
| Concern | Implementation |
|---|---|
| Source registry | Reuses core script, scriptRevision, scriptRevisionFile. Each save appends a new revision; nothing here updates one. |
| Separate steps | saveDraft / check / test / publish / bind, each with its own permission (scripting.draft, .check, .test, .publish, .bind) and a named Actor. |
| Strict builds | BuildCoordinator sends a hashed snapshot to a WorkerProvider, requires strict mode and its pinned toolchain id, re-hashes the bundle, and signs (HMAC-SHA256) the provenance. Failed builds store diagnostics and no artifact. |
| Optional warm preparation | Explicit tests/publication/binding may prepare verified immutable code using WarmWorkerPreparation; no hook or hot execution fallback. Current authority and pins are rechecked. Contract and full173 verification. |
| Native warm lifecycle | Optional per-node startup/reconnect reconciliation under a declared metadata principal, bounded exact signed pins, idle authenticated heartbeat and current aggregate readiness. No hot-path preparation. Contract and full192 verification. |
| Immutable artifacts | scripting_artifact rows hold bundle bytes, SHA-256, source hash, toolchain, coordinator id, attestation and a per-project version. Verified again before every execution. |
| Pinning | scripting_publication numbers published versions. A scripting_binding names one publication; new drafts or publications do not change it. |
| Capabilities | Read-only CapabilityDeclaration (table, fields, max rows, max calls). Builds declare, bindings grant a subset. CapabilityBroker issues opaque scoped tokens; every read is re-checked (table, fields, filter fields, budgets, size) and then runs through QQQ QueryAction under the current actor. |
| Hooks | ScriptHooks.install composes native insert/update/delete hooks with existing Java callbacks. Early pre hooks may patch or veto; delete may veto; post callbacks are observations, never commit notifications. See native hook contract. |
| Context scripts | validatePublishedContext freezes publication id, artifact id, bundle hash and signed capabilities. executePublishedContext rechecks that pin, current actor/project permission and narrowed grants. JSON in/out; no dependency on consumers. |
| History | Independently committed append-only STARTED and terminal scripting_execution events share an operation id. A rejected record write cannot roll back its execution history. A crash leaves an unresolved STARTED event. |
| Git (optional) | GitSourceImporter port for an exact 40-hex commit. The base ships no importer; the optional github module (qbit-scripting-github, GitHubSourceImporter) implements it without coupling to another QBit. |
Existing core Script model: assessment
Decision: reuse, do not replace, and do not migrate data now.
scriptis the project,scriptRevision+scriptRevisionFileare draft source revisions. They already have an author, sequence number, per-file contents and the existing script UI.- Fit: file names up to 100 chars (our path limit follows), contents unbounded in the schema (we bound them at 256 KiB each, 2 MiB total). Reviewed core isolation guards enforce append-only source revisions; builds also record a source hash and
publishrefuses a revision whose files changed after the build. - Core isolation: project creation uses
ScriptExecutionIsolation.insertIsolatedScript; drafts never activate. Tested publication advancesscript.currentScriptRevisionIdthroughactivateIsolatedRevisionwithin the publication transaction and verifies the persisted pointer. The monotonic ISOLATED marker prevents legacy execution. Runtime consumers still require an immutable publication ID and signed artifact, never the pointer alone. Source lifecycle guards preserve parent associations and reject edits/deletes/reparenting of isolated revisions/files. - Core
Script/ScriptRevisionhave no build, artifact, capability or publication concepts and no place for them; those live in the fivescripting_*tables keyed by the core ids. - Open migration items (see docs/STATUS.md): legacy Java-Script scripts are not converted or imported; a
ScriptTypefor strict-TypeScript hook projects is a host decision; legacy projects keep their existing dialog; isolated projects use the semantic source editor and central core guards refuse legacy source mutation/execution.
Worker contract (what a provider must be)
WorkerProvider exchanges JSON text only (WorkerProtocol, version qbit-scripting-worker/1). Bounds: compile messages 4 MiB, execute 2 MiB, results 128 KiB, broker messages 64 KiB, bundle 1 MiB, payload 256 KiB, JSON depth 12, 100 diagnostics, 500-char messages. Inbound messages are size-checked before parsing and unknown keys are rejected.
- Compile: files + pinned toolchain id + entry point in; strict flag, toolchain id, diagnostics, bundle and its SHA-256 out. The host re-hashes the bundle and compares the toolchain identifier. Actual compiler enforcement depends on the independently qualified worker; a matching string is not isolation evidence.
- Execute: artifact id, artifact SHA-256, bundle, entry point, event,
{proposed, prior}hook payload or{context: ...}CONTEXT payload, opaque capability token, limits (wall, CPU, memory, output, broker calls, clamped to absolute maxima). The worker must verify the hash before running. There is no credential, session or connection field. - Broker: the worker sends
queryrequests carrying the token; the host answers or refuses. No other operation exists. qualificationId()must equal the evidence id the host configured (ScriptingRuntime.withRequiredQualificationId). Blank or mismatched means refuse both compilation and execution; the default is blank.
The host signs the exact capability manifest separately from the artifact. Stored manifest or project/source associations are checked before capabilities are used. scripting_build.manifest_attestation is a required schema addition; old unsigned builds are refused and must be rebuilt.
Broker callbacks must be synchronous on the initiating QQQ thread and session. The host independently checks total calls and result size. Tokens expire within the trusted provider's transport budget (default 15 seconds, absolute maximum 120 seconds including container setup), are closed after execution, and per-table budget accounting is serialized. This does not extend the script's wall/CPU budget. Hooks omit hidden, password, binary and field-restricted values; patch permission is rechecked against current metadata and field policy.
CoordinatorWorkerProvider is the concrete trusted Java transport to worker/dist/coordinator-cli.js. Operator configuration supplies absolute executable/CLI paths, a digest-pinned image, matching external evidence id, stable owner id, private durable POSIX state directory and controlled executable search path. It uses no shell, clears inherited environment, keeps broker callbacks on the caller thread, bounds UTF-8 frames and discarded diagnostics, enforces a deadline with graceful then forced cleanup, and accepts a matching final result only after exit 0. The Node coordinator verifies runsc and runtime provenance and journals container ownership; its reaper must be deployed. See worker/README.md. This adapter and its protocol-only process tests do not qualify the actual sandbox.
Hooks and durability
ScriptHooks.install composes existing trusted Java customizers for all six native record events, preserving host phases and invoking each once. Early pre-insert/update patches pass through native validation; late host phases support veto only. Post events run after the backend action and before the caller transaction commits; they cannot promise durable after-commit effects. See native hook semantics. Separate ScriptAfterHooks.install(instance, table, AFTER_...) stages required same-transaction Jobs intents for committed observations; INSERT/UPDATE read current authorized state, DELETE carries metadata only. See durable AFTER semantics and current qualification.
Draft/revision/file saves, build/artifact storage, publication and binding changes use real QQQ backend transactions. Participating tables must share a transactional backend; authoring rejects an ambient transaction to avoid returning an uncommitted publication. Only the explicit memory backend is allowed as a volatile test fixture. Database unique keys resolve publication/version and enabled-binding races; a conflict fails rather than replacing a publication. Supported targets are native MySQL 8.4 (src/main/resources/db/mysql/, with the optional RDBMS adapter's real-provider tests) and synchronous file H2; deployment qualification of those schemas, immutable guards and backup/restore remains a host gate (see docs/TODO.md). The PostgreSQL scripts under src/main/resources/db/postgresql/ are prospective only and are not a supported runtime promise. Hosts must also enforce unique (scriptId, sequenceNo) source revisions in the existing core schema. H2 tests exercise rollback, independent connections and concurrent publication.
Execution STARTED and terminal events commit on independent transactions, temporarily suspending the caller transaction. Missing audit storage prevents worker invocation; failure to record a terminal event prevents using its result. The pool needs capacity for the caller and independent audit connection. An unresolved STARTED event is an incomplete attempt, never success or exactly-once execution.
Wiring
ScriptingRuntime runtime = new ScriptingRuntime()
.withWorker(myQualifiedProvider) // omit => fail closed
.withCoordinator(new BuildCoordinator("coord-1", key32Bytes, "typescript-6.0.3-node-24"))
.withRequiredQualificationId("worker-qual-2026-11-03");
new ScriptingQBitProducer()
.withConfig(new ScriptingQBitConfig().withBackendName("rdbms").withRuntime(runtime))
.withTableEnricher(table -> table.withBackendDetails(...)) // RDBMS hosts
.produce(qInstance); // core script tables must exist first
ScriptHooks.install(table, HookEvent.PRE_INSERT, HookEvent.PRE_UPDATE, HookEvent.PRE_DELETE);
Hosts grant scripting.view|draft|check|test|publish|bind|execute through their normal permission mechanism. Every service Actor must match the active identified QQQ session and both must hold the action permission; external authentication must establish that session first. Source authoring and lifecycle operations also require access to the owning core Script project. Generic scripting and source views are filtered by that project; ordinary generic source totals/aggregates and joined source reads are denied because they cannot apply that row policy. Source metadata hides count/statistics capabilities; authorized bounded project history remains available. The signing key is host configuration and must come from the host's secret store, never from a script project or the worker.
Build
The optional middleware artifact registers native authenticated authoring routes and generated OpenAPI. Its source-first frontend integration uses the shared Script registry, with separate draft/build/context-test/publication actions, selected-record hook dry runs and explicit binding activation/state controls. The reviewed local core/RDBMS isolation artifact is now a required selective dependency; ordinary RC20 core is not sufficient. Native HTTP creation/save/build/test/publication succeeds in tests with a protocol-only worker. The frontend has a bounded TypeScript6 semantic worker, scoped declarations and explicit exact-commit GitHub imports. Live sandbox/host credentials and debugger qualification remain outstanding.
./mvnw verify
Wrapper 3.9.11 (.mvn/wrapper/maven-wrapper.properties, mvnw, mvnw.cmd are copied from qbit-printing).
Local verification
Wrapper install runs all verify gates: real QQQ memory/JDBC actions and protocol-only process fixtures, with Checkstyle including tests and JaCoCo minimum 85% instruction/65% branch coverage. Test counts are not recorded here; run the gate. No Java test executes hostile script text or qualifies the actual runtime/production database. Maven still reports the shared published parent-resolution warning; do not suppress it or edit the local cache.
Maven publication
Published POMs are self-contained: the estate's flatten-maven-plugin (ossrh mode) resolves ${revision} and the qqq.* version properties at build time, so a consumer needs no -Drevision/-D override and sees the exact pinned core. Set qqq.core.version, qqq.version (and for Record Files qqq.snapshot.version) once in the release build. The inherited qbit-build-parent POM still warns until its owner publishes a literal version.