From 0b1276843f160f352aa64bd3a85c01b0a0c178bd Mon Sep 17 00:00:00 2001 From: Juergen Holly Date: Tue, 20 Oct 2026 11:42:15 +0200 Subject: [PATCH] propose fix for tonersave queue default --- .../.openspec.yaml | 2 + .../fix-tonersave-queue-default/design.md | 130 ++++++++++++++++++ .../fix-tonersave-queue-default/proposal.md | 80 +++++++++++ .../specs/printserver/cups/spec.md | 33 +++++ .../fix-tonersave-queue-default/tasks.md | 83 +++++++++++ 5 files changed, 328 insertions(+) create mode 100644 openspec/changes/fix-tonersave-queue-default/.openspec.yaml create mode 100644 openspec/changes/fix-tonersave-queue-default/design.md create mode 100644 openspec/changes/fix-tonersave-queue-default/proposal.md create mode 100644 openspec/changes/fix-tonersave-queue-default/specs/printserver/cups/spec.md create mode 100644 openspec/changes/fix-tonersave-queue-default/tasks.md diff --git a/openspec/changes/fix-tonersave-queue-default/.openspec.yaml b/openspec/changes/fix-tonersave-queue-default/.openspec.yaml new file mode 100644 index 0000000..9076a66 --- /dev/null +++ b/openspec/changes/fix-tonersave-queue-default/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-20 diff --git a/openspec/changes/fix-tonersave-queue-default/design.md b/openspec/changes/fix-tonersave-queue-default/design.md new file mode 100644 index 0000000..af6b6c3 --- /dev/null +++ b/openspec/changes/fix-tonersave-queue-default/design.md @@ -0,0 +1,130 @@ +# 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 -l` and then calls + `lpadmin -p -o `, logging `queue default 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/.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 -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 ` 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 -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. + +## 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 -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 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 (`<>`). The +spool file is the last file in `/var/spool/cups/d-*` (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. +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). + +**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. +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 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 ` inside the container with the + same PPD, which produces the same PostScript text without a queue. +- `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. +- 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. diff --git a/openspec/changes/fix-tonersave-queue-default/proposal.md b/openspec/changes/fix-tonersave-queue-default/proposal.md new file mode 100644 index 0000000..4783626 --- /dev/null +++ b/openspec/changes/fix-tonersave-queue-default/proposal.md @@ -0,0 +1,80 @@ +# Proposal + +## Why + +While verifying the `revert-colour-mode-default` change, an operational check +found a divergence for one PPD option: after the entrypoint applies +`PRINTER_OPTIONS="BRColorMode=None TonerSaveMode=True"`, the queue shows +`BRColorMode ... *None` (consistent everywhere) but `TonerSaveMode ... *False` +even though the queue's PPD copy at `/etc/cups/ppd/.ppd` contains +`*DefaultTonerSaveMode: True`. The rejected tool option +`lpadmin -p -o TonerSaveMode=True` (config-only, immediately surfaced) +reproduces the same split. `TonerSaveMode` is the option that the +`APPrinterPreset` entries use to implement the "draft prints with toner saving" +behaviour recorded in the spec, so a queue default that only half applies +undermines one of the documented scenarios. + +The current spec (`Queue defaults are configurable at runtime` → *Options are +applied on start*) is written as a configuration check ("the queue defaults +include `TonerSaveMode=False`") and is satisfied by the settings file alone, so +it does not pin down whether a job actually receives `TonerSaveMode=True`. That +gap is why the divergence went unnoticed. This change turns the requirement +into an observable one — the emitted job — and makes whatever the investigation +finds explicit in the spec instead of leaving the contradiction undocumented. + +## What Changes + +- **Investigate** the `TonerSaveMode` split during apply: determine whether a + job with the queue default `TonerSaveMode=True` actually emits the toner-save + PostScript (`statusdict/tonersave` → `true`) or silently falls back to the + PPD default `False`, and record the root cause in the design/tasks notes. +- **Spec** (`printserver/cups`): replace the configuration-only *Options are + applied on start* scenario with an observable job-level scenario; add a + scenario that pins down what a job whose queue default conflicts with the PPD + default must emit. Add the `TonerSaveMode` observable to the colour-mode + requirement's scope (BRColorMode only) so the two are not conflated. +- **Tests** (`tests/`): add a job-level integration test that applies the + documented `PRINTER_OPTIONS="BRColorMode=Vivid TonerSaveMode=False"` example, + submits a job with the bundled sample, and asserts the emitted PostScript + carries `UseCIEColor true` (Vivid) **and** `statusdict ... tonersave ... false` + (TonerSaveMode) — i.e. the queue default wins over the PPD default in the + output. If the investigation shows it cannot win, the test instead asserts the + honest current behaviour and the spec records the limitation. +- **README**: state what `PRINTER_OPTIONS` queue defaults do to a job, with the + verified example output, and note any option-specific caveat found. + +No change to `entrypoint.sh` plumbing is planned: it already calls +`lpadmin -o