cctally 1.108.0 → 1.110.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/README.md +6 -6
  3. package/bin/_cctally_account.py +4 -0
  4. package/bin/_cctally_alerts.py +6 -2
  5. package/bin/_cctally_cache.py +1652 -166
  6. package/bin/_cctally_config.py +3 -2
  7. package/bin/_cctally_core.py +594 -33
  8. package/bin/_cctally_dashboard.py +1585 -201
  9. package/bin/_cctally_dashboard_conversation.py +494 -48
  10. package/bin/_cctally_dashboard_envelope.py +167 -11
  11. package/bin/_cctally_dashboard_share.py +154 -36
  12. package/bin/_cctally_dashboard_sources.py +2615 -422
  13. package/bin/_cctally_db.py +678 -5
  14. package/bin/_cctally_diagnosis_sources.py +238 -26
  15. package/bin/_cctally_doctor.py +99 -18
  16. package/bin/_cctally_five_hour.py +1295 -40
  17. package/bin/_cctally_forecast.py +124 -28
  18. package/bin/_cctally_journal.py +408 -38
  19. package/bin/_cctally_milestone_history.py +150 -14
  20. package/bin/_cctally_parser.py +5 -0
  21. package/bin/_cctally_percent_breakdown.py +6 -1
  22. package/bin/_cctally_project.py +48 -76
  23. package/bin/_cctally_quota.py +39 -7
  24. package/bin/_cctally_quota_model.py +17 -2
  25. package/bin/_cctally_record.py +1647 -443
  26. package/bin/_cctally_rederive.py +899 -30
  27. package/bin/_cctally_refresh.py +2 -1
  28. package/bin/_cctally_share.py +24 -5
  29. package/bin/_cctally_source_analytics.py +1035 -78
  30. package/bin/_cctally_statusline.py +83 -19
  31. package/bin/_cctally_store.py +61 -4
  32. package/bin/_cctally_transcript.py +27 -2
  33. package/bin/_cctally_tui.py +264 -76
  34. package/bin/_cctally_weekrefs.py +28 -5
  35. package/bin/_lib_blocks.py +127 -20
  36. package/bin/_lib_cache_report.py +20 -5
  37. package/bin/_lib_codex_conversation_query.py +297 -64
  38. package/bin/_lib_codex_hooks.py +102 -0
  39. package/bin/_lib_codex_metadata.py +207 -0
  40. package/bin/_lib_conversation.py +119 -0
  41. package/bin/_lib_conversation_dispatch.py +31 -1
  42. package/bin/_lib_conversation_query.py +315 -51
  43. package/bin/_lib_conversation_retention.py +413 -25
  44. package/bin/_lib_dashboard_sources.py +267 -7
  45. package/bin/_lib_diagnosis.py +10 -0
  46. package/bin/_lib_diff_kernel.py +12 -1
  47. package/bin/_lib_doctor.py +230 -8
  48. package/bin/_lib_ingest_frontier.py +1366 -49
  49. package/bin/_lib_journal.py +19 -2
  50. package/bin/_lib_merge_gate.py +307 -0
  51. package/bin/_lib_pricing.py +588 -29
  52. package/bin/_lib_record.py +130 -12
  53. package/bin/_lib_rederive.py +903 -2
  54. package/bin/_lib_render.py +9 -3
  55. package/bin/_lib_retained_size.py +27 -2
  56. package/bin/_lib_segment_summary.py +66 -8
  57. package/bin/_lib_share_templates.py +28 -18
  58. package/bin/_lib_snapshot_cache.py +115 -1
  59. package/bin/_lib_source_retry.py +320 -0
  60. package/bin/_lib_statusline_candidates.py +400 -36
  61. package/bin/_lib_tick_stats.py +119 -0
  62. package/bin/_lib_view_models.py +256 -76
  63. package/bin/cctally +69 -0
  64. package/dashboard/static/assets/ConversationsView-BB9vUiL6.js +72 -0
  65. package/dashboard/static/assets/{DoctorModal-CWn3U4Wl.js → DoctorModal-DFY2iPDf.js} +1 -1
  66. package/dashboard/static/assets/ModalRoot-DvRKHbQE.js +1 -0
  67. package/dashboard/static/assets/ProjectsDrillPanel-DAv39uSo.js +1 -0
  68. package/dashboard/static/assets/SourceDetailModal-DSAWo2Fp.js +1 -0
  69. package/dashboard/static/assets/{UpdateModal-D3m8GG6V.js → UpdateModal-R_9dOVnh.js} +3 -3
  70. package/dashboard/static/assets/dashboardStream.shared-worker-DbG6Ef0T.js +1 -0
  71. package/dashboard/static/assets/index-COhxe3vU.css +1 -0
  72. package/dashboard/static/assets/index-DdU62sov.js +13 -0
  73. package/dashboard/static/assets/outlineNavigation-BOCLcRa6.js +9 -0
  74. package/dashboard/static/assets/useKeymap-CW7Up0Ay.js +1 -0
  75. package/dashboard/static/dashboard.html +3 -3
  76. package/package.json +4 -1
  77. package/dashboard/static/assets/ConversationsView-BOSaBRtu.js +0 -72
  78. package/dashboard/static/assets/ModalRoot-SR070V6c.js +0 -1
  79. package/dashboard/static/assets/ProjectsDrillPanel-QLI9i5mZ.js +0 -1
  80. package/dashboard/static/assets/SourceDetailModal-pv0wlFxl.js +0 -1
  81. package/dashboard/static/assets/dashboardStream.shared-worker-1XTMV3nr.js +0 -1
  82. package/dashboard/static/assets/index-BHu4mxd8.css +0 -1
  83. package/dashboard/static/assets/index-CIWsbux3.js +0 -13
  84. package/dashboard/static/assets/outlineNavigation-CJvKqmLV.js +0 -9
  85. package/dashboard/static/assets/useKeymap-ffqsS5G0.js +0 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,153 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [1.110.0] - 2026-09-22
