Files

Optional Record Files integration

This standalone module connects the scanner's verified device/session authority to Record Files' retained upload API. Neither base qbit depends on the other. A host that installs both qbits may add this module and configure new QCodeReference(MobileCaptureIdentityProvider.class) as the UploadConfiguration capture identity provider. Install MobileScannersRouteProvider on the same native QQQ server. Use the registered Record Files API; this module creates no alternate upload endpoint. Call MobileRecordFilesContract.register(instance) to opt into the independently versioned mobile-record-files 1.0.0 widget vocabulary and schemas.

The provider reads only the server-verified request context or its retained VerifiedDeviceSession. Each capture/revalidation checks current actor, private authentication binding, public mobile session, durable termination and device approval. A public identity DTO contains only provider key, actor, device and public session IDs. It is never authorization proof. An authenticated, unbound desktop session may return no device identity; an Android request or any already-bound mobile session without verified device proof is refused.

Semantic native widget

Host widget renderers may return RecordFilesCaptureWidgetData with an explicit, server-selected parent record. The native extension vocabulary is:

{
  "type": "composite",
  "blockType": "COMPOSITE",
  "subBlocks": [{
    "blockId": "recordFilesCapture",
    "blockType": "RECORD_FILES_CAPTURE",
    "values": {
      "parent": {"tableName": "document", "recordId": "1"},
      "title": "Record photos",
      "instructions": "Capture or choose a photo."
    }
  }]
}

Use an ordinary QQQ frontend WIDGET component and widgetName; clients must not infer parent IDs from arbitrary process values or field names. Parent IDs remain opaque strings. title and instructions are optional display text. Clients ignore additive unknown properties, but refuse missing/invalid parent references. Photo controls require compatible record-files extension discovery and successful record-specific capabilities with canUpload=true. Every upload operation still independently authorizes the parent. Metadata visibility never grants permission.

Before showing capture actions, clients verify mobile-record-files discovery and GET /qqq/v1/qbit/mobile-record-files/contract, sending no credentials. Its static response is {"version":"1.0.0","blockType":"RECORD_FILES_CAPTURE","widgetType":"composite"}; OpenAPI operation ID is mobileRecordFilesContract. The descriptor spec registers typed parent, values, block and widget schemas. The current core registry rejects extensions without endpoint specs and provides no schema-only hook, so this small descriptor supplies versioned vocabulary without changing any upload endpoint or the frozen mobile-scanners 1.0 contract. A future schema-only hook can improve this.

This widget is self-contained: successful upload neither advances the surrounding process nor mutates its fields. “Saved” requires a matching COMPLETE receipt; retained files normally remain QUARANTINED. They are not thereby released for download. Camera/gallery uploads use the exact immutable manifest, binary chunk, hash, replay and finalize contract in the frozen Record Files documentation.

/version advertises name, version and path prefix, without operation IDs. OpenAPI identifiers are getQbitRecordFilesCapabilities, postQbitRecordFilesUploads, getQbitRecordFilesUploadsByUploadId, putQbitRecordFilesUploadsByUploadIdItemsByItemIdChunksByChunkIndex, postQbitRecordFilesUploadsByUploadIdFinalize and postQbitRecordFilesUploadsByUploadIdCancel. Native invoker/credential-scope IDs instead use METHOD /qbit/record-files/...; capability gating must distinguish them.

Publication

This module is a module of the repository's reactor (root pom.xml, artifact qbit-mobile-scanners-reactor), next to base (qbit-mobile-scanners). It inherits the root's single <revision> and the same qbit-build-parent 2.1.0, Apache-2.0 metadata and canonical Gitea URLs. One root mvn deploy (the shared Gitea Actions maven workflow) tests and publishes the reactor POM, qbit-mobile-scanners and qbit-mobile-scanners-record-files at one version to the Gitea Maven registry. Its dependencies are immutable releases from that registry: qbit-record-files 0.1.0-RC.1 and qbit-collaboration 1.1.0-RC.1, with QQQ 4.1.0-RC.22 from the root. Build only this module and the base with ./mvnw -pl integrations/record-files -am verify.

