Skip to content

fix: expire the docs cache and skip an unpublished area - #10

Merged
kim-em merged 2 commits into
mainfrom
fix/docs-cache-ttl-and-unpublished-area-skip
Aug 10, 2026
Merged

fix: expire the docs cache and skip an unpublished area#10
kim-em merged 2 commits into
mainfrom
fix/docs-cache-ttl-and-unpublished-area-skip

Conversation

@kim-em

@kim-em kim-em commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

This PR gives the published-documentation disk cache a one-hour expiry and makes plan skip a roadmap area whose cursor is newer than the documented build, instead of aborting the whole plan on it.

Docs._get cached every fetched page to /tmp/tauceti-docs-cache with no expiry, so source_commit() kept returning whichever build the cache happened to see first. A worker was found holding an index five days old. Every window plan could close therefore ended at that old commit, and a roadmap area whose first pull request merged after it bootstrapped to a cursor outside the documented history. window_prs refuses on such a cursor, build_plan only catches files.FormatError, and so a single new roadmap stopped progress reporting for the whole fleet until someone deleted the cache directory by hand. Four areas were in that state (EllipticCurves first, on 2026-08-07), and the worker's error breaker had latched after three consecutive failures.

The TTL is TAUCETI_DOCS_TTL, defaulting to 3600 seconds: far shorter than the deploy cadence and far longer than one run, so the cache still does its real job of serving the hundreds of module pages a bootstrap reads. Within a run the in-process memo remains the authority, so an expiry landing mid-run cannot mix two builds into one report.

The skip is deliberately narrow. A cursor that is in neither the documented history nor ref is the rewritten-branch case, and that still falls through to area_window and raises exactly as before.

🤖 Prepared with Claude Code

kim-em and others added 2 commits August 10, 2026 02:28
`Docs` cached every fetched page to disk for ever, so `source_commit()` returned whichever build
the cache first saw. A worker held an index five days old; every window `plan` could close ended
there, and a roadmap whose first pull request merged after that commit bootstrapped to a cursor
outside the documented history. `window_prs` refuses on such a cursor, and the exception aborted
the whole plan, so one new roadmap stopped all progress reporting until the directory was deleted
by hand.

Give the disk cache a one-hour TTL (`TAUCETI_DOCS_TTL`), and skip an area whose cursor is newer
than the documented build rather than failing the plan. The refusal itself is unchanged for a
cursor that is in no reachable history: that is the rewritten-branch case and stays loud.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011LgBAGJrhF7Frp6TbBZaY8
…wide aborts

Addressing a review of the first commit.

Per-URL expiry does not make a run coherent: the probe page can cross its TTL and fix
`source_commit()` at a new build while another page is still served, in TTL, from the old one.
`facts.collect` then reads line spans from one build and blames them at the other's commit, which
shifts a declaration's span onto whatever now occupies those lines. `declarations` now checks every
page against the build the reader already committed to, re-fetches once, and refuses if the site is
still moving.

Also: write cache entries through a temp file and `os.replace`, so a concurrent worker cannot read a
truncated page (a half-written module page parses as zero declarations, which reads as an honest
empty result); require the cached age to be non-negative, so a clock correction cannot make an entry
immortal again; fall back to the transport when a fresh entry cannot be read.

Two more single-area conditions that aborted the whole plan: say which of the two cases a cursor
ahead of the documented build is, rather than calling a documentation rollback "not published yet";
and check `has_commit` before asking `merge-base` about a merge commit GitHub named, since a pull
request merged since the last fetch is unknown to the checkout and `merge-base` fails rather than
answering "no". The second was hit for real while verifying this branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011LgBAGJrhF7Frp6TbBZaY8
@kim-em

kim-em commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Second opinion from Codex, and what changed as a result. Its blocker was correct and is now fixed.

Blocker — per-entry TTL did not give one run one build. Confirmed the mechanism: facts.collect takes docs_sha = docs.source_commit() and blames at that commit, while info["start"]/["end"] come from whichever module page was served, with no check that they agree. So a probe page crossing its TTL mid-run could fix source_commit() at the new build while another page was still served, in TTL, from the old one, shifting a declaration's span onto whatever now occupies those lines. declarations now validates every page against the build the reader already committed to, re-fetches once, and refuses with a clear error if the site is still redeploying. The overclaiming test name went too: the memo only makes one URL stable, which is what it now says.

