cctally 1.116.0 → 1.117.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 (72) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +4 -6
  3. package/bin/_cctally_account.py +57 -3
  4. package/bin/_cctally_cache.py +5720 -885
  5. package/bin/_cctally_claude_attribution.py +997 -0
  6. package/bin/_cctally_completion.py +101 -28
  7. package/bin/_cctally_core.py +235 -12
  8. package/bin/_cctally_dashboard.py +224 -7
  9. package/bin/_cctally_dashboard_conversation.py +107 -21
  10. package/bin/_cctally_dashboard_envelope.py +34 -0
  11. package/bin/_cctally_db.py +928 -80
  12. package/bin/_cctally_diagnosis_sources.py +78 -13
  13. package/bin/_cctally_doctor.py +211 -86
  14. package/bin/_cctally_five_hour.py +389 -18
  15. package/bin/_cctally_forecast.py +30 -6
  16. package/bin/_cctally_journal.py +2418 -158
  17. package/bin/_cctally_oauth_samples.py +185 -7
  18. package/bin/_cctally_parser.py +38 -0
  19. package/bin/_cctally_project.py +34 -8
  20. package/bin/_cctally_quarantine.py +821 -343
  21. package/bin/_cctally_quota.py +69 -4
  22. package/bin/_cctally_quota_calibration.py +677 -3
  23. package/bin/_cctally_quota_model.py +927 -127
  24. package/bin/_cctally_record.py +922 -445
  25. package/bin/_cctally_rederive.py +119 -15
  26. package/bin/_cctally_statusline.py +1048 -176
  27. package/bin/_cctally_store.py +544 -110
  28. package/bin/_cctally_tui.py +93 -9
  29. package/bin/_cctally_update.py +77 -14
  30. package/bin/_lib_account_protocol.py +227 -19
  31. package/bin/_lib_claude_attribution.py +421 -0
  32. package/bin/_lib_clone_copy.py +30 -0
  33. package/bin/_lib_codex_conversation_query.py +45 -9
  34. package/bin/_lib_codex_conversation_watch.py +72 -13
  35. package/bin/_lib_codex_rollout_container.py +234 -19
  36. package/bin/_lib_codex_worker_lifecycle.py +374 -0
  37. package/bin/_lib_credit.py +22 -13
  38. package/bin/_lib_dashboard_sources.py +163 -10
  39. package/bin/_lib_detector_access_registry.py +1335 -166
  40. package/bin/_lib_doctor.py +276 -92
  41. package/bin/_lib_ingest_frontier.py +269 -36
  42. package/bin/_lib_journal.py +273 -22
  43. package/bin/_lib_jsonl.py +12 -0
  44. package/bin/_lib_meter_rate_change.py +72 -0
  45. package/bin/_lib_outcome_quarantine.py +917 -554
  46. package/bin/_lib_provider_activity.py +24 -6
  47. package/bin/_lib_quota_calibration.py +57 -3
  48. package/bin/_lib_quota_copy.py +7 -2
  49. package/bin/_lib_quota_lineage.py +613 -0
  50. package/bin/_lib_quota_model.py +435 -22
  51. package/bin/_lib_rate_change_delivery.py +16 -0
  52. package/bin/_lib_reclaim_planner.py +6 -4
  53. package/bin/_lib_rederive.py +2046 -21
  54. package/bin/_lib_selector_state.py +131 -3
  55. package/bin/_lib_stale_proof.py +113 -4
  56. package/bin/_lib_statusline.py +72 -3
  57. package/bin/_lib_statusline_candidates.py +246 -42
  58. package/bin/_lib_write_io.py +418 -5
  59. package/bin/cctally +24 -11
  60. package/dashboard/static/assets/{ConversationsView-Dflb_myA.js → ConversationsView-Dcfn8T6f.js} +1 -1
  61. package/dashboard/static/assets/{DoctorModal-DFX5CfIs.js → DoctorModal-BqmXbnhn.js} +1 -1
  62. package/dashboard/static/assets/{ModalRoot-HlxoagIa.js → ModalRoot-Ck3WJK2V.js} +1 -1
  63. package/dashboard/static/assets/{ProjectsDrillPanel-C3PJy1xx.js → ProjectsDrillPanel-B-HZkDnT.js} +1 -1
  64. package/dashboard/static/assets/{SourceDetailModal-BczsWKhD.js → SourceDetailModal-QhJkHB7i.js} +1 -1
  65. package/dashboard/static/assets/{UpdateModal-Os-Lo5cs.js → UpdateModal-CkXEIQcK.js} +1 -1
  66. package/dashboard/static/assets/index-D34eYhAc.css +1 -0
  67. package/dashboard/static/assets/{index-CYZ3NLqF.js → index-HKS1s56o.js} +3 -3
  68. package/dashboard/static/assets/{outlineNavigation-k-zIif6W.js → outlineNavigation-BNGaNPCp.js} +1 -1
  69. package/dashboard/static/assets/{useKeymap-CW7Up0Ay.js → useKeymap-CUgEnnua.js} +1 -1
  70. package/dashboard/static/dashboard.html +3 -3
  71. package/package.json +5 -1
  72. package/dashboard/static/assets/index-BKITk1CT.css +0 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,42 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [1.117.0] - 2026-11-02
9
+
10
+ ### Added
11
+ - `cctally doctor` adds `accounts.claude_cost_attribution`, reporting how recent Claude cost was attributed to accounts and the progress of the one-time upgrade step that records existing attribution.
12
+
13
+ ### Changed
14
+ - `cctally quota` no longer withholds a forecast over a small Sonnet 5.5 share shift that meter evidence shows to be immaterial, and withheld lines name the family reason. Stored calibrations go stale until the next `cctally quota` run.
15
+ - When a reviewed `cctally db rederive` correction changes a calibration's inputs, `cctally forecast` and the dashboard withhold calibrated figures ("recalibration needed") until `cctally quota` refits. Stats rebuilds once on upgrade.
16
+ - A metering-rate change fitted from corrected inputs no longer shows its status-line marker or sends its notification; the dashboard's Recent Alerts keeps it, labelled "superseded by a correction".
17
+ - After a gap without a running `cctally dashboard`, its first transcript catch-up writes less to `conversations.db`, and `cctally dashboard-perf` and `cctally doctor` no longer judge that catch-up as steady-state disk writes.
18
+ - Automatic credit, early-reset and status-line drop detection paused by an older cctally process now resumes on its own, per account and per limit; `cctally doctor` shows what each limit waits for. Stats rebuilds once on upgrade.
19
+
20
+ ### Fixed
21
+ - A `stats.db` upgrade rebuild or `cctally db rebuild --db stats` no longer reprices closed five-hour blocks and their milestones at the current rate card when it recovers missing records.
22
+ - Archived Codex threads now count in `codex-*` reports, quota history and the dashboard, and archiving no longer deletes history. Archived usage first seen with no recorded account stays unattributed unless quota windows assign it.
23
+ - On Python builds that link SQLite 3.54.1, a running `cctally dashboard` reclaims transcript space freed by retention again, so `conversations.db` shrinks instead of keeping the freed pages.
24
+ - An idle Claude Code session's stale `cctally statusline` reading no longer records a false weekly reset, five-hour credit or zero-cost milestone once a later usage check reads higher; the stats index rebuilds once after upgrading.
25
+ - Claude cost is attributed to the account active when Claude Code wrote it, not the one active when cctally read it. Upgrades and `cctally cache-sync --rebuild` keep each row's account.
26
+ - `cctally statusline` measures `🧠` context % against the session's real context window (1M for Opus, Sonnet and Haiku 5.5), so an Opus 5.5 session no longer shows a red percentage five times too high and Fable sessions no longer show `N/A`.
27
+ - A `stats.db` upgrade rebuild could exit early as busy when a status-line check for a running rebuild landed at the instant it started, delaying the rebuild; that check no longer interferes with a starting rebuild.
28
+ - `cctally explain` and the dashboard's Explain view measure Opus, Sonnet and Haiku 5.5 requests against their 1M context window and now evaluate Fable requests, so short conversations are no longer flagged for large context against 200K.
29
+ - `cctally five-hour-blocks`, `cctally five-hour-breakdown` and the dashboard keep each five-hour window's genuine peak, milestones and readings through a usage credit and through every stats.db rebuild, instead of dropping some.
30
+ - `cctally record-credit` records the exact `--at` time instead of the start of that hour, keeping readings taken earlier in that hour, and a `--force` replacement at the replaced credit's own second now takes effect.
31
+ - A weekly usage credit is no longer lost when recording is interrupted while it is being saved.
32
+ - After stats.db is rebuilt from a missing or corrupt file, the stats index keeps updating incrementally instead of falling back to rereading the whole recorded history.
33
+ - On an exFAT data folder, cctally now quarantines and rebuilds a corrupt `conversations.db` or `cache.db` and can run `db repair --db stats`, and a rebuild no longer fails after 20 s while another cctally process has the store open but idle.
34
+ - On macOS, `cctally cache-sync --rebuild` no longer writes a full copy of `conversations.db` on volumes without copy-on-write clones (HFS+, exFAT); there and on Linux ext4 its integrity check runs in place when no write-ahead log is pending.
35
+ - `cctally dashboard` no longer waits on a reverse-DNS lookup of its bind address before it prints its URL and serves, a wait that lasted the resolver's whole timeout where the network never answers that lookup.
36
+ - Codex hooks installed by `cctally setup` no longer stall Codex sessions for over 10 seconds on large histories or after upgrading, or for over a minute with Codex budget alerts on; leftover work runs in the background.
37
+ - `cctally db rederive` restores a five-hour milestone's cost when an earlier rederive repriced it under a closed block; one whose history is ambiguous keeps its cost and is listed in `withheldMilestoneRepairs`.
38
+
39
+ ## [1.116.1] - 2026-10-28
40
+
41
+ ### Maintenance
42
+ - Internal improvements only; no user-facing changes.
43
+
8
44
  ## [1.116.0] - 2026-10-28
