fix(photon): support immutable install trees for the sidecar (NS-606)

The Photon iMessage sidecar needs node_modules under
plugins/platforms/photon/sidecar/, but hosted/managed images keep the
whole install tree under an immutable /opt/hermes — every install and
self-heal path (setup CLI, stale-deps reinstall, cold install) died on
EROFS, and hosted users have no shell to work around it.

Three-layer fix, mirroring the WhatsApp bridge resolver pattern:

1. Bake the deps into the image. The Dockerfile now runs npm ci for the
   sidecar in the layer-cached dependency stage (deterministic installs
   from the committed lockfile; the postinstall spectrum-ts patch runs
   at build time). Hosted happy path needs no runtime install at all.

2. New sidecar_paths.resolve_sidecar_dir() decides where the sidecar
   runs from: PHOTON_SIDECAR_DIR override > writable source dir (dev
   installs, unchanged) > read-only dir with baked fresh deps (managed
   image) > mirror to $HERMES_HOME/photon/sidecar (writable data
   volume) when deps are missing or stale in a read-only tree. The
   mirror refreshes changed source files on image updates while
   keeping node_modules, so the existing lockfile-staleness self-heal
   works there.

3. connect() can now cold-install: _start_sidecar() runs the bounded
   npm ci bootstrap when node_modules is missing instead of raising
   immediately, and check_requirements() reports available when a
   self-install is possible (npm present + writable resolved dir) so
   the gateway actually creates the adapter on hosted instances. A
   failed bootstrap still raises the actionable error, which connect()
   surfaces as the retryable SIDECAR_FAILED fatal state on the
   dashboard.

Tests: resolver decision table (env override, in-place, mirror,
refresh, fail-open), cold-install lifecycle paths, and a Dockerfile
contract test guarding the baked-deps + no-chown invariants.

Fixes NS-606.
This commit is contained in:
Shannon Sands 2026-07-23 15:55:29 +10:00 • committed by Teknium
parent a65494ed00
commit 0dfd5546fc
9 changed files with 472 additions and 10 deletions

View file

@ -30,8 +30,11 @@ from hermes_cli.colors import Colors, color
from . import auth as photon_auth
from .adapter import _NPM_ERROR_LOG_MAX_CHARS, sidecar_deps_installed
from .sidecar_paths import resolve_sidecar_dir
_SIDECAR_DIR = Path(__file__).parent / "sidecar"
# Writable sidecar runtime dir (mirrors to HERMES_HOME on immutable
# installs — NS-606). All npm/setup work happens here.
_SIDECAR_DIR = resolve_sidecar_dir()
# Written on npm failure so check_requirements() can surface the root cause
# when called later (gateway start, hermes status). Cleared on success.
_NPM_ERROR_LOG = _SIDECAR_DIR / ".photon-npm-error.log"