docir — design documents Graph

Documents / Decisions / adr-909734bced92

docir doctor — one report for every way docir is subtly wrong

Why the environment checks scattered across five commands became one command, and why the corpus is deliberately not part of it.

adr-909734bced92decisionaccepted#cli#daemon#embeddings#integrity
View as Markdown◉ View in graph

Context

docir has more ways to be subtly wrong than it has ways to fail. A daemon serves reads from the code it loaded and keeps doing so after an upgrade; DOCIR_EMBEDDER survives a test run and every read scores shared vocabulary instead of meaning; the index is derived and gitignored, so a fresh clone has none and answers nothing; a peer is skipped for schema skew; the schema moves in the package with nothing in git diff. None of these raises. Each produces an answer that imitates a correct one.

Every one was already detected somewhere — daemon status, self status, a stderr line during a read, one finding among a hundred in check. What none of them was is reportable together, which is the property that matters: you do not know which of the six to look at, because if you did you would already know what was wrong.

Decision

docir doctor reports the environment — the installation, this store's derived index, the embedding model in force, the daemon, and each declared peer — as facts plus findings, each finding carrying the command that closes it. It reuses the existing checks rather than adding new ones.

Severity is derived from the finding's kind, the rule CheckIssue already follows. error means docir cannot work correctly here; warning means it works less well than the caller believes. --strict gates on errors only.

The corpus is not its question

docir check owns the graph and answers it with a full scan. Doctor never walks it. A diagnosis that costs what check costs is one nobody runs while something is actually wrong, and orphan alone would bury every environment finding under the default state of a healthy corpus.

The one corpus-shaped number it does carry is the pair documents / documents_on_disk. That is not a corpus question but a wiring one: the index is a projection of the files, so a difference is "your reads are answering from stale state", and it is the only way a fresh clone stays visible once anything has created an empty index.

The environment is snapshotted before the first dispatch

Every command runs ensure_running, which stops a daemon serving other code and replaces it, and every container build creates a missing index. So a doctor that dispatched first would repair two of the conditions it exists to report and then call them clean.

snapshot() therefore reads only this process, this environment and the filesystem, and it runs first. The daemon finding is worded in the past tense for the same reason: by the time it is printed, the stale daemon is gone.

The store half is a dispatcher command

The index's account of itself is store_status, in the module that owns the index — which keeps the version comparison and the drift diff implemented once, and makes the half an agent can use reachable over MCP as docir_store_status (adr-354a4270ecd8).

The split is not a convenience. The rest of doctor is unanswerable from the daemon: which build this process loaded, what is in this shell's environment, which store this working directory resolved to. A whole-command dispatcher tool would have the daemon reporting on its own process, which makes "is the daemon stale?" inexpressible.

A broken store still produces a report

execute turns a domain error into a process exit. Doctor uses try_execute, which returns the message instead, because "the store will not open" is the finding — and exiting there prints nothing at the one moment somebody needs the environment half.

Consequences

  • Every finding kind classifies itself by being in ERROR_KINDS or not, so a new one cannot forget to.
  • The model is loaded only under --probe, which is the one check that can change the machine: a cold cache downloads it. probe_embedder catches bare Exception — a truncated ONNX cache is not in docir's error taxonomy, and a traceback would make the diagnostic command the thing needing diagnosis.
  • No network call, ever. The published-release answer is read from the cache the daemon leaves, so latest absent means unknown rather than up to date.
  • The global ~/.docir is excluded from shadowed-store: it sits above every store under the user's home directory, and being shadowed by a project store is what it is for. Reporting it would fire the finding on the ordinary correct setup, which is how a warning stops being read.

To amend: Re-verify: