implement fix for color mode default
ci/woodpecker/push/build Pipeline was successful

This commit is contained in:
juergen committed 2026-10-20 15:59:53 +02:00
1 parent b22e82a8c2
commit 509af2a410
5 files changed
+238 -35

No files matched your search

@@ -36,6 +36,56 @@ approach:
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:**
@@ -69,19 +119,25 @@ 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 spooled by cupsd.**
Rationale: it 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
spool file is the last file in `/var/spool/cups/d<JobId>-*` (its name comes from
the preceding `lpstat -W completed -o` line), and is readable as root inside the
container for a few minutes while the socket connection to the placeholder
`PRINTER_IP` fails.
**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 — 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 file read — rejected).
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
@@ -92,6 +148,11 @@ 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"
@@ -112,18 +173,18 @@ the test checks by applying the documented `PRINTER_OPTIONS` example.
## Risks / Trade-offs
- The spool file is lifetime-bounded (retention) and the test container's job
never reaches a printer, so the test must read the file soon after submission
and tolerate `connection refused` state messages; if it races, the preferred
per-job capture is `cupsfilter <sample.pdf>` inside the container with the
same PPD, which produces the same PostScript text without a queue.
- 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 so a future base-image bump
does not silently reinterpret the assertion.
- If the investigation finds the drop is a CUPS/driverd quirk with no image-side
fix, the change still lands value (honest spec + regression guard) but leaves
`TonerSaveMode` as a documented limitation; that outcome must be reported, not
absorbed.
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