9
+
10
+ ### Added
11
+ - Claude cost reports price `claude-opus-5-5` at its published input, output, cache and Fast-mode rates instead of showing $0 for new usage.
12
+ - Codex cost reports price `gpt-6-luna` at its published Standard, long-context and Fast-mode rates, and resolve `gpt-daybreak-red-latest` to the current Cyber card.
13
+ - Historical `gpt-5.5-cyber` Codex turns use their retained LiteLLM OpenAI-provider rate card instead of the legacy `gpt-5` fallback.
14
+ - Codex reporting prices `gpt-6-sol` from OpenAI's published Standard, cached-input, long-context and Fast-mode rates instead of the legacy `gpt-5` fallback.
15
+ - The dashboard's `Blocks` panel marks Claude rows whose cost and model figures come from the facts retained when the block closed, without recalculating those figures from the current cache.
16
+ - Maintainers can turn latest full-backlog triage into a dependency-safe Codex execution plan and run automatically in two Sol/High worktree slots, with refresh-safe splitting, one resumable monitor, and no worker-created orchestration chain.
17
+
18
+ ### Fixed
19
+ - Codex long-context requests above 272K input tokens now use OpenAI's higher input, cached-input and output rates for the entire request, including Fast-mode costs and dashboard cache savings.
20
+ - The dashboard promptly releases a Conversation Viewer live-tail thread when its reader closes, instead of retaining each one until the next 15-second keep-alive.
21
+ - The dashboard's Current Week modal reliably returns keyboard focus to the hero after Escape, even when a source-selector click is still pending under load.
22
+ - The `Blocks` detail modal connects its retained-facts explanation to the cost and token figures for screen readers.
23
+ - The `dashboard` fully hides its onboarding hint while an alert toast is active on a phone, so the alert action stays visible while the original onboarding timer continues.
24
+ - `cctally db rederive` retains a weekly cost snapshot when replay names the same range instants with different UTC-offset spelling, so reviewed corrections do not rewrite unchanged cost facts.
25
+ - `cctally db rederive` can apply an operator-reviewed weekly correction without carrying unrelated baseline drift. Version 2 manifests can preserve or release an accepted snapshot identity and pin both plan hashes before append.
26
+ - `cctally db rederive` preserves closed five-hour costs and accepted snapshot references when current replay rules revisit the same retained evidence.
27
+ - A Conversation Viewer link that waits for its outline now limits a failed page load to three requests, then shows the connection error with a Retry button for the linked message.
28
+ - `cctally db rederive` now refuses a correction that would add a burst of matching weekly reset events, and its JSON preview breaks action counts out by event kind for inspection.
29
+ - Dashboard diagnosis and conversation outlines now bound request surges and stalled reads. A timed-out diagnosis process is retired so later requests recover, and unchanged dashboard files revalidate without rereading or recompression.
30
+ - The dashboard's `Current Week` five-hour block now follows the account that supplied the displayed weekly percentage. Its credits and crossings stay with that account when two accounts share a window, including while browsing older blocks.
31
+ - The dashboard's Claude Session detail now shows cache rebuilds and cache savings over plain HTTP LAN access, including the healthy zero-rebuild state. Opening the same session over loopback and LAN shows the same verified outline.
32
+ - Dashboard panel headings now shorten before their action buttons escape narrow cards, including when Projects data is unavailable.
33
+ - The dashboard calls Claude's credited window a cycle in the hero and milestone modal. Its cost comparison names a comparable cycle, multi-account spend no longer implies one common cycle, and week-scoped panels keep their week labels.
34
+ - The dashboard's phone-width account cards now show a swipe cue while more cards remain, and long snapshot ages stay readable inside the hero.
35
+ - At phone widths where the dashboard source-status chip is shown, warning labels now name both the affected area and its state, such as `Accounts unavailable` or `Projects partial`.
36
+ - The dashboard's `Send test alert` preview now shows the same account chip and dismiss hint as a real alert on single-account and multi-account installs.
37
+ - Closing a dashboard modal now returns keyboard focus to its opener, and explanatory card text no longer opens a modal when clicked.
38
+ - The dashboard's All-source Daily detail shows a single-provider day's model costs once, without an empty second provider row.
39
+ - The dashboard's Projects modal presents its cycle window and chart scale as separate radio groups for screen readers.
40
+ - Corrupt Codex token or timestamp values no longer empty the provider. Valid costs and projects remain visible while affected rows are skipped and counted, including rows in a budget period older than the detail view.
41
+ - Long identifiers in Doctor remediation text now wrap inside the modal at narrow screen widths instead of being clipped.
42
+ - One Codex conversation whose stored project path cannot be read no longer empties the whole Codex provider. Every other project is published from current data, and the affected rows are reported as withheld.
43
+ - A Codex project path the store cannot read is counted and named in `cctally doctor`, under a third reason beside the missing key and missing thread join it already reports.
44
+ - A refresh that could not read project metadata is replaced on the next refresh instead of being kept for up to two minutes.
45
+ - A refresh whose project metadata could not be read states so in the Projects panel body, so an empty panel is no longer silent about why.
46
+ - The Conversation Viewer opens a conversation whose stored project path cannot be read, showing no project instead of failing, and its spawned-agent links, raw export and live tail work again.
47
+ - No conversation list, filter, search hit or outline shows a project for a conversation whose stored project path can no longer be read.
48
+ - An anonymized transcript export refuses while any Codex project path cannot be read, rather than producing bytes that only look scrubbed. `cctally transcript export` exits 3 and the viewer says so; `--raw` is unaffected.
49
+ - An account-scoped anonymized Codex transcript now scrubs project paths tied to that account. If path ownership is uncertain, export and the viewer's Anon copy refuse instead of claiming the copy is anonymous.
50
+ - In the Conversation Viewer, a refused anonymized per-turn copy now shows a red X on the control, so a tap visibly reports that nothing was copied.
51
+ - The dashboard's Session detail no longer logs a failed outline request when a transcript was not kept. The Conversation Viewer names a temporarily unavailable transcript store on deep links and while search results load.
52
+ - `cctally explain` and the dashboard's Explain view retry a brief database change before reporting that the read could not complete.
53
+
54
+ ## [1.109.0] - 2026-09-15
55
+
56
+ ### Added
57
+ - The dashboard picks up a new pricing revision without a restart. It re-reads the deployed pricing file every 60 seconds, adopts a complete newer revision, and keeps serving; no connected client is dropped.
58
+ - The conversation filter popover says `Projects appear once indexing finishes.` in place of `No projects.` while the transcript index is being rebuilt. Model counts stay live on the Claude tab.
59
+ - The dashboard's Projects panel gives every billing cycle of a credited week its own percentage. Both cycles shared one calendar date, and only the later reading survived, so the second cycle could never carry one.
60
+ - The milestone modal names an observation gap where it showed a bare em dash, and states each back-filled run once above the table, as `cctally percent-breakdown` already did. A gap in the current cycle is disclosed too.
61
+
62
+ ### Changed
63
+ - The dashboard's conversation routes open the transcript store read-only. Every browse used to run two write-capable pragmas first, so a read competed with maintenance for the SQLite write lock and lost.
64
+ - A conversation route answers with a typed degraded envelope instead of HTTP 500 while a rebuild, a reclaim pass or a schema upgrade holds the store. Sessions reappear on their own; no request returns a server error.
65
+ - Transcript reclaim runs under a two-second budget and continues on the next pass, so it no longer holds the maintenance and both provider locks until the freelist drains. It is reported as its own timed phase.
66
+ - `cctally project` reports the same per-cycle percentages as `cctally weekly` on the same store. A credited week now contributes one percentage per cycle to `totals.usedPercent`, so a week credited once reports both figures instead of one.
67
+ - A cycle with no observation of its own is reported as missing rather than given the previous cycle's reading, so a week credited `n` times can report up to `n + 1` independently missing observations.
68
+ - An observation captured at the exact instant of a weekly credit belongs to the cycle that starts there, matching where the spend recorded at that instant already goes.
69
+ - The Trend panel, the Trend modal, the Projects modal and the period modal count in billing cycles for both providers, and share artifacts follow.
70
+ - The Projects window pills render bare numbers under a `cycles` label, the Projects grid columns drop the `wk` prefix, and the period modal's `Subscription window` field is now `Reset cycle`.
71
+ - Two cycles of a credited week that fall on one calendar day now render distinct labels, with a time suffix on each. An uncredited week and an ordinarily credited week are unchanged.
72
+ - The dashboard's Projects share artifact states the period its data actually covers. It multiplied a cycle count by seven days, which overstates the span once a credited week is inside it.
73
+ - An All-tab project drill widens to a window that reaches the ranking the row came from, instead of assuming every cycle spans seven days.
74
+ - `cctally project`'s `Used %` cell counts billing cycles: `(3wk)` now reads `(3cy)`, and the trend share artifacts' `Δ` rows follow. A credited week contributes two cycles, so the week abbreviation named the wrong unit.
75
+ - Screen readers announce the Weekly modal's cost chart and the Current Week navigator in billing cycles on the Claude tab. They announced "week" and "period" while every visible label beside them said cycle.
76
+ - `--no-sync` no longer runs the two transcript derivations at startup, so a frozen dashboard starts promptly. While either is owed, conversation reads report that state rather than an empty list, until you run `cctally cache-sync`.
77
+ - A Codex project or quota block reports itself partial when any accounting record inside the past year is unreadable, and states that project metadata is withheld for that item, so a cost is no longer missing with nothing saying so.
78
+ - A momentary database error marks that refresh's Codex project and quota block views partial, and the next refresh recovers on its own once the store is healthy.
79
+ - When the dashboard cannot read the account registry, the Codex source says so: it reports the refresh as partial and states that per-account detail is withheld, instead of presenting the install as if it held one account.
80
+ - The source status chip names the withheld account detail instead of reading `Source degraded`, so a registry the dashboard could not read is distinguishable from a source that could not be built at all.
81
+ - The first journal rebuild after upgrading re-reads every segment once, because the stored segment-summary format changed. Later rebuilds skip unchanged segments as before.
82
+
83
+ ### Fixed
84
+ - `cctally record-credit` plans a credit from the confirmed account's own snapshots, floors and five-hour readings, and its preview names the account it will write to. An install with one account is unchanged.
85
+ - `cctally record-usage`, `cctally report` and `cctally percent-breakdown` read each account's own week boundary, so one account's earliest capture no longer sets another account's week.
86
+ - `cctally explain` decides Codex prompt evidence per conversation. A conversation whose transcript is no longer retained leaves the evaluated population and lowers reported coverage, instead of counting as a confident non-contributor.
87
+ - A journal segment summary survives an ordinary volume remount, after proving the file's bytes are unchanged, so a rebuild no longer re-reads the whole journal once an external or network volume is remounted.
88
+ - The dashboard rebuilds a Codex view whose project metadata was incomplete instead of serving it again for the life of the process, and retries on a bounded schedule rather than once.
89
+ - The dashboard tells a temporary read failure apart from a record it cannot interpret, so only the second advises `cctally cache-sync --source codex --rebuild`.
90
+ - The Projects drill issues one request at the width where two panels are mounted, so resizing the window no longer fetches the same project twice.
91
+ - `cctally five-hour-breakdown` and the current-usage modal report the weekly percentage a reader saw at each five-hour crossing, not the raw reading a held tick recorded. Where that reading is no longer retained the column reads `—`.
92
+ - A five-hour block shows only its own account's crossings. One window owns one block per account, and both the current-usage modal and the historical cycle view listed every account's crossings under each.
93
+ - `cctally five-hour-blocks` and `cctally five-hour-breakdown` withhold a weekly percentage a weekly credit retired, at a block's start and end, closed and reset-crossing blocks included. A block observed only before the credit is unchanged.
94
+ - A held status-line tick keeps its five-hour reading through an in-place weekly credit. The credit's cleanup of stale pre-credit replays removed that tick's row, the only record of the reading it carried.
95
+ - A status-line tick whose weekly percentage is held at its high-water mark no longer discards that tick's five-hour usage, so `cctally five-hour-blocks` and the dashboard's five-hour panel stay current.
96
+ - A status-line tick held at the weekly high-water mark does not advance the weekly percentage or its freshness stamp. `cctally forecast`, `cctally report`, the status line, the TUI and the dashboard keep the last observed reading.
97
+ - The Codex source recovers on the next refresh once the account registry is readable again. A refresh that could not read it republished that result for the life of the process, so the per-account panels stayed missing until a restart.
98
+ - A Codex quota block whose retained metadata is incomplete opens and states that, instead of replacing the whole panel with a message saying the dashboard had updated. Nothing had updated.
99
+ - Above one Codex account, clicking a quota block row in the Blocks panel opens that row's own account while the account selector is on All accounts. One of two rows sharing a window opened the other account's block.
100
+ - Opening a Codex project or quota block in the dashboard is immediate. Each click read a year of Codex accounting first, which on a large store is every row it has, and now reads only that project's or that window's own rows.
101
+ - A Codex quota block opens while one unreadable Codex accounting record sits elsewhere in the store. The view converted a year of records it never used, so a single bad record failed the whole request.
102
+ - A Codex quota block renders every observation of its own window. The detail read a thousand recent rows, so a busy window could lose members, or arrive empty and report the block as missing.
103
+ - Above one Codex account, opening a quota block returns that account's block. Two accounts whose blocks share a root, a limit, a slot, a window length and a reset produced one link, and whichever sorted first was shown.
104
+ - A Codex quota block older than the two hundred and fiftieth most recent now opens instead of reporting itself missing.
105
+ - A Claude transcript keeps its account after its volume is remounted. A remount renumbers the device each transcript records, and the next time one grew, its whole history took the account signed in then. Existing damage is not repaired.
106
+ - The conversation list names the transcript-import state and the command that clears it, instead of reporting a load failure beside a Retry that could never succeed. The server reported that state; the page discarded it.
107
+ - The current-usage modal describes a reconciling Codex quota projection the way the hero does, rather than calling it unavailable directly above the figures it publishes. The Codex-only view carries that qualification too.
108
+ - A Codex hero that is showing its last coherent figure says so in text and to assistive technology. The explanation was in a tooltip, which a touch user cannot reach.
109
+ - Opening a Codex row in Recent Sessions is immediate. The detail rebuilt every Codex session of the past year on each click, and now uses the row the dashboard already published, so it reads no accounting data at all.
110
+ - The dashboard's Codex hero keeps its last known spend, with a quiet updating marker, while the quota projection reconciles. It blanked the figure for that moment, and the account card beneath it went on showing a number.
111
+ - A Codex hero says it is pending, rather than rendering an empty value that reads as no spend, both before its first coherent reading and whenever its weekly cycle stops resolving afterwards.
112
+ - The dashboard publishes a recorded metering-rate change on the next refresh. Nothing the dashboard compared between refreshes changed when one was written, so an idle dashboard kept serving the alert list from before it.
113
+ - The dashboard evicts its least recently used Codex caches at their retained-memory budget instead of discarding all of them. A store permanently above the budget rebuilt every cached view on every refresh. Published figures are unchanged.
114
+ - The visible-population memo is still evicted whole, not in part, because it is one indivisible population. Two other cases take every Codex cache cold: a total eviction cannot reduce, and a refresh whose generation moved mid-build.
115
+ - The dashboard no longer discards its most expensive Codex caches first. Two reported no age and no size to the eviction order, so the visible-population memo and the quota memo always went first, however recently they had been used.
116
+ - `cctally dashboard-perf` reports what the Codex source caches retain. The figure came from a background measurement that never finished on a large store, so it read as zero.
117
+ - Codex spend and transcripts reach the dashboard within one tick of a rollout being appended to. An append made mid-turn left no evidence any freshness check examined, so only the two-minute safety walk ever found it.
118
+ - A Codex rollout cctally has read since this release is re-read from the start when it is replaced in place, even at an unchanged size and modification time. An older read position carries no file identity to compare.
119
+ - Adding or removing a `$CODEX_HOME` entry is noticed immediately. A configured root could be added or removed with nothing on disk changing, and the dashboard would keep reporting itself up to date.
120
+ - The conversation viewer keeps every session title and the browse list's cost, project and date columns while `cctally cache-sync --rebuild` replays. The rebuild cleared those tables first, so the list showed no titles throughout.
121
+ - A transcript sync keeps reading a file that the running session appended to mid-read. Any size or modification-time change read as a replacement, so an ordinary append discarded that pass's records and left the whole rebuild to run again.
122
+ - A transcript rebuild drops session titles whose sessions no longer exist. Every rebuild kept the previous titles in full, so titles for deleted sessions stayed forever and title search returned them.
123
+ - A transcript rebuild keeps each message's recorded account instead of re-attributing it to whichever account is active. The attribution was held in memory, so a rebuild resumed in a fresh process lost it.
124
+ - A transcript file replaced in place is recognised as a new file even when its size and modification time are unchanged, so records that no longer exist cannot lend their identity to whatever replaced them.
125
+ - `cctally cache-sync --rebuild` refuses, before deleting anything, when the volume holding `conversations.db` is nearly full, instead of failing partway through and leaving the store half-rebuilt.
126
+ - `cctally doctor` warns when it cannot read a store's rollup pricing fingerprint, instead of reporting no refused write. A locked store read as one that recorded nothing, and every writer of conversation cost authorized itself over it.
127
+ - `cctally cache-sync --prune-orphans` exits 3 and names the affected files when the conversation rollup re-derive is refused. It reported success, and the dashboard's automatic prune said nothing at all.
128
+ - The conversation browse rail falls back to live aggregation when the rollup's state cannot be read, instead of reporting it authoritative over a failed read. Sessions stay visible; cost sorting and project filtering degrade.
129
+ - `cctally doctor` reports a transcript reclaim backlog that is not draining, and fails above 16 GiB. A rebuild is refused at that point, because it adds churn to a store already failing to return the space it freed.
130
+ - `cctally cache-sync --rebuild` measures the free space it needs against the store instead of a fixed 64 MiB. A rebuild replays the whole corpus before the pages it frees come back, so the fixed figure admitted rebuilds that ran out of room.
131
+ - A transcript rebuild that finds no files on disk no longer deletes every message's recorded account. An unmounted volume produced that walk, and the account is the one thing in the store that cannot be derived again.
132
+ - A `cctally dashboard --no-sync` run advances the transcript schema once at startup. Nothing else does in that mode, so a store one migration behind stayed behind and every conversation route stayed degraded.
133
+ - `cctally five-hour-blocks`, `five-hour-breakdown` and the dashboard's Blocks panel record a five-hour credit only when one reporting source both observes the drop and confirms it. A stale status-line sample below an API reading minted one.
134
+ - `cctally statusline` records a weekly reset within 180 seconds even while another session still reports the pre-reset percentage. Agreement from every session was required, so one session that never agreed held the reset back forever.
135
+ - `cctally record-credit` keeps its journalled cleanup when the operation is retried. The list of rows to remove followed database row order, so a retry could describe the same removal differently and have the second description withheld.
136
+ - The stats-database writer guard `cctally doctor` reports now checks every write, not only the first of its kind on a connection. A repeated write that reused an earlier statement went unchecked, so it was neither refused nor recorded.
137
+ - `cctally five-hour-blocks` totals, their breakdowns and the dashboard's Blocks panel count an entry once when a reset shift makes two windows overlap. The later window also priced the earlier one's overlapping entries.
138
+ - The dashboard's Blocks panel and its block modal report a closed five-hour block's recorded totals and model split. They recomputed from the cache, so a session ingested after the block closed changed numbers that are meant to be final.
139
+ - The Blocks share artifact counts a project's cost in one five-hour block only. It read the recorded rollup with a key that never matched, then swept each block's raw interval, so two overlapping windows both claimed the shared entries.
140
+ - The dashboard's block modal says when a closed five-hour block's headline comes from the record kept at its close while the chart below is drawn from the local cache. The two can differ, and nothing said so.
141
+ - After `cctally cache-sync --rebuild`, `cctally doctor` warns that `conversations.db` has space to reclaim until the passes drain it. The dashboard drains it; without one, run `cctally db vacuum --db conversations`.
142
+ - `cctally cache-sync --rebuild` still runs when the free space on its volume cannot be measured. A failed measurement is now unknown and the check is skipped; it used to read as too little space and defer every rebuild.
143
+ - The conversation viewer's live-tail stream answers immediately while a rebuild holds the transcript store, instead of holding the connection open for the whole rebuild. Two other conversation-route paths answer the same way.
144
+ - A dashboard request, panel rebuild or transcript sync that is under way when a new pricing revision is adopted finishes on the revision it started with, so one response is never priced from two revisions.
145
+ - `cctally cache-report`'s per-call tier threshold follows an adopted pricing revision. It was a fixed figure that a reload could not replace.
146
+ - A transcript schema upgrade is started once when several conversation requests find the store behind head at the same moment, instead of once per request.
147
+ - The dashboard's Codex cache report keeps each row on the day it belongs to when a row moves to another day. Its former day kept a copy, so the report either refused to build or published the row under a date it had left.
148
+
149
+ ### Maintenance
150
+ - The test-evidence scrub no longer redacts a diagnostic line for containing the ordinary word "token". A token credential now needs a real separator, so a line such as `unexpected token ')' at line 4` is published rather than replaced.
151
+ - A failing envelope-oracle verification keeps the envelope that disagreed with the baseline, and the sanitized failure extract names it, so a mismatch can be diagnosed from the retained evidence instead of by reproducing it.
152
+ - The sanitized failure extract is withheld whole, and says which check withheld it, when the scrub's transformer fails a health probe before publication. A per-line refusal could otherwise publish beside a secret the backstop cannot see.
153
+ - The envelope-oracle harness emits its results in the one form the failure-extract reader parses, bringing it under the harness scrub contract so its output survives sanitization.
154
+
8
155
  ## [1.108.0] - 2026-09-05
