LearnNewsExamplesServices
Frontmatter
id17157
titleBroken viewer binding renders binding-unavailable, never an empty fleet
stateClosed
labels
enhancementaiagent-os
assigneesneo-fable-clio
createdAtAug 15, 2026, 11:18 AM
updatedAtAug 15, 2026, 2:52 PM
githubUrlhttps://github.com/neomjs/neo/issues/17157
authorneo-fable-clio
commentsCount0
parentIssue16737
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 15, 2026, 2:52 PM

Broken viewer binding renders binding-unavailable, never an empty fleet

Closed Backlog/active-chunk-16 enhancementaiagent-os
neo-fable-clio
neo-fable-clio commented on Aug 15, 2026, 11:18 AM

Split leaf of #16737 (S3 roster) — leg 2 of the four remaining: the identity-binding third-signal render. Sibling of #17153 (leg 1, PR #17154 in review).

Context

#16737's AC: "Three independent signals, none inferring another: presence-fresh ≠ wake-route-healthy ≠ identity-bound. A broken VIEWER binding renders 'binding unavailable' (blocker-code pattern), never 'no peers online'." Today the information survives only as redacted prose: a binding-class failure in the plane presence read throws, the adapter catches, and the capability degrades with the error text as reason — indistinguishable BY TYPE from "plane unreachable". A renderer that wants to show "binding unavailable" distinctly would have to string-match prose, which the codebase's own precedent explicitly warns against.

The Problem

  • The presence read path (planeWhoIsOnlineReaderplaneMailboxClient.callTool('who_is_online')) has binding-class failure modes the CLIENT already recognizes at throw time: the reconnect identity re-verify refuses on a changed/unprovable identity (client-owned throw, no replay), and plane-side admission/ingress denials surface as tool-error text ("that text is OUR plane's service message", the client doc notes).
  • None of these throws carries a typed marker, so every consumer downstream sees an opaque Error — the type knowledge dies at the throw site.
  • The established classification pattern (the "blocker-code pattern" the parent cites) lives in fleetMailboxMirrorAdapter.mjs: scoped-specific contract-text classification with an explicit anti-pattern warning — "Matching bare Unauthorized would misclassify any unrelated authorization failure as the named admission decision" (ADMISSION_SCOPE = 'CAN_READ_INBOX_OF').

The Architectural Reality

  • ai/services/fleet/planeMailboxClient.mjs — owns the identity re-verify throw (knows it is binding-class at throw time) and maps tool-error text (the plane's own service messages).
  • ai/services/fleet/planeWhoIsOnlineReader.mjs — the thin reader; stays thin.
  • ai/services/fleet/fleetPresenceStateAdapter.mjs — catches thrown reads into the degraded envelope (capability.reason, redacted); rows go unknown with the read reason.
  • ai/services/fleet/fleetCockpitStatus.mjs — passes the SUPPLIED presence capability through to the cockpit DTO (verified: supplied object wins over the not-wired default).
  • Tier-degradation contract governs: absence of classification produces absence of the typed code, never a guessed verdict.

The Fix

  1. Producer classifies at the site that knows (never downstream message-matching): the client stamps a typed property (e.g. error.planeBlockerCode = 'viewer-binding-unavailable') on the throws it ALREADY knows are binding-class — the identity re-verify refusal, and the scoped-specific admission/ingress denial texts per the mirror-adapter pattern (exact-text contract match, never bare Unauthorized). First implementation step: enumerate the client's throw sites and bind the exact contract texts (the mirror-adapter discipline).
  2. Adapter passes through, never infers: a recognized typed code on the caught error lands verbatim as capability.reasonCode beside the existing redacted reason; absent code ⇒ absent field (closed passthrough, no adapter-side vocabulary invention).
  3. Cockpit DTO carries it: the presence capability's reasonCode reaches the cockpit snapshot unchanged (expected zero-diff in fleetCockpitStatus — verify by spec, not assumption).
  4. Render-side consumption ("binding unavailable" pane state) is C3's job and out of scope here — this leg makes the distinction RENDERABLE (typed in the DTO), which is what the parent AC requires of the roster surface.

Acceptance Criteria

  • Client: binding-class throws carry the typed code; at least the identity re-verify site + the enumerated admission-denial contract text(s); non-binding failures (transport, timeout, 5xx, malformed payload) carry NO code.
  • Adapter: capability.reasonCode passthrough — present iff the caught error carries a recognized code; reason prose behavior unchanged; rows unchanged.
  • Cockpit: supplied presence capability with reasonCode reaches the DTO intact (spec-verified).
  • Specs cover: binding-classified read → envelope degraded + reasonCode: 'viewer-binding-unavailable' + rows unknown (never an empty/fabricated fleet); unclassified failure → degraded WITHOUT reasonCode; the anti-misclassification case (an unrelated auth-ish error text does NOT classify).
  • Scoped receipt: adapter + mirror/cockpit spec files green locally, receipts in the PR.

Out of Scope

C3 render-side pane states · legs 3–4 of #16737 (wake-route health, S5-gated scoping) · any change to the plane's own service messages · retrofitting typed codes onto non-presence consumers of the client (mirror adapter keeps its own established classification).

Related

#16737 (parent) · #17153 / PR #17154 (leg-1 sibling) · #16824 (scoped-empty roster — the C3-adjacent cousin) · D#16720 (source discussion).

Live latest-open sweep: latest 15 checked 2026-08-15T09:24Z (newest #17155); no equivalent. A2A in-flight sweep: recent traffic re-checked this hour; active claims (Vega #16972 · Grace #17141 · Phoebe #17147/PR #17156 · Ada #17148 · Euclid #17132-analysis) are disjoint.

Origin Session ID: 1deebbe1-b7e6-4f76-b39d-9cfcbe342596

Retrieval Hint: "viewer binding unavailable blocker code presence capability reasonCode" · "planeMailboxClient identity re-verify typed throw"

tobiu referenced in commit cf61828 - "feat(fleet): broken viewer binding renders binding-unavailable, never an empty fleet (#17157) (#17159)" on Aug 15, 2026, 2:52 PM
tobiu closed this issue on Aug 15, 2026, 2:52 PM