Wait for the background scan's outcome in the test instead of reading it once, and say where brother_health sits (issue 1552)
mesh/merge-gate head 3757d75d6acf merged into trunk 9aa97926141f — pass: builds brother → shanks; no bus step; every machine composes with the change as it…
mesh/repo-check head 3757d75d6acf merged into trunk 9aa97926141f — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered

This commit is contained in:
2026-10-21 02:08:50 +02:00
parent b7243505ca
commit 3757d75d6a
2 changed files with 12 additions and 7 deletions
+2 -2
View File
@@ -66,7 +66,7 @@ makes it (mode 0755) when it files the first scan.
| `scanner.scan` | `source` (`glass`, default, or `feeder`), `duplex` (feeder only) and `apart` (glass only): asks the device process to scan, crop (glass only), keep the PDF in the bucket under its sha256 and say `scan.added`; then fetches it in parts, checks the sha256 and files it in `scan-folder`, in the background, so a scan is filed whether or not the caller still waits. A glass scan is waited for up to 180 s. Several glass pages in one PDF (a page turned over, a stapled document): `document` `start` scans page 1 into a new document and answers its id (nothing is filed yet), `document` with that id scans the next page, and `document` with the id and `finish` true closes it into one multi-page PDF, filed once with one `scan.added` (nothing is scanned). The pages are kept in the bucket under `pending/<id>/` (not in memory, so a restart of the device process loses none); a document that gets no page for 30 minutes is closed and filed so by the tool process (a restart of the tool process forgets that timer; `finish` still works). The pages in the bucket are not deleted, the bucket having no delete. A feeder scan is waited for 25 s (the mesh console gives up after 30 s); if it is not done, the answer is `started: true` with its `id`, and `scanner.scan {id}` reads the outcome (`state` running, done with the event's body and `path`, or failed with `why`). The device process ends a feeder job at 20 s a page (at least 3 min, at most 30 min). A scan whose PDF the tool could not fetch (over 100 MiB) is refused before it is kept or announced. Answers the event's body and `path` |
| `scanner.document` | one part of a kept scan (`object`, `part` from 1): `part`, `parts`, `size`, `sha256` (of the whole), `data` (base64). Join the parts and check the sha256 |
| `scanner.status` | whether the scanner answers, its state (Idle or Processing) and its document feeder's state; a scanner that does not answer is `answers: false` with why. |
| `brother_health` | the module's health, asked by the node-engine every 60 s (ADR 0240): the certificates the printer serves, one for each key type (ECDSA and RSA, told apart by the cipher suites offered under TLS 1.2), read with no login and compared with the pinned `printer-certificate` list. Unhealthy when one is not pinned, `why` naming the new fingerprint, so the pin can be changed before the admin pages are needed. With `printer` or `printer-certificate` not given it answers `state: waits` with `waits` naming each setting (the SDK's health.Waiting, ADR 0283), not an error. Each handshake is bounded by 3 s and the whole check by 7 s. A printer that does not answer is healthy, with `why` saying nothing was compared (it may be switched off). The process's liveness is judged by the node-engine without a declaration |
| `brother_health` | the module's health, asked by the node-engine every 60 s (ADR 0240) as the tool check of the `watch` process, beside the device process's unit check: the certificates the printer serves, one for each key type (ECDSA and RSA, told apart by the cipher suites offered under TLS 1.2), read with no login and compared with the pinned `printer-certificate` list. Unhealthy when one is not pinned, `why` naming the new fingerprint, so the pin can be changed before the admin pages are needed. With `printer` or `printer-certificate` not given it answers `state: waits` with `waits` naming each setting (the SDK's health.Waiting, ADR 0283), not an error. Each handshake is bounded by 3 s and the whole check by 7 s. A printer that does not answer is healthy, with `why` saying nothing was compared (it may be switched off). The process's liveness is judged by the node-engine without a declaration |
| `scanner.list` | the newest filed scans, newest first (`limit`, default 20). Read-only |
## The printer seat
@@ -113,7 +113,7 @@ writes each answer, once, into the state `device-answers` under the ask's id (te
title, links and form fields, or `error`. It alone reads the password, pins the certificate, logs in, reads the
page and logs out. An ask older than two minutes is dropped without contacting the printer; at most three
deliveries. It reads `settings.json` (mode 0644, no secret in it) and the state directory is 0711 so that it can
reach its own files; both are the choices that let its account read what it needs. Its health is `brother_health` (the printer's certificates against the pins); its liveness is judged by the node-engine.
reach its own files; both are the choices that let its account read what it needs. The device process is judged by its unit; `brother_health` (the printer's certificates against the pins) is the tool check of the `watch` process beside it.
The login reads the form each time: the device generates the password field's name, so the code takes the name from
the form, posts it with the form's hidden fields, and keeps the session cookie. A login is failed when the answer is
+10 -5
View File
@@ -217,11 +217,16 @@ func TestALongFeederScanAnswersStartedAndIsFiledWithoutTheCaller(t *testing.T) {
close(release)
// Nobody asks again: the scan is filed all the same.
waitFor(t, "the scan to be filed", func() bool { e, _ := os.ReadDir(folder); return len(e) == 1 })
done, err := r.call(t, "scanner.scan", map[string]any{"id": id})
if err != nil {
t.Fatal(err)
}
d := done.(map[string]any)
// The file is renamed into place before the job is marked done: read the outcome until it is.
var d map[string]any
waitFor(t, "the outcome to be done", func() bool {
got, err := r.call(t, "scanner.scan", map[string]any{"id": id})
if err != nil {
t.Fatal(err)
}
d = got.(map[string]any)
return d["state"] != "running"
})
if d["state"] != "done" || d["pages"] != float64(3) || d["path"] == nil {
t.Fatalf("outcome = %v, want done with pages and the path", d)
}