9
156
 
10
157
  ### Added
package/README.md CHANGED
@@ -30,15 +30,15 @@ Your Claude Code plan meters you with a percentage that creeps up all week. ccta
30
30
  </p>
31
31
 
32
32
  <!-- cctally:latest-stable:begin -->
33
- **Latest stable: v1.104.0** (2026-08-29)
33
+ **Latest stable: v1.109.0** (2026-09-15)
34
34
 
35
- Highlights from the `v1.101.0` to `v1.104.0` stable upgrade:
35
+ Highlights from the `v1.104.0` to `v1.109.0` stable upgrade:
36
36
 
37
- - `cctally quota` reports how much of your weekly quota your usage consumes and whether Anthropic has changed the metering rate, from a budget fitted to your own history rather than a shipped constant. See `docs/commands/quota.md`.
38
- - `cctally quota --json` publishes the fit, the current week's consumption and projection, the composition support radii and the detector's own bound under `schemaVersion` 1.
39
- - `cctally quota --reset-calibration` discards the stored calibration for one account, and `--since` and `--watch-from` narrow or override the window the analysis uses.
37
+ - The dashboard picks up a new pricing revision without a restart. It re-reads the deployed pricing file every 60 seconds, adopts a complete newer revision, and keeps serving; no connected client is dropped.
38
+ - The conversation filter popover says `Projects appear once indexing finishes.` in place of `No projects.` while the transcript index is being rebuilt. Model counts stay live on the Claude tab.
39
+ - The dashboard's Projects panel gives every billing cycle of a credited week its own percentage. Both cycles shared one calendar date, and only the later reading survived, so the second cycle could never carry one.
40
40
 
