Skip to content

Anchor-Repair Service

Status: ✅ Shipped (active repair)anchor_repair_service.py ships the four existence checks + region_anchor_missing detection and Phase-11 reinjection (reinject_missing_anchors / placement helpers reproducing capital / capital+1 / capital+9 / total−5 with cluster fallbacks). Daily scan repairs missing anchors under a per-region advisory lock; emits region_anchor_repaired / region_anchor_repair_failed. There is still no GalaxyGenerator._place_phase_11_anchors — this module is that helper (DECISIONS anchor-repair-active-loop-build). (detect-only thin v1: 9d2b65ba; active loop: WO-FIX-ANCHOR-REPAIR-ACTIVE-LOOP)

Shipped. Detection + reinjection. Canonical decision in ADR-0053 WR12. Placement rules match galaxy generation Phase 11 per ./galaxy-generator-design.md.

Galaxy generation Phase 11 anchors four critical structures per region: the Capital welcome planet (TERRA), the Class-1 commerce station, SpaceDock #1 (starter-cluster anchor), and SpaceDock #2 (frontier anchor). These are load-bearing for new-player onboarding, faction-rep economics, and ship-construction services. Phase 11 is worldgen-time only — no runtime mechanism detects-and-repairs a missing anchor (e.g., destroyed in late-game faction war, or never injected due to a Phase 11 transient failure that wasn't caught by Phase 13 validation).

This service is the daily-cadence safety net that detects missing anchors and re-injects them.

Cadence and scan target

Cadence: once per canonical day, gated via Galaxy.state day-anchor (mirrors region_lifecycle's own gate discipline) — rides the governance sweep's Phase 8 tick, not a separate cron file.

Scan target (shipped): each Region with status = 'active' (region_is_scannable), skipping CENTRAL_NEXUS. The doc's broader pending/suspended/grace/terminated/generation_corrupt/attachment_pending skip list matches — any non-active status is excluded by the same active-only check.

Anchor checks per region

For each active region, verify all four Phase-11 anchors:

Anchor Check
Capital welcome planet EXISTS row in Planet with region_id = region.id, sector_number = region.capital_sector_number, planet_type = 'TERRA'
Class-1 commerce station EXISTS row in Station with region_id = region.id, station_class = CLASS_1, sector_number IN (region.capital_sector_number + 1, ...starter-cluster fallback)
SpaceDock #1 (starter-cluster anchor) EXISTS row in Station with region_id = region.id, is_spacedock = true, region_assignment_role = 'starter' (shipped value — not 'starter_cluster'), in starter cluster. Unset roles across every SpaceDock in the region return unverifiable rather than a false missing.
SpaceDock #2 (frontier anchor) EXISTS row in Station with region_id = region.id, is_spacedock = true, region_assignment_role = 'frontier', in Frontier zone. Same unverifiable fallback as above.

Each check is a single existence query. Total per-region cost: 4 SQL queries; very small. Shipped as-is (scan_region / check_capital_terra / check_class1_commerce / check_spacedock_role).

Action on missing anchor — ✅ Shipped

Detection emits a region_anchor_missing event (type/region_id/region_name/anchor_type/sector_id) and a logger.warning, then run_daily_scan calls reinject_missing_anchors:

  1. Re-inject via Phase 11 placement logic. Implemented in anchor_repair_service.py (place_capital_terra / place_class1_commerce / place_spacedock + candidate helpers). There is no separate GalaxyGenerator._place_phase_11_anchors to import — this is the helper.
  2. Idempotent placement: preferred sectors are capital (TERRA), capital+1 (CLASS_1), capital+9 (starter SpaceDock), total−5 (frontier SpaceDock); occupied station slots fall back within the starter cluster / outer frontier band. Re-scan under advisory lock prevents double-place.
  3. On success: emit region_anchor_repaired (dict + logger.info) with the anchor type and placement sector, then fan out post-commit via _broadcast_events to the region room and admins (LEG-157 / c2538f02).
  4. On failure (no eligible sector): emit region_anchor_repair_failed + logger.warning (same post-commit realtime fan-out). Region stays active with the missing anchor until the next daily pass or manual ops.

Idempotency

  • A region with all four anchors intact is a no-op pass (4 quick existence queries, 0 actions).
  • Running the service twice on a region with a missing anchor is safe — the second pass finds the anchor already injected by the first pass.
  • A missing anchor that fails injection is logged and alerted, but the service does not retry within the same daily run; the next day's pass tries again.

Why daily, not faster

Anchor destruction is rare. Capital welcome planets, Class-1 stations, and SpaceDocks are heavily defended (citadel-level structures + faction patrol presence). Players generally cannot destroy them; the failure mode is mostly "Phase 11 had a transient bug that Phase 13 missed, and the anchor was never injected" — a worldgen-time issue caught at the next daily scan.

A more frequent cadence (hourly, every-15-min) would burn cycles re-checking unchanged regions for the rare destruction event. Daily catches the realistic failure modes without overhead.

Failure modes

Mode Detection Handling
Service down for > 1 day Heartbeat absent for > 24h On restart, runs immediately; backlogged repairs surface.
Re-injection fails (sector occupied + fallback exhausted) Placement helper returns failure Emit region_anchor_repair_failed + admin alert. Region operates with missing anchor until manual ops fix.
Re-injection succeeds but the new anchor is in an unexpected location Placement-helper picked a fallback Emit region_anchor_repaired with the chosen sector in the payload; operator can review and decide if a manual relocation is warranted.
Multiple service instances scanning concurrently Two instances both attempt re-injection on the same region The placement helper uses a per-region advisory lock (pg_advisory_xact_lock(hashtext('region:' || region_id))), per the SK18 pattern from ADR-0050. One instance wins; the other no-ops.

Source map

Concern Path Status
Detection entry point services/gameserver/src/services/anchor_repair_service.py:run_daily_scan / run_daily_scan_gated ✅ Shipped
Cadence gate Galaxy.state day-anchor, rides the governance sweep's Phase 8 tick ✅ Shipped
Detect event region_anchor_missing (dict + logger.warning; post-commit bus fan-out) ✅ Shipped
Phase 11 placement helper services/gameserver/src/services/anchor_repair_service.py (reinject_missing_anchors + place_*) ✅ Shipped
Re-injection kernel same module — wired from run_daily_scan ✅ Shipped
Per-region advisory lock pg_advisory_xact_lock via _lock_region (ADR-0050 SK18 pattern) ✅ Shipped
Realtime event emission region_anchor_missing / region_anchor_repaired / region_anchor_repair_failed via scheduler._common._broadcast_eventsbroadcast_to_region + broadcast_to_admins (LEG-157 / c2538f02 / PR #649); bus failures logged, never raised into the repair path ✅ Shipped