Context
Readiness today proves the session store works, not that data backends are reachable. /readyz (nautilus/transport/fastapi_app.py:326-356) returns 200 iff lifespan startup finished AND store.aget(_READY_PROBE_KEY) succeeds against the session store — it never touches any configured data source. A broker can therefore advertise "ready" while every backend is down; source failures only surface as per-request sources_errored entries (broker lazy-connects on first request, broker.py:1620-1631). Operators have no preflight for source connectivity short of issuing a real request.
The Adapter Protocol (nautilus/adapters/base.py:153) currently defines only connect, execute, close, get_schema — there is no health/ping surface (grep for ping|health_check|aping|verify_connectivity across the 8 adapter modules returns nothing). The CLI health subcommand (nautilus/cli/health.py) just GETs /readyz.
Task
- Add a lightweight liveness-probe capability to adapters (e.g.
async def aping(self) -> None) and implement per-backend checks in the built-in adapters (SELECT 1, ES ping, Neo4j verify_connectivity, etc.).
- Extend
/readyz with an opt-in source-aware mode (?sources=true or a separate /readyz/sources) that aggregates adapter probes and returns per-source up/down status without leaking DSNs or credentials.
- Add
nautilus health --sources to surface the same matrix from the CLI.
Where
nautilus/adapters/base.py:153 — the Adapter Protocol
- The 8 built-in adapter modules:
postgres.py, pgvector.py, elasticsearch.py, neo4j.py, rest.py, servicenow.py, influxdb.py, s3.py
nautilus/transport/fastapi_app.py:326-356 — readyz handler
nautilus/cli/health.py and CLI wiring in nautilus/cli/__init__.py:267-268
Decision needed (do not skip)
The proposal's "default no-op on the Protocol" detail collides with existing design rationale. base.py:152-170 explicitly documents why members are NOT added to the @runtime_checkable Protocol when they would break isinstance/type[Adapter] assignability, and get_schema was deliberately a default-raising method, not a silent no-op. The broker's structural adapter check (broker.py:154) validates only connect/execute/close. Pick one before coding: (a) Protocol method with a benign default, (b) optional capability detected via hasattr/registry (mirrors how capabilities is handled), or (c) reuse connect() as the probe. Resolve this with a maintainer.
Acceptance criteria
Size: M
Context
Readiness today proves the session store works, not that data backends are reachable.
/readyz(nautilus/transport/fastapi_app.py:326-356) returns 200 iff lifespan startup finished ANDstore.aget(_READY_PROBE_KEY)succeeds against the session store — it never touches any configured data source. A broker can therefore advertise "ready" while every backend is down; source failures only surface as per-requestsources_erroredentries (broker lazy-connects on first request,broker.py:1620-1631). Operators have no preflight for source connectivity short of issuing a real request.The
AdapterProtocol (nautilus/adapters/base.py:153) currently defines onlyconnect,execute,close,get_schema— there is no health/ping surface (grep forping|health_check|aping|verify_connectivityacross the 8 adapter modules returns nothing). The CLIhealthsubcommand (nautilus/cli/health.py) just GETs/readyz.Task
async def aping(self) -> None) and implement per-backend checks in the built-in adapters (SELECT 1, ESping, Neo4jverify_connectivity, etc.)./readyzwith an opt-in source-aware mode (?sources=trueor a separate/readyz/sources) that aggregates adapter probes and returns per-source up/down status without leaking DSNs or credentials.nautilus health --sourcesto surface the same matrix from the CLI.Where
nautilus/adapters/base.py:153— theAdapterProtocolpostgres.py,pgvector.py,elasticsearch.py,neo4j.py,rest.py,servicenow.py,influxdb.py,s3.pynautilus/transport/fastapi_app.py:326-356—readyzhandlernautilus/cli/health.pyand CLI wiring innautilus/cli/__init__.py:267-268Decision needed (do not skip)
The proposal's "default no-op on the Protocol" detail collides with existing design rationale.
base.py:152-170explicitly documents why members are NOT added to the@runtime_checkableProtocol when they would breakisinstance/type[Adapter]assignability, andget_schemawas deliberately a default-raising method, not a silent no-op. The broker's structural adapter check (broker.py:154) validates onlyconnect/execute/close. Pick one before coding: (a) Protocol method with a benign default, (b) optional capability detected viahasattr/registry (mirrors howcapabilitiesis handled), or (c) reuseconnect()as the probe. Resolve this with a maintainer.Acceptance criteria
/readyzsemantics are unchanged (back-compat — still 200 only when startup done + session store reachable)./v1/sourceslisting atfastapi_app.py:300-319).NotImplementedErrorfrom the aggregated probe path.nautilus health --sourcesprints the same matrix and exits non-zero when any source is down.-m integration).Size: M