cctally 1.117.0 → 1.117.2
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.
- package/CHANGELOG.md +16 -0
- package/bin/_cctally_dashboard.py +39 -10
- package/bin/_cctally_dashboard_envelope.py +10 -0
- package/bin/_cctally_five_hour.py +57 -4
- package/bin/_cctally_forecast.py +272 -0
- package/bin/_cctally_milestone_history.py +379 -91
- package/bin/_cctally_tui.py +362 -33
- package/bin/_lib_dashboard_sources.py +94 -8
- package/bin/_lib_quota_model.py +29 -11
- package/bin/_lib_snapshot_cache.py +8 -0
- package/bin/_lib_tick_stats.py +10 -5
- package/bin/_lib_write_io.py +1 -1
- package/bin/cctally +4 -0
- package/dashboard/static/assets/{ConversationsView-Dcfn8T6f.js → ConversationsView-oqNsWzWV.js} +1 -1
- package/dashboard/static/assets/{DoctorModal-BqmXbnhn.js → DoctorModal-B0DnU9d9.js} +1 -1
- package/dashboard/static/assets/ModalRoot-CYua2wP_.js +1 -0
- package/dashboard/static/assets/{ProjectsDrillPanel-B-HZkDnT.js → ProjectsDrillPanel-DSxXQSa-.js} +1 -1
- package/dashboard/static/assets/{SourceDetailModal-QhJkHB7i.js → SourceDetailModal-BleDpQqG.js} +1 -1
- package/dashboard/static/assets/{UpdateModal-CkXEIQcK.js → UpdateModal-CMqaAkSK.js} +1 -1
- package/dashboard/static/assets/{index-HKS1s56o.js → index-BjQV-0-D.js} +2 -2
- package/dashboard/static/assets/{outlineNavigation-BNGaNPCp.js → outlineNavigation-CO-VWBm7.js} +1 -1
- package/dashboard/static/dashboard.html +1 -1
- package/package.json +1 -1
- package/dashboard/static/assets/ModalRoot-Ck3WJK2V.js +0 -1
|
@@ -1020,14 +1020,22 @@ def _decorated_combined_leg(
|
|
|
1020
1020
|
5, provider, "account_reconciliation_failed"),)
|
|
1021
1021
|
|
|
1022
1022
|
total = math.fsum(cost for _key, cost in card_rows)
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1023
|
+
# The cards and their certified contributions above (keys, order, costs
|
|
1024
|
+
# within 1e-9, periods) are what certify the leg, and the headline is their
|
|
1025
|
+
# sum. A decorated Codex hero IS that sum (`build_codex_source_state`), so
|
|
1026
|
+
# it must still agree. A decorated Claude hero is not: under #836's U3 it is
|
|
1027
|
+
# the DISPLAYED account's own spend, by design one card rather than all of
|
|
1028
|
+
# them, so requiring equality would withhold the combined headline on every
|
|
1029
|
+
# multi-account Claude install.
|
|
1030
|
+
if provider != "claude":
|
|
1031
|
+
hero = _combined_hero(state)
|
|
1032
|
+
hero_cost = hero.get("cost_usd") if hero is not None else None
|
|
1033
|
+
if (
|
|
1034
|
+
_counter_reason(hero_cost, integral=False) is not None
|
|
1035
|
+
or abs(float(hero_cost) - total) > 1e-9
|
|
1036
|
+
):
|
|
1037
|
+
return None, (_CombinedCause(
|
|
1038
|
+
5, provider, "account_reconciliation_failed"),)
|
|
1031
1039
|
return {
|
|
1032
1040
|
"state": "current",
|
|
1033
1041
|
"scope": "account_cycles",
|
|
@@ -2206,6 +2214,84 @@ def codex_stats_digest(stats_conn: sqlite3.Connection) -> str:
|
|
|
2206
2214
|
)
|
|
2207
2215
|
|
|
2208
2216
|
|
|
2217
|
+
# #836 spec 5.7 item 1: the Claude weekly REJECTION state. A weekly credit's
|
|
2218
|
+
# retry can retire a stale replica in place with no new snapshot and a
|
|
2219
|
+
# deduplicated reset, and a suppression replacement or a convergence can
|
|
2220
|
+
# restore one; a DELETE of a referenced snapshot rewrites the milestone's FK to
|
|
2221
|
+
# the `0` sentinel without moving any `MAX(id)`. None of those moves another
|
|
2222
|
+
# signature leg, so this one digests the two relations they change:
|
|
2223
|
+
#
|
|
2224
|
+
# * the suppression-retired snapshots (held, without the held-write payload
|
|
2225
|
+
# marker), which the card's sample selection depends on;
|
|
2226
|
+
# * the five-hour milestones whose effective weekly join is unavailable
|
|
2227
|
+
# (absent snapshot, the `0` sentinel, or retired), which every published
|
|
2228
|
+
# `effective_seven_day_pct_at_crossing` depends on.
|
|
2229
|
+
#
|
|
2230
|
+
# The predicates are `_cctally_five_hour.effective_weekly_value_sql` /
|
|
2231
|
+
# `weekly_join_unavailable_sql`'s, spelled here because this kernel may not
|
|
2232
|
+
# import the glue. Ids only, rowid-ordered (no sorter); both relations are
|
|
2233
|
+
# empty on a store that never retired a row, and the wrapper maps that state to
|
|
2234
|
+
# "" so `data_version` and the Claude source version keep their bytes.
|
|
2235
|
+
_WEEKLY_RETIRED_PREDICATE = (
|
|
2236
|
+
"{a}weekly_observation_held = 1 AND "
|
|
2237
|
+
"json_extract({a}payload_json, '$.weeklyObservationHeld') IS NOT 1"
|
|
2238
|
+
)
|
|
2239
|
+
|
|
2240
|
+
|
|
2241
|
+
def _claude_weekly_retired_relations(
|
|
2242
|
+
stats_conn: sqlite3.Connection,
|
|
2243
|
+
) -> tuple[tuple[str, str], ...]:
|
|
2244
|
+
try:
|
|
2245
|
+
cols = {str(row[1]) for row in stats_conn.execute(
|
|
2246
|
+
"PRAGMA table_info(weekly_usage_snapshots)")}
|
|
2247
|
+
except sqlite3.Error:
|
|
2248
|
+
cols = set()
|
|
2249
|
+
if "weekly_observation_held" in cols:
|
|
2250
|
+
retired = _WEEKLY_RETIRED_PREDICATE.format(a="")
|
|
2251
|
+
unavailable = ("s.id IS NULL OR ("
|
|
2252
|
+
+ _WEEKLY_RETIRED_PREDICATE.format(a="s.") + ")")
|
|
2253
|
+
else:
|
|
2254
|
+
# A store without the column cannot hold a held row
|
|
2255
|
+
# (`_cctally_core.weekly_held_exclusion`).
|
|
2256
|
+
retired = "0"
|
|
2257
|
+
unavailable = "s.id IS NULL"
|
|
2258
|
+
return (
|
|
2259
|
+
(
|
|
2260
|
+
"weekly_usage_snapshots_retired",
|
|
2261
|
+
f"SELECT id FROM weekly_usage_snapshots WHERE {retired} "
|
|
2262
|
+
"ORDER BY id",
|
|
2263
|
+
),
|
|
2264
|
+
(
|
|
2265
|
+
"five_hour_milestones_unavailable_join",
|
|
2266
|
+
"SELECT m.id FROM five_hour_milestones AS m "
|
|
2267
|
+
"LEFT JOIN weekly_usage_snapshots AS s ON s.id = m.usage_snapshot_id "
|
|
2268
|
+
f"WHERE {unavailable} ORDER BY m.id",
|
|
2269
|
+
),
|
|
2270
|
+
)
|
|
2271
|
+
|
|
2272
|
+
|
|
2273
|
+
#: `_stats_relations_digest` over two EMPTY relations: the no-rejection state.
|
|
2274
|
+
_EMPTY_TWO_RELATION_DIGEST = hashlib.sha256(b"[[],[]]").hexdigest()
|
|
2275
|
+
|
|
2276
|
+
|
|
2277
|
+
def claude_weekly_retired_digest(stats_conn: sqlite3.Connection) -> str:
|
|
2278
|
+
"""One digest over the Claude weekly rejection state (#836, spec 5.7
|
|
2279
|
+
item 1), or ``""`` when no snapshot is retired and no five-hour
|
|
2280
|
+
milestone's join is unavailable.
|
|
2281
|
+
|
|
2282
|
+
Read through `_stats_relations_digest` under its own memo namespace,
|
|
2283
|
+
statement-scoped and outside any stats transaction (as
|
|
2284
|
+
`_tui_compute_dispatch_signature` reads every stats leg), so a settled
|
|
2285
|
+
rollback-journal index costs no scan on an idle tick.
|
|
2286
|
+
"""
|
|
2287
|
+
digest = _stats_relations_digest(
|
|
2288
|
+
stats_conn,
|
|
2289
|
+
_claude_weekly_retired_relations(stats_conn),
|
|
2290
|
+
memo_namespace="claude_weekly_retired",
|
|
2291
|
+
)
|
|
2292
|
+
return "" if digest == _EMPTY_TWO_RELATION_DIGEST else digest
|
|
2293
|
+
|
|
2294
|
+
|
|
2209
2295
|
# #1015 spec 5.4: the two relation digests of the dashboard dispatch key's
|
|
2210
2296
|
# `calibration_dependency` component. Neither is a `SnapshotSignature` leg, so
|
|
2211
2297
|
# the published `data_version` contract is unchanged; both ride the dispatch
|
package/bin/_lib_quota_model.py
CHANGED
|
@@ -866,6 +866,24 @@ def family_participation(family: str | None) -> str:
|
|
|
866
866
|
return FAMILY_PARTICIPATION.get(family, "unknown")
|
|
867
867
|
|
|
868
868
|
|
|
869
|
+
def _float_total(values) -> float:
|
|
870
|
+
"""`math.fsum` of `values`, or the plain `sum()` when `fsum` cannot.
|
|
871
|
+
|
|
872
|
+
`fsum` is correctly rounded, so a total is the same on Python 3.11, whose
|
|
873
|
+
float `sum()` is uncompensated, and on 3.12+, whose `sum()` is not; a
|
|
874
|
+
plain sum printed different last digits from one interpreter to the next.
|
|
875
|
+
But `fsum` raises where `sum()` saturates: `OverflowError` once finite
|
|
876
|
+
terms pass the float range, `ValueError` on `inf + -inf`. The plain sum's
|
|
877
|
+
`inf`/`nan` is what the section-39 `_finite_quantity` guards withhold, so
|
|
878
|
+
that path returns it unchanged rather than raising out of the kernel.
|
|
879
|
+
"""
|
|
880
|
+
items = list(values)
|
|
881
|
+
try:
|
|
882
|
+
return math.fsum(items)
|
|
883
|
+
except (OverflowError, ValueError):
|
|
884
|
+
return sum(items)
|
|
885
|
+
|
|
886
|
+
|
|
869
887
|
def _finite_quantity(value) -> bool:
|
|
870
888
|
"""True when a token quantity is a real, finite number.
|
|
871
889
|
|
|
@@ -1001,8 +1019,8 @@ def class_units(classes, era: CoefficientEra) -> float:
|
|
|
1001
1019
|
a multiplication of its own.
|
|
1002
1020
|
"""
|
|
1003
1021
|
weights = era.weight_map()
|
|
1004
|
-
return
|
|
1005
|
-
|
|
1022
|
+
return _float_total(float(classes.get(name, 0.0)) * weights[name]
|
|
1023
|
+
for name in TOKEN_CLASSES)
|
|
1006
1024
|
|
|
1007
1025
|
|
|
1008
1026
|
class EntryWeighting(typing.NamedTuple):
|
|
@@ -1463,7 +1481,7 @@ def build_daily_series(segments, entries, *, now, newest_entry_at,
|
|
|
1463
1481
|
entry_units = class_units(quantities, era)
|
|
1464
1482
|
units += entry_units
|
|
1465
1483
|
families[family] = families.get(family, 0.0) + entry_units
|
|
1466
|
-
raw_total =
|
|
1484
|
+
raw_total = _float_total(raw.values())
|
|
1467
1485
|
|
|
1468
1486
|
# Cause precedence follows STATUS_PRECEDENCE: an unknown split
|
|
1469
1487
|
# outranks incomplete history, which outranks an unsupported mix.
|
|
@@ -1698,7 +1716,7 @@ def composition_centre(vectors) -> dict[str, float] | None:
|
|
|
1698
1716
|
return None
|
|
1699
1717
|
keys = sorted({k for row in rows for k in row})
|
|
1700
1718
|
raw = {k: median([row.get(k, 0.0) for row in rows]) for k in keys}
|
|
1701
|
-
total =
|
|
1719
|
+
total = _float_total(raw.values())
|
|
1702
1720
|
if not math.isfinite(total) or total <= 0.0:
|
|
1703
1721
|
return None
|
|
1704
1722
|
return {k: v / total for k, v in raw.items()}
|
|
@@ -1825,8 +1843,8 @@ def aggregate_composition(entries, *, era: CoefficientEra) -> tuple | None:
|
|
|
1825
1843
|
raw[name] += qty
|
|
1826
1844
|
units = class_units(quantities, era)
|
|
1827
1845
|
families[family] = families.get(family, 0.0) + units
|
|
1828
|
-
raw_total =
|
|
1829
|
-
unit_total =
|
|
1846
|
+
raw_total = _float_total(raw.values())
|
|
1847
|
+
unit_total = _float_total(families.values())
|
|
1830
1848
|
if not _finite_quantity(raw_total) or not _finite_quantity(unit_total):
|
|
1831
1849
|
return None
|
|
1832
1850
|
if raw_total <= 0.0 or unit_total <= 0.0:
|
|
@@ -2060,8 +2078,8 @@ def jackknife_fit(days) -> tuple[float, Interval] | None:
|
|
|
2060
2078
|
if any(not _finite_quantity(d.units) or not _finite_quantity(d.meter_delta)
|
|
2061
2079
|
for d in rows):
|
|
2062
2080
|
return None
|
|
2063
|
-
total_units =
|
|
2064
|
-
total_meter =
|
|
2081
|
+
total_units = _float_total(d.units for d in rows)
|
|
2082
|
+
total_meter = _float_total(d.meter_delta for d in rows)
|
|
2065
2083
|
if total_meter <= 0.0 or total_units <= 0.0:
|
|
2066
2084
|
return None
|
|
2067
2085
|
point = total_units / total_meter
|
|
@@ -2071,10 +2089,10 @@ def jackknife_fit(days) -> tuple[float, Interval] | None:
|
|
|
2071
2089
|
if rest_meter <= 0.0:
|
|
2072
2090
|
return None
|
|
2073
2091
|
replicates.append((total_units - row.units) / rest_meter)
|
|
2074
|
-
mean =
|
|
2092
|
+
mean = _float_total(replicates) / n
|
|
2075
2093
|
# A product, not `** 2`, which is the C library's `pow`.
|
|
2076
|
-
se = math.sqrt((n - 1) / n *
|
|
2077
|
-
|
|
2094
|
+
se = math.sqrt((n - 1) / n * _float_total((v - mean) * (v - mean)
|
|
2095
|
+
for v in replicates))
|
|
2078
2096
|
half = t_quantile_975(n - 1) * se
|
|
2079
2097
|
return point, Interval(max(0.0, point - half), point + half)
|
|
2080
2098
|
|
|
@@ -131,6 +131,12 @@ class SnapshotSignature(NamedTuple):
|
|
|
131
131
|
# legs above, this one is a digest and is never empty: a store with no
|
|
132
132
|
# armed Claude alert carries a constant hash, not the empty string.
|
|
133
133
|
claude_stats_digest: str = ""
|
|
134
|
+
# #836 spec 5.7 item 1: the Claude weekly rejection state
|
|
135
|
+
# (`_lib_dashboard_sources.claude_weekly_retired_digest`) — a credit
|
|
136
|
+
# retry's in-place retirement, a restoration, or the deletion of a
|
|
137
|
+
# referenced snapshot moves no leg above. Empty on a store with no retired
|
|
138
|
+
# snapshot and no unavailable five-hour join, so it is byte-neutral there.
|
|
139
|
+
claude_weekly_retired_digest: str = ""
|
|
134
140
|
|
|
135
141
|
|
|
136
142
|
def _max_id(conn: sqlite3.Connection, table: str) -> int:
|
|
@@ -247,6 +253,7 @@ def compute_signature(
|
|
|
247
253
|
codex_stats_digest: str = "",
|
|
248
254
|
accounts_digest: str = "",
|
|
249
255
|
claude_stats_digest: str = "",
|
|
256
|
+
claude_weekly_retired_digest: str = "",
|
|
250
257
|
) -> SnapshotSignature:
|
|
251
258
|
"""Composite data-version signature across cache.db + stats.db (spec §3).
|
|
252
259
|
|
|
@@ -273,6 +280,7 @@ def compute_signature(
|
|
|
273
280
|
accounts_digest=str(accounts_digest),
|
|
274
281
|
codex_ingest_backlog_sig=_codex_ingest_backlog_sig(cache_conn),
|
|
275
282
|
claude_stats_digest=str(claude_stats_digest),
|
|
283
|
+
claude_weekly_retired_digest=str(claude_weekly_retired_digest),
|
|
276
284
|
)
|
|
277
285
|
|
|
278
286
|
|
package/bin/_lib_tick_stats.py
CHANGED
|
@@ -35,12 +35,17 @@ import types
|
|
|
35
35
|
import _lib_write_io
|
|
36
36
|
|
|
37
37
|
RING_CAPACITY = 64
|
|
38
|
-
|
|
38
|
+
#: Raised from 65,536 for the v1.117.1 cut: the #939 D5 admission-hold
|
|
39
|
+
#: state put the full rings plus the write state at 65,032 bytes on
|
|
40
|
+
#: Python 3.12 and 3.13 but 66,000 on 3.11, whose objects are larger.
|
|
41
|
+
#: The bound stays structural (ring capacities, PUBLISHED_AT_MAX_CHARS);
|
|
42
|
+
#: this figure only checks it on every supported interpreter.
|
|
43
|
+
MEMORY_BUDGET_BYTES = 73728
|
|
39
44
|
|
|
40
45
|
#: `published_at` is the only variable-length field in a record, so it is the
|
|
41
46
|
#: only one that can put the whole state over `MEMORY_BUDGET_BYTES`. Measured:
|
|
42
47
|
#: the stored field set is 27,189 bytes over a full ring, and 64 distinct 4 KiB
|
|
43
|
-
#: strings would be 283,039 —
|
|
48
|
+
#: strings would be 283,039 — 3.8x over budget. All three production callers
|
|
44
49
|
#: pass a UTC ISO-8601 instant (32 characters), so this is not reachable today;
|
|
45
50
|
#: the cap makes the budget hold by CONSTRUCTION rather than by convention.
|
|
46
51
|
PUBLISHED_AT_MAX_CHARS = 64
|
|
@@ -154,7 +159,7 @@ class ConversationSyncRecord:
|
|
|
154
159
|
MAINTENANCE_PHASES = ("delete", "reclaim", "checkpoint", "other")
|
|
155
160
|
|
|
156
161
|
#: The maintenance ring is deliberately SHORTER than the two tick rings. The
|
|
157
|
-
#: whole published state is capped at a frozen
|
|
162
|
+
#: whole published state is capped at a frozen 73,728 bytes, and a third
|
|
158
163
|
#: full-length ring would spend that budget on a phase that runs about once a
|
|
159
164
|
#: day rather than about once a second. Sixteen records is several weeks of
|
|
160
165
|
#: maintenance history at the throttled cadence, and many continuation passes
|
|
@@ -181,7 +186,7 @@ MAINTENANCE_SKIP_REASONS = (
|
|
|
181
186
|
SQLITE_SOURCE_ID_MAX_CHARS = 96
|
|
182
187
|
PLAN_DIGEST_MAX_CHARS = 16
|
|
183
188
|
#: One shared object per distinct SQLite source id, so sixteen records of
|
|
184
|
-
#: one runtime hold one string (the
|
|
189
|
+
#: one runtime hold one string (the 73,728-byte budget).
|
|
185
190
|
_SOURCE_IDS: "dict[str, str]" = {}
|
|
186
191
|
|
|
187
192
|
|
|
@@ -224,7 +229,7 @@ class MaintenancePhaseRecord:
|
|
|
224
229
|
# #901 Q8 (a deletion's reservation inputs) and Q9 (a reclaim chunk's
|
|
225
230
|
# plan and outcome, §5.5): `OPERATION_INT_FIELDS` packed into ONE
|
|
226
231
|
# array, -1 for "not recorded", because sixteen records of twenty
|
|
227
|
-
# separate int objects do not fit the frozen
|
|
232
|
+
# separate int objects do not fit the frozen 73,728-byte budget. Read
|
|
228
233
|
# them by name (`record.page_count`); `as_wire` publishes them by name.
|
|
229
234
|
numbers: "array.array | None" = None # typecode "i"
|
|
230
235
|
|
package/bin/_lib_write_io.py
CHANGED
|
@@ -499,7 +499,7 @@ def local_instance() -> "dict | None":
|
|
|
499
499
|
|
|
500
500
|
|
|
501
501
|
def state_for_budget() -> tuple:
|
|
502
|
-
"""Every object this module retains, for the
|
|
502
|
+
"""Every object this module retains, for the tick-stats state budget."""
|
|
503
503
|
state = _STATE
|
|
504
504
|
return (state.t, state.b, state.p, state.ledger, state.ex[0],
|
|
505
505
|
state.ex[1:], state.open, state.hysteresis, state.local_instance,
|
package/bin/cctally
CHANGED
|
@@ -1772,6 +1772,10 @@ BudgetRow = _cctally_forecast.BudgetRow
|
|
|
1772
1772
|
_resolve_forecast_now = _cctally_forecast._resolve_forecast_now
|
|
1773
1773
|
_fetch_current_week_snapshots = _cctally_forecast._fetch_current_week_snapshots
|
|
1774
1774
|
_apply_midweek_reset_override = _cctally_forecast._apply_midweek_reset_override
|
|
1775
|
+
ClaudeCycleScope = _cctally_forecast.ClaudeCycleScope
|
|
1776
|
+
_claude_select_current_owner = _cctally_forecast._claude_select_current_owner
|
|
1777
|
+
_claude_current_cycle_scope = _cctally_forecast._claude_current_cycle_scope
|
|
1778
|
+
_claude_cycle_scope_samples = _cctally_forecast._claude_cycle_scope_samples
|
|
1775
1779
|
_current_week_five_hour_percent = _cctally_forecast._current_week_five_hour_percent
|
|
1776
1780
|
_current_week_five_hour_reading = _cctally_forecast._current_week_five_hour_reading
|
|
1777
1781
|
_resolve_current_budget_window = _cctally_forecast._resolve_current_budget_window
|