Semantic search over a Markdown knowledge base, served over MCP.
Semantic versioning says a major version is required to break the public API. It does not say what the public API is. For a library that question answers itself — the API is what the crate exports. For a program with a command line, an MCP surface, an HTTP transport, a configuration format, and files it writes into the user’s home directory, the answer is whatever the maintainer says it is, and if the maintainer says nothing, users will reasonably assume it is everything they can observe.
Measured on the day of this decision:
| public Rust items | 408, across 24 pub mod |
| command-line flags | 138, across 10 subcommands |
| MCP tools / prompts | 6 / 4, plus the kb:// resource scheme |
| HTTP routes | 5 |
| configuration sections | 11, with deny_unknown_fields in 25 places |
| on-disk artifacts | index database, config, exclusion file, eval set and history, service registrations |
Freezing all of that until 2.0.0 leads to one of two failures: the project never improves the parts nobody depends on, or it ships major versions for changes nobody would have noticed. Both are worse than saying, in advance and in writing, which parts are the promise.
One item makes this concrete rather than theoretical. The web interface at /ui is
a placeholder that says so in its own HTML, and it is scheduled to be rebuilt. If
1.0.0 ships without a declaration, rebuilding it becomes a 2.0.0.
cargo package -p grooveseek fails with “dependency groove-tray
does not specify a version”, because the workspace uses unversioned path
dependencies. Nothing on crates.io can depend on these 408 items, so tagging
1.0.0 would not freeze them even if we wanted it to./ui rework a major release.The stable surface is written down in docs/stability.md (and
its Japanese counterpart) rather than left to inference. In summary: the command
line, the machine-readable JSON, the MCP surface, /mcp and /healthz,
configuration keys and defaults, the default embedding model, and the names written
into the user’s filesystem. Explicitly outside: the admin web surface, all
human-readable text output, the database’s internal schema, log wording, and the
Rust API.
Three parts of that deserve their reasoning here rather than in the policy.
The Rust API is excluded, and grooveseek is marked publish = false. Marking
it rather than only documenting it makes the intent enforceable: cargo publish
refuses, so the exclusion cannot be undone by accident. This is not a way of dodging
the question — the crate genuinely cannot be packaged today, so the exclusion
describes reality. Publishing later is a separate decision that would come with its
own stability statement.
The admin web surface is unstable. /ui, /api/search and /api/admin/status
are loopback-only by design because GrooveSeek has no authentication
(ADR-0007 covers the naming; the
loopback decision predates it). They serve the operator looking at their own data,
not integrators. Declaring them unstable is what keeps the planned rework a minor
release.
Configuration is not forward compatible, and unknown keys stay an error. The
alternative — warn and continue — trades a loud failure for a quiet one. A
misspelled model key would leave the knowledge base indexed by the default model
while a single warning scrolls past on a daemon’s stderr that nobody reads. A
configuration file belongs to the binary version that reads it; this policy says so
instead of pretending otherwise.
Origin default, and
writing out the full search response.cargo package still fails, and that is now deliberate rather than an
oversight. If the failure is ever fixed, it should be because publishing was
decided on, not as a drive-by cleanup.