Files
printserver/openspec/changes/fix-tonersave-queue-default/design.md
T
juergen 509af2a410
ci/woodpecker/push/build Pipeline was successful
implement fix for color mode default
2026-10-20 15:59:53 +02:00

11 KiB
Raw Blame History

Design

Context

See proposal.md — Why for the observed split. The pieces that shape the approach:

  • entrypoint.sh (apply_printer_options, lines ~341–383) validates every key=value against lpoptions -p <queue> -l and then calls lpadmin -p <queue> -o <opt>, logging queue default <opt> applied on exit status 0. Exit status is the only signal it has, and lpadmin returns 0 even when the setting does not take effect.
  • The queue's settings are persisted in the queue's PPD copy /etc/cups/ppd/<queue>.ppd (*DefaultTonerSaveMode: True, *DefaultBRColorMode: None), not as printers.conf Default* lines; with this CUPS the settings file alone therefore cannot distinguish "applied" from "accepted and dropped".
  • Confirmed in two containers (manual lpadmin -o and the entrypoint's own startup batch): after lpadmin -o TonerSaveMode=True the PPD copy says True, while lpoptions -p <queue> -l keeps showing TonerSaveMode/Toner Save: *False True; BRColorMode in the same batch is consistent (PPD copy None, -l *None).
  • TonerSaveMode is a Boolean PPD option (assets/.../BR4150_2.PPD line 327) whose True branch emits statusdict/tonersave known{statusdict begin true tonersave end}if; the six APPrinterPreset entries set *TonerSaveMode True|False for draft/normal/high, which is how the documented driverless draft behaviour is implemented.
  • The rejected observation point: lpoptions -p <queue> without -l lists only IPP queue attributes, so it never shows PPD option defaults. The archived fix-colour-defaults-and-client-options verification used lpoptions -p <queue> -l (correct), and the revert-colour-mode-default task 3.2 command was corrected to match during apply.
  • The repo has no test suite yet; the job-level check introduced here is a self-contained script that drives podman, a placeholder PRINTER_IP and a minimal hand-written PostScript/PDF sample (no sample fixtures exist in the repo), so it stays runnable without a printer and without network access to a real device.

Investigation result (tasks 1.1–1.3)

Measured on printserver:latest (cups-daemon 2.4.2-3+deb12u9, cupsfilter/ gs present in the image), container started with PRINTER_OPTIONS="BRColorMode=None TonerSaveMode=True".

The TonerSaveMode queue default does reach the job. The emitted PostScript carries the option branch for the queue default; the vendor False default only appears when the option is not set at all:

options supplied to the filter %%BeginFeature emitted effect line
none (bare PPD, vendor defaults) *TonerSaveMode True …begin true tonersave end…
BRColorMode=None TonerSaveMode=True (queue defaults) *TonerSaveMode True …begin true tonersave end…
BRColorMode=Vivid TonerSaveMode=False (job's own) *TonerSaveMode False …begin false tonersave end…
BRColorMode=None…Vivid variants *BRColorMode None / *BRColorMode Vivid <</UseCIEColor false>> / true

An explicit per-job option visibly wins in the same channel (row 3, and None→Vivid), so the job-level path is real and settable — not a fixed vendor value.

The *False in lpoptions -p <queue> -l is a display artefact, not a lost setting. All three views of the same container agree that the setting is stored — /etc/cups/ppd/<queue>.ppd line 328 *DefaultTonerSaveMode: True, cupsd's live GET /printers/<queue>.ppd (the same PPD cupsd uses for filtering) also *DefaultTonerSaveMode: True — while lpoptions -p <queue> -l renders TonerSaveMode/Toner Save: *False True, stable across three separate containers. Cause: TonerSaveMode is a PPD Boolean (*OpenUI *TonerSaveMode … : Boolean), and for a Boolean lpoptions -l marks the False/Off entry as current instead of reflecting the keyword default; the PickOne option BRColorMode reflects *None correctly. The ordering is a rendering rule, not a second copy of the setting — so the misreading that opened this change is an observation defect (the wrong view was trusted), and lpoptions -l must not be used to assert a TonerSaveMode default.

Method caveat found during the probe (affects D2). A job submitted with the usual lp -d <queue> never reached the spool file in a usable state here: the client's d0000N-001 still holds the original PDF (cupsd retains it until the printer accepts the job), and the job sticks at "The printer may not exist or is unavailable", so no filters run and no PostScript exists to inspect. cupsfilter -j <job-id> cannot read cupsd's job without the control file (c0000N) inside the container, which is not worth reconstructing. The working reproducible route is therefore cupsfilter -p <queue-ppd> -m application/vnd.cups-postscript -o <opt=value> <sample>, with the queue's PPD copy from /etc/cups/ppd/<queue>.ppd — it emits the same %%BeginFeature/effect-line structure the queue path produces and needs no printer. This implementation uses that route; the "read cupsd's own spool" idea in D2 is recorded there as unworkable in this environment.

Goals / Non-Goals

Goals:

  • Determine whether a job actually receives an applied TonerSaveMode queue default, from the emitted PostScript, not from settings files.
  • Turn the runtime-queue-default requirement into one that a job-level test can falsify, so the same class of "accepted but ineffective" defect cannot pass again.
  • Leave the spec honest: a verified behaviour becomes the scenario; a verified limitation is recorded instead of an aspiration.

Non-Goals:

  • Changing entrypoint.sh validation/plumbing unless the investigation identifies a fixable server-side cause.
  • Re-testing the colour-saturation limitation (documented by revert-colour-mode-default) or re-opening BRColorMode behaviour, which is verified.
  • Adding job-level assertions for every PPD option; the example in the requirement (BRColorMode + TonerSaveMode) is the coverage.

Decisions

D1 — Observation point for queue defaults: lpoptions -p <queue> -l (asterisks) plus the queue PPD copy; never lpoptions -p without -l. Rationale: -p without -l shows IPP queue attributes only (see Context) and produced a false negative during revert-colour-mode-default. -l reflects the option the queue would use; the PPD copy shows what was persisted. Both are still configuration views, so they are used only to set up and cross-check a scenario, never as the acceptance signal. Alternatives considered: printers.conf Default* lines (absent with this CUPS — rejected); the CUPS web admin page (same settings source, needs auth — rejected); lpstat -l -p (no option detail — rejected).

D2 — Acceptance signal is the emitted PostScript, produced by cupsfilter with the queue's PPD. Rationale: the emitted PostScript is the only artefact that shows what the printer receives, it covers presets, queue defaults and per-job options in one channel, and the existing spec already reasons in those terms (<</UseCIEColor false>>). The concrete command is cupsfilter -p /etc/cups/ppd/<queue>.ppd -m application/vnd.cups-postscript -o <opt=value> <sample>, run inside the container, asserting on %%BeginFeature: *<Name> <Value> lines and the effect line that follows. "Read cupsd's own spool file after lp" (the original form of this decision) was disproved by the investigation: without a reachable printer the filters never run, so the spool holds the unfiltered input, and cupsfilter -j needs a control file cupsd does not write in this state. Set-six is captured where later reasoning needed it. Alternatives considered: asserting on lpoptions -l alone (that is exactly the blind spot being fixed, and it is additionally wrong for Booleans — rejected as the acceptance signal, kept as setup cross-check); parsing the source /usr/share/cups/model/BR4150_2.PPD in the image (proves packaging, not the job — rejected); a throwaway CUPS-side hook or debug logging (more moving parts than one filter run — rejected).

D3 — Investigate before specifying the TonerSaveMode outcome. Rationale: the change must not encode an unverified assumption. Task group 1 runs the probe and records the result (spool PostScript, -l state, and — for a second data point — an explicit per-job -o BRColorMode=Vivid in the same job that must visibly differ from the queue default None). If the queue default does reach the job, the scenario as written stands. If it does not, the investigation result is added to this design and the scenario is reworded to the verified behaviour plus an explicit limitation note before the checkboxes for task group 2/3 are ticked. Outcome (see investigation result above): the queue default does reach the job, so the scenario stands as written — with one refinement: the observable for TonerSaveMode=False must be the emitted *TonerSaveMode False feature (or its absence of the toner-save call), never an lpoptions -l asterisk, which mis-renders Boolean options. Alternatives considered: writing the scenario as aspirational and letting the test fail until someone fixes it (leaves a red suite and an untrue spec — rejected); downgrading the scenario up front to "the PPD copy is written" (keeps the blind spot — rejected).

D4 — Prefer a PostScript-text assertion over a rendered/printed test. Rationale: it is deterministic, needs no printer, and is CI-friendly. A physical print (toner save visibly lighter) stays a human check and is not part of the automation. Alternatives considered: rasterising the output and comparing image statistics (flaky, tooling-heavy — rejected).

D5 — TonerSaveMode is compared against the vendor False default, not the preset wiring. Rationale: the requirement asserts the queue default path; whether a client preset overrides it is the separate, already documented driverless scenario. Both must remain visible in the emitted PostScript, which the test checks by applying the documented PRINTER_OPTIONS example.

Risks / Trade-offs

  • The original D2 risk ("the spool file is lifetime-bounded, so read it soon after submission") materialised as a hard blocker rather than a race: with an unreachable printer the filters never run, so there is no filtered spool to read at all. Mitigation is the cupsfilter -p <ppd> route now recorded in D2, which needs no queue and no printer.
  • lpadmin -o behaviour is option- and CUPS-version-specific; the test must state the CUPS version it was verified against (2.4.2-3+deb12u9) so a future base-image bump does not silently reinterpret the assertion.
  • lpoptions -p <queue> -l mis-renders Boolean PPD options (shows *False for a stored True), so a future maintainer may "fix" the wrong layer; the test header and the README note must say that the emitted PostScript is the authority for TonerSaveMode.
  • Boolean TonerSaveMode is the same option the draft presets tune, so a future preset change can mask a broken queue default; the job-level test deliberately submits a job without print-quality/preset attributes to keep the two paths separate.