# Expected generation intent and read-only print acceptance `DocumentGenerationService.Request` accepts an optional `Expected(templateId, revisionId, Stock)` tuple. Existing five-argument construction keeps its prior behavior: accept the current publication on first use and reuse the accepted publication for the same actor/request key. An explicit expected tuple compares every dimension, margin and DPI against the immutable published revision. Admission shares the template row lock used by publication. The current publication comparison and generation request insertion commit together before context execution, render, intake or print. The lock needs template read ceilings; it does not grant template editing rights. Existing publication/edit paths retain their mutation ceilings. The accepted request's revision reference and immutable revision stock snapshot retain the tuple without another registry or schema. `Result.accepted` returns that exact tuple. `Result.intentSha256` is a versioned, length-framed digest of the accepted actor/key/kind/source/template/revision/stock/ format/destination identity. It contains no source payload. Supplying or omitting an equivalent expected tuple does not create a second accepted intent. A later publication does not alter completed replay; a new key carrying the old tuple is refused. Preview keeps its separate draft-fingerprint contract and refuses an expected published tuple. `lookupAcceptedPrint(Request)` requires an explicit tuple and destination, the current actor's view/print capabilities, native source/retained-owner read authority, and credential/channel read ceilings. It queries the actor's existing generation request and passes its original operation ID to the optional printing adapter. It never calls generate, reconcile, submit, advances state, or writes a generation audit event. An absent request or absent accepted print returns empty; unavailable or uncertain provider lookup throws. A found receipt can therefore report the Printing owner's committed acceptance while Generation remains `INCOMPLETE` after a lost response. Lookup does not repair that state. The default `PrintDispatch.find` refuses unsupported lookup. The optional Printing bridge uses only `RetainedPrintFacade.find`, validates its exact retained source, actor, destination and acceptance event, and does not read output bytes. No Printing or Record Files types enter the Generation foundation. Server acceptance is not a device observation or proof of physical output. The coordinated owner gate passed 61 foundation tests (34 service, five expected intent, 22 file-H2 queue tests) and ten Printing bridge tests, with zero failures, errors and Checkstyle violations. Evidence: `target/expected-print-intent-verification.log`, terminal session 83722. This is source/package verification, not a published artifact, deployment or physical-print qualification. ## Selected media and typed refusals: subsequent candidate The subsequent source candidate adds nullable `GenerationPorts.MediaProfile(id, revision)` to Request and PrintRequest. Existing five- and six-argument Request constructors, five-argument PrintRequest and four-/six-argument Result constructors remain available. Profile IDs use `[A-Za-z0-9._-]{1,128}` and revisions use exact lowercase SHA-256 hex, matching the Printing owner's public media contract. Neither destination nor absent media implies a default choice. Apply the shipped `20261101-print-media-profile-h2.sql` or MySQL counterpart before adopting the new metadata. Both additions on `document_generation_request` are nullable. Old rows keep null profile identity and their prior intent digest. A nonnull choice adds a separately framed media domain/id/revision to the digest; changing, adding or omitting that choice cannot reuse an accepted key. Native request status exposes both columns. Batch/queue APIs have not silently gained media options. Existing reprints retain their prior no-profile contract. `PrintDispatch.supportsMediaProfile()` defaults to false. The current legacy Printing bridge still returns that default: its old owner stores arbitrary options but does not enforce current media identity/revision. Generation refuses such a selection before creating an intent, and direct bridge submit/find also refuse before contacting that owner. The bridge's bounded `options.mediaProfile` encoding is staged for the corrected owner cohort, **not enabled by a claim that arbitrary JSON storage establishes media support**. A host may adopt a reviewed owner enforcement explicitly with `PrintingDispatch.withMediaCapability(...)` (a copy; the default instance stays denied; evaluated per request for submit and lookup). Tests against the old facade prove only that the bounded `options.mediaProfile` binding reaches submit and that revoking the capability denies both paths. A `true` callback is **not** proof the owner enforces current media: the Printing 2.2 owner must expose a typed enforcement capability that is wired and verified, with the actual pre-spool profile/stock check, before any Echo production claim. Actual profile/stock compatibility and pre-spool enforcement remain Printing/scanner-owner qualification requirements. The foundation now defines twelve closed `FailureCode` values and a `RenderException(FailureCode)` with only the enum name as its public message. A verified renderer refusal is stored in the existing `errorCode` column and thrown again on incomplete replay without rendering again. Current native process failure/status surfaces therefore expose the fixed code, not a payload or encoder message. Result adds nullable `failureCode` and `mediaProfile`; generic operational failures keep `GENERATION_INCOMPLETE`. No new error schema or parallel web API is introduced. The paired renderer bridge verifies the complete error envelope before constructing this exception; unknown/malformed responses remain generic refusals. The coherent media/typed-error test gate passed 135 tests: 94 foundation (34 service, ten expected-intent, 28 process, 22 file-H2 queue), 30 renderer bridge and eleven Printing bridge tests. All reported zero failures, errors, skips and Checkstyle violations. Evidence: `target/expected-media-typed-error-verification.log`, terminal session 70653. This was the Maven test phase using freshly compiled current classes; it was not a packaging or native-image gate. The earlier 61+10 result above applies to its original source snapshot. The actual H2 migration test removes both media columns from an accepted legacy null-profile request, applies the shipped additive SQL, and verifies unchanged receipt/digest, one request/submission and no additional audit events. The MySQL DDL was inspected but was not executed in this gate. The selected-media positive test uses an explicitly capable fixture; the actual legacy Printing bridge is separately tested to refuse selected media. No real-owner selected-media support, new renderer image, device observation or physical output is claimed by this gate. ## Render target: 2026-11-12 `DocumentGenerationService.Request` gains a trailing `Integer targetDpi`: the resolution a label is rendered at. It is absent (the stock's own resolution, as before) or 203, 300 or 600; any other value throws `IllegalArgumentException("A render target is 203, 300 or 600 dpi.")` when the request is built. Every earlier constructor stays and means no target. `GenerationPorts.RenderRequest.targetDpi` carries it to the renderer. - **Persistence.** One nullable `target_dpi` column on `document_generation_request`. A host applies exactly one statement before adopting this version, from the shipped script for its database: `20261112-render-target-dpi-h2.sql`, `20261112-render-target-dpi-mysql.sql` or `20261112-render-target-dpi-postgres.sql` (`ALTER TABLE document_generation_request ADD COLUMN target_dpi INTEGER NULL`). Earlier rows stay null and read as no target. Nothing else in the schema changes; the bootstrap scripts of `DocumentGenerationSchema` include the column for a new database. - **Idempotent retry and lookup.** The stored target is compared with the request's in the same check as destination, media and copies, so the same request key with another target, or with none, is refused as a different generation intent, for `generate` and `lookupAcceptedPrint`. - **Digest.** A request with no target (including every earlier row) keeps the exact earlier intent digest; a stated target adds a framed `target-dpi/v1` domain after `copies/v1`, also when it equals the stock's resolution. - **Refusals before any row, render or submit.** A target for a PDF is refused ("A render target applies to ZPL output only."). A target is refused with `RENDER_TARGET_UNSUPPORTED` unless the configured renderer declares `IsolatedRenderer.supportsRenderTarget()`, which defaults to false: an adapter that does not read the field would print at another resolution than was asked for. The renderer bridge declares it. - **Refusals by the renderer.** The bridge refuses a version 1 scene asked for another resolution than its stock's as `TARGET_REQUIRES_SCENE_2`, and a version 2 scene on an old image as `RENDERER_TOO_OLD`. Both are typed: the request is `INCOMPLETE` with that `errorCode` and a retry throws it again without rendering. - **Not changed.** Reprints reuse retained bytes and carry no target. Queued and batch generation render at the stock's resolution. The draft preview that retains its output (`preview(Request, fingerprint)`) refuses a target. The request status process shows the stored `targetDpi`, and the generate process form offers it. ## Copies per print: source candidate, 2026-11-02 An owner-handoff source candidate, not a deployed or public API release. Nothing here was built or run; the pinned combined gate is run separately. `DocumentGenerationService.Request` and `GenerationPorts.PrintRequest` gain a trailing `Integer copies`, normalized in the canonical constructor: absent means one and anything outside 1 through 100 throws `IllegalArgumentException("Copies must be an integer from 1 through 100.")`, so an invalid count cannot reach admission, rendering or submission. Every earlier constructor stays and means one copy. The count is how many copies the one queued job prints; it is never a number of jobs. - **Persistence.** One nullable `copies` column on the request table; apply `20261102-print-copies-h2.sql` or the MySQL counterpart before adopting. Earlier rows stay null and read as one copy. - **Idempotent retry and lookup.** The stored count (null normalized to one) is compared with the request's in the same check as destination and media, so a different count under the same request key is refused as a different generation intent, for both `generate` and `lookupAcceptedPrint`. A legacy default-one retry still matches. - **Digest.** One copy (including every legacy row) keeps the exact earlier intent digest; a larger count adds a framed `copies/v1` domain. - **Dispatch.** `PrintDispatch.supportsCopies()` defaults to false. A count above one, or a count above one without a destination, is refused with `PRINT_COPIES_UNSUPPORTED` or "Copies require a print destination" before any row, render or submit. Reprints and queued/batch generation keep one copy. - **Printing bridge.** `PrintingDispatch.supportsCopies()` is true and sends `options.copies` (one submission) only when the count is above one, so options of one-copy requests are byte-for-byte unchanged (`{}`). The committed acceptance options must carry exactly the requested count; an absent count matches only one copy. It requires a Printing owner artifact whose `RetainedPrintFacade` honors `options.copies` (1 through 100); the facade itself is unchanged.