cctally 1.103.0 → 1.105.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.
- package/CHANGELOG.md +94 -0
- package/README.md +6 -6
- package/bin/_cctally_alerts.py +104 -10
- package/bin/_cctally_cache.py +193 -71
- package/bin/_cctally_config.py +62 -2
- package/bin/_cctally_core.py +62 -1
- package/bin/_cctally_dashboard.py +694 -70
- package/bin/_cctally_dashboard_conversation.py +400 -6
- package/bin/_cctally_dashboard_envelope.py +336 -14
- package/bin/_cctally_dashboard_perf.py +93 -0
- package/bin/_cctally_dashboard_share.py +56 -25
- package/bin/_cctally_dashboard_sources.py +988 -94
- package/bin/_cctally_db.py +30 -3
- package/bin/_cctally_diagnosis_sources.py +699 -186
- package/bin/_cctally_doctor.py +77 -0
- package/bin/_cctally_forecast.py +932 -51
- package/bin/_cctally_journal.py +430 -24
- package/bin/_cctally_parser.py +66 -0
- package/bin/_cctally_project.py +535 -10
- package/bin/_cctally_quota.py +28 -0
- package/bin/_cctally_quota_calibration.py +146 -0
- package/bin/_cctally_quota_model.py +2142 -0
- package/bin/_cctally_record.py +204 -30
- package/bin/_cctally_share.py +16 -8
- package/bin/_cctally_statusline.py +34 -0
- package/bin/_cctally_tui.py +531 -115
- package/bin/_lib_codex_conversation_query.py +258 -12
- package/bin/_lib_codex_hooks.py +26 -0
- package/bin/_lib_conversation_query.py +145 -15
- package/bin/_lib_dashboard_json.py +105 -0
- package/bin/_lib_dashboard_settings_contract.py +2 -0
- package/bin/_lib_diagnosis.py +21 -2
- package/bin/_lib_doctor.py +215 -1
- package/bin/_lib_forecast.py +337 -43
- package/bin/_lib_ingest_frontier.py +889 -0
- package/bin/_lib_meter_rate_change.py +360 -0
- package/bin/_lib_perf.py +22 -0
- package/bin/_lib_pricing.py +30 -3
- package/bin/_lib_quota_calibration.py +311 -0
- package/bin/_lib_quota_copy.py +157 -0
- package/bin/_lib_quota_model.py +2520 -0
- package/bin/_lib_rate_change_delivery.py +119 -0
- package/bin/_lib_record.py +50 -0
- package/bin/_lib_rederive.py +10 -0
- package/bin/_lib_render.py +6 -0
- package/bin/_lib_retained_size.py +187 -0
- package/bin/_lib_share_templates.py +37 -5
- package/bin/_lib_snapshot_cache.py +504 -17
- package/bin/_lib_statusline.py +200 -2
- package/bin/_lib_tick_stats.py +28 -4
- package/bin/_lib_view_models.py +30 -12
- package/bin/cctally +32 -0
- package/dashboard/static/assets/ConversationsView-DA4j7Yso.js +72 -0
- package/dashboard/static/assets/DoctorModal-D4PbVnbE.js +1 -0
- package/dashboard/static/assets/ModalRoot-D4oSPxPp.js +1 -0
- package/dashboard/static/assets/ProjectsDrillPanel-DkPlB9JH.js +1 -0
- package/dashboard/static/assets/SourceDetailModal-BE7FONsS.js +1 -0
- package/dashboard/static/assets/UpdateModal-BJuI8glf.js +7 -0
- package/dashboard/static/assets/index-klO46NcU.css +1 -0
- package/dashboard/static/assets/index-w_bINkJ8.js +13 -0
- package/dashboard/static/assets/outlineNavigation-zDVm6Hdd.js +9 -0
- package/dashboard/static/assets/useKeymap-CJ-Pi17D.js +1 -0
- package/dashboard/static/dashboard.html +3 -2
- package/package.json +10 -1
- package/dashboard/static/assets/index-Di2hljvB.css +0 -1
- package/dashboard/static/assets/index-XYCIWjVG.js +0 -97
package/bin/_cctally_core.py
CHANGED
|
@@ -400,7 +400,17 @@ _init_paths_from_env()
|
|
|
400
400
|
# removing the shared-memory WAL page map implicated by the retained corruption
|
|
401
401
|
# bundles. The one-time rebuild is the mode transition; no stats migration is
|
|
402
402
|
# added and cache/conversations remain WAL/NORMAL.
|
|
403
|
-
|
|
403
|
+
# 1010 -> 1011 (#661 S2 §6.4): the meter-rate-change event family. Adds
|
|
404
|
+
# `meter_rate_change_events`, a durable forward-only record that one provider
|
|
405
|
+
# metering-rate transition was observed, keyed
|
|
406
|
+
# `(provider, account_key, effective_from)`. It is an epoch bump and NOT a
|
|
407
|
+
# stats migration for the two reasons every bump since 1005 carries: the
|
|
408
|
+
# 13-entry registry is frozen, AND an epoch-current open returns before any
|
|
409
|
+
# schema work, so a `@stats_migration` handler would never run on an upgraded
|
|
410
|
+
# install and the table would simply never appear. `quota_alert_arming` is the
|
|
411
|
+
# precedent the family follows — a journaled STATE record whose boundary
|
|
412
|
+
# survives a rebuild, so history cannot re-fire.
|
|
413
|
+
STATS_INDEX_EPOCH = 1011
|
|
404
414
|
LEGACY_STATS_HEAD = 13
|
|
405
415
|
|
|
406
416
|
#: #496 S1 F1. A NEW branch, for a state that cannot occur before the
|
|
@@ -965,6 +975,12 @@ _ALERTS_CONFIG_VALID_KEYS = {
|
|
|
965
975
|
"weekly_thresholds",
|
|
966
976
|
"five_hour_thresholds",
|
|
967
977
|
"projected_enabled",
|
|
978
|
+
# #661 S2 §6.2. The metering-rate-change family's PUSH toggle, and only
|
|
979
|
+
# its push toggle: events are recorded from the first upgrade whatever
|
|
980
|
+
# this says, so every pull surface shows the state with no configuration.
|
|
981
|
+
# It defaults OFF like every other alert toggle, so an upgrade never
|
|
982
|
+
# produces a surprise notification.
|
|
983
|
+
"rate_change_enabled",
|
|
968
984
|
"notifier",
|
|
969
985
|
"command_template",
|
|
970
986
|
}
|
|
@@ -1062,6 +1078,17 @@ def _get_alerts_config(cfg: "dict | None") -> dict:
|
|
|
1062
1078
|
f"{type(projected_enabled).__name__}: {projected_enabled!r}",
|
|
1063
1079
|
field="alerts.projected_enabled",
|
|
1064
1080
|
)
|
|
1081
|
+
# #661 S2 §6.2: the rate-change PUSH toggle, default OFF. Recording is
|
|
1082
|
+
# unconditional and does not consult this — the split is what lets a user
|
|
1083
|
+
# learn that the rate changed without opting in while nothing arrives
|
|
1084
|
+
# unbidden. Bool-validated rather than coerced, like `projected_enabled`.
|
|
1085
|
+
rate_change_enabled = block.get("rate_change_enabled", False)
|
|
1086
|
+
if not isinstance(rate_change_enabled, bool):
|
|
1087
|
+
raise _AlertsConfigError(
|
|
1088
|
+
f"alerts.rate_change_enabled must be a JSON boolean, got "
|
|
1089
|
+
f"{type(rate_change_enabled).__name__}: {rate_change_enabled!r}",
|
|
1090
|
+
field="alerts.rate_change_enabled",
|
|
1091
|
+
)
|
|
1065
1092
|
# Dispatch-global keys (Phase B). `notifier` selects the backend;
|
|
1066
1093
|
# `command_template` is an argv list for the `command` backend (and may be
|
|
1067
1094
|
# set ahead of switching the backend). The cross-field constraint
|
|
@@ -1109,6 +1136,7 @@ def _get_alerts_config(cfg: "dict | None") -> dict:
|
|
|
1109
1136
|
"weekly_thresholds": weekly,
|
|
1110
1137
|
"five_hour_thresholds": five_hour,
|
|
1111
1138
|
"projected_enabled": projected_enabled,
|
|
1139
|
+
"rate_change_enabled": rate_change_enabled,
|
|
1112
1140
|
"notifier": notifier,
|
|
1113
1141
|
"command_template": command_template,
|
|
1114
1142
|
}
|
|
@@ -1637,6 +1665,39 @@ def _apply_quota_projection_schema(conn: sqlite3.Connection) -> None:
|
|
|
1637
1665
|
PRIMARY KEY(source)
|
|
1638
1666
|
);
|
|
1639
1667
|
|
|
1668
|
+
-- #661 S2 §6: a provider metering-rate transition, recorded from the
|
|
1669
|
+
-- first upgrade whether or not notification is enabled (§6.2). The
|
|
1670
|
+
-- row is the durable forward-only latch: its presence is what stops
|
|
1671
|
+
-- the same transition alerting twice, and it survives a stats.db
|
|
1672
|
+
-- rebuild because the journal event that created it carries a
|
|
1673
|
+
-- self-sufficient payload (§6.5).
|
|
1674
|
+
--
|
|
1675
|
+
-- The identity is `(provider, account_key, effective_from)` and the
|
|
1676
|
+
-- promise is stated at exactly that precision — once per exact key,
|
|
1677
|
+
-- not "once per provider regime". `reduce_state` closes and
|
|
1678
|
+
-- re-creates regimes on a fingerprint change and a revised detector
|
|
1679
|
+
-- can select a different `effective_from` for the same underlying
|
|
1680
|
+
-- transition; separately, R8 can turn a formerly merged identity into
|
|
1681
|
+
-- a real account key. Both produce a new key for a transition a human
|
|
1682
|
+
-- would call the same one. The FINGERPRINT stays out of the key
|
|
1683
|
+
-- deliberately: including it would re-alert on our own algorithm
|
|
1684
|
+
-- revisions.
|
|
1685
|
+
CREATE TABLE IF NOT EXISTS meter_rate_change_events (
|
|
1686
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1687
|
+
provider TEXT NOT NULL,
|
|
1688
|
+
account_key TEXT NOT NULL DEFAULT 'unattributed',
|
|
1689
|
+
effective_from TEXT NOT NULL,
|
|
1690
|
+
previous_units_per_point REAL NOT NULL,
|
|
1691
|
+
new_units_per_point REAL NOT NULL,
|
|
1692
|
+
severity TEXT NOT NULL,
|
|
1693
|
+
detected_at_utc TEXT NOT NULL,
|
|
1694
|
+
created_at_utc TEXT NOT NULL,
|
|
1695
|
+
notified_at TEXT,
|
|
1696
|
+
UNIQUE(provider, account_key, effective_from)
|
|
1697
|
+
);
|
|
1698
|
+
CREATE INDEX IF NOT EXISTS idx_meter_rate_change_events_key
|
|
1699
|
+
ON meter_rate_change_events(provider, account_key, effective_from);
|
|
1700
|
+
|
|
1640
1701
|
CREATE TABLE IF NOT EXISTS quota_alert_arming (
|
|
1641
1702
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1642
1703
|
source TEXT NOT NULL,
|