Repository navigation
Description
Problem
The metrics session frame is capability-authorized in the query phase but never checked at all in the joined phase. The frame always carries a capability (ClientFrame::Metrics { capability: String }, pkgs/id/src/world_session/types.rs:87-89; the shell contract lists metrics in the opening, query, and joined phases), and the two phases treat it inconsistently:
- Query phase (
pkgs/id/src/world_session/mod.rs:1324-1352): the handler takes the frame'scapability, builds aJoinCapability, and gates onservice.world().snapshot(token)— exactly likeinfo. A bogus capability getsjoin denied. The scripted-session test pins this:metrics_frame_reports_each_host_metric_after_a_scripted_session(pkgs/id/src/world_session/tests.rs:193, bogus capability refused at :214, valid capability served at :230). - Joined phase (
pkgs/id/src/world_session/mod.rs:2124-2134): the comment says "Likeinfo, the joined phase may read the world's metrics with the session's own capability", but the handler matchesClientFrame::Metrics { .. }— discarding the capability — and callsworld.metrics().await.WorldHandle::metrics(pkgs/id/src/world/handle.rs:116-128) takes no token parameter, so no validation is possible on this path. Neither the frame's capability nor the session's join token is ever presented.
Contrast the joined-phase info handler directly above it (mod.rs:2108-2123), which passes token.clone() to world.snapshot(...) and refuses with join denied when the capability no longer joins.
Why it matters
- A participant whose capability has been revoked keeps read access to the world's host metrics for as long as their frames win the race against the session loop's revocation branch (
mod.rs:1836-1843breaks the loop when the revocation broadcast is received; a metrics frame already selected is served first). The codebase's stated enforcement model is per-delivery re-validation — "Projecting the event re-validates the capability on every delivery, which is what disconnects a revoked participant" (mod.rs:1796-1798) — and the metrics path bypasses it entirely. - The metrics are operational data about a world the participant may no longer have any grant for: live session count, command/journal/checkpoint rates and latencies, frame bytes (
MetricsSnapshot,pkgs/id/src/world/metrics.rs:83-108). - The query phase and its test establish the intended authorization ("holding a capability that snapshots the world is enough to read its metrics",
mod.rs:1326-1327); the joined phase silently not enforcing it is a defect in F11.7 slice 1 (id(world): F11.7 host metrics, Prometheus endpoint and tracing spans #553, PR feat(id): F11.7 slice 1 — host metrics core + world stats surfacing (#553) #1437), not a design difference — the joined handler's own comment claims theinfopattern it does not implement.
Scope note for triage: the joined-phase caps handler (mod.rs:2136-2149) has the same token-less shape via WorldHandle::caps_report (handle.rs:372). It predates F11.7 and has no authorizing query-phase twin, so it is not filed here — triage should rule whether the fix pattern extends to it in the same pass or a follow-up.
Acceptance criteria
- Joined-phase
metricsvalidates the session's capability the way joined-phaseinfodoes (gate onsnapshot(token), or a token-taking metrics path onWorldHandle); a metrics frame from a session whose capability no longer joins is refused (join denied/ session ended), never served. - A scripted-session test mirrors the query-phase test: join, revoke the participant, send a joined-phase
metricsframe, assert no metrics reply is served. The existing query-phase metrics test and the metrics unit tests still pass. - The joined-phase handler's comment matches the implemented behaviour.
- Triage records a ruling for joined-phase
caps(same fix pass or a follow-up issue).
Found by new-issue-creator (lane A) on main 931f1771, from a line-by-line read of the F11.7 slice-1 wiring (PR #1437). Related: #553 (the F11.7 epic — this is a defect in its shipped slice 1, not a duplicate).
Activity
developing-today commentedon Oct 27, 2026
CODE-569
dezren39 commentedon Oct 27, 2026
🤖 Agent note · filed
71 remaining items
dezren39 commentedon Oct 31, 2026
🤖 Agent note · parked
dezren39 commentedon Oct 31, 2026
Agent note — pickup (resume, merge shift)
Merge pull request #2922 from developing-today/queue/1768-1506-joined…
dezren39 commentedon Oct 31, 2026
Agent note — done (merged)