[v0.1/P1] Make daemon status resource scopes unambiguous #26
Labels
No labels
priority/P0
priority/P1
priority/P2
release/v0.1.0
status/blocked
status/planned
type/bug
type/design
type/test-gap
type/tracker
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
natsukium/felis#26
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent: #12 (P1.6). Supersedes the status-reporting part of #10.
Why
daemon statuscurrently prints every resource asused / limit, although some values are daemon-wide totals, some are deepest-subject samples, and limits may be global, per-session, or per-subscriber. The displayed ratios are dimensionally ambiguous.Scope
used/limitwithtotal_used,max_subject_used,per_subject_limit, andglobal_limit.Acceptance criteria
Triage plan (2026-09-03)
Source-grounded triage against
mainat69076d42, reviewed through seven rounds of an independent reviewer (pisol/luna) until it passed with no findings. The dependency order that supersedes the tracker's is posted on #12.Claim check
Accurate on substance; one part overstated.
crates/felis-protocol/proto/felis.proto:1389-1401ResourceReport { resource, unit, used, limit?, scope }, with the comment "usedandlimitdo not always share a denominator, which is whatscopestates".LimitScope(:1433-1444) saysSESSION: "usedis the daemon-wide sum ... the pair is not a ratio";SUBSCRIBER: "usedis the deepest single subscriber".crates/felis-daemon/src/serve.rs:928-1010daemon_status:image_bytesis a sum over sessions against a per-session cap (:944,:975-981);decode_bytesis a sum against the per-sessionREASSEMBLY_BUFFER_LIMIT(:993-999);deepest_outboxis a max against the per-subscriber cap (:1000-1006). So one field name (used) carries a sum in two rows and a max in a third. The per-session max for image bytes is not reported at all, though the actor already returns it (session_task.rs:51-59SessionStats.image_bytes), so an operator seeingimage_store_bytes 300 MiB / 256 MiB per sessioncannot tell whether any single session is near its cap.crates/felis-cli/src/cli_daemon.rsprint_status:"{used} / {limit} per {scope}", with the comment "The scope is printed on every row ... a reader who must notice an absent qualifier has been given a trap"), anddocs/reference/cli.md:275-281has a table saying which rows are not ratios. The ambiguity is real but it is the/glyph plus the hidden sum/max inconsistency, not a missing label.image_store_bytesandin_flight_decode_bytes(no max-subject value), passes forsessions(serve.rs:965-971) andsubscriber_queue_bytes(already a max).SessionCmd::Statsreplies (serve.rs:940-951); no hot-path counter exists to drift. Criterion "keep sampling authoritative" is satisfied by construction and stays so if the new fields are computed in the same loop.Verdict
accept-with-changes. The four-field shape is right because #14 (P0, lands first) introduces daemon-wide budgets beside per-subject caps, at which point a row genuinely has two ceilings and a single
limit+scopepair can no longer say which is which. Without #14 aglobal_limit/per_subject_limitpair would be two optionals with at most one set, the optional-bag shape #48 is removing elsewhere. So: land after #14 and read the ceilings from its admission object.Changes to the written scope:
scope(as subject scope:daemon | session | subscriber), sincemax_subject_usedis meaningless without naming the subject. Drop the "limit scope" meaning; the two limit fields carry that now.total_usedis always present;max_subject_usedandper_subject_limitare absent fordaemonscope;global_limitis absent where #14 sets no aggregate budget. proto3optionalon all three.image_store_bytes total 300 MiB · max session 120 MiB / 256 MiB per session · 1 GiB per daemon); nevertotal / per-subject.Approach
Protocol:
ResourceReport { resource, unit, uint64 total_used, optional uint64 max_subject_used, optional uint64 per_subject_limit, optional uint64 global_limit, SubjectScope scope }(felis.proto:1389-1401); renameLimitScope→SubjectScopeand reword its comments; updateOpsStatusReplycomment (:1369-1387). Addbool drainingfor #25.messages.rs/convert.rsfollow.Daemon (
serve.rs:928-1010): in the same sampling loop, trackmax_image_bytes = max(...)andmax_decode_bytesbeside the sums;deepest_outboxbecomesmax_subject_usedand a newsumof backlogs becomestotal_used(SessionStats.max_subscriber_backlogneeds a siblingtotal_subscriber_backlog,session_task.rs:1220-1228). Rows:sessions(daemon: total, global_limit),image_store_bytes(session: total, max, per_subject_limit=image_cap, global_limit from #14),in_flight_decodes(daemon),in_flight_decode_bytes(session),subscriber_queue_bytes(subscriber).Client core / CLI:
DaemonStatus/ResourceReportpassthrough;cli_output.rs:570-585ResourceObjectgains the fields (skip_serializing_if = Option::is_none);cli_daemon.rsresource_object,print_status, andrender_amountreworked per verdict item 3;scope_tokenunchanged.Docs cascade:
docs/reference/cli.md:253-300(example block, machine object, the scope table becomes a field-presence table),docs/reference/spec.md:262REQ-1102 wording ("pairing observed usage with the configured limit" → the four dimensions),docs/reference/ipc.mdOps section and:1817(subscriber_queue_bytessentence), ledger/2.0 base,docs/reference/control-surfaces.md:33,docs/explanation/architecture/control-surfaces.md:140-160(record why a max-subject sample and not a per-subject table: the table issessions info's job; "Revisit if" an operator needs to name the offending session from status alone),skills/felis/SKILL.md:213-237(the "Readscopebefore comparing" paragraph),CHANGELOG.md.Tests:
serve/tests.rs:4432and:4522extended with two sessions holding different image byte counts (asserttotal == a + b,max == max(a, b));cli_daemon.rstests: golden JSON for one row of each scope (field presence exact), and a human-render test asserting no line contains atotalvalue followed by aper session/per subscriberdenominator;every_resource_has_its_documented_tokenkeeps the tokens.Dependencies
global_limit; deciding the shape before it means guessing which rows get an aggregate).drainingto the same reply; land together or #26 first).usedis a break).Risk/effort
S–M. Mechanical once #14's ceilings exist. Main risk: doing it before #14 and then re-breaking the reply when the aggregate budgets arrive, which is the "clean baseline immediately accumulates another break" failure #52 warns about.
Labels
Keep
release/v0.1.0(the machine object freezes at the tag). Priority could drop to P2 on user impact alone, since scope is already labelled on every row, but the field rename must precede #30, sopriority/P1stays for sequencing.