Documents / Decisions / adr-bea870d0b666
The release notice is a store's decision, and says nothing it cannot act on
Why the ambient update notice is opted into by `docir init` rather than a shell, what it refuses to tell a checkout, and where MCP carries it.
Context¶
adr-a555ee6bc484 built the release check and left it off, on three arguments. Two
still hold and one was aimed at the wrong reader.
The two that hold: this is the only network call docir makes in its life, and a
documentation tool that phones home unasked is not one people keep installed. The
third was that a notice repeating on every command until someone acts on it stops
being read — the argument DOCIR_SCHEMA_NOTICE settles one field over.
That third one describes a person at a prompt. docir's reader is an agent, and an agent does not learn to skip a line; it follows it. So the same notice that a person tunes out is one an agent will act on — including in the middle of something else, and including where the command it names cannot run.
Which is the defect the feature actually had. The notice said run docir self upgrade to every installation. The package step of that command declines for two
of the six kinds Installation distinguishes: an ephemeral uvx environment has
nothing to upgrade, and a project install is docir as a dependency of the tree
being worked in. project is what an editable checkout detects as — docir's own
repository, on the day it publishes the release the notice would name — and what
every lockfile-managed project detects as. Four of the six cells were wrong.
The other half of the gap: an opt-in that lives in one person's shell reaches one person's shell. A store is committed and read by a whole team, and the MCP transport — where an agent has no stderr to read — carried nothing at all.
Decision¶
The opt-in is recorded in the store, and docir init writes it. A new
committed config.yaml beside docs-schema.yaml, holding update_check: true.
Creating a store is the act that opts in: deliberate, taken once, and inherited
by everyone who clones the repo. Nobody who never ran docir init is contacted,
so the promise in SECURITY.md survives — it now names this file as well as the
variable.
A new file, never a new key in an existing one. A store is read by whatever
docir each teammate installed (adr-ab4598c6f707), and a build that has never
heard of config.yaml cannot fail on it. A key added to stores.yaml is exactly
how 0.20.0 came to refuse every read of a store a later build wrote.
Precedence is environment, then CI, then the store. DOCIR_UPDATE_CHECK set
to anything decides, in both directions — it is how one person opts out of a
committed decision, and how one opts into a CI run that would otherwise be
silent. CI being set forces it off: a build server cannot act on the notice,
and an agent that acts on it there makes the job's docir version depend on the
day it ran.
An installation that cannot act on the notice is not given one. notice_for
is the decision table the single line used to be. A project install gets
nothing. An installation with an upgrade_command is told to run docir self upgrade; one without carries its own explanation, which already names the
thing that does work (uvx docir@latest, or the lockfile).
The notice names the moment, not just the command. self upgrade replaces
the running process, stops and respawns a daemon whose build stamp no longer
matches, and rebuilds the index. Between tasks that is right; during one it means
the remaining steps run on a build nothing verified. The line says so, because an
agent has no other way to know.
It is announced once per version per day per store. release-check.json
gains announced and announced_on beside the fetched answer, so the file is
now read-modify-write from both sides — the daemon fetches, the CLI announces.
docir self status stays unthrottled: it is the answer for a reader who is
asking rather than being told.
MCP carries it in the server's instructions. Read once, at the handshake, which is the cadence the notice wants. Not a tool result: three of those answer with a bare JSON array that has nowhere to put a key, and a line appended to every result would spend the context budget that body-less skeletons exist to protect.
Nothing fetches outside the daemon. A CLI-side background refresh was
considered and does not work: a docir query process answers and exits in
0.4–0.7s, PyPI replies in 0.4–0.5s, and a daemon=True thread is killed at
interpreter exit — so the cache would never be written and every command would
pay a TLS handshake for nothing. With --no-daemon and no daemon ever run, the
notice is simply silent, which is the same rule latest already follows: absent
means unknown.
Consequences¶
- One more committed file per store.
docir self upgradetops it up the way it tops up.gitignore, and both append and never rewrite: a key already present is an answer somebody gave, whatever its value, so an opt-out survives the upgrade that would otherwise reverse it. --jsonis unchanged. The notice stays on stderr for the CLI, becausequery,searchandcontextemit a bare array and an envelope around them would break every consumer and every installed build to deliver a courtesy.- A long-lived MCP server started before a release ships does not learn about it;
the next client session does, and
docir_doctoranswers on demand. - docir's own repository is silent by construction, so this feature cannot be
exercised from a checkout. The tests inject the installation rather than
detecting it, and a guard asserts that a
projectinstall is told nothing.