D323 — The server applies the safe repairs by itself; a person is asked only what needs one¶
Decision. A KB with no auto_repair key has the default list
(nonstandard_field, tool_param_field, invalid_field_value,
duplicate_link, prose_value); auto_repair: [] is the explicit "none".
Every doctor_auto_interval (default one day, "0" off) the HTTP server runs
kb_repair for each listed check, at most 50 concepts per check, through the
tool's own write path, one commit per check, Reason: auto-repair (background),
and appends the run to .cartographer/auto-repair-log.jsonl. The Atlas gains a
read-only Maintenance panel (last runs, last and next doctor session, the
deferred open_question concepts, the repair commits with the command that
undoes each); the undo is repair_revert (an advanced tool) or
cartographer kb repair <kb> --revert <sha>, accepted only for kb_repair and
auto-repair commits.
Why. A KB drifted from zero findings to ten in three days of ordinary
writes, and every piece of upkeep (D299) needed someone to start it: no
operator had ever listed an auto_repair check, and the doctor proposal
needs an agent session to read it. The five defaults are those whose fix is
deterministic, never rewrites body text and never removes a link; the two
excluded fixable checks that do (broken_link, reciprocal_link_item) stay
opt-in because D309 showed a link-dropping repair can lose hundreds of edges
in one commit. Going through kb_repair instead of a second repair path means
the lock, the stale-write guard, the commit and the sync are the ones every
write already has; a background run that bypassed them would be a second
writer to keep in step.
Alternatives rejected.
- One check per interval, round-robin: a full cycle would take five days to
reach a KB that the plan wanted corrected daily, and each check is cheap and
idempotent. The per-run cap (50 concepts per check) is the budget instead:
an imported KB is worked down over several days, not in one huge commit.
- A client hook or an operator cron: hooks fire only in some clients and
modes (D300) and a cron needs setting up, which is why nothing ran.
- An Atlas "revert" button: the Atlas stays read-only; a write route would
put a destructive action behind a UI token. The panel shows the exact
command and copies it.
- Writing the run into log.md: it would be a concept-adjacent file, move
last_doctor (D290) and silence the doctor proposal for the judgement work
the heartbeat cannot do. A local JSONL under .cartographer/ is neither
linted nor committed.
- Running the heartbeat in stdio mode: that process belongs to one client
session, and a write nobody asked for during it would surprise the person
watching.
- Skipping the run on any failed git state: it is sticky after one network
blip and a quiet KB would never recover; the write path already refuses to
write when its sync-in fails, so only an open conflict or a degraded
state skips a run.
Consequences. An operator who upgrades and runs kb repair --apply now
applies the five defaults where the same command did nothing before; the
report says so. auto_repair nil versus empty is a distinction of the loaded
config (KBSpec.AutoRepairChecks), pinned by a test through the YAML loader;
kb.KB.AutoRepair is already resolved. repair_revert is hidden from the
agent profile so the D285 tools/list budget does not move. A revert that
conflicts with a later edit is undone and refused, never forced: the operator
reconciles by hand. Anything that adds a check to DefaultAutoRepair needs a
decision of its own.