D224 — Detection records which heuristic matched, and the evidence is labelled with it¶
Decision. agents.Agent carries DetectedBy, one of five named heuristics
(binary, config-dir, env-config-dir, provider-root, app-dir), set by
the same probe that fills Evidence. cartographer agents prints it as a
DETECTION column next to EVIDENCE, --output json as detected_by, and the TUI
uses it as the label of the evidence line instead of the hardcoded binary.
Installed keeps its current meaning, and every caller that reads it — the
connect target set above all — is untouched.
Why. All five heuristics collapse into one boolean, so a live client and a
directory a removed client left behind are indistinguishable downstream (#305:
a Windows machine with no claude binary anywhere, %USERPROFILE%\.claude
still on disk, reported installed and provisioned with the full artifact set).
The directory probes are not the defect — a client installed as an application
with nothing on PATH is found only that way — so the fix is to stop discarding
which probe answered. The cost is a field that every future heuristic must
name, and one more column in a table that already carries four.
Alternatives rejected.
- Trust only the binary probe. Deletes the application-install case the config and app-dir probes exist for (D216), turning a reporting defect into a detection regression.
- A confidence score (strong/weak). Ranks the heuristics inside the package, where the ranking is not knowable: how much a config directory is worth depends on the caller. A name lets each caller decide; a score decides for it.
- Encode it in
Evidenceas free text. Keeps the string the only output, which is what made it unparseable in the first place. - Narrow
connect/connect allto binary-backed providers in the same change. That is a behaviour change to what gets written into people's home directories, with its own failure mode (a provider that was being configured silently stops). It needs its own decision, and this field is the evidence it will be argued from.
Consequences. A positive detection is now made in one place,
Agent.found, so the three fields cannot drift apart; a heuristic added without
a value of its own fails TestHeuristicsAreDistinct. providerStatus gained an
optional detected_by — additive to cartographer.status/v1, absent when
nothing was detected. The DETECTION column is sized on the widest value it
prints, like PROVIDER (#303), so the table stays narrow on a machine where every
answer is binary. Anything that later consumes the field to decide what to
write must state that in its own decision: reading it changes nothing, acting
on it does.