Files
qbit-printing/docs/RETAINED-PRINT-FACADE.md
james.maesandCodex GPT-6 08ee0efa3d feat: authorize operator printing with exact retained acceptance
Add current-user printer media catalog, stable request recovery and atomic retained-source acceptance. Recheck native authority through admission and commit, bind history to the accepted printer record, and gate selected media on the current client capability.

Co-Authored-By: Codex GPT-6 <noreply@openai.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-01 13:04:03 -06:00

7.1 KiB

Retained print acceptance (additive candidate)

This optional Java integration preserves the existing native generation print process and the existing printing queue. It adds no device wire fields or REST routes. It is disabled until the host supplies PrintingQBitConfig.withRetainedSourceAuthority. Current printing_operator (or administrator) authority and native tenant/device/printer record checks remain mandatory. Submission also requires the shared printingSubmit process permission. This grants no device management or generic job-history access.

Public contract

RetainedPrintFacade.Request(requestId, sourceLocator, printerRecordId, title, optionsJson) contains no actor or asserted source binding. Request IDs use [A-Za-z0-9._:-]{1,128}, including owner namespaces such as echo:<scanId>. submit derives the signed-in native actor and returns an immutable Acceptance; it includes the exact source, job/event identities, tenant, initiating actor, destination, title and normalized options JSON, including any mediaProfile ID/revision. find authorizes the same complete request and returns that acceptance, null for observed absence, or a conflict for an unresolved upload reservation. Reuse the same request identity after a lost response. Never convert an unavailable lookup into absence or create another intent automatically.

The host-configured RetainedPrintSourceAuthority.resolve(locator, requestId) must authorize the current native actor, source association and currently eligible immutable version. It returns SourceReference(owner, opaqueReference, sha256, sizeBytes, mediaType). opaqueReference is at most 4000 Java characters, stable and canonical under that owner's rules; printing does not parse owner-specific fields. The optional Record Files adapter must encode its complete version/source/generation-request/initiating-actor/tenant tuple and compare generation's committed receipt. Accepted MIME types are application/pdf and application/vnd.zebra-zpl; the current configured PDF/ZPL limits apply before the byte read, within the existing integer-length storage ceiling. read(reference, requestId) must enforce the same current permissions and return only those exact bytes. Hash/length verification remains in printing's existing submission path. Do not supply arbitrary source authorities per request or reconstruct a user session.

Source admission remains the owner's policy. An explicitly configured published template may qualify generated output without a per-label release dialog; external-upload quarantine is unchanged. Printing performs no automatic release and creates no human-release audit. resolve must be read-only and must not invoke a generation reconciliation operation that can generate or dispatch work.

Source/destination checks surround owner callbacks and precede acceptance. Submission refuses an ambient QQQ transaction: queue acceptance is independently committed and cannot be rolled back by a caller's transaction. Submission also enforces the native job-table credential and channel mutation ceilings; lookup uses the corresponding read ceilings without granting generic table access. find calls no byte reader, submission or event writer, and its option normalization cannot initialize settings. It reauthorizes the source and destination records, then compares the exact accepted intent; a later exposure, media inventory or dispatch-limit change does not reinterpret that historical intent. A later permission loss may deny returning a receipt whose job already committed; it never authorizes a replacement job.

Persistence and replay

Existing printing_job gains nullable retained_actor_id, retained_binding_json, and accepted_event_id. Existing printing_event gains nullable event_origin. initialize applies additive upgrades; old jobs/events remain unbound/unclassified. None of these internal fields are added to the device JOB_COLUMNS DTO or native table metadata.

Submission and exact lookup both use the existing tenant admission lock, so an in-flight inline acceptance cannot be mistaken for absence. The unique (tenant_id, idempotency_key) serialize submissions. Retained keys use retained: plus the request ID. Ordinary/bound collisions refuse. Complete normalized intent includes actor, owner reference, content hash/length/media, destination, title, options and tenant. The existing storage reservation/orphan path is reused; it may commit an upload reservation before writing object storage. The final job, retained binding, and server acceptance event commit together. Failed acceptance-event insertion rolls back the job. Rollback or orphan-cleanup failures are suppressed onto the original failure rather than replacing it. Existing initialization widens legacy 300-character titles to 1024; the bound title preserves exact whitespace and Unicode and is compared on replay. Acceptance-event JSON has type=ACCEPTED, origin=SERVER_ACCEPTANCE, status=QUEUED; it does not claim device receipt or physical output.

This is atomic acceptance binding inside printing, not a distributed transaction over the retained owner's authorization database. Deployment still owns durable database/storage settings, bounded provider calls and current session policy.

Committed observations

observation(requestId, eventId) reads only an exact committed event joined to the accepted job. New ordinary device events are stamped DEVICE_OBSERVATION; pre-upgrade unclassified events and server acceptance events cannot be returned as device evidence. The current native operator must have current device/printer row authority and satisfy the job credential/channel read ceiling. Before returning any source metadata, printing calls the configured owner's authorizeObservation(source, requestId) under the current reporter session, then rechecks the current ceiling and destination authority. Its default implementation refuses; adapters must explicitly implement their current source-observation policy. This callback does not grant content access or impersonate the initiating actor. This is an operator-authorized observation lookup, not device enrollment or permission to forge reporter identity. The optional RF authority adapter must preserve initiating actor separately from current reporting actor and map only supported stored states. Neither SPOOLED nor SPOOLER_COMPLETED proves physical output.

Verification scope

The tests use real H2 transactions and native QQQ row authorization with synthetic printers. They never contact a device or emit physical output. Coverage includes commit-then-throw acknowledgment loss, concurrent exact replay, held-admission-lock lookup, mutation ceilings, owner observation refusal, callback revocation, exact content hash/length, configured document bounds, title upgrade/replay, read-only historical lookup and original-error preservation when rollback fails. Qualification of this adopted source is recorded in the session notes; the older peer candidate's test result is not evidence for this revision. Cross-QBit integration remains a separate gate. No release, install, shared-cache substitution or runtime migration has occurred.