41
- [See every change in this stable upgrade](https://github.com/omrikais/cctally/releases/tag/v1.104.0)
41
+ [See every change in this stable upgrade](https://github.com/omrikais/cctally/releases/tag/v1.109.0)
42
42
  <!-- cctally:latest-stable:end -->
43
43
 
44
44
  ## Quick start
@@ -446,6 +446,10 @@ def _cmd_account_show(args: argparse.Namespace) -> int:
446
446
  ).fetchone()
447
447
  a = ({col: row[i] for i, col in enumerate(_ACCOUNT_COLUMNS)}
448
448
  if row is not None else None)
449
+ # HELD-INCLUSIVE, decided rather than overlooked (#769 S11,
450
+ # #824). This is an inventory of the rows the account actually
451
+ # wrote, not a reading of its weekly level, and a held row is a
452
+ # real write by a real tick.
449
453
  snap_count = _count_scoped(conn, "weekly_usage_snapshots", key)
450
454
  milestone_count = _count_scoped(conn, "percent_milestones", key)
451
455
  # Resolved while the connection is still open — the map is
@@ -292,7 +292,9 @@ def _alert_label_prefix(axis: str, account_key: "str | None",
292
292
  import _cctally_store
293
293
  conn = _cctally_store.stats_open_guarded(
294
294
  db_path,
295
- connect=lambda p: _sq.connect(f"file:{p}?mode=ro", uri=True),
295
+ # #778: forward the opener's `cached_statements=0`.
296
+ connect=lambda p, **kw: _sq.connect(
297
+ f"file:{p}?mode=ro", uri=True, **kw),
296
298
  )
297
299
  try:
298
300
  if not _cctally_account.provider_is_decorated(conn, vendor):
@@ -543,7 +545,9 @@ def _test_alert_account_key(vendor: str) -> str:
543
545
  return _lib_accounts.VENDOR_WIDE
544
546
  conn = _cctally_store.stats_open_guarded(
545
547
  db_path,
546
- connect=lambda p: _sq.connect(f"file:{p}?mode=ro", uri=True),
548
+ # #778: forward the opener's `cached_statements=0`.
549
+ connect=lambda p, **kw: _sq.connect(
550
+ f"file:{p}?mode=ro", uri=True, **kw),
547
551
  )
548
552
  try:
549
553
  for row in _cctally_account.load_accounts(conn, vendor):