mesh-mail: bound a fetch by bytes, mail_accounts to grantees only, say why grants are unread, judge a secret without reading it (hq issue 733) #305

Closed
mesh-admin wants to merge 0 commits from adr-359-mesh-mail-733 into main
Contributor

Issue 733: the follow-ups from the independent review of #300.

Base: adr-359-mesh-mail (#300), because #300 is not merged yet. Once #300 is merged, I will merge origin/main into this branch (never a force-push) and retarget the PR to main. Merge this only after #300.

Changes

  1. A fetch is bounded by bytes.
    • New messages are measured first: UID FETCH (RFC822.SIZE), 500 at a time.
    • Bodies are then fetched in batches of at most 25 messages and at most 64 MiB (BatchBytes).
    • A message larger than the cap is fetched alone, and the keeper logs: "… is N bytes, more than a batch holds; it is fetched alone".
    • Batches() is a pure function with its own unit test.
  2. mail_accounts lists only the accounts the caller is granted. For any other account it says nothing: not its name, folders, counts, state or error text. A caller granted nothing gets "accounts": []. The module's overall state remains mail_health's to say.
  3. The reason grants could not be read is kept. mail_read, mail_search and mail_accounts now say "the setting grants cannot be read, so it grants nobody: ". Before, they only said the caller was not granted.
  4. SecretGiven only looks at the file. It uses os.Stat: a regular, non-empty file counts as given. The watch never reads the password. A secret the watch cannot read (owned by root) still counts as given.

Tests

  • Batches cuts by count and by bytes, and a larger message goes alone.
  • Over a real IMAP conversation with a 600-byte cap, all three mails are indexed and said, and exactly one is said to be fetched alone.
  • mail_accounts called by an ungranted caller leaks no account name, address, folder, count, state or error text. A granted caller sees all of them.
  • A grants file that cannot be read makes all three tools give the reason.
  • SecretGiven:
    • an empty file is not given;
    • a file mode 000 is given;
    • its code reads no file.

Checks

  • go test -race passes.
  • With the controller built from mesh-controller main (5d22d7df):
    • module check says mesh-mail: ok, 4 tool(s), ready: keep by unit every 30s; watch by its tool mail_health every 1m0s;
    • merge-check.sh exits 0, with resources without health at 62, as kept.

Acceptance

No batch of bodies holds more than 64 MiB unless it is one message. An ungranted caller learns nothing of an account from mail_accounts. A refusal caused by unreadable grants says why. The watch never reads a password.

Issue 733: the follow-ups from the independent review of #300. **Base:** `adr-359-mesh-mail` (#300), because #300 is not merged yet. Once #300 is merged, I will merge origin/main into this branch (never a force-push) and retarget the PR to main. Merge this only after #300. ## Changes 1. **A fetch is bounded by bytes.** - New messages are measured first: `UID FETCH (RFC822.SIZE)`, 500 at a time. - Bodies are then fetched in batches of at most 25 messages and at most 64 MiB (`BatchBytes`). - A message larger than the cap is fetched alone, and the keeper logs: "… is N bytes, more than a batch holds; it is fetched alone". - `Batches()` is a pure function with its own unit test. 2. **`mail_accounts` lists only the accounts the caller is granted.** For any other account it says nothing: not its name, folders, counts, state or error text. A caller granted nothing gets `"accounts": []`. The module's overall state remains `mail_health`'s to say. 3. **The reason `grants` could not be read is kept.** `mail_read`, `mail_search` and `mail_accounts` now say "the setting grants cannot be read, so it grants nobody: <reason>". Before, they only said the caller was not granted. 4. **`SecretGiven` only looks at the file.** It uses `os.Stat`: a regular, non-empty file counts as given. The watch never reads the password. A secret the watch cannot read (owned by root) still counts as given. ## Tests - `Batches` cuts by count and by bytes, and a larger message goes alone. - Over a real IMAP conversation with a 600-byte cap, all three mails are indexed and said, and exactly one is said to be fetched alone. - `mail_accounts` called by an ungranted caller leaks no account name, address, folder, count, state or error text. A granted caller sees all of them. - A grants file that cannot be read makes all three tools give the reason. - `SecretGiven`: - an empty file is not given; - a file mode 000 is given; - its code reads no file. ## Checks - `go test -race` passes. - With the controller built from mesh-controller main (5d22d7df): - `module check` says `mesh-mail: ok, 4 tool(s), ready: keep by unit every 30s; watch by its tool mail_health every 1m0s`; - `merge-check.sh` exits 0, with resources without health at 62, as kept. ## Acceptance No batch of bodies holds more than 64 MiB unless it is one message. An ungranted caller learns nothing of an account from `mail_accounts`. A refusal caused by unreadable grants says why. The watch never reads a password.
Author
Contributor

Delivery novox/mesh-catalog@b5417c28d026 — stopped since 2026-10-15T08:49:31Z

Covered: covered after its stop: a later walk that contains it was delivered: plan-1792054175111921309 (novox/mesh-catalog at 0914a33f into main, done, contains its merge 0914a33f)

Delivery plan — builds mesh-mail, sent nowhere; no bus step

  • tier 0: mesh-mail

Transitions

  • 2026-10-15 08:45 (new) → proposed (announced): novox/mesh-catalog#305's head announced
  • 2026-10-15 08:47 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
  • 2026-10-15 08:47 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
  • 2026-10-15 08:49 ready → stopped (closed): the pull request closed unmerged

The commit's note under refs/notes/mesh-plan keeps every transition: git log --notes=mesh-plan.

<!-- mesh-delivery:view --> **Delivery** `novox/mesh-catalog@b5417c28d026` — **stopped** since 2026-10-15T08:49:31Z **Covered**: covered after its stop: a later walk that contains it was delivered: plan-1792054175111921309 (novox/mesh-catalog at 0914a33f into main, done, contains its merge 0914a33f) **Delivery plan** — builds mesh-mail, sent nowhere; no bus step - tier 0: mesh-mail **Transitions** - 2026-10-15 08:45 (new) → proposed (announced): novox/mesh-catalog#305's head announced - 2026-10-15 08:47 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit - 2026-10-15 08:47 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail - 2026-10-15 08:49 ready → stopped (closed): the pull request closed unmerged The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
Author
Contributor

Merge check at be53f516
Judged: head be53f5167a39 merged into trunk 2130f9961291; read beside it: mesh-controller 86057f455194, mesh-controller-main 60cc9ec363df, mesh-host 1a2677882380, mesh-tools 5d70eb3e35a9

  • mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: new:modules/mesh-mail)
  • mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)

