DESIGN.md §9.11 codifies one canonical right-side detail-sheet shell (cyan
rail, mono crumb, italic serif name, mono meta, ESC chip + close glyph,
fixed three-slot toolbar, cyan-underline tabs, ScrollArea body, footer
freshness band). Today the 16 sheet consumers each render their own
header chrome with stock shadcn SheetHeader/SheetTitle.
Introduce <SystemSheet> + <SheetSection> in
frontend/src/components/ui/system-sheet.tsx, composing the existing
<Sheet>/<SheetContent> primitive. Add a backward-compatible showClose
prop to SheetContent so SystemSheet can render its own ESC chip + close
glyph instead of the stock cyan square close.
Migrate the four mesh sheets as the first batch:
* MeshActivitySheet: crumb Fleet › Mesh › Activity, footer freshness from
most-recent event timestamp.
* MeshOptInSheet: crumb Fleet › Mesh › {nodeName}, meta of opted-in
count, drops the redundant bottom Close button (ESC chip dismisses).
* MeshDiagnosticsSheet: removes the icon-prefixed title (forbidden by
§9.11), lifts Refresh/Restart buttons from the body into the toolbar
band, three SheetSection blocks for sidecar status, streams, cache.
* MeshRouteDetailSheet: adds Overview/Events/Raw tabs, lifts Test probe
into the toolbar primary slot, footer surfaces last probe latency.
* feat(fleet): sencho mesh in traffic and routing tab
Lights up Sencho Mesh: cross-node container forwarding rendered as if the
container next to you were on localhost. Builds on the dormant TCP frame
plumbing from the prior PR (pilot tunnel TCP frames + sencho-mesh sidecar
package) and exposes the Admiral-only orchestrator surface.
Backend
- New mesh_stacks table (per-node opt-ins) + nodes.mesh_enabled column
via DatabaseService.migrateMeshTables.
- MeshService singleton: sidecar lifecycle via Dockerode, opt-in/out with
cascading override regeneration, request-based resolver from sidecar
control WS, cross-node TCP forwarding via PilotTunnelManager (same-node
fast path included), in-memory 1000-event activity ring buffer with
durable mirror to audit_log for state-change events, per-node and
per-route diagnostics, and the Test upstream probe.
- MeshComposeOverride: pure YAML generator that injects extra_hosts using
host-gateway. The user's docker-compose.yml is never mutated; overrides
live under DATA_DIR/mesh/overrides.
- ComposeService deploy/update splice the override file when the stack
is opted in; non-mesh stacks behave identically to today.
- Pilot agent resolveMeshTarget consults the local mesh_stacks table
(defense in depth) and resolves Compose containers via Dockerode.
- /api/mesh router with 13 Admiral-gated endpoints covering status,
enable/disable, stack opt-in/out, alias listing, per-route diagnostic,
Test upstream probe, per-node diagnostic, sidecar restart, activity
log paginated and SSE.
- meshControl WS slot at /api/mesh/control validates the mesh_sidecar
JWT minted by MeshService; dispatched as upgrade slot 2 (canonical
order preserved).
Frontend
- New Traffic Routing tab in FleetView, gated by isAdmiral and wrapped
in AdmiralGate. Tab uses the cyan brand glyph and italic-serif state
typography from the audit.
- RoutingTab masthead with mesh activity drawer, per-node card grid
with TogglePill, alias rows with five-state pill taxonomy
(healthy / degraded / unreachable / tunnel-down / not-authorized),
inline Test buttons.
- Four sheets: opt-in picker with port-collision inline error,
per-route detail with diagnostic + filtered activity, per-node
diagnostics with active streams + resolver cache + restart action,
fleet-wide activity log with filters.
- meshRouteState helper centralizes pill-state mapping; pure-function
tests cover all five states.
Docs
- User docs at /docs/features/sencho-mesh.mdx covering opt-in,
troubleshooting, security model (4 guarantees + 4 explicit
non-guarantees), and V1 limitations.
- Internal architecture and runbook pages.
- websocket-dispatch internal doc updated with the new slot.
* fix(mesh): validate stack name before path use; fix test DB lifecycle
Two surgical fixes against the prior PR.
Path-injection (CodeQL js/path-injection): MeshService.optInStack,
optOutStack, ensureStackOverride, and removeStackOverride now validate
stackName via isValidStackName from utils/validation, reject malicious
names at the API boundary, and additionally check isPathWithinBase on
the resolved override file path for defense in depth. The dataflow from
req.params.stackName to fs.writeFile no longer reaches an unsanitized
path expression.
Test DB lifecycle: mesh-service.test.ts used per-test setupTestDb /
cleanupTestDb, which deletes the temp dir while DatabaseService still
holds an open SQLite handle. On Linux CI this raises
SQLITE_READONLY_DBMOVED on the next prepare() because the inode has
been unlinked. Switched to file-scoped beforeAll/afterAll matching
agents-routes.test.ts, with a per-test beforeEach that truncates
mesh_stacks plus non-default nodes and resets the MeshService singleton
in-memory state. Adds a new test case asserting the path-traversal
rejection.
* fix(compose): use discovered compose filename instead of hardcoded docker-compose.yml
composeArgs() hardcoded `-f docker-compose.yml` for every deploy. Sencho
writes its canonical compose file as `compose.yaml`, so any stack created
via the UI failed to deploy with `open ...docker-compose.yml: no such
file or directory`.
When no mesh override applies, drop the explicit `-f` so docker compose's
built-in discovery resolves the actual filename. When an override exists,
look up the real base filename via FileSystemService.getComposeFilename()
and pass both files explicitly.
Also hoist the MeshService import to module top now that the dependency
is known to be acyclic, and revert the matching unit-test assertion.