fix: derive kb health status from evidence instead of asserting it - #2319
Merged
Conversation
Six defects this release shared one shape: accepted, reports healthy, cannot work. The common cause is that summary fields were constants or configuration-presence checks while the evidence of capability lived in sibling fields nothing aggregated. kb_service_health_object wrote status:"ok" as its FIRST statement and never revised it. db2_ok, pgvec_ok, embed_ok and the dimension-refusal counter were all published beneath it as siblings, so the response could carry the proof it was broken and still say ok. server_health_add_kb had the same shape one layer up (status = reachable ? "ok" : "unreachable") and its own comment already admitted it. The transport breaker hit that wall earlier and was answered by adding ANOTHER sibling for the CLI to special-case, which is the gap reproducing rather than closing. Add one aggregation point. The kb collects blockers at the sites that discover the evidence and derives status from them; a blocker means cannot work and names its remedy, while advisory findings stay in warnings and leave status ok (a stale ingest or zero vectors on a fresh install are supported states, and degrading on them would dilute the signal in the direction this change exists to correct). kb_client_health required status == "ok" and returned -1 otherwise. Left alone, the moment the kb learned to say degraded, a running kb that was telling us exactly what was wrong would have been reported to every caller as unreachable with its blockers discarded unread: the original defect inverted, and worse, because unreachable sends operators to the network. process_ok now means something answered, and the verdict is carried alongside for callers to act on. The container healthcheck is left as-is on purpose. curl -fsS reads only the HTTP status, and a width mismatch is not fixed by restarting, so failing it would convert a diagnosable fault into a restart loop. The body carries the verdict the HTTP status cannot; the runbook now says so. hud.c re-derived a verdict from two booleans, making it a fourth copy and leaving the HUD blind to a missing embedder and to width drift. It now prefers the kb's verdict and keeps the booleans as a fallback for an older kb. Tests, red verified before green: a degraded kb parses as reachable and carries its blockers across the boundary; an older kb sending no verdict is not read as degraded; and server_health_add_kb keeps unreachable, degraded and ok distinct, including the breaker-open case.
Measured on the e2e VM while verifying the health verdict: booting a 768-dimension embedder over a corpus recorded at 384 makes the startup guard refuse, correctly and with a good message naming both widths and the remedy. But it refuses BEFORE the health port is bound, so DB2 never becomes ready, the container crashloops, and the diagnosis exists only in the kb's own log. Every operator-facing surface then said "unreachable" and "the knowledge base did not answer", which reads as a network problem. It is not one, and the one place that already knew the answer went unnamed. Name it. This is the same shape as the verdict fix in the previous commit: the evidence existed and the summary did not carry it. A fail-closed startup cannot report through the health endpoint by construction, so the pointer is the honest fix here rather than another field.
…5b65-fbc9c6239b01c12f
This was referenced Aug 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
Six defects found across this release cycle shared one shape: accepted, reports healthy, cannot work. This is the common cause.
kb_service_health_object()wrotestatus:"ok"as its first statement and never revised it. Every finding below it —db2_ok,pgvec_ok,embed_ok, the dimension-refusal counter — was published as a sibling field that nothing aggregated. The response could carry the proof it was broken and still sayok.That is not a bug in any single check. It is the absence of a place where the checks add up, and it had already reproduced three times:
kb_service_health_object—statusis a constant.server_health_add_kb—status = reachable ? "ok" : "unreachable", with its own comment already admitting the gap.queries_suppressed) for the CLI to special-case. The gap reproducing rather than closing.User-visible result:
aimee statusprintedaimee-kb: okdirectly aboveembedder: not configured, and both were true.The fix
One aggregation point. The kb collects
blockersat the sites that discover the evidence, and derivesstatusfrom them. A blocker means cannot work and names its remedy; advisory findings stay inwarningsand leavestatusatok— a stale ingest or zero vectors on a fresh install are supported states, and degrading on them would dilute the signal in the direction this change exists to correct.A trap this nearly walked into
kb_client_healthrequiredstatus == "ok"and returned-1otherwise. Left alone, the moment the kb learned to saydegraded, a running kb telling us exactly what was wrong would have been reported to every caller as unreachable, with its blockers discarded unread. That is the original defect inverted, and strictly worse, because "unreachable" sends operators to debug the network.process_oknow means something answered; the verdict travels alongside.hud.cre-derived its own verdict from two booleans — a fourth copy, blind to a missing embedder and to width drift. It now prefers the kb's verdict, keeping the booleans as a fallback for an older kb.Deliberately not changed
The container healthcheck stays
curl -fsS. It reads only the HTTP status, so a container can behealthywhile the kb cannot embed — which is correct: a width mismatch is not fixed by restarting, and failing the healthcheck would convert a diagnosable fault into a restart loop. The body carries the verdict the HTTP status cannot; the runbook now says so explicitly.Second commit
While verifying the above on the e2e VM, booting a 768-dim embedder over a 384 corpus showed the startup guard refusing correctly, with a good message naming both widths and the remedy — but it refuses before the health port is bound. DB2 never becomes ready, the container crashloops, and the diagnosis exists only in the kb's own log while every operator-facing surface says "did not answer". Same shape, one layer out. A fail-closed startup cannot report through the health endpoint by construction, so the fix is to name the log rather than add another field.
Verification
Unit, red verified before green (both reverts confirmed failing first):
server_health_add_kbkeepsunreachable/degraded/okdistinct, including breaker-openLive, on the e2e VM, images built from this branch:
status = ok,blockers = [](no false degradation),aimee statuscleanGate: clang-format →
make lint(41/41) →check-docs.py→ all green.