Files
ee782b7d6e
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Failing after 7s
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Failing after 5s
QRun-IO/ci-policy: native / native-result (push) Successful in 6s
chore: preserve unfinished document generation source and status
Checkpoint implementation at user-requested stop; document known failures and incomplete release qualification.

Co-Authored-By: Claude <noreply@anthropic.com>

Co-Authored-By: GPT-6 Codex <noreply@openai.com>
2026-11-04 10:45:09 -06:00

7.5 KiB
Raw Permalink Blame History

Isolated renderer bridge

This source requires qqq-barcodes-9 in worker responses and its bundled barcode reference, pairing with the renderer's finite physical-fit profile and scanner-mode checks. Typed worker errors are mapped only to the foundation's closed GenerationPorts.RenderException codes after exact request/protocol/version envelope validation; unknown or malformed errors stay sanitized. This is a development snapshot: no renderer image or runtime has been qualified for production, and earlier catalog-specific evidence below remains historical. Current verification (private source-matched reactor, 2026-11-02): bridge 52 tests, renderer 167 tests, zero failures/errors/skips, Checkstyle 0; see docs/reviews/code-standards-followup-20261102/ in qbit-documents for exact commands.

Three transports share the same bridge, asset, wire and output policy:

  • Production (default): new IsolatedRendererBridge(configuration, assetReader) builds the disposable runsc Docker boundary with a pinned repository digest. It fails closed when the daemon, runtime or image cannot be qualified; nothing falls back to another transport.
  • Explicit trusted-local cold transport (non-production): TrustedLocalDockerExchange is a separately constructed opt-in for a local development machine whose daemon has no gVisor (for example Docker Desktop, runc). TrustedLocalDockerExchange.configuration(...) takes the real local image ID and the literal qualification TRUSTED_LOCAL_DEVELOPMENT_UNQUALIFIED; any other configuration is refused. It reuses the sandbox controls (one request per fresh container, no network, read-only root, no mounts, dropped capabilities, limits, log driver none, exact image ID and actual containment verified, exact container removed before output). Use new IsolatedRendererBridge(exchange.limits(), assetReader, exchange).
  • Optional warm pool (non-production, local lab): WarmRendererPool keeps a few warmed single-use guests; see WARM-POOL.md. It requires the explicit trustedLabRunc setting and is never selected automatically.

A matching Windows-friendly journal profile used by the two local transports skips only the POSIX-permission and directory-fsync requirements; production requires a private POSIX state directory. Local transports are labeled non-production and do not establish sandbox qualification.

Optional com.kingsrook.qbits:qbit-document-generation-renderer:0.1.0-SNAPSHOT implements GenerationPorts.IsolatedRenderer. It depends on the generation API and Jackson, not scripting, retained-file internals, or the renderer implementation. Unused legacy PDFBox/OpenHTML engines inherited through QQQ core are excluded from this bridge dependency graph; the application remains responsible for its own core dependency baseline. Register new IsolatedRendererBridge(configuration, authorizedAssetReader) using DocumentGenerationQBitConfig.withRenderer(...).

AssetReader.readAuthorized(actorId, asset, maximumBytes) is a trusted application adapter: enforce current QQQ session/actor, tenant, parent-record access, immutable retained-version identity and available/quarantine state, then perform a bounded read. The bridge verifies the pinned SHA-256, length, supported media and offline manifest. A raw filesystem or object-store implementation without these checks is not an authorized adapter. Font assets carry validated CSS family, weight100–900 and normal/italic style; old four-field references retain family=name and400/normal defaults. No asset URLs or application tokens enter the worker.

barcodeReference() reads the bounded renderer-owned catalog packaged at META-INF/documentation/BARCODES.md. It does not start a worker or grant runtime qualification. Native source authoring can discover supported symbologies/options without duplicating the encoder catalog in the foundation.

Production has no in-process rendering fallback. The Java bridge starts an absolute, operator-owned Docker executable with a fixed argument vector, cleared environment, local default daemon, verified Linux/runsc availability, digest-pinned image and matching image ID. It creates a disposable non-root container with no network, host mounts, capabilities or persistent writes; 512 MiB memory/no swap, one CPU quota, 64 PIDs, 15-second per-process CPU rlimit, 64 descriptors, 64 MiB file limit and 64 MiB tmpfs. External render deadline is at most 30 seconds. Cgroup CPU quota limits rate; the wall deadline also limits aggregate work, while RLIMIT_CPU is per-process. Control operations have independent 10-second deadlines. Actual create metadata is checked before start.

Admission is shared across adapter instances in the JVM by canonical state directory and owner, with a configured maximum (default one, hard maximum sixteen) and immediate rejection before assets or large buffers. A cross-process file lock also caps durable active leases before container creation; failed cleanup retains capacity until recovery, so repeated cleanup failures cannot accumulate unlimited containers. Deployments sharing a state directory must share the same owner and capacity configuration. Independent state directories/process allocations still require a host-wide capacity plan.

Input is one bounded JSON line (16 MiB). Output is bounded before buffering, strict UTF-8 JSON, duplicate/trailing rejection, matching request/protocol/toolchain/catalog identity, exact response fields, media, byte count and independently recomputed digest. ZPL receives a separate host parser that permits only the renderer's transient label/text/box/uncompressed-raster grammar; encoded field data cannot become commands. PDF receives only header/footer and metadata/size checks: the QQQ JVM does not parse hostile PDF internals. Page count and PDF content safety require the qualified isolated renderer/inspection and retained-file policy; a compromised worker can lie about these. The host check is not a PDF sanitizer.

Every attempt is durably journaled before Docker create. Success is withheld until the exact owned container has been removed and cleanup evidence fsynced. Recovery uses immutable container ID plus exact name, owner, attempt and image identity, never wildcard deletion. Application process death leaves a two-minute lease for the independently scheduled RendererReaper. Unknown daemon errors retain the lease; operational evidence contains request/attempt IDs and outcomes, not source/context. These files are operational lifecycle evidence, not a substitute for Record Files generation/download/print audits.

The deployment must provide a private POSIX state directory, atomic rename and directory fsync. Windows is a fixture-test platform and, through the explicit non-production local profiles above, a development host only. Configuration and qualification evidence are trusted operator inputs, never template fields. Merely choosing a matching string or naming a runtime runsc is not qualification. No image/runtime has been qualified by this implementation session. See deployment instructions.

Run ./mvnw verify from this directory. Tests use an in-memory Docker fixture and fixed harmless Java child programs. They do not run a renderer image or hostile code.

Catalog 5 development pairing: the current bridge requires qqq-barcodes-5 in both worker responses and its bundled barcode reference. It refuses older catalog4 responses; sealed catalog4 bridge/image artifacts remain unchanged. Deploy only a matching newly qualified bridge/image pair. This change adds no host renderer or new protocol port. New combined candidate verification is pending.