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.
Files changed (64) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/bin/_cctally_account.py +32 -8
  3. package/bin/_cctally_cache.py +4394 -279
  4. package/bin/_cctally_completion.py +1287 -0
  5. package/bin/_cctally_config.py +37 -0
  6. package/bin/_cctally_core.py +178 -2
  7. package/bin/_cctally_dashboard.py +2 -2
  8. package/bin/_cctally_dashboard_conversation.py +292 -23
  9. package/bin/_cctally_dashboard_envelope.py +16 -3
  10. package/bin/_cctally_dashboard_sources.py +18 -9
  11. package/bin/_cctally_db.py +316 -5
  12. package/bin/_cctally_doctor.py +436 -27
  13. package/bin/_cctally_five_hour.py +82 -31
  14. package/bin/_cctally_forecast.py +81 -9
  15. package/bin/_cctally_journal.py +3884 -112
  16. package/bin/_cctally_journal_repair.py +101 -12
  17. package/bin/_cctally_parser.py +42 -0
  18. package/bin/_cctally_project.py +8 -2
  19. package/bin/_cctally_quarantine.py +652 -0
  20. package/bin/_cctally_quota.py +26 -4
  21. package/bin/_cctally_quota_model.py +360 -83
  22. package/bin/_cctally_record.py +677 -149
  23. package/bin/_cctally_rederive.py +297 -32
  24. package/bin/_cctally_refresh.py +84 -13
  25. package/bin/_cctally_source_analytics.py +23 -2
  26. package/bin/_cctally_statusline.py +6137 -517
  27. package/bin/_cctally_store.py +287 -48
  28. package/bin/_cctally_transcript.py +8 -7
  29. package/bin/_cctally_tui.py +57 -53
  30. package/bin/_lib_account_binding.py +152 -0
  31. package/bin/_lib_account_protocol.py +386 -0
  32. package/bin/_lib_aggregators.py +4 -0
  33. package/bin/_lib_cache_coverage.py +13 -1
  34. package/bin/_lib_cache_writer_lock.py +19 -3
  35. package/bin/_lib_clone_copy.py +142 -0
  36. package/bin/_lib_codex_conversation_query.py +133 -19
  37. package/bin/_lib_codex_conversation_watch.py +59 -14
  38. package/bin/_lib_codex_drain_admission.py +214 -0
  39. package/bin/_lib_codex_metadata.py +18 -1
  40. package/bin/_lib_codex_publication_barrier.py +277 -0
  41. package/bin/_lib_codex_rollout_container.py +502 -0
  42. package/bin/_lib_conversation_dispatch.py +3 -0
  43. package/bin/_lib_conversation_query.py +11 -3
  44. package/bin/_lib_detector_access_registry.py +3817 -0
  45. package/bin/_lib_doctor.py +452 -1
  46. package/bin/_lib_five_hour.py +126 -0
  47. package/bin/_lib_ingest_frontier.py +414 -20
  48. package/bin/_lib_journal.py +141 -10
  49. package/bin/_lib_jsonl.py +5 -4
  50. package/bin/_lib_legacy_spool.py +126 -0
  51. package/bin/_lib_legacy_writer_evidence.py +136 -0
  52. package/bin/_lib_merge_gate.py +29 -302
  53. package/bin/_lib_meter_rate_change.py +10 -0
  54. package/bin/_lib_outcome_quarantine.py +690 -0
  55. package/bin/_lib_pricing.py +7 -2
  56. package/bin/_lib_quarantine.py +154 -0
  57. package/bin/_lib_quota_calibration.py +53 -8
  58. package/bin/_lib_quota_copy.py +2 -1
  59. package/bin/_lib_quota_model.py +288 -46
  60. package/bin/_lib_rederive.py +94 -5
  61. package/bin/_lib_segment_summary.py +35 -2
  62. package/bin/_lib_statusline_candidates.py +292 -23
  63. package/bin/cctally +23 -1
  64. package/package.json +14 -1
@@ -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
- suppressed_snapshot_ids = set()
1394
+ weekly_named: set = set()
1395
+ five_hour_named: set = set()
1338
1396
  for record in (*current.values(), *desired.values()):
1339
- suppression = (record.get("payload") or {}).get("suppression")
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
- suppressed_snapshot_ids.update(snapshot_ids & set(suppression))
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, snapshot_ids):
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, snapshot_ids):
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 = 3
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", "decoded_entry_count", "complete_prefix_sha256",
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))