Skip to content

docs: a design for seeing what the services say to each other - #52

Draft
pierre-rouanet wants to merge 2 commits into
mainfrom
monitor-design
Draft

docs: a design for seeing what the services say to each other#52
pierre-rouanet wants to merge 2 commits into
mainfrom
monitor-design

Conversation

@pierre-rouanet

@pierre-rouanet pierre-rouanet commented Aug 10, 2026

Copy link
Copy Markdown
Member

Design only — nothing implemented. docs/design/monitor-design.md, plus a pointer from architecture.md §8 and a row in the docs index.

The shape

"Monitor the message exchange" is two requests: the trace (a developer at the bench, asking where an operation stopped) and the meter (support on a live robot, asking whether IPC is working and who is using it). One event stream, folded two ways — the same relationship robotctl monitor already has with robot.state. §9.1 adds a third fold: the last of both, on disk, for whoever arrives after the service died.

The decisions

The tap goes at the dispatch boundary, not on the socket. An interposing proxy is the cheap design and disqualifies itself twice: may_mutate reads SO_PEERCRED, which behind a proxy reports the proxy's uid for every caller — so it either breaks mutating calls or grants any peer permission to change the robot — and it can only see one of the four transports §4.1 commits us to. Bytes on a wire also cannot say a call was refused by policy, or that a frame was dropped because a subscriber lagged.

The shared serve loop comes first, before any monitoring is written. A tap each new service must remember to call is exactly the _ => None wildcard route.rs refuses on the grounds that it is safe in the moment and wrong over time. The three loops differ in four ways that are requirements rather than drift — a peer policy in two of three, updaterd's 1 MiB line cap against 64 KiB, a push side in two of three, two connection shapes — so this is a real abstraction over dispatcher, policy, cap and push stream. Underestimating it is the main risk in the plan, and three implementations is the cheapest it ever gets.

Params are opt-in and redacted through the typed Call. The tap sees the line before the hand-written Debug impls that keep the wifi PSK and the pairing PIN out of the journal. At dispatch the redaction is the natural path; at the socket it is a thing to remember. The on-disk record never carries params at all.

The correlation id echoes on responses and notifications, not only requests, so a captured line is self-describing — a log excerpt pasted into an issue reconstructs into a tree without the process memory that held the mapping. Omitted when absent; about a kilobyte a second on a 50 Hz stream. Needs a PROTOCOL_VERSION bump, so board and laptop move together.

Every error response reaches info, not only mutating ones, since read-only is most of the surface and its failures are invisible today. That hands a retrying client the ability to evict a journal, so the loop collapses identical consecutive errors and demotes a connection past an error budget — §8.1's retention argument applies here harder than it did there.

The meter gets a crash record: last snapshot plus a ring of the last N events, flushed to a fixed-size file under /var/lib, outside release dirs so it survives update and rollback. The ring is the part worth having — the trace cannot be left on permanently, and the events immediately before a service died are exactly what nobody can go back and collect.

Surfaced by the refactor, deliberately not settled here

robotd passes no peer policy, and Call::is_mutating() does not cover robot.enable — so the call that starts a policy running on a walking robot is classified alongside hello. Defensible, since safety is authoritative regardless of what a client asks, but it has never been written down as a decision and it should not be answered by accident in a diff about serve loops. Noted with the consequence if revisited: padd sends robot.enable, so classifying intents as mutating denies the gamepad unless its user is allowlisted.

Other open questions: what mediad changes as the first service that is both client and gateway; how the trace ring is sized, given padd alone produces 50 events a second; and whether the shared loop is its own crate or a duck-ipc-proto module, which is really a question about keeping the types free of tokio.

Two requests wear the name "monitor the message exchange": a developer at
the bench asking where an operation stopped, and support asking whether a
robot's IPC is working and who is using it. They are one event stream folded
two ways, and building them separately is how a log and a counter end up
disagreeing with no way to tell which lied.

The tap goes at the dispatch boundary rather than on the socket. An
interposing proxy is the cheap design and disqualifies itself twice: it
reports its own uid to `may_mutate`, so it either breaks mutating calls or
launders authorisation for every caller, and it can only ever see the unix
socket — not the BLE leg, not mediad's gateway. Bytes on a wire also cannot
say a call was refused by policy or a frame dropped because a subscriber
lagged, which are the events worth having.

