Skip to content

fix: derive kb health status from evidence instead of asserting it - #2319

Merged
JBailes merged 3 commits into
testingfrom
aimee/session/b1e45b65-fbc9c6239b01c12f
Aug 4, 2026
Merged

fix: derive kb health status from evidence instead of asserting it#2319
JBailes merged 3 commits into
testingfrom
aimee/session/b1e45b65-fbc9c6239b01c12f

Conversation

@JBailes

@JBailes JBailes commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

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() wrote status:"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 say ok.

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:

  1. kb_service_health_objectstatus is a constant.
  2. server_health_add_kbstatus = reachable ? "ok" : "unreachable", with its own comment already admitting the gap.
  3. The transport-breaker fix, which answered the same problem by adding another sibling (queries_suppressed) for the CLI to special-case. The gap reproducing rather than closing.

User-visible result: aimee status printed aimee-kb: ok directly above embedder: not configured, and both were true.

The fix

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; advisory findings stay in warnings and leave status at 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.

A trap this nearly walked into

kb_client_health required status == "ok" and returned -1 otherwise. Left alone, the moment the kb learned to say degraded, 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_ok now means something answered; the verdict travels alongside.

hud.c re-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 be healthy while 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):

  • a degraded kb parses as reachable and carries its blockers across the boundary
  • an older kb sending no verdict is not read as degraded
  • server_health_add_kb keeps unreachable / degraded / ok distinct, including breaker-open

Live, on the e2e VM, images built from this branch:

  • healthy kb on the new image → status = ok, blockers = [] (no false degradation), aimee status clean
  • 768-dim embedder over the 384 store → startup guard fires; crashloop reproduced; this is what motivated the second commit

Gate: clang-format → make lint (41/41) → check-docs.py → all green.

JBailes added 3 commits August 4, 2026 06:38
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.
@JBailes
JBailes merged commit 774248c into testing Aug 4, 2026
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant