GitHub keeps adding counts to a day for well over a day after it closes, and
documents none of it. Every window catnip drew ended on days that were still
being counted, and the tannery agent that was supposed to notice reported
success without running anything.
Added
CATNIP_SETTLE_HOURS— how long after a day closes GitHub is still
adding to it. 36 is a measured floor: the variable raises the wait for an
account that settles slower and cannot lower it, because a short wait
costs nothing visible and publishes part of a day as all of it.catnip.runfiles— one reader for a run directory's raw payloads,
shared by the four traffic analyses. Each had its own copy of the
raw/repo_<slug>_<endpoint>.jsonconvention, and each copy inlined
config.slug_forwithout its sanitizing step: a repo whose name carries
any character outside[A-Za-z0-9_.-]resolved to a filename the fetcher
never wrote,is_file()returned False, and the repo left anomaly,
profile, funnel and correlation output with no error and no row. No repo
on this account triggers it today.meta.jsonrecordswindow_digest,settle_hoursand
store_latest_day.
Changed
- Windows move back. No window ends inside the settling wait, so at a
midday cron1dcovers the day before yesterday. The TUI header names the
day it is showing instead of calling it "last day", and the report's
provenance table carries asettled throughrow. - Correlation is more conservative. A repo whose third non-zero day was
an unsettled one no longer qualifies for the pair scan. - Upgrading is handled, not waited out.
meta.jsonchanged what
latest_daymeans: reports written before this recorded the store's
newest day, which is a fetch day and ahead of what those reports covered.
Comparing a settled day against it suppresses reports for exactly the
cycles the store is gaining settled days, so ameta.jsonwithout
settle_hoursis recognised as pre-settling and does not gate the next
report. - The tannery needs leather v0.5.3 or newer. Older builds parse
require_tool:as prompt text instead of enforcing it.
Fixed
- No window ends on a day GitHub has not finished counting. The last-day
view printed a column of zeros ranked alphabetically, because the newest
day in the store is the day the fetch ran and GitHub returns that day as a
flat zero. Re-reading one day out of four run directories, over the same
35 repos every time, it carried 33% of its final views and 47% of its
final clones at 12h after closing and was complete at 30h; days older than
that were identical in every read.derive.settled_dayends every window
settle_hours()behind the newest fetch — hours, not a day count, because
the same "two days back" means different amounts of settling depending on
what hour the timer fires.docs/metrics.mdcarries the measurement and
#5 tracks making it
self-checking. - The edge is never the last day carrying traffic. Trimming trailing
zeros was the tempting fix and is wrong: this account recorded ten
genuinely empty days in June, and reading a quiet day as an unfinished one
slides every window a day left without saying so. The previous code did
exactly that on the CSV path, where it had been dead for as long as a
store existed. - Trend arrows were biased downward.
totals.pysummedclones_7dover
a window ending on the fetch day whileclones_prior_7dwas complete, so
the comparison measured the shape of the collection. - The per-run analyses no longer score days GitHub is still counting. A
run filed aminor dipagainst a day eighteen hours old. The anomaly
detector, clone profiler and correlation analysis read settled days now,
which also lifts their baselines onto complete data. catnip reportno longer treats a fetch as new data, and no longer
freezes a day it got wrong. The interval guard compared the store's
newest day, which advances on every run whether or not GitHub counted
anything. It compares settled days now, and records awindow_digestof
the numbers each report stated so a day revised after publication reopens
the report. Without it, a report written before a day settled asserted a
third of that day's traffic forever, because the only question asked was
whether a new day had appeared.catnip prunekeeps runs whose days may still be revised. Ingested
means a run's numbers are in the store, not that they were the final
numbers. The run directory is the only record of what one fetch saw, and
history --rebuildreconstructs from surviving runs alone.- A tannery turn that must act can no longer end on prose. The collect
agent answered "catnip-run call succeeded" without calling it on
2026-08-08 and 2026-08-09, finished in 1.4s instead of 41 minutes, and
recorded an inventedlatest_day: 2024-01-15over an untouched store, as
success. Replayed against the same served model the tool was in scope
every time, and the model answered in prose in 4 of 16 samples. Every
acting turn declaresrequire_tool:, and record turns writeunmeasured
withaction: failedwhen a{{value}}arrives unsubstituted. - The tannery no longer does date arithmetic in the model.
catnip-store-statusemitssettled_day,settled_expectedand a
stale: yes|noverdict, and the collect agent's rule is "failed if stale
is yes". A model comparing dates to "yesterday" is arithmetic nobody
checks, and it returned a passing verdict on both failed nights.