Files
qbit-record-files/docs/PRINT-OBSERVATIONS.md
162af9a7b8
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: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Failing after 6s
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 / not-enrolled (push) Failing after 6s
QRun-IO/ci-policy: native / native-result (push) Successful in 6s
chore: preserve paused QBit work and document remaining issues
Save the current implementation and README, status, and known-issue documentation at the user-requested stop. No completed release or deployment is claimed.

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

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

12 KiB
Raw Permalink Blame History

Trusted retained-file printing observations

Record Files owns this narrow contract. No dependency on qbit-printing is added, no printing queue/event table is read directly, and no file bytes, printer queue or second file registry is owned here. Existing manual PrintObservationService retains its existing permission/source-visibility behavior and PRINT_STATE_OBSERVED event type. Trusted evidence uses the distinct VERIFIED_PRINT_STATE_OBSERVED type; consumers must never interpret a manual free-text event as verified proof.

Public contract

Host configuration installs a trusted Java QCodeReference through RecordFilesQBitConfig.withPrintObservationAuthority. Inline/script code references and caller-selected providers are refused. There is no default provider and no request DTO for proof fields.

  • PrintObservationAuthority.verify(String printIntentId, String eventId) returns VerifiedPrintObservation or throws QException/a typed PrintObservationException.
  • TrustedPrintObservationService.record(String printIntentId, String eventId) returns true only after independently durable proof has been written and read back; false means an identical full receipt was already stored. A timeout or retryable failure does not prove no event committed.
  • VerifiedPrintObservation is sealed. Delivery contains Identity, DeliveryState and nullable spoolerJobId. PhysicalOutput contains Identity plus required evidenceKind/evidenceId. A physical receipt is a separate result type, never a flag on delivery state.
  • Identity includes authorityId, tenantId, printIntentId, exact accepted RetainedContentReference, jobId, eventId, authenticated reporterDeviceId, printerId and committed server recordedAt.
  • DeliveryState is QUEUED, CLAIMED, RECEIVED, SUBMITTING, SPOOLED, SPOOLER_COMPLETED, FAILED or OUTCOME_UNKNOWN. Neither SPOOLED nor SPOOLER_COMPLETED establishes physical output.

Identifiers and evidence fields are bounded to 128 characters; mediaType allows the FILE schema limit of 255 characters; source actor/record identity have their existing larger limits. Accepted requestId must match the existing reference correlation grammar: ASCII letters/digits/dot/underscore/colon/hyphen, 1–64 characters. The complete encoded receipt envelope must be at most 4000 UTF-8 bytes. Malformed surrogate sequences are rejected before UTF-8 hashing; valid Unicode is retained. Oversize proof is INVALID_EVIDENCE and fails before append; AuditTrail's text truncation is never relied upon.

Printing adapter obligations

Before making a retained-source job dispatchable, the printing adapter must durably bind the configured authority and tenant, immutable printIntentId, exact accepted source reference including initiatingActorId/requestId, printing job, authenticated reporting device and printer destination. It must compare the accepted document SHA-256 and byte length. A standalone job without this accepted binding cannot produce a retained observation proof.

verify must fetch a committed tenant-scoped event receipt from the printing owner and compare it against that durable accepted binding. The owner receipt must identify the job/event/device/printer (reporterDeviceId is the authenticated device on the original committed event, not the current retry caller), stored protocol state, spooler job identity when supplied, exact accepted document hash/length and immutable server event time, which must remain identical across retries. A job ID, current job snapshot, browser-submitted observation, unauthenticated device statement or arbitrary JSON is insufficient. Never silently retarget the accepted destination/reference.

The adapter must independently authorize the current authenticated reporter for the configured authority/tenant/device. RetainedContentReference has no tenant field, so Record Files cannot infer this authorization from its file row. Tenant/authority strings are evidence supplied only by the trusted adapter; they are never user-selected authority. The adapter must not fabricate an initiating-user principal or reuse a stored browser session. For physical receipts it must verify independent retained confirmation evidence under a documented host policy and return its stable kind/id.

This grants only observation recording. Enqueue, source content release, pending dispatch and reprint still require their independent current-original-actor/source authorization, including permission revocation. Revocation after accepted delivery does not erase a verified spool observation and cannot recall paper.

Recording authorization and retained identity

The trusted receipt supports tenant and printer identifiers up to 200 characters to preserve existing PrintingService identities without truncation; the total 4000-byte receipt limit still applies. The adapter maps an absent or exactly empty committed spoolerJobId to null, keeps the original committed payload in printing's audit, and preserves nonempty identifiers exactly (no trimming or inference from the latest job snapshot). Missing committed event rows cannot be replaced with a synthesized observation for QUEUED, CLAIMED or cancellation states.

The current reporter must hold record-files.observe-print and be an identified QQQ user. EVENT table credential scope, client channel deny and write ceilings apply before provider invocation. No record-files.view/manage, source read/write permission or initiating-user session is substituted. QInstance, QSession and current reporter are rechecked after the adapter returns; authorization is checked again before persistence.

An external ambient transaction is refused as INVALID_REQUEST before authority invocation. Non-Java/inline or incompatible configured authority classes are UNSUPPORTED_CONFIGURATION. After authoritative verification, the service locks the retained file and compares every immutable raw FILE identity field: version-row fileId, logical fileKey, versionNumber, attachmentId, source table/record, SHA-256, size and media type. It never substitutes the latest version. initiatingActorId/requestId are separately authority-attested accepted-intent fields, not fields independently verified against FILE; authorityId/tenantId are likewise checked by the trusted adapter against its configured binding. Current source permissions or lifecycle changes cannot suppress a historical accepted observation; proof of the accepted released source belongs to the printing binding. This operation cannot release/quarantine/change/read the file.

Audit, deduplication and failure

The event key is SHA-256 of fixed-order JSON authorityId/tenantId/eventId. The audit detail retains the complete fixed-order JSON proof and its SHA-256, including type, all source fields, initiating actor/request, stored event time, device/destination/state and physical evidence where applicable. Existing receipt comparison uses the full canonical JSON plus persisted source/job/request/type associations. A differing file, intent, timestamp, evidence or any other component is CONFLICT. Current reporting actor is excluded from the receipt fingerprint: a legitimate duplicate retry by another authorized reporter preserves the first actual actorUserId. InitiatingActorId remains separately in receipt detail.

A FILE row lock serializes ordinary same-file replay. The database event-key unique constraint arbitrates cross-file collisions; on a failed insert, any committed matching key is reloaded and compared. Independent audit commit survives a caller rollback. Success requires committed proof readback; an audit port that merely returns cannot claim acceptance. No exactly-once delivery or physical printing guarantee is implied.

PrintObservationException exposes stable reasons: INVALID_REQUEST, ACCESS_DENIED, MISSING_EVIDENCE, INVALID_EVIDENCE, CONFLICT, UNSUPPORTED_CONFIGURATION and DEPENDENCY_UNAVAILABLE. Only DEPENDENCY_UNAVAILABLE is retryable. Missing configuration, dependency outage or failed durable audit is unavailable; absent committed proof/file is missing evidence; mismatched proof is conflict. Authority implementations must throw QPermissionDeniedException for reporter/tenant denial, QNotFoundException for missing committed evidence, or the explicit typed reason for conflicts/invalid proof. QBadRequestException becomes INVALID_EVIDENCE; other QException/runtime failures conservatively become retryable unavailability. Verification I/O belongs in verify with bounded timeouts, not the configured Java constructor. Public messages are sanitized; trusted diagnostic causes must not be serialized to clients. Consumers should retry unchanged stable IDs and reconcile the same event, never mint a replacement event to bypass conflict.

Verification and status

Main/test compilation and style checks pass. Eleven real retained-file/H2 fixtures cover revoked source access and separate reporting principals, duplicate preservation, cross-file/time/type conflict, raw identity, physical proof, EVENT ceilings before verification, missing/failed authority and audit, oversized/malformed Unicode receipt refusal, later quarantine, instance/session replacement, ambient/wrong configuration and a synchronized two-file event-key race. The initial six print fixtures passed in parent elevated run82013 with22 archive/reader/dispatch regressions. Updated eleven-test snapshot is awaiting parent verification; sandbox file-backed H2 reopens read-only. No concrete printing adapter, committed printing eventReceipt API, live device qualification or physical printing is claimed here. Printing owns those pieces and their tests.

Native reporting process — 2026-10-31

The production producer now registers record_files_observe_print, labeled Record verified print observation, in the Record Files app's Verified print observations section. Standard clients render an EDIT_FORM with exactly intentId (accepted print intent ID) and eventId (committed device event ID), both required strings bounded to 128 characters, followed by a VIEW_FORM result. No selected file record, custom frontend, new REST endpoint, caller receipt/state, byte read or initiating-user session is required.

Discovery checks the current authenticated reporter's record-files.observe-print permission, the existing EVENT credential/client-channel mutation ceiling and a configured Java printObservationAuthority (no inline code). An unconfigured authority leaves this process unavailable. Observation-only reporters can discover the app/action without acquiring file-view/configuration rights; existing table permissions remain unchanged. Configuration inspection does not instantiate or call the authority.

Submission rechecks the current reporting permission/ceiling and calls only TrustedPrintObservationService.record(intentId, eventId). That existing service resolves configured committed evidence, compares exact immutable owner identity, and independently durably audits/deduplicates the full receipt. Extra caller fields cannot supply evidence or state. recorded=true means a new verified audit observation was committed; false means the identical verified receipt already existed. Missing/conflicting/unavailable evidence and audit failures propagate without a success result. Neither result means physical output unless the separately configured authority supplies the existing distinct physical-proof type; a spool status cannot become that proof through this form.

Parent gate 57042 passed all 27 selected tests with Checkstyle: six new native metadata/backend-step tests, 12 trusted-observation service tests and nine producer/schema tests. The new cases cover configuration/discovery, observation-only access, distinct-reporter replay, caller-state exclusion, current permission/credential/channel revocation, invalid IDs, missing/unavailable evidence, and durable audit failure/retry using real file-H2 owner/audit state and a trusted synthetic authority. This verifies the native reporting entrypoint, not physical printing or a full production deployment. Existing manual observation/service contracts remain unchanged.