Files
james.maes cef1476d85
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 539bec410f7926fec3ef7fabe51934a91e0248be] (push) Successful in 10s
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 9s
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-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (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-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (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) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Successful in 7s
maven / build (push) Failing after 14m12s
build / maven (push) Failing after 14m12s
Merge pull request 'feat: allow host-controlled admission for captured files' (#1) from feat/captured-photo-admission-policy into develop
2026-11-06 16:43:26 +00:00

7.9 KiB

QBit Record Files

First release candidate in preparation — 2026-11-06. 0.1.0-RC.1 will be the first artifact published from this repository (Gitea Maven registry, via the shared ci-workflows Maven workflow). It is a release candidate, not a production 1.0, and it is not published yet.

Start with current status and build prerequisites and known issues. These documents supersede older release-readiness statements below.

The root Maven module contains the QBit and its integration-test fixture classifier. Byte storage and attachment intake use the existing Collaboration attachment contracts. Generation and printing integrations live in their owning bridge modules, not here.

Retained, ordered, reviewed and audited files for QQQ records, built on the standard collaboration attachment path. Release candidate, not a production 1.0. See docs/TODO.md for exactly what is and is not done and docs/V1-REMAINING.md for the current v1 assessment.

What it does

  • Bytes are stored once, through AttachmentsService.upload; Record Files adds a retained-file row that references the attachment (checksum, size, media type, storage reference, provenance, lifecycle) and never writes collaboration tables or copies bytes.
  • A lifecycle adapter (RecordFilesAttachmentLifecycle) plugs into collaboration's attachment SPI so every standard upload to a configured parent table is registered and retained, ordinary deletion and parent cleanup cannot remove it, and link issuance is gated and audited. Collaboration never imports this module.
  • File collections hold files of one source record in an explicit order; user-defined categories and review statuses classify files. Service changes commit with their outcome events. User-created categories and statuses use standard QQQ create/edit processes with atomic before/after audit; generic direct writes are denied.
  • Events are append-only and carry actor, server time and correlation id. A signed-link event means "link authorized", not "downloaded".

Tables and permissions

Table Written by
record_files_category, record_files_review_status audited create/edit processes requiring record-files.configure
record_files_file, _collection, _collection_item, _event services only (direct writes denied)

Permissions: record-files.view, .manage (intake, collections), .review, .release (leave quarantine), .configure, .audit (read events). Every read and every service action is also limited by whether the caller can see the source record.

Services

RetainedFileService (idempotent intake, get, listForRecord), CollectionService, FileReviewService, PrintObservationService (observations only). Each has a no-arg constructor that resolves the installed configuration and an injectable constructor.

Install

Produce collaboration first with the adapter as its attachment lifecycle, then this QBit. The producer refuses to install otherwise. Pick an independent durable audit port suitable for your backend (RecordFilesQBitConfig.withAuditPort); the default TableAuditPort opens and commits its own real JDBC transaction. It currently qualifies file-backed H2 with WRITE_DELAY=0 and refuses memory/no-op transactions. Apply RecordFilesSchema.h2Script(config) using host migration tooling after the owner attachment schema. The prospective PostgreSQL script is not a supported runtime promise.

Not a 1.0

Resumable staging/session/finalization services and exact-version audited content reads exist; HTTP/client integration is active work. Production storage lock, malware scanning, completed-transfer observation and verified restore remain release gates. New files are quarantined until released because nothing here scans content. quarantineGeneratedFiles is an optional host setting: null (the default) inherits quarantineNewFiles; explicit true/false selects initial quarantine only for trusted Java intake with origin GENERATED. A host may choose false for renderer outputs so native Preview returns its retained original immediately. This policy applies to all trusted generated outputs, not only previews. Hosts must trust every in-process producer allowed to supply that origin; origin is provenance, not a cryptographic attestation of safe content.

quarantineCapturedFiles is a second optional host setting with the same shape (getQuarantineCapturedFiles() / withQuarantineCapturedFiles(Boolean); null, the default, inherits quarantineNewFiles). It applies only to intake with origin CAPTURE and a nonblank device identity, and GENERATED keeps its own policy. In the upload path that identity is the server-persisted capture identity that the host's CaptureIdentityProvider resolved and revalidated; the manifest cannot carry a device, actor, policy or CAPTURE label (strict JSON rejects them; CAMERA and GALLERY items map to CAPTURE, so a device that passes the provider can label an item either way). A desktop request with no provider identity, or any UPLOAD, SCANNER_IMPORT, IMPORT or attachment-panel file, keeps quarantineNewFiles. Direct Java RetainedFileService.intake callers are trusted host code, like GENERATED: they could pass CAPTURE plus a device id, so hosts must not expose that call to untrusted input.

Host prerequisites before setting it to false: a CaptureIdentityProvider that actually approves each device and actor (a device id alone proves nothing, and Record Files does not decide approval); an UploadConfiguration that bounds media types, sizes and quotas to what the host accepts (for example JPEG/PNG only); and parent-table visibility and permissions as before. Nothing scans content, so this is a host trust decision, not a malware or human-review result. Existing quarantined files are not released by configuration; release them through the normal release process. Compatibility: the setting is additive and defaults to null, so existing hosts and persisted data behave exactly as before.

Upload manifests cannot select GENERATED or change host policy. Uploaded, imported and standard attachment-panel originals, and captures without a device identity, retain quarantineNewFiles. Initial admission records FILE_REGISTERED with the chosen policy and lifecycle, without a release, scan or human-review claim. Hash failures still produce FAILED; permissions, current parent authorization and mandatory audit remain enforced. Changing configuration never releases existing quarantined versions, including replayed intakes. Production hosts must explicitly decide whether their generated-output producers justify this policy; the product default does not opt in.

Build

Requires JDK 21 and a qbit-collaboration release that contains the attachment lifecycle SPI (qbit-collaboration.version in pom.xml). Built against released QQQ 4.1.0-RC.21 (qqq.version, imported through qqq-bom-pom) and parent qbit-build-parent 2.1.1.

./mvnw verify

Maven publication

Published POMs are self-contained: the estate's flatten-maven-plugin (ossrh mode) resolves ${revision} and the version properties at build time, so a consumer needs no -Drevision override. The version comes from <revision> in pom.xml. The inherited qbit-build-parent POM still warns until its owner publishes a literal version. Release candidate, not GA.

Artifacts are published to the Gitea Maven registry, https://git.kof22.com/api/packages/QRun-IO/maven, by the shared KofTwentyTwo/ci-workflows Maven workflow (.gitea/workflows/build.yaml): develop deploys 0.1.0-SNAPSHOT, a push to release/0.1 publishes the next 0.1.0-RC.N and tags it, and a v0.1.0 tag publishes the stable release. The registry requires sign-in for reads; configure a <server> for it in your private Maven settings. See docs/RELEASE-STATUS.md for the release steps and CHANGELOG.md for what each version contains.