跳转至

Infra-013: Service Registry as Single Source of Truth

Status: In Progress Owner: Infra Priority: P1 Branch: infra-013-service-registry-ssot (PR 1)

Goal

The deploy.py Deployer class of each service is the single registry of that service's facts; every other per-service / per-environment list is derived from or audited against it, so adding or changing a service cannot silently drift its monitoring, routing, backup, or fan-out config.

Context

A staging false alert (signoz/clickhouse probes targeting non-existent -staging hosts, firing every 30 min — see PR infra2#307) exposed a structural problem, not a one-off typo: the same service facts are hand-copied into three layers of parallel lists with little or no audit linking them.

① truth:   platform/*/deploy.py attrs (subdomain / service_port / prod_only / data_path)
② re-registries (libs/common.py, NO audit): SERVICE_SUBDOMAINS, CONTAINERS,
            SHARED_PLATFORM_SERVICES; sync_runner.py ALL_SERVICES / SERVICE_TASK_MAP
③ downstream (hand-copied): watchdog-signals.yaml, INFRA_PROBE_SPECS, wrangler
            targets, the backup inventory (BackupFacet-derived, #542), the vault self-refresh inventory (SecretsFacet-derived, #542),
            DNS DEFAULT_RECORDS
+ 99+ inline `platform-<svc>:port` strings in compose files (prefect missed
  ${ENV_SUFFIX} → staging Prefect auth hits PROD Authentik).
pure duplication (service name, container, subdomain, port, prod_only, data_path) should be derived. irreducible external data (health paths, expected HTTP codes, SLAs, backup/restore methods, SSH probe commands) should live ONCE on the service class, then ride along the generated skeleton — not scattered in yaml.

Scope (MECE, delivered as minimal PRs)

  • [x] P0 — base library + collapse the cleanest duplication (PR 1, this branch)
  • libs/service_registry.py: read Deployer attrs once (AST), expose all_services(), services_in_env(env), shared_services(), subdomains().
  • Audit: sync_runner.ALL_SERVICES must equal all_services() (fail-closed).
  • [ ] P0.1 — reconcile common.py re-registries (PR 2)
  • Make SERVICE_SUBDOMAINS / SHARED_PLATFORM_SERVICES derive from (or be audited against) deploy.py subdomain / prod_only. Resolve the role-vs- service keying mismatch deliberately.
  • [ ] P1 — generate watchdog config from the registry (PR 3)
  • Add health_path + expected_codes to the Deployer class (the only external probe data). Generate INFRA_PROBE_SPECS + watchdog-signals.yaml skeleton + wrangler targets via get_probe_targets(env) / get_public_routes(env); CI asserts generated == committed. Kills the original drift class structurally.
  • [x] Identity slice: all internal specs and Cloudflare targets carry canonical service_id; the audit derives the expected value from registry and checks compose↔inventory in both directions. Irreducible endpoint data remains local.
  • [x] P1.1 — cross-plane identity contract
  • ServiceIdentity v1 renders one registry-owned coordinate into deploy env, OTEL resources, Docker labels/adapters and low-cardinality alert labels.
  • Deployer/fixed-promote/preview sign the identity; missing legacy runtime identity triggers one reconcile without entering the idempotence hash.
  • Probes, SigNoz rules, deploy-queue, container-breakdown and Cloudflare watchdog carry the same service_id + environment coordinate.
  • [ ] P2 — fix prefect ${ENV_SUFFIX} bug + compose lint (PR 4, parallel)
  • Fix platform/23.prefect/compose.yaml:114 (missing suffix → prod Authentik).
  • Lint: any platform-<svc> compose reference that should carry ${ENV_SUFFIX} but does not → CI fails.
  • [x] First slice landed: INFRA_PROBE_SPECS env-suffix is now registry-derived — libs/tests/test_service_registry_downstream.py fails CI if a prod_only service's probe carries ${ENV_SUFFIX} (the documented permanent-false-positive trap) or a per-env service's probe omits it. (prefect compose fix + the general compose-ref lint still pending.)
  • [ ] P3 — derive skeletons for backup / vault-refresh / DNS (PR 5)
  • Generate the service-list skeleton; keep hand-authored external annotations.
  • [ ] P3 — de-dup ENV_SUFFIX logic (PR 6)
  • One implementation (sync_runner vs common.py).
  • [ ] Docs — MANIFEST.yaml: mark generated yaml inventories as lockfiles, not hand-authored SSOT; deploy.py becomes the registry SSOT.

Out of scope

  • Full templating / runtime rendering of compose hostnames (static YAML can't call Python; the P2 lint captures ~80% of the risk at a fraction of the cost).
  • Moving irreducible external data out of the service class into a generator.

Deliverables

  • libs/service_registry.py base library (get_*-style accessors).
  • Fail-closed audits binding each downstream list to the registry.
  • Generated (not hand-authored) watchdog / inventory skeletons.
  • PR 1 (P0): this branch — base library + ALL_SERVICES audit.

Change Log

Date Change
2026-06-14 Initialized project; PR 1 delivers the base library + ALL_SERVICES audit
2026-06-24 P2 first slice: probe_container_bases() accessor + INFRA_PROBE_SPECS env-suffix lint vs registry prod_only. Reframed P1 for probe specs: they carry irreducible per-probe truth (health paths, expected codes, severities, cascade deps, command round-trips), so the registry is made the ENFORCED source for the shared prod_only/suffix fact rather than generating the file (matches the Out-of-scope note).
2026-07-17 Added versioned cross-plane ServiceIdentity; deployment entry points sign it, runtime monitoring resolves it, telemetry and alerts use it, and CI audits registry/alert/watchdog agreement. The bidirectional watchdog audit exposed and registered 16 previously untracked internal env×signal entries.

Verification ("The Proof")

  • [x] pytest libs/tests/test_service_registry.py — registry derives, ALL_SERVICES matches
  • [x] python tools/watchdog_consistency_audit.py — bidirectional signal coverage + registry-derived service identity
  • [x] python tools/service_identity_audit.py — deploy/runtime/telemetry/alert boundary contract
  • [x] pytest libs/tests/test_service_identity.py libs/tests/test_service_identity_audit.py
  • [x] pytest libs/tests/test_service_registry_downstream.py — INFRA_PROBE_SPECS env-suffix == registry prod_only (first fail-closed audit binding a downstream list to the registry)
  • [ ] Each later PR adds a fail-closed audit proving its list == registry-derived

References