Concurrent writes were not atomic. write_text truncates first, so a concurrent worker sharing /tmp/tauceti-docs-cache could see a fresh mtime on a partial file — and a half-written module page parses as zero declarations, which reads as an honest empty result. Now a temp file plus os.replace.

Freshness edge cases. Age must now be non-negative as well as small, so a corrected clock or a skewed shared filesystem cannot make an entry immortal again; ttl <= 0 is explicit; and an unreadable fresh entry falls back to the transport instead of raising.

A recorded cursor ahead of the docs is no longer described as "not published yet." A bootstrapped cursor ahead of the build is ordinary. A recorded one means the documentation went backwards, and the two now get different messages, because they want different reactions from whoever reads them.

One more plan-wide abort, hit for real while verifying the branch. unaccounted_prs asks GitHub for a merge commit and then asks merge-base about it. For a pull request merged since the checkout's last fetch that commit is unknown locally, and merge-base fails rather than answering "no" — aborting the entire plan for every area, the same shape as the outage this PR is about. Guarded with git cat-file -e first.

Left as follow-ups, both pre-existing and neither a regression here:

  • is_ancestor(from, tip) proves ancestry, not membership of tip's first-parent chain. A cursor naming a side-branch commit is incomparable with to_sha and would be treated as "ahead". Bootstrap cursors are first-parent by construction and recorded ones are previous documented SHAs, so reaching this needs a hand-edited PROGRESS.md, which the merge gate already constrains.
  • A cursor in no reachable history still aborts the plan rather than quarantining just that area. That is deliberate for now — it is the rewritten-branch case and staying loud is the point — but Codex is right that loud and fleet-wide are separable.

Tests: 16 in test_docs.py (positive-TTL expiry via a backdated mtime, future mtime, unreadable entry, atomic write, cross-build page, redeploy refusal) and 4 new in test_window.py. Full suite green, plus an end-to-end plan against the live site and a checkout deliberately one fetch behind.

@kim-em
kim-em merged commit 880e8b9 into main Aug 10, 2026
1 check passed
kim-em added a commit to TauCetiProject/TauCetiRoadmap that referenced this pull request Aug 10, 2026
Move the merge gate and the announcer to the same TauCetiProgress build the worker now runs, per the
must-match rule both pins document. Picks up TauCetiProject/TauCetiProgress#10, which fixes the
never-expiring documentation cache behind the five-day progress reporting outage, and stops three
single-area conditions from aborting the whole plan.

Neither the STATUS.md / PROGRESS.md formats nor the gate's validators changed, so this is a version
move rather than a behaviour change on this side.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011LgBAGJrhF7Frp6TbBZaY8
kim-em added a commit to kim-em/TauCetiWorker that referenced this pull request Aug 10, 2026
Picks up the documentation-cache expiry and the three plan-wide aborts fixed in
TauCetiProject/TauCetiProgress#10. Until this lands the worker keeps running f735789, whose
documentation cache never expires — so the outage it fixes returns as soon as
/tmp/tauceti-docs-cache repopulates.

The pins in TauCetiRoadmap/.github/workflows/progress-{merge,announce}.yml move to the same SHA.


Claude-Session: https://claude.ai/code/session_011LgBAGJrhF7Frp6TbBZaY8

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
kim-em added a commit to TauCetiProject/TauCetiRoadmap that referenced this pull request Aug 10, 2026
Move the merge gate and the announcer to the same TauCetiProgress build the worker now runs, per the
must-match rule both pins document. Picks up TauCetiProject/TauCetiProgress#10, which fixes the
never-expiring documentation cache behind the five-day progress reporting outage, and stops three
single-area conditions from aborting the whole plan.

Neither the STATUS.md / PROGRESS.md formats nor the gate's validators changed, so this is a version
move rather than a behaviour change on this side.


Claude-Session: https://claude.ai/code/session_011LgBAGJrhF7Frp6TbBZaY8

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant