cctally 1.114.0 → 1.115.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 +25 -0
- package/bin/_cctally_account.py +32 -8
- package/bin/_cctally_cache.py +4394 -279
- package/bin/_cctally_completion.py +1287 -0
- package/bin/_cctally_config.py +37 -0
- package/bin/_cctally_core.py +178 -2
- package/bin/_cctally_dashboard.py +2 -2
- package/bin/_cctally_dashboard_conversation.py +292 -23
- package/bin/_cctally_dashboard_envelope.py +16 -3
- package/bin/_cctally_dashboard_sources.py +18 -9
- package/bin/_cctally_db.py +316 -5
- package/bin/_cctally_doctor.py +436 -27
- package/bin/_cctally_five_hour.py +82 -31
- package/bin/_cctally_forecast.py +81 -9
- package/bin/_cctally_journal.py +3884 -112
- package/bin/_cctally_journal_repair.py +101 -12
- package/bin/_cctally_parser.py +42 -0
- package/bin/_cctally_project.py +8 -2
- package/bin/_cctally_quarantine.py +652 -0
- package/bin/_cctally_quota.py +26 -4
- package/bin/_cctally_quota_model.py +360 -83
- package/bin/_cctally_record.py +677 -149
- package/bin/_cctally_rederive.py +297 -32
- package/bin/_cctally_refresh.py +84 -13
- package/bin/_cctally_source_analytics.py +23 -2
- package/bin/_cctally_statusline.py +6137 -517
- package/bin/_cctally_store.py +287 -48
- package/bin/_cctally_transcript.py +8 -7
- package/bin/_cctally_tui.py +57 -53
- package/bin/_lib_account_binding.py +152 -0
- package/bin/_lib_account_protocol.py +386 -0
- package/bin/_lib_aggregators.py +4 -0
- package/bin/_lib_cache_coverage.py +13 -1
- package/bin/_lib_cache_writer_lock.py +19 -3
- package/bin/_lib_clone_copy.py +142 -0
- package/bin/_lib_codex_conversation_query.py +133 -19
- package/bin/_lib_codex_conversation_watch.py +59 -14
- package/bin/_lib_codex_drain_admission.py +214 -0
- package/bin/_lib_codex_metadata.py +18 -1
- package/bin/_lib_codex_publication_barrier.py +277 -0
- package/bin/_lib_codex_rollout_container.py +502 -0
- package/bin/_lib_conversation_dispatch.py +3 -0
- package/bin/_lib_conversation_query.py +11 -3
- package/bin/_lib_detector_access_registry.py +3817 -0
- package/bin/_lib_doctor.py +452 -1
- package/bin/_lib_five_hour.py +126 -0
- package/bin/_lib_ingest_frontier.py +414 -20
- package/bin/_lib_journal.py +141 -10
- package/bin/_lib_jsonl.py +5 -4
- package/bin/_lib_legacy_spool.py +126 -0
- package/bin/_lib_legacy_writer_evidence.py +136 -0
- package/bin/_lib_merge_gate.py +29 -302
- package/bin/_lib_meter_rate_change.py +10 -0
- package/bin/_lib_outcome_quarantine.py +690 -0
- package/bin/_lib_pricing.py +7 -2
- package/bin/_lib_quarantine.py +154 -0
- package/bin/_lib_quota_calibration.py +53 -8
- package/bin/_lib_quota_copy.py +2 -1
- package/bin/_lib_quota_model.py +288 -46
- package/bin/_lib_rederive.py +94 -5
- package/bin/_lib_segment_summary.py +35 -2
- package/bin/_lib_statusline_candidates.py +292 -23
- package/bin/cctally +23 -1
- package/package.json +14 -1
package/bin/_lib_rederive.py
CHANGED
|
@@ -144,6 +144,24 @@ _OP_CLASSIFICATIONS = {
|
|
|
144
144
|
"retained", "operator Codex window attribution is outside claude-usage"),
|
|
145
145
|
"codex_window_attribution_retract": KindClassification(
|
|
146
146
|
"retained", "operator Codex window retraction is outside claude-usage"),
|
|
147
|
+
# #825/#832 spec 5.1. The versioned reviewed decision is replayed into
|
|
148
|
+
# scratch exactly like its previous-release twin.
|
|
149
|
+
"claude_weekly_observation_decision_v2": KindClassification(
|
|
150
|
+
"retained_input",
|
|
151
|
+
"reviewed weekly-axis decisions are replayed into scratch"),
|
|
152
|
+
# The account-bound protocol ops are fence metadata: they decide what is
|
|
153
|
+
# admitted and anchor the legacy spool baseline, and nothing in the
|
|
154
|
+
# claude-usage family derives them.
|
|
155
|
+
"account_bound_cutover": KindClassification(
|
|
156
|
+
"retained", "the account-bound journal fence is replayed, never "
|
|
157
|
+
"re-derived"),
|
|
158
|
+
"legacy_spool_activity": KindClassification(
|
|
159
|
+
"retained", "legacy spool evidence is replayed, never re-derived"),
|
|
160
|
+
# The versioned journal-repair acknowledgement is an audit record over the
|
|
161
|
+
# raw prefix, outside claude-usage (its previous-release twin is consumed
|
|
162
|
+
# by the selector directly and never registered).
|
|
163
|
+
"journal_protocol_resolution_v2": KindClassification(
|
|
164
|
+
"retained", "journal-repair acknowledgements are audit records"),
|
|
147
165
|
}
|
|
148
166
|
|
|
149
167
|
|
|
@@ -1310,6 +1328,45 @@ def _reconcile_snapshot_identities(
|
|
|
1310
1328
|
)
|
|
1311
1329
|
|
|
1312
1330
|
|
|
1331
|
+
def _suppression_deletes_snapshot(record: Mapping, *, weekly: bool,
|
|
1332
|
+
five_hour: bool) -> bool:
|
|
1333
|
+
"""Whether the stale-replica suppressions naming one snapshot delete it
|
|
1334
|
+
(#825/#832 spec 4.7 revisions 6 and 7), as the journal appliers
|
|
1335
|
+
materialize them. A five-hour credit retires an unheld row's five-hour
|
|
1336
|
+
reading in place and deletes a held row; a weekly suppression makes a row
|
|
1337
|
+
that carries its own five-hour reading weekly-held and deletes a row that
|
|
1338
|
+
keeps no evidence on either axis. Both retirements together delete the
|
|
1339
|
+
row, in either order."""
|
|
1340
|
+
payload = record.get("payload") or {}
|
|
1341
|
+
held = payload.get("weekly_observation_held") == 1
|
|
1342
|
+
carries_five_hour = (
|
|
1343
|
+
payload.get("five_hour_percent") is not None
|
|
1344
|
+
and payload.get("five_hour_resets_at") is not None
|
|
1345
|
+
and payload.get("source") != "record-credit"
|
|
1346
|
+
)
|
|
1347
|
+
if weekly and five_hour:
|
|
1348
|
+
return True
|
|
1349
|
+
if five_hour:
|
|
1350
|
+
return held
|
|
1351
|
+
if weekly:
|
|
1352
|
+
return not carries_five_hour
|
|
1353
|
+
return False
|
|
1354
|
+
|
|
1355
|
+
|
|
1356
|
+
def _without_snapshot_suppression(payload: Mapping) -> Mapping:
|
|
1357
|
+
"""``payload`` without its `weekly_usage_snapshots` stale-replica
|
|
1358
|
+
suppression list. Since spec 4.7 revision 7 (fix round K16b) a
|
|
1359
|
+
suppression that names a reviewed-held snapshot can leave it
|
|
1360
|
+
materialized (a weekly credit keeps a row carrying its own five-hour
|
|
1361
|
+
reading weekly-held), and that list is then a retirement effect on the
|
|
1362
|
+
snapshot, not a reference that depends on it."""
|
|
1363
|
+
if ("suppression" not in payload
|
|
1364
|
+
or payload.get("suppression_table", "weekly_usage_snapshots")
|
|
1365
|
+
!= "weekly_usage_snapshots"):
|
|
1366
|
+
return payload
|
|
1367
|
+
return {key: value for key, value in payload.items() if key != "suppression"}
|
|
1368
|
+
|
|
1369
|
+
|
|
1313
1370
|
def _preserve_reviewed_hold_dependencies(
|
|
1314
1371
|
current: Mapping[str, Mapping], desired: dict[str, Mapping],
|
|
1315
1372
|
reviewed_weekly_hold_ids=(), historical_close_markers=None,
|
|
@@ -1334,9 +1391,11 @@ def _preserve_reviewed_hold_dependencies(
|
|
|
1334
1391
|
}
|
|
1335
1392
|
if not snapshot_ids:
|
|
1336
1393
|
return
|
|
1337
|
-
|
|
1394
|
+
weekly_named: set = set()
|
|
1395
|
+
five_hour_named: set = set()
|
|
1338
1396
|
for record in (*current.values(), *desired.values()):
|
|
1339
|
-
|
|
1397
|
+
payload = record.get("payload") or {}
|
|
1398
|
+
suppression = payload.get("suppression")
|
|
1340
1399
|
if suppression is None:
|
|
1341
1400
|
continue
|
|
1342
1401
|
if not isinstance(suppression, list) or any(
|
|
@@ -1346,7 +1405,26 @@ def _preserve_reviewed_hold_dependencies(
|
|
|
1346
1405
|
"reviewed weekly hold suppression has an unfamiliar shape: "
|
|
1347
1406
|
+ str(record.get("id"))
|
|
1348
1407
|
)
|
|
1349
|
-
|
|
1408
|
+
named = snapshot_ids & set(suppression)
|
|
1409
|
+
if (payload.get("suppression_table", "weekly_usage_snapshots")
|
|
1410
|
+
!= "weekly_usage_snapshots"):
|
|
1411
|
+
continue
|
|
1412
|
+
if payload.get("kind") == "five_hour_credit":
|
|
1413
|
+
five_hour_named.update(named)
|
|
1414
|
+
else:
|
|
1415
|
+
weekly_named.update(named)
|
|
1416
|
+
# #825/#832 spec 4.7 revision 7 (*Versioning and replay*, fix round
|
|
1417
|
+
# K16b): suppression-list membership no longer implies whole-snapshot
|
|
1418
|
+
# deletion. Each axis's stale-replica suppression retires its own axis
|
|
1419
|
+
# (`_cctally_journal._apply_reset_with_suppression`), so a snapshot is
|
|
1420
|
+
# deleted only where the materialization deletes it.
|
|
1421
|
+
suppressed_snapshot_ids = {
|
|
1422
|
+
snapshot_id for snapshot_id in weekly_named | five_hour_named
|
|
1423
|
+
if _suppression_deletes_snapshot(
|
|
1424
|
+
(desired.get(snapshot_id) or current.get(snapshot_id) or {}),
|
|
1425
|
+
weekly=snapshot_id in weekly_named,
|
|
1426
|
+
five_hour=snapshot_id in five_hour_named)
|
|
1427
|
+
}
|
|
1350
1428
|
if suppressed_snapshot_ids:
|
|
1351
1429
|
for event_id, record in (*current.items(), *desired.items()):
|
|
1352
1430
|
payload = record.get("payload") or {}
|
|
@@ -1368,7 +1446,8 @@ def _preserve_reviewed_hold_dependencies(
|
|
|
1368
1446
|
payload = record.get("payload") or {}
|
|
1369
1447
|
usage_ref = payload.get("usage_snapshot_ref")
|
|
1370
1448
|
if usage_ref not in snapshot_ids:
|
|
1371
|
-
if _contains_any_value(payload,
|
|
1449
|
+
if _contains_any_value(_without_snapshot_suppression(payload),
|
|
1450
|
+
snapshot_ids):
|
|
1372
1451
|
raise RederiveConflict(
|
|
1373
1452
|
"reviewed weekly hold dependency has an unfamiliar shape: "
|
|
1374
1453
|
+ event_id
|
|
@@ -1413,7 +1492,8 @@ def _preserve_reviewed_hold_dependencies(
|
|
|
1413
1492
|
payload = record.get("payload") or {}
|
|
1414
1493
|
usage_ref = payload.get("usage_snapshot_ref")
|
|
1415
1494
|
if usage_ref not in snapshot_ids:
|
|
1416
|
-
if _contains_any_value(payload,
|
|
1495
|
+
if _contains_any_value(_without_snapshot_suppression(payload),
|
|
1496
|
+
snapshot_ids):
|
|
1417
1497
|
raise RederiveConflict(
|
|
1418
1498
|
"reviewed weekly hold dependency has an unfamiliar shape: "
|
|
1419
1499
|
+ event_id
|
|
@@ -1625,6 +1705,7 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
1625
1705
|
reviewed_weekly_hold_ids=(),
|
|
1626
1706
|
uncertain_cost_fact_ids=(),
|
|
1627
1707
|
historical_close_markers=None,
|
|
1708
|
+
physical_revisions=None,
|
|
1628
1709
|
enforce_guard=True) -> RederivePlan:
|
|
1629
1710
|
"""Diff current effective events against one scratch-derived desired set.
|
|
1630
1711
|
|
|
@@ -1660,6 +1741,11 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
1660
1741
|
the same way closes are decided before milestones."""
|
|
1661
1742
|
conflicted_event_ids = frozenset(conflicted_event_ids or ())
|
|
1662
1743
|
historical_close_markers = dict(historical_close_markers or {})
|
|
1744
|
+
# #825/#832 fix round A (Codex fix-round-1 Q2): the highest revision of
|
|
1745
|
+
# every physical record admission hides (an excluded legacy event or
|
|
1746
|
+
# correction action). A new revision is allocated above it, so the
|
|
1747
|
+
# previous release never reads a same-revision conflict.
|
|
1748
|
+
physical_revisions = dict(physical_revisions or {})
|
|
1663
1749
|
desired = _desired_by_id(desired_events)
|
|
1664
1750
|
current = {
|
|
1665
1751
|
event_id: selected.record
|
|
@@ -1824,6 +1910,9 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
1824
1910
|
disposition = "add"
|
|
1825
1911
|
at = str(desired_record["at"])
|
|
1826
1912
|
payload = dict(desired_record.get("payload") or {})
|
|
1913
|
+
floor = physical_revisions.get(event_id)
|
|
1914
|
+
if floor is not None and revision <= floor:
|
|
1915
|
+
revision = floor + 1
|
|
1827
1916
|
counts[disposition] += 1
|
|
1828
1917
|
kind_record = (
|
|
1829
1918
|
current_record if disposition == "tombstone"
|
|
@@ -84,7 +84,7 @@ SIDECAR_NAME = ".segment-summaries"
|
|
|
84
84
|
#: Bumped by hand when a summary's FIELD SET or the meaning of one of its
|
|
85
85
|
#: numbers changes. It is only one of the three inputs to `summary_version`; the
|
|
86
86
|
#: other two are derived, for the reason that function's docstring gives.
|
|
87
|
-
SUMMARY_SHAPE_VERSION =
|
|
87
|
+
SUMMARY_SHAPE_VERSION = 4
|
|
88
88
|
|
|
89
89
|
#: The refusal reasons `summary_is_elidable` returns. They are stable, because
|
|
90
90
|
#: the rebuild record reports them (spec section 6.3, "recorded, not silent").
|
|
@@ -121,6 +121,14 @@ class SegmentSummary:
|
|
|
121
121
|
malformed: int = 0
|
|
122
122
|
#: True when the segment holds ZERO records of a retained type.
|
|
123
123
|
quota_only: bool = True
|
|
124
|
+
#: #825/#832 fix round A (Codex fix-round-1 Q4): True only when EVERY
|
|
125
|
+
#: decoded record in the summarized extent is a positively identified
|
|
126
|
+
#: Codex quota observation — no Claude, administrative, protocol or
|
|
127
|
+
#: unknown record. The one proof that lets a segment past the account-bound
|
|
128
|
+
#: cutover's frozen extents be elided: a summary describes its lines
|
|
129
|
+
#: WITHOUT admission, and `quota_only` alone still admits a stamped Claude
|
|
130
|
+
#: observation that feeds the quarantine and the derivation completion.
|
|
131
|
+
codex_quota_only: bool = False
|
|
124
132
|
#: Elements the segment contributed to the rebuild's `decoded` list —
|
|
125
133
|
#: retained records plus placeholders, and never a malformed line. `None`
|
|
126
134
|
#: means the summary predates the field and cannot be elided against.
|
|
@@ -227,7 +235,8 @@ def summary_is_elidable(
|
|
|
227
235
|
_FIELDS = (
|
|
228
236
|
"segment_name", "st_dev", "st_ino", "summarized_size",
|
|
229
237
|
"complete_line_covered_offset", "lines", "bytes", "decodes", "malformed",
|
|
230
|
-
"quota_only", "
|
|
238
|
+
"quota_only", "codex_quota_only", "decoded_entry_count",
|
|
239
|
+
"complete_prefix_sha256",
|
|
231
240
|
"last_seen_stamped",
|
|
232
241
|
"last_seen_legacy_claude_at", "last_seen_legacy_codex_at",
|
|
233
242
|
)
|
|
@@ -285,6 +294,7 @@ def to_wire(summary) -> dict:
|
|
|
285
294
|
"decodes": int(summary.decodes),
|
|
286
295
|
"malformed": int(summary.malformed),
|
|
287
296
|
"quota_only": bool(summary.quota_only),
|
|
297
|
+
"codex_quota_only": bool(summary.codex_quota_only),
|
|
288
298
|
"decoded_entry_count": (
|
|
289
299
|
None if summary.decoded_entry_count is None
|
|
290
300
|
else int(summary.decoded_entry_count)),
|
|
@@ -326,6 +336,7 @@ def from_wire(item) -> SegmentSummary:
|
|
|
326
336
|
decodes=int(item["decodes"]),
|
|
327
337
|
malformed=int(item["malformed"]),
|
|
328
338
|
quota_only=bool(item["quota_only"]),
|
|
339
|
+
codex_quota_only=bool(item["codex_quota_only"]),
|
|
329
340
|
decoded_entry_count=None if count is None else int(count),
|
|
330
341
|
complete_prefix_sha256=(
|
|
331
342
|
None if item["complete_prefix_sha256"] is None
|
|
@@ -427,6 +438,28 @@ def read_sidecar(path):
|
|
|
427
438
|
return None
|
|
428
439
|
|
|
429
440
|
|
|
441
|
+
def admission_permits_elision(boundary, name, pinned_raw_extent,
|
|
442
|
+
summary) -> bool:
|
|
443
|
+
"""Whether the account-bound admission fence lets segment ``name`` be
|
|
444
|
+
elided at all (#825/#832 fix round A, Codex fix-round-1 Q4). The rebuild
|
|
445
|
+
and the prefix validation share it; every other elision condition is
|
|
446
|
+
still `summary_is_elidable`'s.
|
|
447
|
+
|
|
448
|
+
A summary describes its lines without admission. A wholly historical
|
|
449
|
+
extent (inside the cutover's frozen extent for that segment) needs no
|
|
450
|
+
admission. Past it, only a summary proving every decoded record a Codex
|
|
451
|
+
quota observation — always admitted, never a quarantine or completion
|
|
452
|
+
input — may stand in for reading the segment; anything unproven (no
|
|
453
|
+
summary, an older shape, a mixed segment) is scanned.
|
|
454
|
+
"""
|
|
455
|
+
if boundary is None:
|
|
456
|
+
return True
|
|
457
|
+
extent = boundary.frozen_extents.get(name)
|
|
458
|
+
if extent is not None and int(pinned_raw_extent) <= int(extent):
|
|
459
|
+
return True
|
|
460
|
+
return summary is not None and bool(summary.codex_quota_only)
|
|
461
|
+
|
|
462
|
+
|
|
430
463
|
def with_identity(summary, *, st_dev, st_ino) -> SegmentSummary:
|
|
431
464
|
"""``summary`` restamped with the inode identity of the file just read."""
|
|
432
465
|
return replace(summary, st_dev=int(st_dev), st_ino=int(st_ino))
|