Reproducible isolated verification (legacy local harness)

The isolated scripts below date from the pre-publication frozen-candidate adoption on a Windows workstation and are not part of the release path; they predate the reactor layout and are kept as evidence tooling only.

From this directory, run ./Verify-Isolated.ps1. It verifies every checksum in the retained bundle, seeds a private repository from the local Maven cache, copies and builds the scanner base outside its normal target, installs frozen candidates only into that private repository, then runs this module's tests and writes the full runtime classpath and SHA-256 manifest under target/. A later run can use -SkipSeed; -SkipBaseBuild is only for iterating module-only changes. Full runs recreate a validated scratch source tree and clean both builds. Module output lives in target/module, separate from the sibling private repository and wrapper. Test-IsolatedBuildPaths.ps1 verifies deleted Java/resource/class removal, repeat synchronization, sibling preservation and refusal of unsafe/reparse paths. Module-only runs explicitly hash the prior scanner jar and do not overlay new source. The accepted 0515 candidate defaults to -RepositoryName isolated-m2-0515 to preserve the earlier cache. See the 0515 adoption record for exact hashes, Jobs migration requirements and the consumer verification status.

The current verified bundle is retained-contract-20261031-0515 from the documents owner. All 33 root entries and seven source entries are verified. The script uses the owner's exact resolved collaboration POM, preserving its original bytes. It also installs the exact flattened parent POM and matched core/RDBMS pair. Earlier 0238/0352 evidence and caches remain historical. This candidate uses the frozen isolated core/RDBMS, RC.20 middleware, and H2 2.2.224. The fixture found that H2 2.4.240 fails the shipped scanner CHECK (status IN (...)) constraint after the DDL connection closes (ConditionInConstantSet references its closed session). A complete runtime manifest is necessary; matching only a core version label is insufficient evidence of compatibility.

Host authentication order: after validating the login (in both createSession and session revalidation such as isSessionValid) and assigning the host's own roles and keys, call CollaborationSecurity.applyToSession(QInstance, QSession) (com.kingsrook.qbits.collaboration). This must happen before the first Mobile capture; the scanner compares security keys as sets, so repeating the call is safe, but adding or changing keys after capture is treated as an authority change and is rejected. There is no late wrapper mutation or exemption.

The HTTP fixture uses real QQQ table/password sessions, native enrollment and record-screen approval processes, native Record Files capabilities/create/chunks/ finalize, disk-backed H2 metadata and the standard attachment owner with a file-backed test storage adapter. It does not qualify a cloud object provider, production malware scanning, production authentication or audit export. Its upstream audit fixture is synthetic. Never deploy these test adapters/accounts.

After verification, ./Start-ConsumerFixture.ps1 prepares an owner-only Windows directory and starts a timed, isolated fixture. Its returned object contains PID and private credential-file path, never the password. Read the file locally for the ephemeral base URL and synthetic account; enroll/approve through ordinary native APIs. mobileRecordPhotos targets document/1. Stop only that PID or allow its lifetime to expire. The launcher and native synthetic camera/gallery flow have been exercised on TC22; docs/SESSION-STATE.md records exact verified scope. Private ACLs apply only to a newly created run directory, preserving its parent's ACL. The JVM is bounded to two visible processors and a 512 MiB heap. Startup waits a bounded time for the private readiness file instead of returning immediately.

Remaining release boundary

The adopted SPI executes operations through the scanner's executeAuthorized guard, including expected absence for genuine desktop callers. Eighteen passing integration/contract tests cover current identity, parent grants, actual retained registration/association/receipt commits, concurrent revoke/end and rollback. Current 0515 evidence is retained under evidence/20261031-124632Z; earlier 0352 guard evidence remains under evidence/20261031-094704Z. The guard is scoped to one JVM; clustered operation requires a separately qualified transactional strategy. Immutable published dependency adoption, optional-module hosted CI and production storage/content-processing qualification remain open release gates. Test adapters and passing synthetic intake do not qualify a production storage deployment.