2026-05-23 13:14:09 -06:00
|
|
|
"""Fleet fan-out for navi-admin.
|
Add navi-admin service (extraction #7) (#7)
* Add navi-admin service (extraction #7)
Net-new fleet admin aggregator on :8427 — no port from recon (recon has no
/api/admin route; Phase A §3). Three @require_auth routes:
GET /api/admin/fleet fan-out to all 6 navi-* /api/admin/<svc>/info
+ recon /api/health, merged; never 5xx
(failures land in errors[])
GET /api/admin/recon/info recon /api/health wrapped in the info shape
GET /api/admin/navi-admin/info self-describe
Fan-out forwards the caller's X-Authentik-Username so the @require_auth
upstreams accept it; per-service admin endpoints stay localhost-only (this is
the single edge-exposed admin surface). Service discovery: hardcoded list in
fleet.py (Option B). No secrets, no DB.
Deploy artifacts (NOT applied here): navi-admin.env.example, systemd unit,
nginx ^~ /api/admin snippet, and deploy/caddy notes for the @authed_api edit
(first Caddy change since #2).
12 hermetic tests (fleet happy-path, per-service timeout/500 → errors[],
auth-header forwarding, recon-down degraded-not-5xx, self-info no-secrets,
auth-required). Full monorepo suite green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR #7 review fixes
1. Symmetric degraded-entry handling in fleet.build_fleet — every
probed service now appears in `services` with a uniform degraded
dict on failure (matches recon's existing pattern), AND in errors[].
Operators see "everything I tried + which broke" consistently.
2. Catch ValueError specifically in _get_json — non-JSON 200 responses
now surface as `error: 'invalid JSON'` instead of opaque 'ValueError'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR #7 review fixes (round 2)
1. Unified degraded shape: wrap_recon_health calls _degraded_entry on
failure — no more runtime.status vs runtime.recon_status asymmetry.
Every probed service has the same shape on failure
(runtime.status == 'unreachable'). recon-specific runtime fields
(recon_status/recon_uptime/pipeline) remain only on the success path.
2. DRY'd git short-SHA helper into shared/git_sha.py — was duplicated in
7 service app.py files + fleet.recon_git_sha. One implementation,
one place to fix when behavior changes. Adds shared/tests (testpaths
now includes "shared").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: zvx-echo6 <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 21:21:00 -06:00
|
|
|
|
|
|
|
|
navi-admin is a stateless aggregator: it fans out over localhost to each
|
2026-05-23 13:14:09 -06:00
|
|
|
navi-* service's ``/api/admin/<svc>/info`` endpoint, merging them into one
|
|
|
|
|
fleet response. Every per-service admin endpoint is ``@require_auth``, so the
|
|
|
|
|
fan-out forwards the caller's validated ``X-Authentik-Username`` header —
|
|
|
|
|
otherwise the upstreams would 401.
|
Add navi-admin service (extraction #7) (#7)
* Add navi-admin service (extraction #7)
Net-new fleet admin aggregator on :8427 — no port from recon (recon has no
/api/admin route; Phase A §3). Three @require_auth routes:
GET /api/admin/fleet fan-out to all 6 navi-* /api/admin/<svc>/info
+ recon /api/health, merged; never 5xx
(failures land in errors[])
GET /api/admin/recon/info recon /api/health wrapped in the info shape
GET /api/admin/navi-admin/info self-describe
Fan-out forwards the caller's X-Authentik-Username so the @require_auth
upstreams accept it; per-service admin endpoints stay localhost-only (this is
the single edge-exposed admin surface). Service discovery: hardcoded list in
fleet.py (Option B). No secrets, no DB.
Deploy artifacts (NOT applied here): navi-admin.env.example, systemd unit,
nginx ^~ /api/admin snippet, and deploy/caddy notes for the @authed_api edit
(first Caddy change since #2).
12 hermetic tests (fleet happy-path, per-service timeout/500 → errors[],
auth-header forwarding, recon-down degraded-not-5xx, self-info no-secrets,
auth-required). Full monorepo suite green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR #7 review fixes
1. Symmetric degraded-entry handling in fleet.build_fleet — every
probed service now appears in `services` with a uniform degraded
dict on failure (matches recon's existing pattern), AND in errors[].
Operators see "everything I tried + which broke" consistently.
2. Catch ValueError specifically in _get_json — non-JSON 200 responses
now surface as `error: 'invalid JSON'` instead of opaque 'ValueError'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR #7 review fixes (round 2)
1. Unified degraded shape: wrap_recon_health calls _degraded_entry on
failure — no more runtime.status vs runtime.recon_status asymmetry.
Every probed service has the same shape on failure
(runtime.status == 'unreachable'). recon-specific runtime fields
(recon_status/recon_uptime/pipeline) remain only on the success path.
2. DRY'd git short-SHA helper into shared/git_sha.py — was duplicated in
7 service app.py files + fleet.recon_git_sha. One implementation,
one place to fix when behavior changes. Adds shared/tests (testpaths
now includes "shared").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: zvx-echo6 <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 21:21:00 -06:00
|
|
|
|
|
|
|
|
Service discovery: a hardcoded module-level list (Option B). The set of navi-*
|
|
|
|
|
services changes only when we ship a new extraction — the same moment we'd be
|
|
|
|
|
editing this file to add it — so an env list would add a moving part with no
|
|
|
|
|
payoff. (One source of truth: the ports/names live here only.)
|
|
|
|
|
"""
|
|
|
|
|
import os
|
|
|
|
|
import time
|
|
|
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
|
|
|
|
|
|
import requests
|
|
|
|
|
|
|
|
|
|
# (service-name, port) for every shipped navi-* service. The admin-info path is
|
|
|
|
|
# always /api/admin/<service-name>/info. Add a row when a new extraction ships.
|
|
|
|
|
SERVICES = [
|
|
|
|
|
('navi-traffic', 8421), # #1 TomTom traffic tile proxy
|
|
|
|
|
('navi-config', 8422), # #2 deployment profile API
|
|
|
|
|
('navi-contacts', 8423), # #3 contacts + address book
|
|
|
|
|
('navi-landclass', 8424), # #4 PAD-US land classification
|
|
|
|
|
('navi-places', 8425), # #5 OSM place detail + enrichment
|
|
|
|
|
('navi-geo', 8426), # #6 geocode + reverse + reverse bundle
|
2026-05-23 23:17:23 -06:00
|
|
|
('navi-offroute', 8428), # #8 off-network routing + MVUM
|
Add navi-admin service (extraction #7) (#7)
* Add navi-admin service (extraction #7)
Net-new fleet admin aggregator on :8427 — no port from recon (recon has no
/api/admin route; Phase A §3). Three @require_auth routes:
GET /api/admin/fleet fan-out to all 6 navi-* /api/admin/<svc>/info
+ recon /api/health, merged; never 5xx
(failures land in errors[])
GET /api/admin/recon/info recon /api/health wrapped in the info shape
GET /api/admin/navi-admin/info self-describe
Fan-out forwards the caller's X-Authentik-Username so the @require_auth
upstreams accept it; per-service admin endpoints stay localhost-only (this is
the single edge-exposed admin surface). Service discovery: hardcoded list in
fleet.py (Option B). No secrets, no DB.
Deploy artifacts (NOT applied here): navi-admin.env.example, systemd unit,
nginx ^~ /api/admin snippet, and deploy/caddy notes for the @authed_api edit
(first Caddy change since #2).
12 hermetic tests (fleet happy-path, per-service timeout/500 → errors[],
auth-header forwarding, recon-down degraded-not-5xx, self-info no-secrets,
auth-required). Full monorepo suite green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR #7 review fixes
1. Symmetric degraded-entry handling in fleet.build_fleet — every
probed service now appears in `services` with a uniform degraded
dict on failure (matches recon's existing pattern), AND in errors[].
Operators see "everything I tried + which broke" consistently.
2. Catch ValueError specifically in _get_json — non-JSON 200 responses
now surface as `error: 'invalid JSON'` instead of opaque 'ValueError'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR #7 review fixes (round 2)
1. Unified degraded shape: wrap_recon_health calls _degraded_entry on
failure — no more runtime.status vs runtime.recon_status asymmetry.
Every probed service has the same shape on failure
(runtime.status == 'unreachable'). recon-specific runtime fields
(recon_status/recon_uptime/pipeline) remain only on the success path.
2. DRY'd git short-SHA helper into shared/git_sha.py — was duplicated in
7 service app.py files + fleet.recon_git_sha. One implementation,
one place to fix when behavior changes. Adds shared/tests (testpaths
now includes "shared").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: zvx-echo6 <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 21:21:00 -06:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
DEFAULT_FANOUT_TIMEOUT_S = 3.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fanout_timeout():
|
|
|
|
|
try:
|
|
|
|
|
return float(os.environ.get('NAVI_ADMIN_FANOUT_TIMEOUT_S', DEFAULT_FANOUT_TIMEOUT_S))
|
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
|
return DEFAULT_FANOUT_TIMEOUT_S
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def service_info_url(name, port):
|
|
|
|
|
return f'http://127.0.0.1:{port}/api/admin/{name}/info'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_json(url, auth_user, timeout):
|
|
|
|
|
"""GET url, forwarding the auth header. Returns (json_or_None, latency_ms,
|
|
|
|
|
error_or_None) where error is 'timeout' | 'HTTP <code>' | exception name."""
|
|
|
|
|
headers = {'X-Authentik-Username': auth_user} if auth_user else {}
|
|
|
|
|
start = time.monotonic()
|
|
|
|
|
try:
|
|
|
|
|
resp = requests.get(url, headers=headers, timeout=timeout)
|
|
|
|
|
latency_ms = round((time.monotonic() - start) * 1000, 1)
|
|
|
|
|
if resp.status_code != 200:
|
|
|
|
|
return None, latency_ms, f'HTTP {resp.status_code}'
|
|
|
|
|
return resp.json(), latency_ms, None
|
|
|
|
|
except requests.Timeout:
|
|
|
|
|
return None, round((time.monotonic() - start) * 1000, 1), 'timeout'
|
|
|
|
|
except ValueError:
|
|
|
|
|
# 200 with a non-JSON body (e.g. a misrouted upstream serving HTML).
|
|
|
|
|
# json.JSONDecodeError subclasses ValueError — report it plainly.
|
|
|
|
|
return None, round((time.monotonic() - start) * 1000, 1), 'invalid JSON'
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
return None, round((time.monotonic() - start) * 1000, 1), type(exc).__name__
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def probe(name, url, auth_user, timeout=None):
|
|
|
|
|
"""One GET → (summary, full_json, error). summary is the {name, status,
|
|
|
|
|
latency_ms[, error]} shape the per-service admin endpoints use for deps."""
|
|
|
|
|
full, latency_ms, error = _get_json(url, auth_user, timeout or fanout_timeout())
|
|
|
|
|
summary = {'name': name, 'status': 'ok' if error is None else 'error',
|
|
|
|
|
'latency_ms': latency_ms}
|
|
|
|
|
if error:
|
|
|
|
|
summary['error'] = error
|
|
|
|
|
return summary, full, error
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _degraded_entry(name, port, error):
|
|
|
|
|
"""Uniform 'service was probed but failed' entry — matches the
|
|
|
|
|
build_info_response shape so callers see the same keys whether the service is
|
2026-05-23 13:14:09 -06:00
|
|
|
healthy or down. Used for every failure path, so there is exactly one
|
|
|
|
|
degraded shape: runtime.status == 'unreachable'."""
|
Add navi-admin service (extraction #7) (#7)
* Add navi-admin service (extraction #7)
Net-new fleet admin aggregator on :8427 — no port from recon (recon has no
/api/admin route; Phase A §3). Three @require_auth routes:
GET /api/admin/fleet fan-out to all 6 navi-* /api/admin/<svc>/info
+ recon /api/health, merged; never 5xx
(failures land in errors[])
GET /api/admin/recon/info recon /api/health wrapped in the info shape
GET /api/admin/navi-admin/info self-describe
Fan-out forwards the caller's X-Authentik-Username so the @require_auth
upstreams accept it; per-service admin endpoints stay localhost-only (this is
the single edge-exposed admin surface). Service discovery: hardcoded list in
fleet.py (Option B). No secrets, no DB.
Deploy artifacts (NOT applied here): navi-admin.env.example, systemd unit,
nginx ^~ /api/admin snippet, and deploy/caddy notes for the @authed_api edit
(first Caddy change since #2).
12 hermetic tests (fleet happy-path, per-service timeout/500 → errors[],
auth-header forwarding, recon-down degraded-not-5xx, self-info no-secrets,
auth-required). Full monorepo suite green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR #7 review fixes
1. Symmetric degraded-entry handling in fleet.build_fleet — every
probed service now appears in `services` with a uniform degraded
dict on failure (matches recon's existing pattern), AND in errors[].
Operators see "everything I tried + which broke" consistently.
2. Catch ValueError specifically in _get_json — non-JSON 200 responses
now surface as `error: 'invalid JSON'` instead of opaque 'ValueError'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR #7 review fixes (round 2)
1. Unified degraded shape: wrap_recon_health calls _degraded_entry on
failure — no more runtime.status vs runtime.recon_status asymmetry.
Every probed service has the same shape on failure
(runtime.status == 'unreachable'). recon-specific runtime fields
(recon_status/recon_uptime/pipeline) remain only on the success path.
2. DRY'd git short-SHA helper into shared/git_sha.py — was duplicated in
7 service app.py files + fleet.recon_git_sha. One implementation,
one place to fix when behavior changes. Adds shared/tests (testpaths
now includes "shared").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: zvx-echo6 <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 21:21:00 -06:00
|
|
|
return {
|
|
|
|
|
'service': name, 'version': 'unknown', 'port': port,
|
|
|
|
|
'config': {}, 'env': [],
|
|
|
|
|
'dependencies': [{'name': f'{name}-info', 'status': 'error', 'error': error}],
|
|
|
|
|
'filesystem': [],
|
|
|
|
|
'runtime': {'status': 'unreachable'},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_fleet(auth_user):
|
2026-05-23 13:14:09 -06:00
|
|
|
"""Fan out to all navi-* services in parallel; merge. Never raises.
|
Add navi-admin service (extraction #7) (#7)
* Add navi-admin service (extraction #7)
Net-new fleet admin aggregator on :8427 — no port from recon (recon has no
/api/admin route; Phase A §3). Three @require_auth routes:
GET /api/admin/fleet fan-out to all 6 navi-* /api/admin/<svc>/info
+ recon /api/health, merged; never 5xx
(failures land in errors[])
GET /api/admin/recon/info recon /api/health wrapped in the info shape
GET /api/admin/navi-admin/info self-describe
Fan-out forwards the caller's X-Authentik-Username so the @require_auth
upstreams accept it; per-service admin endpoints stay localhost-only (this is
the single edge-exposed admin surface). Service discovery: hardcoded list in
fleet.py (Option B). No secrets, no DB.
Deploy artifacts (NOT applied here): navi-admin.env.example, systemd unit,
nginx ^~ /api/admin snippet, and deploy/caddy notes for the @authed_api edit
(first Caddy change since #2).
12 hermetic tests (fleet happy-path, per-service timeout/500 → errors[],
auth-header forwarding, recon-down degraded-not-5xx, self-info no-secrets,
auth-required). Full monorepo suite green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR #7 review fixes
1. Symmetric degraded-entry handling in fleet.build_fleet — every
probed service now appears in `services` with a uniform degraded
dict on failure (matches recon's existing pattern), AND in errors[].
Operators see "everything I tried + which broke" consistently.
2. Catch ValueError specifically in _get_json — non-JSON 200 responses
now surface as `error: 'invalid JSON'` instead of opaque 'ValueError'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR #7 review fixes (round 2)
1. Unified degraded shape: wrap_recon_health calls _degraded_entry on
failure — no more runtime.status vs runtime.recon_status asymmetry.
Every probed service has the same shape on failure
(runtime.status == 'unreachable'). recon-specific runtime fields
(recon_status/recon_uptime/pipeline) remain only on the success path.
2. DRY'd git short-SHA helper into shared/git_sha.py — was duplicated in
7 service app.py files + fleet.recon_git_sha. One implementation,
one place to fix when behavior changes. Adds shared/tests (testpaths
now includes "shared").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: zvx-echo6 <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 21:21:00 -06:00
|
|
|
|
|
|
|
|
Returns {services: {<name>: <info>}, fetched_at: ISO8601, errors: [{service, error}]}.
|
|
|
|
|
Invariant: EVERY probed service appears in `services` — a full info dict when
|
|
|
|
|
healthy, the uniform `_degraded_entry` shape (runtime.status == 'unreachable')
|
|
|
|
|
when it fails — and `errors` is a parallel listing of which ones failed and
|
2026-05-23 13:14:09 -06:00
|
|
|
why."""
|
Add navi-admin service (extraction #7) (#7)
* Add navi-admin service (extraction #7)
Net-new fleet admin aggregator on :8427 — no port from recon (recon has no
/api/admin route; Phase A §3). Three @require_auth routes:
GET /api/admin/fleet fan-out to all 6 navi-* /api/admin/<svc>/info
+ recon /api/health, merged; never 5xx
(failures land in errors[])
GET /api/admin/recon/info recon /api/health wrapped in the info shape
GET /api/admin/navi-admin/info self-describe
Fan-out forwards the caller's X-Authentik-Username so the @require_auth
upstreams accept it; per-service admin endpoints stay localhost-only (this is
the single edge-exposed admin surface). Service discovery: hardcoded list in
fleet.py (Option B). No secrets, no DB.
Deploy artifacts (NOT applied here): navi-admin.env.example, systemd unit,
nginx ^~ /api/admin snippet, and deploy/caddy notes for the @authed_api edit
(first Caddy change since #2).
12 hermetic tests (fleet happy-path, per-service timeout/500 → errors[],
auth-header forwarding, recon-down degraded-not-5xx, self-info no-secrets,
auth-required). Full monorepo suite green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR #7 review fixes
1. Symmetric degraded-entry handling in fleet.build_fleet — every
probed service now appears in `services` with a uniform degraded
dict on failure (matches recon's existing pattern), AND in errors[].
Operators see "everything I tried + which broke" consistently.
2. Catch ValueError specifically in _get_json — non-JSON 200 responses
now surface as `error: 'invalid JSON'` instead of opaque 'ValueError'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR #7 review fixes (round 2)
1. Unified degraded shape: wrap_recon_health calls _degraded_entry on
failure — no more runtime.status vs runtime.recon_status asymmetry.
Every probed service has the same shape on failure
(runtime.status == 'unreachable'). recon-specific runtime fields
(recon_status/recon_uptime/pipeline) remain only on the success path.
2. DRY'd git short-SHA helper into shared/git_sha.py — was duplicated in
7 service app.py files + fleet.recon_git_sha. One implementation,
one place to fix when behavior changes. Adds shared/tests (testpaths
now includes "shared").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: zvx-echo6 <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 21:21:00 -06:00
|
|
|
timeout = fanout_timeout()
|
|
|
|
|
targets = [(name, port, service_info_url(name, port)) for name, port in SERVICES]
|
|
|
|
|
|
|
|
|
|
def _fetch(name, port, url):
|
|
|
|
|
_, full, error = probe(name, url, auth_user, timeout)
|
|
|
|
|
return name, port, full, error
|
|
|
|
|
|
|
|
|
|
services = {}
|
|
|
|
|
errors = []
|
2026-05-23 13:14:09 -06:00
|
|
|
with ThreadPoolExecutor(max_workers=len(targets)) as ex:
|
Add navi-admin service (extraction #7) (#7)
* Add navi-admin service (extraction #7)
Net-new fleet admin aggregator on :8427 — no port from recon (recon has no
/api/admin route; Phase A §3). Three @require_auth routes:
GET /api/admin/fleet fan-out to all 6 navi-* /api/admin/<svc>/info
+ recon /api/health, merged; never 5xx
(failures land in errors[])
GET /api/admin/recon/info recon /api/health wrapped in the info shape
GET /api/admin/navi-admin/info self-describe
Fan-out forwards the caller's X-Authentik-Username so the @require_auth
upstreams accept it; per-service admin endpoints stay localhost-only (this is
the single edge-exposed admin surface). Service discovery: hardcoded list in
fleet.py (Option B). No secrets, no DB.
Deploy artifacts (NOT applied here): navi-admin.env.example, systemd unit,
nginx ^~ /api/admin snippet, and deploy/caddy notes for the @authed_api edit
(first Caddy change since #2).
12 hermetic tests (fleet happy-path, per-service timeout/500 → errors[],
auth-header forwarding, recon-down degraded-not-5xx, self-info no-secrets,
auth-required). Full monorepo suite green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR #7 review fixes
1. Symmetric degraded-entry handling in fleet.build_fleet — every
probed service now appears in `services` with a uniform degraded
dict on failure (matches recon's existing pattern), AND in errors[].
Operators see "everything I tried + which broke" consistently.
2. Catch ValueError specifically in _get_json — non-JSON 200 responses
now surface as `error: 'invalid JSON'` instead of opaque 'ValueError'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR #7 review fixes (round 2)
1. Unified degraded shape: wrap_recon_health calls _degraded_entry on
failure — no more runtime.status vs runtime.recon_status asymmetry.
Every probed service has the same shape on failure
(runtime.status == 'unreachable'). recon-specific runtime fields
(recon_status/recon_uptime/pipeline) remain only on the success path.
2. DRY'd git short-SHA helper into shared/git_sha.py — was duplicated in
7 service app.py files + fleet.recon_git_sha. One implementation,
one place to fix when behavior changes. Adds shared/tests (testpaths
now includes "shared").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: zvx-echo6 <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 21:21:00 -06:00
|
|
|
futures = [ex.submit(_fetch, name, port, url) for name, port, url in targets]
|
|
|
|
|
for fut in futures:
|
|
|
|
|
name, port, full, error = fut.result()
|
|
|
|
|
services[name] = full if full is not None else _degraded_entry(name, port, error)
|
|
|
|
|
if error:
|
|
|
|
|
errors.append({'service': name, 'error': error})
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
'services': services,
|
|
|
|
|
'fetched_at': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
|
|
|
|
|
'errors': errors,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def dependency_summaries(auth_user):
|
2026-05-23 13:14:09 -06:00
|
|
|
"""{name, status, latency_ms[, error]} for each navi-* admin endpoint — the
|
|
|
|
|
same probes the fleet runs, reused for navi-admin's own /info
|
|
|
|
|
`dependencies`. Sequential (it's a rare, auth-gated call)."""
|
Add navi-admin service (extraction #7) (#7)
* Add navi-admin service (extraction #7)
Net-new fleet admin aggregator on :8427 — no port from recon (recon has no
/api/admin route; Phase A §3). Three @require_auth routes:
GET /api/admin/fleet fan-out to all 6 navi-* /api/admin/<svc>/info
+ recon /api/health, merged; never 5xx
(failures land in errors[])
GET /api/admin/recon/info recon /api/health wrapped in the info shape
GET /api/admin/navi-admin/info self-describe
Fan-out forwards the caller's X-Authentik-Username so the @require_auth
upstreams accept it; per-service admin endpoints stay localhost-only (this is
the single edge-exposed admin surface). Service discovery: hardcoded list in
fleet.py (Option B). No secrets, no DB.
Deploy artifacts (NOT applied here): navi-admin.env.example, systemd unit,
nginx ^~ /api/admin snippet, and deploy/caddy notes for the @authed_api edit
(first Caddy change since #2).
12 hermetic tests (fleet happy-path, per-service timeout/500 → errors[],
auth-header forwarding, recon-down degraded-not-5xx, self-info no-secrets,
auth-required). Full monorepo suite green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR #7 review fixes
1. Symmetric degraded-entry handling in fleet.build_fleet — every
probed service now appears in `services` with a uniform degraded
dict on failure (matches recon's existing pattern), AND in errors[].
Operators see "everything I tried + which broke" consistently.
2. Catch ValueError specifically in _get_json — non-JSON 200 responses
now surface as `error: 'invalid JSON'` instead of opaque 'ValueError'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PR #7 review fixes (round 2)
1. Unified degraded shape: wrap_recon_health calls _degraded_entry on
failure — no more runtime.status vs runtime.recon_status asymmetry.
Every probed service has the same shape on failure
(runtime.status == 'unreachable'). recon-specific runtime fields
(recon_status/recon_uptime/pipeline) remain only on the success path.
2. DRY'd git short-SHA helper into shared/git_sha.py — was duplicated in
7 service app.py files + fleet.recon_git_sha. One implementation,
one place to fix when behavior changes. Adds shared/tests (testpaths
now includes "shared").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: zvx-echo6 <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 21:21:00 -06:00
|
|
|
timeout = fanout_timeout()
|
|
|
|
|
summaries = []
|
|
|
|
|
for name, port in SERVICES:
|
|
|
|
|
s, _, _ = probe(name, service_info_url(name, port), auth_user, timeout)
|
|
|
|
|
summaries.append(s)
|
|
|
|
|
return summaries
|