cctally 1.117.2 → 1.118.1

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 (58) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +7 -3
  3. package/bin/_cctally_cache.py +2873 -193
  4. package/bin/_cctally_completion.py +106 -33
  5. package/bin/_cctally_core.py +69 -4
  6. package/bin/_cctally_dashboard.py +188 -13
  7. package/bin/_cctally_dashboard_conversation.py +5 -1
  8. package/bin/_cctally_dashboard_envelope.py +50 -19
  9. package/bin/_cctally_dashboard_sources.py +23 -1
  10. package/bin/_cctally_db.py +193 -153
  11. package/bin/_cctally_diagnosis_sources.py +63 -7
  12. package/bin/_cctally_doctor.py +865 -93
  13. package/bin/_cctally_five_hour.py +67 -50
  14. package/bin/_cctally_forecast.py +35 -13
  15. package/bin/_cctally_journal.py +186 -18
  16. package/bin/_cctally_milestone_history.py +859 -62
  17. package/bin/_cctally_oauth_samples.py +286 -15
  18. package/bin/_cctally_parser.py +2 -2
  19. package/bin/_cctally_quota.py +104 -12
  20. package/bin/_cctally_record.py +153 -29
  21. package/bin/_cctally_rederive.py +608 -68
  22. package/bin/_cctally_statusline.py +108 -81
  23. package/bin/_cctally_store.py +179 -38
  24. package/bin/_cctally_tui.py +892 -181
  25. package/bin/_cctally_update.py +32 -13
  26. package/bin/_lib_codex_conversation_query.py +52 -14
  27. package/bin/_lib_codex_drain_admission.py +224 -6
  28. package/bin/_lib_codex_metadata.py +39 -0
  29. package/bin/_lib_conversation_anon.py +15 -8
  30. package/bin/_lib_conversation_dispatch.py +7 -0
  31. package/bin/_lib_conversation_query.py +289 -70
  32. package/bin/_lib_dashboard_sources.py +37 -1
  33. package/bin/_lib_detector_access_registry.py +497 -26
  34. package/bin/_lib_doctor.py +85 -20
  35. package/bin/_lib_doctor_scan_index.py +321 -0
  36. package/bin/_lib_ingest_frontier.py +20 -1
  37. package/bin/_lib_oauth_samples.py +23 -7
  38. package/bin/_lib_pricing.py +146 -16
  39. package/bin/_lib_quota.py +19 -0
  40. package/bin/_lib_rederive.py +11 -6
  41. package/bin/_lib_retained_records.py +516 -0
  42. package/bin/_lib_snapshot_cache.py +10 -0
  43. package/bin/_lib_stale_proof.py +272 -43
  44. package/bin/_lib_stats_content_revision.py +831 -0
  45. package/bin/_lib_write_io.py +11 -4
  46. package/bin/cctally +4 -1
  47. package/dashboard/static/assets/ConversationsView-D4Iv4ku0.js +72 -0
  48. package/dashboard/static/assets/{DoctorModal-B0DnU9d9.js → DoctorModal-D4QSq6uz.js} +1 -1
  49. package/dashboard/static/assets/ModalRoot-C9F5dhZi.js +1 -0
  50. package/dashboard/static/assets/{ProjectsDrillPanel-DSxXQSa-.js → ProjectsDrillPanel-WxAdRDMt.js} +1 -1
  51. package/dashboard/static/assets/{SourceDetailModal-BleDpQqG.js → SourceDetailModal-NyyEfXmc.js} +1 -1
  52. package/dashboard/static/assets/{UpdateModal-CMqaAkSK.js → UpdateModal-CFLq9pT1.js} +1 -1
  53. package/dashboard/static/assets/{index-BjQV-0-D.js → index-BKTWH9JE.js} +2 -2
  54. package/dashboard/static/assets/{outlineNavigation-CO-VWBm7.js → outlineNavigation-BnGUwCS3.js} +3 -3
  55. package/dashboard/static/dashboard.html +1 -1
  56. package/package.json +4 -1
  57. package/dashboard/static/assets/ConversationsView-oqNsWzWV.js +0 -72
  58. package/dashboard/static/assets/ModalRoot-CYua2wP_.js +0 -1
@@ -639,7 +639,11 @@ def _codex_observation_hold_only(stats, *, executor_owned=False):
639
639
  return False
640
640
  if getattr(stats, "barrier_retry", False) and not executor_owned:
641
641
  return False
