Follow-up from a declined review finding on #13 (thread).
What
publish() in scripts/docs.ts swaps a documentation tree into place with two renames: the existing tree moves aside, the new one moves in. Between them the canonical cache path does not exist.
Why it was left
The window is metadata-only and the cost is bounded. A reader that loses the race falls through to a re-download, because resolveDocs treats a missing or incomplete tree as something to repair. The only path that turns into an unavailable result is --no-fetch during a concurrent refresh.
The two remedies suggested in review both cost more than that:
- A per-tag lock adds stale-lock recovery to a cache whose whole failure story today is "re-download", and a crashed holder blocks every reader until a timeout nobody can tune correctly.
- A stable indirection — a symlink pointing at a content-addressed directory — removes the window properly, but it changes the on-disk layout that
path and index hand to callers.
What to do here
Build the indirection, if --no-fetch robustness under concurrent refresh turns out to matter:
- Unpack to
docs/<tag>.<digest>/, then renameSync a symlink at docs/<tag> onto it — a symlink swap is atomic, so no reader ever observes a missing path.
- Decide what
path and index report: the symlink keeps callers stable, the resolved target makes Grep output legible. Pick one and document it.
- Reclaim superseded content directories the way
sweepLeftovers reclaims staging and displaced ones.
- Check Windows, where symlink creation needs either Developer Mode or elevation. A junction or a small pointer file may be the portable form.
Not urgent
No user has hit this. Two resolvers have to publish the same tag at the same moment, and the loser re-downloads unless it passed --no-fetch.
Follow-up from a declined review finding on #13 (thread).
What
publish()inscripts/docs.tsswaps a documentation tree into place with two renames: the existing tree moves aside, the new one moves in. Between them the canonical cache path does not exist.Why it was left
The window is metadata-only and the cost is bounded. A reader that loses the race falls through to a re-download, because
resolveDocstreats a missing or incomplete tree as something to repair. The only path that turns into anunavailableresult is--no-fetchduring a concurrent refresh.The two remedies suggested in review both cost more than that:
pathandindexhand to callers.What to do here
Build the indirection, if
--no-fetchrobustness under concurrent refresh turns out to matter:docs/<tag>.<digest>/, thenrenameSynca symlink atdocs/<tag>onto it — a symlink swap is atomic, so no reader ever observes a missing path.pathandindexreport: the symlink keeps callers stable, the resolved target makesGrepoutput legible. Pick one and document it.sweepLeftoversreclaims staging and displaced ones.Not urgent
No user has hit this. Two resolvers have to publish the same tag at the same moment, and the loser re-downloads unless it passed
--no-fetch.