Delivery plan — builds new: modules/mesh-mail, sent nowhere; no bus step

Run by the build seat on shanks as build-1792011477699265627 (builds --log build-1792011477699265627).

--- the gate: new:modules/mesh-mail
mesh-mail: ok, 4 tool(s), ready: keep by unit every 30s; watch by its tool mail_health every 1m0s
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 0
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-14T20:36:53Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-14T20:36:53Z

modules the change moves:
  - mesh-mail (added)

machines:
  - a machine (f35): composes
  - holds mesh-dns-resolver (idt): composes
  - holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
  - a machine (yvbpsm): composes

a merge would rebuild 0 module(s) in 0 tier(s)
--- its merge-check.sh (go toolchain)
ok  	git.novox.be/novox/mesh-catalog/checks/words	0.003s
words: 12 retired words and 70 daemons' lines, none in the 47646 strings of 731 files
ok  	git.novox.be/novox/mesh-catalog/checks/agenthome	0.013s
agent-home: no archive and no file written into under the agent's home
ok  	git.novox.be/novox/mesh-catalog/checks/secrets	0.013s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
ok  	git.novox.be/novox/mesh-catalog/checks/paths	0.006s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 62, as kept
ok  	git.novox.be/novox/mesh-catalog/checks/deps	0.222s
testing modules/mesh-mail
ok  	mesh-mail/cmd/mesh-mail	3.124s
--- its merge-check-typescript.sh (typescript toolchain)
**Merge check** at `be53f516` Judged: head `be53f5167a39` merged into trunk `2130f9961291`; read beside it: mesh-controller `86057f455194`, mesh-controller-main `60cc9ec363df`, mesh-host `1a2677882380`, mesh-tools `5d70eb3e35a9` - `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: new:modules/mesh-mail) - `mesh/repo-check`: **PASS** — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript) **Delivery plan** — builds new: modules/mesh-mail, sent nowhere; no bus step Run by the build seat on shanks as `build-1792011477699265627` (`builds --log build-1792011477699265627`). ``` --- the gate: new:modules/mesh-mail mesh-mail: ok, 4 tool(s), ready: keep by unit every 30s; watch by its tool mail_health every 1m0s long-running resources without health: 0 files asking for a setting without saying whether it is trusted: 0 1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-14T20:36:53Z says they run composing every machine as the mesh is the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued composing every machine with the change the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose) judged by controller development build against the facts of 2026-10-14T20:36:53Z modules the change moves: - mesh-mail (added) machines: - a machine (f35): composes - holds mesh-dns-resolver (idt): composes - holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes - a machine (yvbpsm): composes a merge would rebuild 0 module(s) in 0 tier(s) --- its merge-check.sh (go toolchain) ok git.novox.be/novox/mesh-catalog/checks/words 0.003s words: 12 retired words and 70 daemons' lines, none in the 47646 strings of 731 files ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.013s agent-home: no archive and no file written into under the agent's home ok git.novox.be/novox/mesh-catalog/checks/secrets 0.013s secrets: no setting can replace a vault secret in a mergeable file (1 known, nested) ok git.novox.be/novox/mesh-catalog/checks/paths 0.006s paths: no module names another's state on its machine (3 known, listed to go) long-running resources without health: 62, as kept ok git.novox.be/novox/mesh-catalog/checks/deps 0.222s testing modules/mesh-mail ok mesh-mail/cmd/mesh-mail 3.124s --- its merge-check-typescript.sh (typescript toolchain) ```
mesh-tools changed target branch from adr-359-mesh-mail to main 2026-10-15 08:44:09 +00:00
mesh-tools added 1 commit 2026-10-15 08:44:09 +00:00
mesh-mail: bound a fetch by bytes, show mail_accounts only to its grantees, say why grants are unread, judge a secret without reading it (hq issue 733)
mesh/merge-gate out of date: judged against trunk 2130f9961291, the trunk is now 5f6a41146c3a; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 2130f9961291, the trunk is now 5f6a41146c3a; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
be53f5167a
The review of #300 found a batch of 25 whole messages could hold a gigabyte,
mail_accounts told ungranted callers folder names, counts and server errors,
an unreadable grants setting was refused without its reason, and the watch
read the password only to learn it was there.
jschoubben added 1 commit 2026-10-15 08:45:18 +00:00
Merge remote-tracking branch 'origin/main' into adr-359-mesh-mail-733
mesh/merge-gate head b5417c28d026 merged into trunk 5f6a41146c3a — pass: builds mesh-mail, sent nowhere; no bus step; every machine composes with the chang…
mesh/repo-check head b5417c28d026 merged into trunk 5f6a41146c3a — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery covered after its stop: a later walk that contains it was delivered: plan-1792054175111921309 (novox/mesh-catalog at 0914a33f into main, do…
b5417c28d0
mesh-tools closed this pull request 2026-10-15 08:45:28 +00:00
Member

Merge check at b5417c28
Judged: head b5417c28d026 merged into trunk 5f6a41146c3a; read beside it: mesh-controller 0b7e740b299c, mesh-controller-main 20e58907b0f8, mesh-host 198fd34ec497, mesh-tools 6b3e15042fee

  • mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: mesh-mail)
  • mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)

