The integrity check mapped four outcomes onto a single null: no response,
a 404, a rejected key, and a genuine answer. Only "rejected key" is
recoverable, and it was being reported as "unreachable" and never repaired —
so an instance registered against the wrong URL stayed broken until someone
edited the database by hand. That is exactly what happened locally.
GetRegisteredMasterUrlAsync now returns an outcome alongside the URL.
Unauthorized triggers registration; 404 is reported as "this host does not
serve the master/slave protocol", which names the actual mistake instead of
hiding it behind a generic contact failure; unreachable and server errors
behave as before.
Registering on a rejected key cannot hijack a slave that belongs to another
master: the slave accepts a registration only when it has none, and refuses
any key that does not match an existing one. So it succeeds exactly in the
case worth recovering and fails harmlessly otherwise. That guarantee lives on
the slave, so the test asserting the refusal now says out loud that the
master depends on it.
Found while diagnosing a status push that failed against a frontend URL.
Small and contained, so fixed here rather than filed as tech debt.
372 tests pass, up from 366.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHoJpxYXzHACSQguHrC5fw
Frontend (Unit 2 completion): dual dev-server tooling (pnpm dev:slave,
pnpm dev:all), per-instance browser tab titles, and a backend
capability check (SystemController + useSystemCapabilities +
ModuleGuard) so a Master-only page is hidden on a slave instance
instead of assuming every backend has every module.
Master/slave protocol fixes surfaced by actually running master and
slave side by side locally:
- Deactivating a CMS instance (Inactive) now releases the slave's
master gate instead of leaving it stuck on its last pushed status.
- The periodic integrity check now also re-pushes status to every
reachable slave (previously URL-verification only) and runs once
immediately on startup.
- Added the originally-specified (but never implemented) slave-pull
path: a slave now periodically polls its own status from the master
(GET /api/v1/SlaveStatus) and fails open to Available if the master
is unreachable for too long, complementing the existing push.
- The slave's own Settings page can no longer "successfully" change
local availability while the master controls it; it's now locked
with an explanatory banner and the backend rejects the write with
409 instead of silently no-op'ing it.
- CMS instance status badges now match the dashboard's color/icon
styling instead of a plain grey badge.
Also corrected the master-cms-module design docs to match this
as-built behavior, and flagged (without a full rewrite) a larger,
pre-existing divergence between its inception-stage application
design and what construction actually built.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>