propose fix for tonersave queue default
This commit is contained in:
1 parent
f556103f3c
commit
0b1276843f
5 files changed
+328
No files matched your search
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-10-20
|
||||
@@ -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 <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.
|
||||
|
||||
## 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 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.
|
||||
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 <sample.pdf>` 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.
|
||||
@@ -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/<queue>.ppd` contains
|
||||
`*DefaultTonerSaveMode: True`. The rejected tool option
|
||||
`lpadmin -p <queue> -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 <option>` for every validated `key=value` pair, and its warning
|
||||
path for unusable entries stays as is. A code change happens only if the
|
||||
investigation identifies a server-side cause that is fixable in the image.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
(none)
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `printserver/cups`: the runtime queue-default requirement becomes
|
||||
job-observable, and a scenario covers a queue default that conflicts with the
|
||||
PPD default.
|
||||
|
||||
## Impact
|
||||
|
||||
- `openspec/specs/printserver/cups/spec.md` (requirement text at sync time;
|
||||
the delta lives in this change).
|
||||
- **No project test suite exists yet** (AGENTS.md: no build/test/lint config) —
|
||||
this change introduces the first one, a self-contained
|
||||
`tests/test-queue-defaults.sh` that drives `podman`/`podman exec`, so the
|
||||
verification is reproducible instead of an ad-hoc shell session.
|
||||
- `README.md` — `PRINTER_OPTIONS` documentation gains the verified job-level
|
||||
behaviour.
|
||||
- `assets/Brother HL-4150CDN/ppd/BR4150_2.PPD` — the six `APPrinterPreset`
|
||||
entries that set `*TonerSaveMode` are the client that exercises the option;
|
||||
they stay unchanged.
|
||||
- `entrypoint.sh` — unchanged unless the investigation finds a server-side
|
||||
cause.
|
||||
- Out of scope: the colour saturation limitation documented by
|
||||
`revert-colour-mode-default` (no server-side lever is known, and none is
|
||||
proposed here).
|
||||
@@ -0,0 +1,33 @@
|
||||
# Spec Delta
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Queue defaults are configurable at runtime
|
||||
The container SHALL accept a `PRINTER_OPTIONS` environment variable holding
|
||||
space-separated PPD option assignments (`key=value`) and apply each assignment
|
||||
as a queue default on every start, so PPD-specific device options survive the
|
||||
recreation of the queue. An applied assignment SHALL also determine what a job
|
||||
prints: a job that does not select that option itself is printed with the
|
||||
configured value, even when that value differs from the PPD's own default. A
|
||||
queue default SHALL be observable for both the colour mode (`BRColorMode`) and
|
||||
the toner-saving switch (`TonerSaveMode`).
|
||||
|
||||
#### Scenario: Options are applied on start
|
||||
- **WHEN** the container is started with `PRINTER_OPTIONS="BRColorMode=Vivid TonerSaveMode=False"`
|
||||
- **THEN** after startup the queue defaults include `BRColorMode=Vivid` and `TonerSaveMode=False`
|
||||
|
||||
#### Scenario: An applied queue default reaches the job
|
||||
- **WHEN** the container is started with `PRINTER_OPTIONS="BRColorMode=Vivid TonerSaveMode=False"` and a colour job is submitted that selects neither option
|
||||
- **THEN** the emitted PostScript applies `Vivid` (`UseCIEColor true`) and, provided the investigation confirms the job-level path, disables toner saving (`statusdict ... tonersave ... false`) — for `TonerSaveMode` the vendor PPD default is `False`, so the observable difference is between omitting the toner-save call and emitting `false`
|
||||
|
||||
#### Scenario: Defaults apply to driverless clients
|
||||
- **WHEN** a macOS or iOS client prints a job that does not specify that PPD option
|
||||
- **THEN** the job is printed with the configured queue default
|
||||
|
||||
#### Scenario: An unusable option must not stop startup
|
||||
- **WHEN** `PRINTER_OPTIONS` contains an option name or value the PPD does not provide
|
||||
- **THEN** the entrypoint logs a warning, applies the remaining assignments and continues starting
|
||||
|
||||
#### Scenario: Nothing configured
|
||||
- **WHEN** `PRINTER_OPTIONS` is unset or empty
|
||||
- **THEN** the queue keeps the PPD defaults and the container starts as before
|
||||
@@ -0,0 +1,83 @@
|
||||
# Tasks
|
||||
|
||||
Aufteilung: 1. Ursache feststellen (blockierend), 2. Testschicht, 3. Spec/README,
|
||||
4. Serverseitiger Fix nur falls 1 ihn hergibt.
|
||||
|
||||
## 1. Ursache feststellen (Verifikation des Ist-Zustands)
|
||||
|
||||
- [ ] 1.1 Jobpfad reproduzieren: Image bauen, Testcontainer mit
|
||||
`PRINTER_OPTIONS="BRColorMode=None TonerSaveMode=True"` starten, einen Job
|
||||
einreichen (`podman exec <c> lp -d <queue> -o raw <sample>` oder
|
||||
`cupsfilter <sample>`), und im versandten PostScript
|
||||
(`/var/spool/cups/d<JobId>-*`, Job-Nummer aus
|
||||
`lpstat -W completed -o`) prüfen, ob der Tonerspar-Zweig
|
||||
(`statusdict/tonersave` … `true`) enthalten ist.
|
||||
Verifikation: Ergebnis (enthalten/nicht enthalten) mit dem ausgelesenen
|
||||
Spool-Pfad und der CUPS-Version in design.md notieren.
|
||||
- [ ] 1.2 Zweiten Messpunkt gegen die Queue-Defaults aufnehmen: denselben Job
|
||||
zusätzlich mit explizitem `-o BRColorMode=Vivid` einreichen und die
|
||||
`UseCIEColor`-Zeile im Spool mit der des ersten Jobs vergleichen.
|
||||
Verifikation: dokumentierter Unterschied (oder dessen Fehlen) zeigt, ob
|
||||
per-Job-Optionen im selben Pfad sichtbar werden — belegt, dass die
|
||||
Messmethode aus 1.1 trägt.
|
||||
- [ ] 1.3 Queue-Zustand desselben Containers gegengelesen:
|
||||
`lpoptions -p <queue> -l` (Sternchen) und
|
||||
`grep '^Default' /etc/cups/ppd/<queue>.ppd`.
|
||||
Verifikation: die Abweichung aus dem Ausgangsbefund ist entweder
|
||||
bestätigt (`*False` bei `DefaultTonerSaveMode True`) oder widerlegt;
|
||||
Ergebnis in design.md festhalten.
|
||||
- [ ] 1.4 Ergebnis einordnen und die Entscheidung nach Design D3 treffen:
|
||||
erreicht der Queue-Default den Job (Szenario in specs/… bleibt) oder nicht
|
||||
(design.md um das Resultat ergänzen, Szenario auf das verifizierte
|
||||
Verhalten umformulieren, Limitation explizit benennen).
|
||||
Verifikation: design.md enthält den Messwert und die getroffene
|
||||
Entscheidung; die betroffenen Tasks aus 2/3 sind entsprechend markiert.
|
||||
Ohne dieses Ergebnis werden 2.x/3.x nicht abgehakt.
|
||||
|
||||
## 2. Automatisierter Nachweis (neue Testschicht)
|
||||
|
||||
- [ ] 2.1 `tests/test-queue-defaults.sh` anlegen: baut das Image, startet einen
|
||||
Container mit `PRINTER_OPTIONS="BRColorMode=Vivid TonerSaveMode=False"`,
|
||||
wartet auf `ready: CUPS on 631/tcp`, reicht einen Job ohne
|
||||
`print-quality`/Preset-Attribute ein und liest das letzte Spool-File
|
||||
(`lpstat -W completed -o` → `/var/spool/cups/d<JobId>-*`).
|
||||
Verifikation: Skript läuft lokal durch (`bash tests/test-queue-defaults.sh`),
|
||||
Ausgabe zeigt den Spool-Pfad und die gefundenen PostScript-Zeilen.
|
||||
- [ ] 2.2 Assertions gemäß Ergebnis aus 1.4 einbauen: `UseCIEColor true` für den
|
||||
Queue-Default `Vivid` und der Tonerspar-Zustand aus dem verifizierten
|
||||
Verhalten für `TonerSaveMode=False`; zusätzlich muss die
|
||||
`UseCIEColor`-Zeile bei explizitem `-o BRColorMode=None` abweichen
|
||||
(Regressionsschutz). Vergleich nur über `grep -F` auf Zeilen, kein Parsen
|
||||
von PDF/PS-Struktur.
|
||||
Verifikation: Skript endet mit Exit 0 und einer PASS-Zeile pro Assertion;
|
||||
eine absichtlich falsch gesetzte Erwartung lässt es mit Exit ≠ 0 und
|
||||
Klartextzeile scheitern.
|
||||
- [ ] 2.3 Lebensdauer/Umgebung absichern: Container immer aufräumen (Trap),
|
||||
kurze Wiederholung des Spool-Lesens statt Polling, und im Skriptkopf die
|
||||
verifizierte CUPS-Version notieren.
|
||||
Verifikation: `podman ps -a` zeigt nach einem Lauf keinen Testcontainer;
|
||||
zweiter Lauf hintereinander ist grün.
|
||||
|
||||
## 3. Spec und README angleichen
|
||||
|
||||
- [ ] 3.1 `README.md`, Abschnitt *Environment variables* / `PRINTER_OPTIONS`:
|
||||
eine Zeile ergänzen, was ein Queue-Default für den Ausdruck bedeutet
|
||||
(Queue-Default schlägt den PPD-Default, sichtbar im versandten
|
||||
PostScript), mit Verweis auf das Testskript.
|
||||
Verifikation: `grep -n 'PRINTER_OPTIONS' README.md` zeigt den neuen Satz;
|
||||
die genannte Assertion stimmt mit 2.2 überein.
|
||||
- [ ] 3.2 `entrypoint.sh`-Hinweis prüfen: der bestehende Kommentar bei
|
||||
`apply_printer_options` (Zeile ~341–345) behauptet, `lpadmin` würde
|
||||
unbekannte Zuweisungen speichern — mit dem Befund aus 1.x abgleichen und
|
||||
nur bei Bedarf präzisieren.
|
||||
Verifikation: `jj diff` zeigt entweder keine Änderung an entrypoint.sh
|
||||
oder ausschließlich Kommentar/Log-Text, ohne Verhaltensänderung.
|
||||
|
||||
## 4. Aktualisierung der Hauptspec (Archive-Zeitpunkt)
|
||||
|
||||
- [ ] 4.1 Beim Archivieren die Delta-Spec nach
|
||||
`openspec/specs/printserver/cups/spec.md` übernehmen und dabei die alte
|
||||
Fassung des Requirements *Queue defaults are configurable at runtime*
|
||||
(Zeilen 69–89) durch die neue, job-beobachtbare Fassung ersetzen.
|
||||
Verifikation: `openspec validate --strict` ohne Findings; die Hauptspec
|
||||
enthält genau eine Fassung des Requirements.
|
||||
Reference in new issue
Block a user