Delivery plan — builds mesh-mail, sent nowhere; no bus step

  • tier 0: mesh-mail
  • read by no module's build: modules/mesh-mail/cmd/mesh-mail/config_test.go, modules/mesh-mail/cmd/mesh-mail/imap_test.go, modules/mesh-mail/cmd/mesh-mail/tools_test.go

Run by the build seat on ace as build-1792053921486290338 (builds --log build-1792053921486290338).

--- the gate: mesh-mail
mesh-mail: ok, 4 tool(s), ready: keep by unit every 30s; watch by its tool mail_health every 1m0s
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 0
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-15T01:09:40Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-15T01:09:40Z

modules the change moves:
  - mesh-mail (added)

machines:
  - a machine (f35): composes
  - holds mesh-dns-resolver (idt): composes
  - holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
  - a machine (yvbpsm): composes

a merge would rebuild 0 module(s) in 0 tier(s)
--- its merge-check.sh (go toolchain)
ok  	git.novox.be/novox/mesh-catalog/checks/words	0.003s
words: 12 retired words and 70 daemons' lines, none in the 48145 strings of 737 files
ok  	git.novox.be/novox/mesh-catalog/checks/agenthome	0.012s
agent-home: no archive and no file written into under the agent's home
ok  	git.novox.be/novox/mesh-catalog/checks/secrets	0.011s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
ok  	git.novox.be/novox/mesh-catalog/checks/paths	0.006s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 62, as kept
ok  	git.novox.be/novox/mesh-catalog/checks/deps	0.209s
testing modules/mesh-mail
ok  	mesh-mail/cmd/mesh-mail	2.993s
--- its merge-check-typescript.sh (typescript toolchain)
**Merge check** at `b5417c28` Judged: head `b5417c28d026` merged into trunk `5f6a41146c3a`; read beside it: mesh-controller `0b7e740b299c`, mesh-controller-main `20e58907b0f8`, mesh-host `198fd34ec497`, mesh-tools `6b3e15042fee` - `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: mesh-mail) - `mesh/repo-check`: **PASS** — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript) **Delivery plan** — builds mesh-mail, sent nowhere; no bus step - tier 0: mesh-mail - read by no module's build: modules/mesh-mail/cmd/mesh-mail/config_test.go, modules/mesh-mail/cmd/mesh-mail/imap_test.go, modules/mesh-mail/cmd/mesh-mail/tools_test.go Run by the build seat on ace as `build-1792053921486290338` (`builds --log build-1792053921486290338`). ``` --- the gate: mesh-mail mesh-mail: ok, 4 tool(s), ready: keep by unit every 30s; watch by its tool mail_health every 1m0s long-running resources without health: 0 files asking for a setting without saying whether it is trusted: 0 1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-15T01:09:40Z says they run composing every machine as the mesh is the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued composing every machine with the change the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose) judged by controller development build against the facts of 2026-10-15T01:09:40Z modules the change moves: - mesh-mail (added) machines: - a machine (f35): composes - holds mesh-dns-resolver (idt): composes - holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes - a machine (yvbpsm): composes a merge would rebuild 0 module(s) in 0 tier(s) --- its merge-check.sh (go toolchain) ok git.novox.be/novox/mesh-catalog/checks/words 0.003s words: 12 retired words and 70 daemons' lines, none in the 48145 strings of 737 files ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.012s agent-home: no archive and no file written into under the agent's home ok git.novox.be/novox/mesh-catalog/checks/secrets 0.011s secrets: no setting can replace a vault secret in a mergeable file (1 known, nested) ok git.novox.be/novox/mesh-catalog/checks/paths 0.006s paths: no module names another's state on its machine (3 known, listed to go) long-running resources without health: 62, as kept ok git.novox.be/novox/mesh-catalog/checks/deps 0.209s testing modules/mesh-mail ok mesh-mail/cmd/mesh-mail 2.993s --- its merge-check-typescript.sh (typescript toolchain) ```

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.
No Reviewers
3 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: novox/mesh-catalog#305