9
45
 
10
46
  ### Added
package/README.md CHANGED
@@ -30,13 +30,11 @@ 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.113.0** (2026-09-29)
33
+ **Latest stable: v1.116.1** (2026-10-28)
34
34
 
35
- Highlights from the `v1.112.0` to `v1.113.0` stable upgrade:
35
+ Highlights from the `v1.113.0` to `v1.116.1` stable upgrade:
36
36
 
37
- - Codex cost reports price `gpt-6.1-sol` at its published input, cached-input and output rates, including long-context and Fast pricing, instead of using fallback rates.
38
-
39
- [See every change in this stable upgrade](https://github.com/omrikais/cctally/releases/tag/v1.113.0)
37
+ [See every change in this stable upgrade](https://github.com/omrikais/cctally/releases/tag/v1.116.1)
40
38
  <!-- cctally:latest-stable:end -->
41
39
 
42
40
  ## Quick start
@@ -135,7 +133,7 @@ See [docs/commands/blocks.md](docs/commands/blocks.md) and [docs/commands/five-h
135
133
 
136
134
  ## Codex
137
135
 
138
- If you also use OpenAI's Codex CLI, cctally tracks it with the same depth. `cctally codex daily`, `monthly`, and `session` are drop-in replacements for the ccusage codex commands, reading from your local `~/.codex/sessions/`. `cctally codex weekly` adds a subscription week rollup, and `cctally codex quota` shows your native Codex rate limit windows.
136
+ If you also use OpenAI's Codex CLI, cctally tracks it with the same depth. `cctally codex daily`, `monthly`, and `session` are drop-in replacements for the ccusage codex commands, reading from your local `~/.codex/sessions/`, archived threads in `~/.codex/archived_sessions/` included. `cctally codex weekly` adds a subscription week rollup, and `cctally codex quota` shows your native Codex rate limit windows.
139
137
 
140
138
  <p align="center">
141
139
  <img src="docs/img/cli-codex-daily.svg" alt="cctally codex daily: Codex cost by day">
@@ -1295,9 +1295,34 @@ def _attribute_apply(planned, *, account_key, mode, since, until, at,
1295
1295
  conn.execute("BEGIN IMMEDIATE")
1296
1296
  _landed, skipped = _cache_mod.rehydrate_codex_window_attributions(
1297
1297
  conn)
1298
+ # #980 F1: the reconciliation's adoption demotes Spark windows
1299
+ # through the RESOLVED `observed_model` (#373), so owed model
1300
+ # resolution runs first, on this transaction and under the
1301
+ # writer flocks this command holds. When it still cannot
1302
+ # resolve, nothing is reconciled over an unresolved model: the
1303
+ # apply aborts (`recordedPending` once records were appended)
1304
+ # and a rerun finishes it.
1305
+ if _cache_mod.codex_model_resolution_pending(conn):
1306
+ _cache_mod._codex_settle_finalization(
1307
+ conn, deadline=None, stages=("expand", "model"))
1308
+ if _cache_mod.codex_model_resolution_pending(conn):
1309
+ raise sqlite3.DatabaseError(
1310
+ _quota.PROJECTION_DEFERRED_MODEL)
1298
1311
  restored, adopted = (
1299
1312
  _cache_mod.reconcile_codex_window_attribution_spend(
1300
1313
  conn, strict=True))
1314
+ # #980 R7: the rehydrate above armed `attribution` debt for
1315
+ # exactly the reconciliation that just succeeded, on this
1316
+ # transaction; settle it here, or every account-scoped hook
1317
+ # read defers until a later settlement repeats it. Kept while
1318
+ # adoption items remain: the reconcile runs after them (D4).
1319
+ # A later failure rolls this back with everything else.
1320
+ debt = _cache_mod.load_codex_finalization_debt(conn)
1321
+ if (debt.attribution and not debt.malformed
1322
+ and not debt.full and debt.first_item() is None):
1323
+ debt.attribution = False
1324
+ debt.strikes = 0
1325
+ _cache_mod._store_codex_finalization_debt(conn, debt)
1301
1326
  # Verify the applied record set against the plan we locked,
1302
1327
  # BEFORE the commit: a silent partial application must abort the
1303
1328
  # transaction rather than be reported as success (spec §8.2).
@@ -1365,20 +1390,49 @@ def _attribute_apply(planned, *, account_key, mode, since, until, at,
1365
1390
  # rerun completes it, because the certificate's attribution revision is
1366
1391
  # still behind the derived table's (`_attribute_tail_pending`).
1367
1392
  try:
1368
- _quota.reconcile_codex_quota_projection()
1393
+ deferred = _attribute_publish_projection()
1369
1394
  except Exception as exc: # noqa: BLE001 - reported, never swallowed
1395
+ deferred = str(exc)
1396
+ if deferred is not None:
1370
1397
  return ("recordedPending", actions, [{
1371
1398
  "code": "recordedPending",
1372
1399
  "message": (
1373
1400
  f"the attribution was recorded and applied but the quota "
1374
- f"projection did not finish ({exc}); rerun the same command "
1375
- f"to complete it"),
1401
+ f"projection did not finish ({deferred}); rerun the same "
1402
+ f"command to complete it"),
1376
1403
  }], fresh)
1377
1404
  status = ("recovered" if ((recovered or completion) and not appended)
1378
1405
  else "applied")
1379
1406
  return (status, actions, [], fresh)
1380
1407
 
1381
1408
 
1409
+ def _attribute_publish_projection() -> "str | None":
1410
+ """Publish the quota projection; return None, or why it is still refused.
1411
+
1412
+ #980 D6: every projection publisher refuses while model resolution is
1413
+ owed, returning a ``deferred_model`` status rather than raising, so a bare
1414
+ reconcile here reported success over an unpublished projection. Owed
1415
+ resolution is therefore settled first, unbudgeted, through the cache
1416
+ writer flocks an unbudgeted sync takes; when the projection still refuses
1417
+ (those flocks were contended) the caller reports ``recordedPending``.
1418
+ """
1419
+ import _cctally_cache as _cache_mod
1420
+ import _cctally_quota as _quota
1421
+
1422
+ settled = False
1423
+ if _cache_mod.codex_model_resolution_pending_now():
1424
+ _cache_mod.settle_codex_finalization_unbudgeted()
1425
+ settled = True
1426
+ result = _quota.reconcile_codex_quota_projection()
1427
+ if (getattr(result, "status", None) == _quota.PROJECTION_DEFERRED_MODEL
1428
+ and not settled):
1429
+ _cache_mod.settle_codex_finalization_unbudgeted()
1430
+ result = _quota.reconcile_codex_quota_projection()
1431
+ if getattr(result, "status", None) == _quota.PROJECTION_DEFERRED_MODEL:
1432
+ return _quota.PROJECTION_DEFERRED_MODEL
1433
+ return None
1434
+
1435
+
1382
1436
  def _cmd_account_attribute(args: argparse.Namespace) -> int:
1383
1437
  """`cctally account attribute` — preview by default, `--yes` applies.
1384
1438