Params stay opt-in and redacted through the typed Call, because the tap sees
the line before the hand-written Debug impls that keep a customer's PSK and
the pairing PIN out of the journal.

The correlation id is the one wire change, and it goes first: it is the same
category as min_supported and schema_version, cheap before clients exist and
not retrofittable after.

Assisted-by: Claude:claude-opus-5
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

Coverage

73.88% lines on this branch. No base measurement to compare against.

Per-file
Filename                         Regions    Missed Regions     Cover   Functions  Missed Functions  Executed       Lines      Missed Lines     Cover    Branches   Missed Branches     Cover
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
btd/src/bluez.rs                     192               192     0.00%          10                10     0.00%         116               116     0.00%           0                 0         -
btd/src/framing.rs                   217                 7    96.77%          17                 1    94.12%          97                 5    94.85%           0                 0         -
btd/src/gatt.rs                        3                 0   100.00%           1                 0   100.00%           3                 0   100.00%           0                 0         -
btd/src/link.rs                       13                 0   100.00%           1                 0   100.00%          17                 0   100.00%           0                 0         -
btd/src/main.rs                       90                90     0.00%          14                14     0.00%          83                83     0.00%           0                 0         -
btd/src/pairing.rs                   217                21    90.32%          21                 7    66.67%         105                 7    93.33%           0                 0         -
btd/src/route.rs                     164                13    92.07%          12                 0   100.00%         203                 8    96.06%           0                 0         -
btd/src/session.rs                   936                68    92.74%          54                 4    92.59%         554                38    93.14%           0                 0         -
btd/src/upstream.rs                  107                24    77.57%           9                 1    88.89%          65                18    72.31%           0                 0         -
configd/src/main.rs                  434               434     0.00%          37                37     0.00%         310               310     0.00%           0                 0         -
configd/src/net.rs                   203                12    94.09%          31                 2    93.55%         155                 4    97.42%           0                 0         -
configd/src/nm.rs                    271               213    21.40%          43                39     9.30%         171               120    29.82%           0                 0         -
configd/src/power.rs                  30                30     0.00%           6                 6     0.00%          28                28     0.00%           0                 0         -
configd/src/store.rs                 350                25    92.86%          28                 2    92.86%         173                 9    94.80%           0                 0         -
duck-control/src/bus.rs              391               282    27.88%          30                18    40.00%         265               192    27.55%           0                 0         -
duck-control/src/imu.rs              370                 8    97.84%          20                 0   100.00%         195                 5    97.44%           0                 0         -
duck-control/src/io.rs               121                19    84.30%          19                 5    73.68%         108                17    84.26%           0                 0         -
duck-control/src/model.rs             89                 1    98.88%          12                 0   100.00%          55                 0   100.00%           0                 0         -
duck-control/src/obs.rs              260                 3    98.85%          23                 1    95.65%         174                 5    97.13%           0                 0         -
duck-control/src/policy.rs           276               182    34.06%          31                16    48.39%         189               116    38.62%           0                 0         -
duck-control/src/safety.rs           364                18    95.05%          28                 3    89.29%         249                 9    96.39%           0                 0         -
duck-ipc-proto/src/lib.rs            880                54    93.86%          58                 5    91.38%         653                27    95.87%           0                 0         -
padd/src/main.rs                     199               199     0.00%           5                 5     0.00%         127               127     0.00%           0                 0         -
robotctl/src/main.rs                1929               897    53.50%         121                54    55.37%        1459               632    56.68%           0                 0         -
robotctl/src/monitor.rs             1264               273    78.40%          81                15    81.48%         771               172    77.69%           0                 0         -
robotd/src/control.rs                 99                78    21.21%           6                 3    50.00%          83                59    28.92%           0                 0         -
robotd/src/intents.rs                133                 3    97.74%          13                 1    92.31%         100                 3    97.00%           0                 0         -
robotd/src/main.rs                  2107               672    68.11%         111                29    73.87%        1279               400    68.73%           0                 0         -
robotd/src/params.rs                 161                 2    98.76%          17                 0   100.00%         121                 0   100.00%           0                 0         -
robotd/src/soc.rs                     47                25    46.81%           4                 1    75.00%          30                15    50.00%           0                 0         -
test-support/src/lib.rs              272                 2    99.26%          22                 0   100.00%         167                 0   100.00%           0                 0         -
updater/src/config.rs                419                21    94.99%          36                 3    91.67%         342                16    95.32%           0                 0         -
updater/src/engine.rs               1784               256    85.65%         125                17    86.40%        1161               144    87.60%           0                 0         -
updater/src/faults.rs                 67                 6    91.04%           7                 0   100.00%          50                 0   100.00%           0                 0         -
updater/src/fsutil.rs                104                30    71.15%          10                 6    40.00%          51                24    52.94%           0                 0         -
updater/src/hooks.rs                 409                10    97.56%          32                 1    96.88%         343                 9    97.38%           0                 0         -
updater/src/ipc.rs                   747               213    71.49%          59                16    72.88%         482               120    75.10%           0                 0         -
updater/src/journal.rs               766                78    89.82%          54                10    81.48%         443                53    88.04%           0                 0         -
updater/src/lib.rs                    39                 9    76.92%           4                 0   100.00%          29                 9    68.97%           0                 0         -
updater/src/main.rs                  548               227    58.58%          33                10    69.70%         375               144    61.60%           0                 0         -
updater/src/manifest.rs              159                 6    96.23%          16                 0   100.00%         110                 1    99.09%           0                 0         -
updater/src/preflight.rs             225                12    94.67%          35                 3    91.43%         166                 9    94.58%           0                 0         -
updater/src/robot.rs                 116                18    84.48%          22                 4    81.82%          75                 9    88.00%           0                 0         -
updater/src/source/github.rs         439               165    62.41%          50                26    48.00%         281                99    64.77%           0                 0         -
updater/src/source/hf_hub.rs         136                72    47.06%          20                12    40.00%          81                41    49.38%           0                 0         -
updater/src/source/http.rs           319                82    74.29%          29                11    62.07%         260                88    66.15%           0                 0         -
updater/src/source/local.rs          346                37    89.31%          36                 9    75.00%         192                26    86.46%           0                 0         -
updater/src/source/mod.rs             39                30    23.08%           4                 2    50.00%          30                24    20.00%           0                 0         -
updater/src/store.rs                 510                40    92.16%          36                 7    80.56%         240                42    82.50%           0                 0         -
updater/src/verify.rs                786                95    87.91%          55                18    67.27%         424                75    82.31%           0                 0         -
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
TOTAL                              20147              5254    73.92%        1548               434    71.96%       13240              3458    73.88%           0                 0         -

