fix(mesh): surface stopped-stack opt-ins on routing node cards (#1098)

Add `currentlyResolvable: boolean` per entry in `MeshNodeStatus.optedInStacks`,
derived from the existing alias cache so the new field stays consistent with
`/api/mesh/aliases` without any extra Dockerode or cross-node inspect on the
status path. The Routing tab renders an amber `suspended` pill for entries
whose stack is opted in but currently has no running services, plus a single
explanatory caption below the suspended list.

Resolves the contradictory `Mesh stacks: 1 / Aliases: 0 / No mesh services
on this node yet` copy on the node card when a meshed stack's container has
been stopped; the misleading line is now only shown when the node truly has
no opt-ins. The opt-in itself remains sticky: when the stack starts again,
its aliases reappear automatically on the next refresh.

Defensive de-dup in the UI filters suspended entries against the live alias
snapshot to handle the transient gap where `/mesh/status` and `/mesh/aliases`
return slightly inconsistent views from their separate fetches.

Tests:
- new `mesh-status-resolvability.test.ts` (6 cases) locks the resolvable /
  suspended / mixed / empty / per-node-scoping / stale-alias-no-phantom
  invariants for `getStatus`.
- `mesh-topology-layout.test.ts` gains a `stacksKey` resolvability-flip case
  and a `meshNodeStateEqual` case asserting a resolvability flip on an
  otherwise identical stack registers as a state change so the topology
  layout re-runs.

Operator docs gain one new troubleshooting accordion in
`/docs/features/sencho-mesh.mdx` explaining the suspended state.
This commit is contained in:
Anso
2026-05-18 01:52:18 -04:00
committed by GitHub
parent 77782ce1ff
commit 554f662563
7 changed files with 237 additions and 11 deletions
+8 -1
View File
@@ -53,7 +53,14 @@ export interface MeshNodeStatus {
reachableReason: string | null;
/** Peer→central reverse path state. `not_applicable` for non-proxy peers. */
reverseCallbackStatus: MeshReverseCallbackStatus;
optedInStacks: string[];
/**
* Stacks opted into the mesh on this node. `currentlyResolvable` is `true`
* iff the central's alias cache currently carries at least one alias for
* that (nodeId, stackName) pair. A suspended opt-in (stack stopped,
* services not running) reports `currentlyResolvable: false`; the Routing
* tab renders a `suspended` pill for those entries.
*/
optedInStacks: Array<{ stackName: string; currentlyResolvable: boolean }>;
activeStreamCount: number;
}