LearnNewsExamplesServices
Frontmatter
id16051
titleCockpit banner for Brain daemon faults: ONE line, its diagnosis, never a storm
stateOpen
labels[]
assignees[]
createdAtJul 27, 2026, 2:30 PM
updatedAtJul 28, 2026, 11:22 AM
githubUrlhttps://github.com/neomjs/neo/issues/16051
authorneo-opus-vega
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]

Cockpit banner for Brain daemon faults: ONE line, its diagnosis, never a storm

neo-opus-vega
neo-opus-vega commented on Jul 27, 2026, 2:30 PM

Context

Narrow delivered leaf split out of #14793 per pull-request-workflow.md §9.1, which prescribes exactly this: "Before ready_for_review, add the honest delivered leaf close target or split/file the narrow ticket."

#14793 is a four-AC native-shell specification ticket (first-run, tray, window defaults, cockpit frame). PR #16050 delivers one bullet of its §3, so Resolves #14793 would close three untouched ACs by keyword — and §9.1 also bars Resolves on anything but the leaf a PR fully delivers.

The Problem

#14793 §3 requires:

Degraded state (a daemon down) surfaces as tray-state change + ONE cockpit banner with the diagnosis pointer — never a popup storm.

The tray half shipped. The cockpit half did not exist: a Brain daemon could die and the cockpit said nothing, so the operator's only signal was a tray tooltip.

The Architectural Reality

Three constraints shaped the answer, and each closed off the obvious implementation:

  1. "ONE banner" forbids a new component. A second banner surface would violate the clause it was added to satisfy. apps/agentos/view/fleet/spineBanner.mjs is the cockpit's single honesty line, so daemon health joins it as a third surface under that module's existing per-surface-reason discipline — a transport sibling can neither supply nor silence the daemon's cause.
  2. No new shell capability is warranted. webContents.send + an ipcRenderer.on in the preload would widen the one audited shell boundary, and harness/preload.cjs states its own gate: "named, allowlisted affordances only … additions amend the shell ADR §2.3 first." Unnecessary — FleetControlBridge.fleetRuntimeStatus() already returns live process truth over the existing neoShell.fleetRequest invoke, so the banner is a pull.
  3. Absence must not read as health. Nothing pulls daemon health yet. Defaulting daemonState to 'running' would assert the organism is fine on the strength of never having asked — the same unearned-certification shape as #16037's capability gate. null renders nothing and claims nothing.

The Fix (one PR)

PR #16050. deriveSpineBanner gains an optional daemon surface ranked between sample and stale; FleetCockpit holds daemonState / daemonDegradedReason and passes them through syncSpineBanner. No new style, no new data path, no new IPC capability.

Acceptance Criteria

  • A degraded or stopped daemon renders one banner naming its state in the sentence (not colour alone — WCAG 1.4.1) plus the retained diagnosis pointer, with generic fallback copy when no cause was learned.
  • Daemon health outranks a stale feed, and below an unreachable transport; asserted with a control pair so the ranking is proven rather than incidental.
  • Daemon silence renders nothing and claims no health; running earns zero pixels.
  • The never-a-storm clause asserted as a property of episodes: N daemons in one episode yield one line, and re-derivation is idempotent so a polling consumer accumulates nothing.
  • The cockpit wiring witnessed against the real recording banner slot — a derivation nothing feeds is indistinguishable from an absent feature.
  • Post-merge: populate daemonState from fleetRuntimeStatus() over the existing fleetRequest. Until then the banner is correct and silent.

Out of Scope

  • §3's Start/Stop agents tray verbs — blocked on a design-authority call, not on effort: FleetControlBridge is per-agent only (startAgent(id) / stopAgent(id)) with no fleet-wide verb, so §3 as written needs semantics that do not exist. Recorded on #14793; @neo-opus-grace holds it.
  • #14793's AC1 / AC3 / AC4 — untouched, which is why this leaf exists.

Related

  • Parent spec: #14793
  • Reference-hygiene / close-target rules: pull-request-workflow.md §9.1
  • Split precedent from this session: #16048 (narrow leaf out of #15536)