The shared serve loop moves from a deferred refactor to step one. The three
loops differ in four ways that are requirements rather than drift — a peer
policy in two of three, updaterd's 1 MiB line cap against 64 KiB, a push side
in two of three, and two connection shapes — so this is a real abstraction
over dispatcher, policy, cap and push stream, not a copy-paste removal. Doing
it at three implementations rather than six is the argument for now.

The extraction surfaces something that should not be answered in a serve-loop
diff: robotd passes no policy, and is_mutating() does not cover robot.enable,
so the call that starts a policy running on a walking robot is classified
alongside hello. Recorded as an open question, with the note that classifying
intents as mutating would deny padd unless its user is allowlisted.

The correlation id now echoes on responses and notifications. The reason is
that a captured line becomes self-describing: a log excerpt pasted into an
issue can be reconstructed into a tree without also having the process memory
that held the mapping. Cost is a field on every reply, omitted when absent,
and about a kilobyte a second on a 50 Hz stream.

Every error response reaches info, not only the mutating ones — which hands a
retrying client the ability to evict a journal, so the loop collapses
identical consecutive errors and demotes a connection past an error budget.

The meter grows a crash record: the last snapshot plus a ring of the last N
events, flushed to a fixed-size file under /var/lib. The ring is the part
worth having — the trace cannot be left on permanently, and the events before
a service died are exactly what nobody can go back and collect.

Assisted-by: Claude:claude-opus-5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant