Skip to content

D820 — The Atlas shows what was retired, read from the root log, as a tab of Activity

Decision. GET /api/ui/v1/kbs/{kb}/retired lists every deprecated, superseded or archived concept with where it went, when, why and what replaced it, and the Atlas draws it on a Retired tab of Activity (?panel=retired). The story of a retirement is read from the root log.md by kb.Retirements, which parses the entries concept_archive and supersede already write; no frontmatter field is added. A retired concept with no matching entry (a status set by hand) carries its status and timestamp and no story, and the tab says no retirement is recorded. The rule that marks a map as an archive, "at least half of its top-level concepts are retired, and it is not a journal", moves from the index_lists_retired lint check into kb.IsArchiveMap, which the lint exemption and the overview's archive flag both call; the rail marks such a map with an "Archive" badge and lists it after the live ones. Search keeps hiding archived pages by default; the UI search route forwards include_archived=1 and the command palette gets an "Include archived" toggle that badges them.

Why. The server tracks retirement, but only as a status in the page: the from-path, the date and the reason exist in the log entry and the commit, so a reader of the Atlas could not tell what a KB had retired, or find a page it had moved. Reading the log covers pages archived before this change, where a new frontmatter field would have covered only the future and would have been one more key for concept_archive to keep in step. One function for the archive rule means a map the rail badges as an archive is one whose index the lint check leaves alone.

Alternatives rejected. A rail entry of its own: retirement happened to the KB, and most KBs retire little, so every reader would carry a button for a short list; a tab of Activity, the way Secrets is a tab of Artifacts, costs nothing until it is opened. A retired_from and retired_at frontmatter pair written by concept_archive: it leaves every earlier retirement without a story and duplicates what the log and the commit trailer hold. A contract key declaring an archive map: a second place to be wrong about a fact the page statuses already determine. Building the list from the graph snapshot: it is cut at 5,000 nodes, so a large KB would silently drop retired concepts; the route walks the permission-filtered concepts like the overview does and never truncates. Showing archived hits in search by default: an agent does not see them either, and the reader who wants them says so.

Consequences. The log is the record, so a concept moved again after its archive, or a log entry edited by hand, loses its story and falls back to "no retirement recorded"; kb.Retirements keys the story on the id the concept holds now (a supersede on its source, an archive on its target; a page archived and then superseded keeps both) and parses only the entry shapes the two tools write, so a change to either log line must change the parser and its test. The reason is read from the entry's title line, so concept_archive writes it on one line (line breaks collapse to a space); the parser also joins the title lines of an older entry that was written with breaks. Every field of the route is derived from what the caller sees: superseded_by is left out when that id is hidden, from and reason when the source is hidden (the reason is free text that may name it), and the journal and map counts come from the visible set, so a narrowed token learns no hidden id or count. The Atlas stays read-only.