fix(usage): reconcile empty-session stats contract after #9305 collision - #9319
Conversation
main is red on test_empty_session_stats[False] and [True]: the actual stats dict carries one key the expectation does not, `refused_transcripts: 0`. Two commits collided semantically, ~74 minutes apart, with no textual overlap: - f654ad0 (#9254, 13:49) made `refused_transcripts` an UNCONDITIONAL field of the `_parse_sessions()` return dict, so the page can say the totals are incomplete instead of rendering a silent under-count on a UNC home. - 5780471 (#9305, 15:04) removed the `{"error": "No sessions directory"}` early return so a missing transcript directory yields complete zero period counts, and rewrote `test_no_directory` into an exact-equality `test_empty_session_stats`. #9305's exact-equality dict was authored against the pre-#9254 shape. Each PR was green on its own merge ref: #9254 never touched the test #9305 rewrote, and #9305's source hunk applied cleanly over #9254's, so neither CI run ever saw both changes. The SOURCE is right and the test expectation is the stale side. A present zero is the contract #9254 established and exactly what #9305 wants for an empty history: nothing was dropped, so the zeros are a fact. Omitting the key would make "complete" and "unknown" indistinguishable on the wire -- the dashboard adapter's `s.refused_transcripts ?? 0` would synthesise a completeness promise the payload never made. So the fix adds the key to the expectation rather than relaxing the assertion or making the field conditional. Tests: test/test_usage.py 95 passed; test_kiro_usage_api.py + test_acp_usage_cost.py + test_hooks_coverage.py 389 passed; test_security_posture.py + test_spawn_audit.py 65 passed.
|
The fix direction here is right, and it matches what I concluded independently while triaging the same red on #9214: the canonical return emits One gap worth closing before this lands: the body names #9305 and #9254 but never #9317, and carries no closing keyword, so For whoever is watching the blast radius: this failure reaches every open PR through |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
Design Review (Fable 5) — ✅ PASSDesign-level review of The diff is a 7-line test-expectation fix: adding Design-Verdict: PASS Correct side of the collision fixed: expectation updated to the deliberate contract, shape lock kept exact, no semantics touched. [DESIGN-REVIEWED] 3f46129 |
Opus 4.8 Review — ✅ no blocking findingsReviewed Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
Heals broken main
mainat 678fc32 is red ontest/test_usage.py::TestParseSessions::test_empty_session_stats[False]and[True], both on the same assertion: the dict_parse_sessions()actually returns carries one key the expectation does not.Root cause: a semantic mid-air collision, 74 minutes apart
Two commits, no textual overlap, each green on its own merge ref:
f654ad072refused_transcriptsan unconditional field of the_parse_sessions()return dict, so the usage page can say the totals are incomplete instead of rendering a silent under-count on a Windows UNC home5780471f3{"error": "No sessions directory"}early return so a missing transcript directory yields complete zero period counts, and rewrotetest_no_directoryinto an exact-equalitytest_empty_session_stats#9305's exact-equality dict was authored against the pre-#9254 shape. Neither CI run ever saw both changes:
test_usage.pybut never touchedtest_no_directory, which at that point asserted only the error dict — so no conflict and nothing to fail.index 48f660ca6..381411ac1in the landed commit is post-fix(usage): surface refused-transcript count on UNC homes #9254), so git had no conflict to report either.Classic both-green-in-isolation, red-together.
Which side is right
The source is right; the test expectation is the stale side.
A present zero is the contract #9254 deliberately established (
"refused_transcripts": refused_transcriptsis emitted on every return), and it is exactly what #9305 wants for an empty history: nothing was dropped, so the zeros are a fact rather than an under-count. Omitting the key would make "complete" and "unknown" indistinguishable on the wire — the dashboard adapter'srefusedTranscripts: s.refused_transcripts ?? 0(website/src/providers/adapters/acp.ts) would synthesise a completeness promise the payload never made, which is the precise failure #9254 exists to prevent.So this adds the key to the expectation. It does not relax the assertion to a subset match, and it does not make the field conditional — either of those would re-open the silent-zero hole. The added comment records why the zero is present rather than absent, so the next author of this expectation does not read it as incidental.
Adjacent
TestParseSessionscases already assertr["refused_transcripts"] == 0on non-empty-but-nothing-refused paths, which independently confirms the field is expected present-and-zero rather than absent.Tests
test/test_usage.pyTestParseSessionsalone)test_kiro_usage_api.py+test_acp_usage_cost.py+test_hooks_coverage.pytest_security_posture.py+test_spawn_audit.pyflake8 test/test_usage.pyscripts/check_black_formatting.pyblack --diffcomplaints are pre-existing file-widewith-statement restyle drift present on pristineorigin/main, and none fall inside this diff's hunk)No frontend file is touched, so the usage adapter and
UsageTab.refusedTranscripts.test.tsxare unaffected by this diff.Test-expectation change only: one key added to an assertion dict. No rendered surface changes —
_parse_sessions()source, the/api/usage/kiropayload, and the usage page are all byte-identical tomain.Pattern harvest
This failure was invisible to both PRs' CI by construction: the mechanism that normally catches a collision (a textual conflict) had nothing to catch, because one PR changed a data shape's producer and the other changed that shape's pinning assertion, in disjoint hunks.
Rule candidate: a PR changing a data shape and its pinning test must be re-run against main tip immediately before merge when neighbors touch the same shape — semantic collisions pass both PRs' own CI.
Two corollaries worth keeping with it:
grepfor the shape's other assertions when adding a field.gh run reruncannot substitute for this: it re-executes the run's original pinned merge ref, so it would have reproduced the same green. Only a fresh run (push, or close/reopen) sees main tip.