v1.108.305 — Only the reader was never fixed
Fixed - the release checklist's CI-environment reproduce never built it
Step 2c read uv run --python 3.13 python -m pytest tests/ -q. CI runs
uv sync --locked --group dev --extra watch first. The documented command
synced nothing and named no extra, so it inherited whatever .venv happened
to hold -- it looked correct for exactly as long as a previous sync's packages
survived.
⚠⚠ Caught mid-release, and the near-miss is the point: it returned EXIT 0 and
the totals reconciled EXACTLY (8,740 + 18 new tests = 8,758), which are the
two things "green" means here. Meanwhile passed fell 8,721 -> 8,634 and
skipped rose 19 -> 124: 105 tests silently did not execute. Confirmed at
the source -- syncing with CI's flags printed + watchfiles==1.1.1, the
[watch] extra CI installs BY NAME.
⚠ Read the SKIP count, not just the exit code and the total. The documented
range is 19-26; a jump means the environment, not the code.
⚠⚠ The checklist lives in .claude/skills/release/SKILL.md, which is
gitignored (the v0.2.6 credential-leak fix), so a correction there is
machine-local and gone on a fresh checkout. Un-ignoring .claude/ to make it
testable would reintroduce the vector that got five releases yanked, so the
durable copy now lives in CLAUDE.md and tests/test_ci_env_reproduce_command.py
binds it to .github/workflows/test.yml -- the two cannot drift apart unnoticed.
⚠ Third instance of one family, and CONTRIBUTING.md already carried the
sentence: CI installs with uv sync and never runs the command the docs give a
human. First was pip install -e ".[test]" (an extra no repo declares); second
was -n 4 --dist loadfile under a bare python -m pytest, which collects
nothing and exits 0.
Added - a lock wait reports itself, because waiting looks like working (#557)
@Ticki84 ran the new phase breakdown on the first build that had it and it
answered immediately: save=9.906s of a 10.000s total, everything else
summing to 0.094s. The cost is entirely store.incremental_save.
⚠⚠ incremental_save takes an indexwrite process lock before it writes, so
from the caller's timer a CONTENDED LOCK and a SLOW WRITE are indistinguishable
-- both are just time spent inside save. Only the wait itself can separate
them, and only process_locks can see it.
_Held.__enter__ now records waited_seconds and logs it: DEBUG for any wait,
WARNING past one second, with the holder NAMED (pid, client_id, age).
A multi-second stall on a single-file reindex is a user-visible problem rather
than a debug detail, and "something else holds the lock" without saying what
sends the reader hunting in the wrong process. watch-all watches every indexed
repo, so a second watcher or an editor-side MCP server is exactly the shape that
would queue here.
⚠ The round 10.000s is what makes contention the leading hypothesis -- real
work rarely lands on a round number -- but it is a hypothesis, and shipping the
instrument is cheaper than asking the reporter to test it. Three earlier
hypotheses on this issue were each measured dead by the reporter.
Fixed - the machine's timezone chose the input format, so 3.10 broke in CI only
The shallow-boundary probe read git log --format=%aI. git renders a UTC
offset as Z, and datetime.fromisoformat could not parse Z until 3.11, so
on 3.10 every boundary date came back unparseable.
⚠⚠ It could not be reproduced on the developer box, and the reason is the
lesson. git emits Z only where the offset IS zero. Every CI runner is UTC;
this box is CDT and got -05:00, which 3.10 parses fine. The host's timezone
selected which spelling git produced, so the integration tests were green
locally on all versions and red on 3.10 across both operating systems.
uv run --python 3.13 could not have caught it either -- it was never a version
the local clock could break.
⚠ test_parse_iso_accepts_both_offset_spellings pins all four spellings as a
UNIT, with no repository, no clock and no timezone. An integration test is
structurally incapable of guarding this: it can only observe whichever spelling
its host happens to produce. Two of the four cases fail against the old parser
under 3.10 on the non-vacuity pass.
⚠ The tri-state held under the fault and that is worth recording. An
unparseable boundary reported complete: None -- could not establish -- rather
than a confident coverage answer off a date nobody read. The design degraded
instead of lying, which is what the CI failure looked like: assert None is False, not a wrong verdict.
⚠ uv run ruff check src/ passed against the broken parser. Lint is not a
correctness signal, and a green lint on a hand-edited revert is not a restore.
Fixed - a scratch file shipped inside the published 1.108.304 sdist
relnotes.md -- a temporary copy of the CHANGELOG entry, written for
gh release create --notes-file -- was swept up by a git add -A in the
release commit. It is in the v1.108.304 tag and inside the sdist on PyPI.
Harmless content, permanently there: PyPI cannot be re-uploaded.
⚠⚠ The canary tests could not have caught it and never could.
tests/test_sdist_exclusions.py plants a canary under each NAMED excluded path
and proves it is absent -- it answers "did a known-bad path get in". A scratch
file has no name to plant a canary under. A denylist catches the instance; an
allowlist catches the class. ALLOWED_ROOT_FILES now enumerates every file
permitted at the sdist root, so anything nobody decided on fails the build.
⚠ Both directions are asserted, because a list that drifts from the artifact
stops being a guard: test_no_unexpected_file_at_the_sdist_root catches an
addition, test_the_allowlist_is_not_stale catches an entry naming a file that
no longer ships. Both fail against their own defect on the non-vacuity pass, the
first naming relnotes.md exactly.
⚠ Build release notes OUTSIDE the repository. A .gitignore entry would
also work and is strictly weaker -- it protects only the spelling someone
remembered.
⚠⚠ The guard found a SECOND live instance within minutes of being written,
and it was the release engineer's own. suite.log -- the file every gate run
in that session redirected pytest into, in the repository root -- failed the new
assertion on the first full-suite run. It had been there all day. A release
cut while one existed would have shipped it, and unlike relnotes.md a pytest
log carries absolute paths and usernames. *.log is now gitignored (hatchling
honours the root .gitignore), but that is defense in depth: it protects one
spelling, and the allowlist is what catches the class.
Fixed - nine tools read a git window none of them could tell was truncated
git log --since=<N> days on a shallow clone returns a short log and exit
status 0. Nothing outside the observatory's own cloner detected that, so
get_churn_rate, get_hotspots, get_file_risk, get_delivery_metrics,
get_tectonic_map, winnow_symbols, decision_context, find_unused_paths
and health_radar.churn_surface all read a truncated history as a calm one.
⚠⚠ Fixed twice before, never in a READER. Practice 6 records
git fetch --depth=1 shortening an already-complete clone in the health-radar
Action; tests/test_observatory_clone_depth.py records the same defect in the
observatory's cloner, measured at 81.3 (B) shallow versus 75.6 (C) full at one
identical commit, churn_surface the only axis that moved. Both fixes made
OUR clones deep. actions/checkout defaults to fetch-depth: 1, so every
user running the Action or jcodemunch-mcp health in their own CI still got a
flattering grade on their own pull requests. Third instance of "we fix the
reported call site and leave the mechanism".
⚠ tools/_git_history.py asks coverage, not shallowness.
--is-shallow-repository is the mechanism; "the history reaches past the
window" is the property. Verified on real clones: --depth=900 at a 90-day
window is shallow AND complete (shallow_but_covers_window); the same clone at
365 days is not. A false alarm on a deep-but-bounded clone would teach people to
ignore the flag. A three-week-old repository is YOUNG, not truncated.
⚠ Tri-state. No git, no repo, an unreadable boundary: complete: None, never
False. churn_is_measurable() collapses None to "do not publish" at the one
place a caller must decide whether to issue a grade -- the _stop_rule rule,
where every uncertainty resolves to False.
⚠ Disclosure is silent on a complete history by design; a block on every
response is one nobody reads. An UNKNOWN is disclosed, because it is not a clean
bill of health.
Fixed - a grade was withheld the wrong way first, and the number got worse
⚠⚠ Recorded because the first fix was wrong in the flattering direction, which
is the direction that matters here. The obvious gate was to pass
top_hotspot_score=None and let churn_surface be omitted, reusing the
convention runtime_coverage already uses. Measured on one tree: pre-fix
84.0 B, "fixed" 88.8 B, full-clone truth 77.3 C. Dropping a
low-scoring axis RAISES a mean, so the fix moved the published grade further
from reality than the defect had.
⚠⚠ The error was collapsing two states this project separates everywhere else.
NOT APPLICABLE -- no trace was ever ingested, the axis does not apply, and
omitting it keeps the composite comparable -- is not COULD NOT MEASURE. Only
the first may be dropped silently.
compute_radar now takes unmeasurable_axes; when non-empty, composite and
grade are withheld entirely (None) with grade_withheld and the measured
axes still reported, plus partial_composite for a caller who knowingly wants a
figure missing an axis. ⚠ The default path is byte-for-byte unchanged and a test
asserts it for both None and [].
⚠⚠ Two None sites the tests found, both user-facing, and one of them is why
.get(k, default) is not a guard: diff_radar read
.get("composite", 0.0), and the default never fires when the key is present
with value None -- it raised. Defaulting to 0.0 would have been worse: a
~77-point "regression" against a side that was never measured. And _verdict,
the one-line string printed on a contributor's pull request, would have rendered
a withheld composite as "no meaningful change" -- the reassuring answer, on
the single occasion nothing was measured.