# 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](docs/RELEASE-STATUS.md) and [known issues](docs/KNOWN-ISSUES.md). 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](docs/TODO.md) for exactly what is and is not done and [docs/V1-REMAINING.md](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. ```bash ./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 `` 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 `` for it in your private Maven settings. See [docs/RELEASE-STATUS.md](docs/RELEASE-STATUS.md) for the release steps and [CHANGELOG.md](CHANGELOG.md) for what each version contains.