A request could not say which printer resolution to render a label for:
RenderRequest.targetDpi existed and nothing set it.
DocumentGenerationService.Request gains a trailing targetDpi (absent, 203,
300 or 600; every earlier constructor stays and means none). It is stored
in a new nullable column, target_dpi, on document_generation_request,
compared on retry and lookup beside destination, media and copies, framed
into the intent digest as target-dpi/v1 when stated, and passed to the
renderer. A request without a target has exactly the digest it had, so
earlier rows and their retries are unchanged; the same key with another
target, or with none, is another intent.
Nobody who asks for 300 dpi may silently get another resolution, so a
target is refused before any row exists when it cannot be honoured: for a
PDF, and for any renderer that does not declare the new
IsolatedRenderer.supportsRenderTarget() (false by default, so an adapter
written before the target cannot be handed one and ignore it).
A host applies one statement, from the shipped script for its database:
db/migrations/20261112-render-target-dpi-{h2,mysql,postgres}.sql
(ALTER TABLE document_generation_request ADD COLUMN target_dpi INTEGER
NULL). The generate process form offers the target and the request status
shows it. Batches, the queue and reprints carry none.
Also from the review of the ports: RenderRequest and PreviewRequest hold
their own unmodifiable copy of the context (null values allowed) and
print only their format and render target (and the request id), never the
record data or the template source. Neither wire form changes.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
12 KiB
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_dpicolumn ondocument_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.sqlor20261112-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 ofDocumentGenerationSchemainclude 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
generateandlookupAcceptedPrint. - Digest. A request with no target (including every earlier row) keeps the exact earlier intent digest; a
stated target adds a framed
target-dpi/v1domain aftercopies/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_UNSUPPORTEDunless the configured renderer declaresIsolatedRenderer.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 asRENDERER_TOO_OLD. Both are typed: the request isINCOMPLETEwith thaterrorCodeand 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 storedtargetDpi, 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
copiescolumn on the request table; apply20261102-print-copies-h2.sqlor 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
generateandlookupAcceptedPrint. 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/v1domain. - Dispatch.
PrintDispatch.supportsCopies()defaults to false. A count above one, or a count above one without a destination, is refused withPRINT_COPIES_UNSUPPORTEDor "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 sendsoptions.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 whoseRetainedPrintFacadehonorsoptions.copies(1 through 100); the facade itself is unchanged.