D192 — Onboarding and release hygiene: text that lied, hints that could not be run, paths that ended half-done¶
Status: implemented. Closes #246.
Context. A read-only audit of installation and onboarding found the machinery solid — one
binary for server and client, Homebrew plus a POSIX installer, four platform targets, an idempotent
service install, CI running vet/test/smoke/E2E/installer — and the documentation and edge
paths lagging behind it. Fifteen items, each verified against the code, none a design question.
They are one decision rather than fifteen because they share no invariant and none blocks another:
grouping them kept fifteen trivial PRs from competing with the substantive work.
Decisions.
- Text that was false is corrected at the source, not paraphrased.
bindingNotYetEnforcedNotestill told the user "bindings are recorded but not yet enforced during sync" — true under D169, false from D170 on, and pinned by a test asserting its presence. The constant and its three call sites are gone, and the test now asserts its absence, which is what stops it coming back.docs/deployment.mdclaimedinstall.sh updaterestarts a running service, contradicting the paragraph immediately above it describingupgrade-repair(D121).docs/agent-install.mdsaid a failedkb create --remotescaffold "was already removed" while D156 deliberately keeps it and prints how to fix or remove it. Thecartographer-opsbundled skill prescribedbrew upgrade+service restart, when the Cask's own post-install hook runsupgrade-repairand no follow-up is needed.kb-createtold the operator to hand-edit.cartographer.yaml, never mentionedcartographer client bind, and contradicted itself on KB naming. - Every hint the tools print can be pasted and run. The no-KB message named only
kb create --remote <url>, leaving an operator without a remote with no working form; it now names--no-remotetoo, with its cost stated. The installer'sPATHwarning now gives the two concrete next steps — invoke the printed path, or add it toPATH— instead of stating a fact and stopping. uninstallrefuses rather than leaving a half state. It removed the binary only, so a machine with a service installed kept launchd/systemd units pointing at a missing executable. It now detects the units, names them and the commands that remove them, and exits non-zero — unless--binary-onlysays the operator meant exactly that, in which case it proceeds and states what it left behind. A coordinated teardown that deletes a user's KB data is not something an installer should do implicitly, so it still does not.- A checksum file that does not cover the asset is an error.
install.shskipped verification when the entry was missing, which is the shape a truncated or tampered manifest has. A release that ships nosha256sums.txtat all remains installable — older tags have none, and refusing them would break a legitimate downgrade. Four scenarios now cover the matrix; the mismatch case asserts no binary is left behind. - The Cask deprecation is upstream's, and is recorded as such. Item 13 of the plan prescribed
replacing
postflightwithpostflight_stepsin.goreleaser.yaml. Verified and corrected during implementation: that file contains nopostflight. It useshomebrew_casks[].hooks.post.install, GoReleaser's current API since v2.13; the deprecated stanza is what GoReleaser emits, confirmed by generating the Cask locally with GoReleaser 2.18.1, which still writespostflight do. No setting here changes it. The finding is recorded as a comment next to the hooks block with the version and date, to be re-checked after a GoReleaser bump. Hand-writing a stanza the template does not own was rejected. Superseded by D199:custom_blockis GoReleaser's supported way to emit such a stanza. - The two undocumented destinations get an alarm, not a move. Codex skills materialize under
.codex/skillswhile the vendor documents$HOME/.agents/skills; OpenCode agents under.opencode/agentwhile the vendor prefers.opencode/agents. Both work against the real clients. A destination change is a migration — prune the old files, re-key the lockfile — and for Codex the right target is the repository path, which only exists once a workspace scope does (D193), so moving it now would mean doing it twice. What was missing was the alarm: a test asserts the declared destination against the client's own discovery output, so it survives D193 changing that destination. - The compatibility test skips where it cannot answer, and only that. It skips when the client
is absent, and also when the client errors, times out or prints nothing — an unrelated client
problem must not turn this into a red suite everyone learns to ignore. Only a successful run
whose output does not mention the declared directory is a signal. On the machine that
implemented this,
codex debug prompt-inputanswered andopencode agent listdid not, which is exactly the case the skip exists for.
Invariants kept. The installer stays POSIX sh and network-free under test. The GoReleaser
guard keeps asserting upgrade-repair through the stable linked binary. Bundled skills keep their
frontmatter contract. uninstall on a machine with no units behaves exactly as before.
Consequences. install.sh uninstall can now exit non-zero where it used to succeed —
behaviour change, release-note it. Everything else is documentation, corrected hints and test
coverage. The provider list in docs/agent-install.md (item 6) was already fixed by the
Antigravity work (D194) and needed no change here.