192 lines
11 KiB
Markdown
192 lines
11 KiB
Markdown
# 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.
|