cctally 1.78.0 → 1.79.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.
@@ -38,6 +38,7 @@ from _lib_dashboard_sources import (
38
38
  dashboard_resource_key,
39
39
  )
40
40
  from _lib_quota import (
41
+ QuotaWindowIdentity,
41
42
  build_blocks,
42
43
  build_history,
43
44
  forecast_quota,
@@ -94,6 +95,9 @@ class CodexCycleBoundary:
94
95
  # Root provenance is server-only accounting input, never public wire data.
95
96
  source_root_keys: tuple[str, ...]
96
97
  used_percent: float | None = None
98
+ # Exact server-side quota identity selected for the hero. It is never
99
+ # serialized; milestone-history keys hash it opaquely.
100
+ quota_identity: QuotaWindowIdentity | None = None
97
101
 
98
102
 
99
103
  @dataclass(frozen=True)
@@ -126,7 +130,7 @@ def _resolve_codex_weekly_cycle(
126
130
  now_utc: dt.datetime,
127
131
  ) -> CodexCycleBoundary:
128
132
  """Select exactly one active account-level 10,080-minute native cycle."""
129
- boundaries: dict[tuple[int, dt.datetime], dict[str, object]] = {}
133
+ boundaries: dict[tuple[int, dt.datetime], list[tuple[object, object]]] = {}
130
134
  stale_weekly_evidence = False
131
135
  for history in build_history(tuple(observations)):
132
136
  if history.identity.window_minutes != 10_080:
@@ -140,23 +144,33 @@ def _resolve_codex_weekly_cycle(
140
144
  stale_weekly_evidence = True
141
145
  continue
142
146
  boundary = (history.identity.window_minutes, baseline.resets_at)
143
- item = boundaries.setdefault(boundary, {"roots": set(), "used": []})
144
- item["roots"].add(history.identity.source_root_key)
145
- if baseline.used_percent is not None:
146
- item["used"].append(float(baseline.used_percent))
147
+ boundaries.setdefault(boundary, []).append((history, baseline))
147
148
  if len(boundaries) != 1:
148
149
  if not boundaries:
149
150
  raise CodexCycleUnavailable("stale" if stale_weekly_evidence else "missing")
150
151
  raise CodexCycleUnavailable("conflicting")
151
- (window_minutes, resets_at), boundary_data = next(iter(boundaries.items()))
152
- root_keys = boundary_data["roots"]
153
- used_values = boundary_data["used"]
152
+ (window_minutes, resets_at), candidates = next(iter(boundaries.items()))
153
+ # Preserve the existing hero's max-used-percent choice, then pin every
154
+ # remaining tie deterministically. The selected full identity—not the
155
+ # union of sibling roots/slots/limits—owns the hero cycle.
156
+ history, baseline = max(
157
+ candidates,
158
+ key=lambda item: (
159
+ float(item[1].used_percent),
160
+ item[1].captured_at.astimezone(UTC),
161
+ item[0].identity.source_root_key,
162
+ item[0].identity.logical_limit_key,
163
+ item[0].identity.observed_slot,
164
+ ),
165
+ )
166
+ selected_identity = history.identity
154
167
  return CodexCycleBoundary(
155
168
  window_minutes=window_minutes,
156
169
  start_at=resets_at - dt.timedelta(minutes=window_minutes),
157
170
  resets_at=resets_at,
158
- source_root_keys=tuple(sorted(root_keys)),
159
- used_percent=max(used_values) if used_values else None,
171
+ source_root_keys=(selected_identity.source_root_key,),
172
+ used_percent=float(baseline.used_percent),
173
+ quota_identity=selected_identity,
160
174
  )
161
175
 
162
176
 
@@ -2080,7 +2094,21 @@ def build_codex_source_state(
2080
2094
  now_utc=context.now_utc,
2081
2095
  display_tz_name=context.display_tz_name,
2082
2096
  )
2083
- quota = {**quota, "blocks": quota_blocks}
2097
+ # Hero-modal historical-milestone navigation index (spec §1c, §3). Built
2098
+ # here on the non-idle codex source rebuild (idle ticks reuse the stored
2099
+ # bundle) over the durable projection — a pure serializer never touches it.
2100
+ # Guarded: an index failure must never fail the codex source build.
2101
+ cycle_index: tuple = ()
2102
+ if cycle is not None and not hero_failure:
2103
+ try:
2104
+ cycle_index = tuple(
2105
+ sys.modules["cctally"].build_codex_cycle_index(
2106
+ context.stats_conn, identity=cycle, now_utc=context.now_utc,
2107
+ )
2108
+ )
2109
+ except sqlite3.Error:
2110
+ cycle_index = ()
2111
+ quota = {**quota, "blocks": quota_blocks, "cycle_index": cycle_index}
2084
2112
  budget_rows = _budget_wire(context.stats_conn)
2085
2113
  projected_budget_rows = _projected_budget_wire(context.stats_conn)
2086
2114
  budget_cost_events = _codex_budget_cost_events(context, budget_entries)
@@ -800,14 +800,28 @@ def doctor_gather_state(
800
800
  # on corruption — both behaviors would hide diagnostic state
801
801
  # (codex H1).
802
802
  config_json_error = None
803
+ config_parsed: dict = {}
803
804
  try:
804
805
  if _cctally_core.CONFIG_PATH.exists():
805
- json.loads(_cctally_core.CONFIG_PATH.read_text(encoding="utf-8"))
806
+ config_parsed = json.loads(
807
+ _cctally_core.CONFIG_PATH.read_text(encoding="utf-8")
808
+ )
806
809
  except json.JSONDecodeError as exc:
807
810
  config_json_error = f"{type(exc).__name__}: {exc}"
808
811
  except OSError as exc:
809
812
  config_json_error = f"OSError: {exc}"
810
813
 
814
+ # Configured update (release) channel (beta-channel, spec 2026-07-21 §3):
815
+ # derived from the SAME raw read (never load_config, which auto-creates on
816
+ # first run). Fail-soft to "stable" — resolve_update_channel already
817
+ # tolerates a non-dict block / junk value.
818
+ try:
819
+ update_channel = c.resolve_update_channel(
820
+ config_parsed if isinstance(config_parsed, dict) else {}
821
+ )
822
+ except Exception:
823
+ update_channel = "stable"
824
+
811
825
  update_state = None
812
826
  update_state_error = None
813
827
  try:
@@ -880,6 +894,7 @@ def doctor_gather_state(
880
894
  codex_jsonl_present=codex_jsonl_present,
881
895
  codex_project_metadata_health=codex_project_metadata_health,
882
896
  codex_project_metadata_error=codex_project_metadata_error,
897
+ update_channel=update_channel,
883
898
  dashboard_bind_stored=dashboard_bind_stored,
884
899
  runtime_bind=runtime_bind,
885
900
  # Conversation viewer (Plan 2, spec §5): only consulted on a LAN bind.