Merge pull request 'Design the brother module whole: scanning, the feeder, printing, settings read and configure' (#448) from design/60-the-brother-module into main
This commit was merged in pull request #448.
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-catalog modules/brother]
|
||||
updated: 2026-10-21
|
||||
decisions:
|
||||
- "ADR 0464"
|
||||
- "ADR 0467"
|
||||
- "ADR 0468"
|
||||
- "ADR 0474"
|
||||
---
|
||||
|
||||
# 60 — The brother module
|
||||
|
||||
**One module serves the operator's Brother multifunction device: its scanner and its printer are one machine on
|
||||
the network, so one module, `brother`, holds both seats, `scanner` and `printer`. It scans the glass or the
|
||||
document feeder into a PDF, keeps every scan in its own bucket and announces it on the bus, prints a document in
|
||||
one call, reads the device's state and its admin pages, restarts it, and changes the settings on the admin pages
|
||||
where a mistake cannot lock the operator out or open the device. Only the module's device process ever holds the
|
||||
device's admin password or the bucket's key.**
|
||||
(ADR 0464, ADR 0467; ADR 0468 and ADR 0474, both proposed.)
|
||||
|
||||
**Acceptance.** The operator, or an agent through the mesh MCP server, scans a document from the glass and finds it
|
||||
cropped in the scan folder; scans five sheets from the feeder on both sides and finds one PDF of ten pages; prints
|
||||
that PDF in one call; reads any admin page; changes a setting on an allowed page and is told the old and the new
|
||||
value; and is refused, with the reason, when asking to change the admin password or the network settings.
|
||||
|
||||
## 1. The parts
|
||||
|
||||
```
|
||||
caller (an agent, a module, the operator)
|
||||
│ scanner.scan · scanner.status · scanner.list · scanner.document
|
||||
│ printer.status · printer.print · printer.upload · printer.settings
|
||||
│ printer.configure · printer.restart
|
||||
▼
|
||||
brother — tool side (served by the node's tool runner, as the operator account)
|
||||
│ holds no credential
|
||||
│ status reads: IPP and eSCL, no login ───────────────────────────▶ the device
|
||||
│ every other verb: an ask on the work queue device-asks
|
||||
▼
|
||||
brother — device process (an account and a bus account of its own)
|
||||
│ holds the admin password and the bucket's key, and nothing else does
|
||||
├── eSCL ─────────────────────────────▶ the scanner
|
||||
├── IPP ─────────────────────────────▶ the printer
|
||||
├── HTTPS to the pinned certificate ───▶ the admin pages
|
||||
├── S3 ─────────────────────────────▶ the module's bucket (minio)
|
||||
└── its answer in the state device-answers ──▶ back to the tool side
|
||||
```
|
||||
|
||||
The two seats are held once for the mesh. A seat names the role, so a later scanner or printer module claims the
|
||||
same seat and no caller changes (ADR 0467 §2).
|
||||
|
||||
The module has two halves. The **tool side** answers the verbs. It runs as the node's operator account, so the
|
||||
scans it files in the scan folder are the operator's, and it never opens a credential. The **device process** runs
|
||||
as an account of the module's own with a bus account of its own (ADR 0436 §1), holds no seat and serves no tool. It
|
||||
alone reads the admin password and the bucket's key, and it alone does the work that needs either. A verb that
|
||||
needs a credential puts an ask on a work queue and reads the device process's answer back from the module's state,
|
||||
waiting at most the verb's bound (ADR 0468 §1). The reads that need no credential, the device's state over IPP and
|
||||
the scanner's over eSCL, stay on the tool side.
|
||||
|
||||
The admin password is the operator's own, given once at the operator's desk and kept in the vault; the mesh never
|
||||
generates or rotates it (ADR 0467 §4). The device process speaks HTTPS to the admin pages and trusts only the
|
||||
certificates whose fingerprints are pinned in the module's config; any other certificate is refused in the
|
||||
handshake, before a password is sent (ADR 0467 §3).
|
||||
|
||||
## 2. Scanning
|
||||
|
||||
- **The glass.** One page over the whole glass. The document lies on the lid, which is a white of its own, so the
|
||||
module measures the lid where no document lies and cuts the document out of it with a small margin. When it finds
|
||||
no document, or one that fills the glass, it keeps the whole page and says it did not crop (ADR 0464 §3).
|
||||
- **Several documents.** Each scan is its own call and its own PDF; the operator lays the next document on the glass
|
||||
and calls again.
|
||||
- **The feeder.** The feeder scans whole sheets, one side or both, and every page of the job goes into one PDF, in
|
||||
the order the scanner gave them. A feeder page is never cropped.
|
||||
- **The real resolution.** The module reads the resolution from the image it receives, never from what it asked: a
|
||||
scanner may answer a request for 300 dpi with 200 dpi (ADR 0464 §2).
|
||||
|
||||
**A lesson for every device the mesh speaks to.** The scanner ignored every scan setting at first: whatever the
|
||||
request asked, it ran its own default job. The cause was the request's body: the device ignores a body that holds a
|
||||
line break, and the same request written on one line is honoured. A device's protocol implementation may be
|
||||
stricter, or stranger, than its standard; when a device ignores a well-formed request, the form of the request
|
||||
(line breaks, whitespace, the order of fields) is the first suspect, and a capture of what a working client sends
|
||||
is the evidence.
|
||||
|
||||
## 3. Printing
|
||||
|
||||
A caller prints in one call: `printer.print` takes a kept scan by its key, or a document it has uploaded, with the
|
||||
copies, the sides and the paper. The device process fetches the document from the bucket and sends it to the printer
|
||||
in a format the printer itself lists as accepted, read before the job; a document in another format is refused with
|
||||
the printer's list (ADR 0468 §5). A document larger than one bus message travels in parts, each under the bus's
|
||||
limit, and a caller joins or sends the parts and checks the whole by its SHA-256 (ADR 0468 §3).
|
||||
|
||||
## 4. The bucket and the events
|
||||
|
||||
Every scan is kept once in the module's own bucket under its SHA-256, and announced with the event
|
||||
`brother.scan.added` once it is stored: its key, size, page count, size in millimetres, resolution and colour mode,
|
||||
never the image. The scan folder is one consumer of the stored scans: the tool side fetches the scan and files it
|
||||
there as the operator. The module deletes nothing from the bucket; the operator removes what they no longer want
|
||||
(ADR 0468 §2, §4).
|
||||
|
||||
The other events are `brother.printer.restarted`, when the device answers again after a restart (ADR 0467 §6), and
|
||||
`brother.printer.configured`, one for each setting changed (section 5).
|
||||
|
||||
## 5. Settings: read and configure
|
||||
|
||||
**Reading.** `printer.settings` reads one admin page: the device process logs in, reads the page and logs out. It
|
||||
answers the page's forms with each field's current value (a radio group's checked button, a checkbox's state, a
|
||||
select's chosen option) and the values each field allows; the page's links and its hidden fields only on ask. A
|
||||
password field's value is never given. Every page may be read, and the menu page's links are the map of the rest.
|
||||
|
||||
**Configuring.** `printer.configure` takes one page and the fields to set on it, by name. The device process logs in
|
||||
and reads the page as the printer serves it, with a fresh form token. It checks each named field: the field exists
|
||||
in one form of the page, and its value is allowed for its type (one of a radio group's or a select's values, on or
|
||||
off for a checkbox, a text within its maximum length). A password, hidden or file field cannot be set. When any
|
||||
check fails, nothing is sent and the answer names each refused field with what it allows. Otherwise the process
|
||||
posts the form as the page served it: its own hidden fields, the named fields at their new values, and every other
|
||||
field at its current value, so nothing the caller did not name changes. It reads the page back, answers each changed
|
||||
field's old and new value, and says a field the printer did not take as not taken. Then it logs out.
|
||||
|
||||
```
|
||||
caller ──configure {page, set}──▶ tool side ──ask──▶ device process
|
||||
login ─▶ read page (form, token, current values)
|
||||
check every named field ──✗──▶ refused, nothing sent
|
||||
post the whole form ─▶ read back ─▶ logout
|
||||
◀──answer: old → new per field
|
||||
brother.printer.configured, one per field
|
||||
told to the operator: page, fields, who asked
|
||||
```
|
||||
|
||||
**What may not be changed.** Some pages are refused, because a mistake on them locks the operator out or opens the
|
||||
device to the network: the login password, the user restriction and setting lock, the reset menu, the firmware
|
||||
update and its setup, the security settings, every wired, wireless and Wi-Fi Direct network page, the protocol and
|
||||
interface pages, and every security page (certificates, TLS, IPsec, the IP and MAC filters). The list is in the
|
||||
module's code and its tests, not in a config value, so no call can widen it; it is checked against the page asked
|
||||
and against the address the form posts to. The operator changes such a page in a browser at the device itself.
|
||||
|
||||
**Who asked, and who is told.** Every change names who asked, the caller the bus proved for the call; a call with no
|
||||
proven caller is refused. Each changed field is announced as `brother.printer.configured` with the page, the field,
|
||||
the old and the new value, who asked and when, and the operator is told through the operator channel, once per call.
|
||||
No password is ever in an answer, an event or a message.
|
||||
|
||||
**Restarting.** `printer.restart` restarts the device through its reboot page, which configure refuses. It is
|
||||
refused while the device holds a print or scan job, and waits at most five minutes for the printer to answer again
|
||||
(ADR 0467 §5).
|
||||
|
||||
**The module's own defaults stay config.** The scan resolution, colour mode and cropping are the module's config
|
||||
values, held by the config module and changed through a proposal on the operator's Approve; configure changes the
|
||||
device's pages and never the module's config.
|
||||
|
||||
## 6. Health
|
||||
|
||||
The tool side's health is whether the scanner answers; the device process's is its unit. A certificate the device
|
||||
serves that is not pinned is refused, and the refusal names the served and the pinned fingerprints. A device serves
|
||||
a new certificate after a reset, a firmware update or a certificate's renewal, so a refusal is either that or
|
||||
another machine answering in its place; only the operator can tell which, and pins the new fingerprint as a config
|
||||
value. A firmware that moves an admin page is said as a page not found, never as success.
|
||||
|
||||
## 7. Still open
|
||||
|
||||
- **A certificate change said before a call fails.** Today a changed certificate shows only when a verb is refused;
|
||||
the module should raise it as its condition, naming both fingerprints. No decision holds it yet.
|
||||
- **Files off the bus.** Documents cross the bus in parts today (ADR 0468 §3). ADR 0468 is to be revised so that a
|
||||
document travels through a signed link to the bucket instead, and only its key crosses the bus.
|
||||
- **Restart is not built yet.** `printer.restart` is decided (ADR 0467 §5) and follows `printer.configure`.
|
||||
@@ -59,6 +59,7 @@ document is written and this one's status becomes `implemented`.
|
||||
| [`57-building-config-and-provisioning.md`](57-building-config-and-provisioning.md) | **Designed.** The work of ADR 0348 and ADR 0349 as 37 packages, each an issue with its proof: foundations, template files, config in its own module, the provisioner and its client, secrets in the vault, bindings from providers, collections, a module's runtime readers, and removing the old forms; at most five worked at once | ADR 0348, ADR 0349 |
|
||||
| [`58-agent-work-as-the-meshs-own.md`](58-agent-work-as-the-meshs-own.md) | **Designed.** Agent work as the mesh's own: a thread seat keeps every thread whole on the bus; a thread handler runs the harness headless in a sandbox per session, the workspace on the thread's own forge branch; a workspace toolkit and named tools, no general command runner; one identity seat for persons, agents and modules, roles of grants, every tool an effect, signed bus logins, and the switch to them one identity at a time | ADR 0383, ADR 0384, ADR 0385, ADR 0386, ADR 0387, ADR 0388, ADR 0389, ADR 0390 |
|
||||
| [`59-the-mail-web-client.md`](59-the-mail-web-client.md) | **In progress.** The operator's mail, every account, in a browser on the mesh's own machines: a module of its own, `mesh-mail-web`, holds `mail-webclient`, renders pages on the server and reads and sends only through `mail-store`'s verbs under a caller granted per account; its route reaches the private network only, the operator signs in with Keycloak, and a mail goes out only on the operator's Approve; then Betterbird is removed | ADR 0440, ADR 0378, ADR 0379 |
|
||||
| [`60-the-brother-module.md`](60-the-brother-module.md) | **In progress.** One module, `brother`, holds the `scanner` and `printer` seats for the Brother multifunction device: scans from the glass (cropped) or the feeder (one PDF), keeps each scan in its bucket and announces it, prints in one call, reads the admin pages and changes settings on those where a mistake cannot lock the operator out, each change recorded and told; only its device process holds the credentials | ADR 0467, ADR 0464, ADR 0468, ADR 0474 |
|
||||
|
||||
## Not yet written
|
||||
|
||||
|
||||
Reference in New Issue
Block a user