642
- skipped = getattr(stats, "container_skipped", {})
642
+ # #1052: every skip the pass saw, its unit phase's included, when the
643
+ # stats object splits them; otherwise its single census dict.
644
+ every_skip = getattr(stats, "all_container_skipped", None)
645
+ skipped = (every_skip() if callable(every_skip)
646
+ else getattr(stats, "container_skipped", {}))
643
647
  return (bool(getattr(stats, "container_pending", 0) or any(skipped.values()))
644
648
  and set(skipped) <= {
645
649
  "decoder-unavailable", "decode-error", "read-error",
@@ -1274,6 +1274,25 @@ def _sync_failure_envelope(
1274
1274
  "action": "cctally db repair --db stats --yes",
1275
1275
  }
1276
1276
 
1277
+ # #932 §4.1: an epoch deferral whose admission FAILED has no rebuild
1278
+ # running (no marker, no worker), so "rebuilding" would be false. It is the
1279
+ # existing server-sync error until a later admission succeeds.
1280
+ def field(item, key, default=None):
1281
+ return (item.get(key, default) if isinstance(item, dict)
1282
+ else getattr(item, key, default))
1283
+
1284
+ for item in attributions or ():
1285
+ if (field(item, "database") == "stats"
1286
+ and not bool(field(item, "corruption", False))
1287
+ and bool(field(item, "deferred", False))
1288
+ and field(item, "deferral_outcome") == "failed"):
1289
+ return {
1290
+ "kind": "server_sync",
1291
+ "label": "⚠ server sync error",
1292
+ "detail": "The server could not complete its background sync.",
1293
+ "action": None,
1294
+ }
1295
+
1277
1296
  if attributed("stats"):
1278
1297
  return {
1279
1298
  "kind": "maintenance_active",
@@ -1676,6 +1695,29 @@ def _header_rate_and_delta(current_rate, current_week_start, trend):
1676
1695
  return current_rate, None
1677
1696
 
1678
1697
 
1698
+ def current_week_freshness(captured, *, now_utc: "dt.datetime",
1699
+ oauth_usage_cfg: "dict | None" = None
1700
+ ) -> "dict | None":
1701
+ """`current_week.freshness` (``{label, captured_at, age_seconds}``) of a
1702
+ confirming-reading instant, or None when there is none. The one
1703
+ derivation the envelope and the source payload's idle refresh share
1704
+ (#1018 twin spec 5.4/5.5)."""
1705
+ if not isinstance(captured, dt.datetime):
1706
+ return None
1707
+ if captured.tzinfo is None:
1708
+ captured = captured.replace(tzinfo=dt.timezone.utc)
1709
+ age_s = max(0.0, (now_utc - captured).total_seconds())
1710
+ _fresh_cfg = (
1711
+ oauth_usage_cfg if oauth_usage_cfg is not None
1712
+ else sys.modules["cctally"]._OAUTH_USAGE_DEFAULTS
1713
+ )
1714
+ return {
1715
+ "label": sys.modules["cctally"]._freshness_label(age_s, _fresh_cfg),
1716
+ "captured_at": _iso_z(captured),
1717
+ "age_seconds": int(age_s),
1718
+ }
1719
+
1720
+
1679
1721
  def snapshot_to_envelope(snap: "DataSnapshot", *,
1680
1722
  now_utc: "dt.datetime",
1681
1723
  monotonic_now: "float | None" = None,
@@ -1850,25 +1892,14 @@ def snapshot_to_envelope(snap: "DataSnapshot", *,
1850
1892
  elif tp == 90:
1851
1893
  budget_90 = dpd
1852
1894
 
1853
- # Freshness envelope for current_week — derived from
1854
- # cw.latest_snapshot_at (a datetime, not an ISO string). None when
1855
- # cw is absent or has no snapshot timestamp; the React client renders
1856
- # a placeholder in that case. Refs spec §3.4.
1857
- cw_freshness: "dict | None" = None
1858
- if cw is not None and cw.latest_snapshot_at is not None:
1859
- captured = cw.latest_snapshot_at
1860
- if captured.tzinfo is None:
1861
- captured = captured.replace(tzinfo=dt.timezone.utc)
1862
- age_s = max(0.0, (now_utc - captured).total_seconds())
1863
- _fresh_cfg = (
1864
- oauth_usage_cfg if oauth_usage_cfg is not None
1865
- else sys.modules["cctally"]._OAUTH_USAGE_DEFAULTS
1866
- )
1867
- cw_freshness = {
1868
- "label": sys.modules["cctally"]._freshness_label(age_s, _fresh_cfg),
1869
- "captured_at": _iso_z(captured),
1870
- "age_seconds": int(age_s),
1871
- }
1895
+ # Freshness envelope for current_week (spec §3.4) — derived from
1896
+ # cw.latest_weekly_reading_at, the newest genuine reading confirming the
1897
+ # displayed value (#1018 twin spec 5.4). None when cw is absent or has no
1898
+ # resolvable confirmation; the React client renders a placeholder then.
1899
+ # `last_snapshot_age_sec` below keeps the stored row's capture.
1900
+ cw_freshness = current_week_freshness(
1901
+ None if cw is None else getattr(cw, "latest_weekly_reading_at", None),
1902
+ now_utc=now_utc, oauth_usage_cfg=oauth_usage_cfg)
1872
1903
 
1873
1904
  week_lbl: "str | None" = None
1874
1905
  reset_at_utc: "dt.datetime | None" = None
@@ -1490,6 +1490,7 @@ def resolve_dashboard_source_semantics(
1490
1490
  config: Mapping[str, object] | None,
1491
1491
  *,
1492
1492
  display_tz_name: str | None,
1493
+ speed: str | None = None,
1493
1494
  ) -> DashboardSourceSemantics:
1494
1495
  """Resolve dashboard semantics through the shipped CLI kernels.
1495
1496
 
@@ -1497,12 +1498,17 @@ def resolve_dashboard_source_semantics(
1497
1498
  it preserves the CLI's all-$CODEX_HOME fast-service-tier behavior. The
1498
1499
  weekly index comes from the same ``get_week_start_name``/``WEEKDAY_MAP``
1499
1500
  pair used by the report and budget command surfaces.
1501
+
1502
+ ``speed`` (#1050 spec 4.7) is a tier that resolver already returned this
1503
+ tick — the dashboard captures it beside its dispatch read and keys the idle
1504
+ gate on it — so the build uses that one value; ``None`` resolves here.
1500
1505
  """
1501
1506
  c = sys.modules["cctally"]
1502
1507
  raw_config = dict(config or {})
1503
1508
  week_start_name = get_week_start_name(raw_config)
1504
1509
  week_start_idx = c.WEEKDAY_MAP[week_start_name]
1505
- speed = c._resolve_codex_speed("auto")
1510
+ if speed is None:
1511
+ speed = c._resolve_codex_speed("auto")
1506
1512
  budget_config = c._get_budget_config(raw_config)
1507
1513
  quota_alerts = c._get_quota_alerts_config(raw_config)
1508
1514
  raw_cache_report = raw_config.get("cache_report")
@@ -5929,6 +5935,20 @@ def reset_codex_account_scope_cache(*, keep_counters: bool = False) -> None:
5929
5935
  _CODEX_DERIVED_COHERENCE.clear()
5930
5936
 
5931
5937
 
5938
+ def _codex_cycle_index_cost_inputs(context: "DashboardReadContext") -> dict:
5939
+ """The cost inputs every Codex cycle stamp names (#1050 spec 4.7): this
5940
+ read's cache handle and resolved speed, and — when the build computed it
5941
+ for its own signature — the `codex_stats_digest` that admits the index's
5942
+ process memo."""
5943
+ stats_identity = context.stats_identity
5944
+ return {
5945
+ "cache_conn": context.cache_conn,
5946
+ "speed": context.speed,
5947
+ "stats_digest": (
5948
+ stats_identity[0] if stats_identity is not None else None),
5949
+ }
5950
+
5951
+
5932
5952
  def _codex_account_scopes_wire(
5933
5953
  context: DashboardReadContext,
5934
5954
  *,
@@ -6060,6 +6080,7 @@ def _codex_account_scopes_wire(
6060
6080
  sys.modules["cctally"].build_codex_cycle_index(
6061
6081
  context.stats_conn, identity=cycle,
6062
6082
  now_utc=context.now_utc, account_key=key,
6083
+ **_codex_cycle_index_cost_inputs(context),
6063
6084
  )
6064
6085
  )
6065
6086
  except sqlite3.Error:
@@ -8704,6 +8725,7 @@ def _build_codex_source_state(
8704
8725
  cycle_index = tuple(
8705
8726
  sys.modules["cctally"].build_codex_cycle_index(
8706
8727
  context.stats_conn, identity=cycle, now_utc=context.now_utc,
8728
+ **_codex_cycle_index_cost_inputs(context),
8707
8729
  )
8708
8730
  )
8709
8731
  except sqlite3.Error: