cctally 1.117.2 → 1.118.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 (58) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +7 -3
  3. package/bin/_cctally_cache.py +2783 -175
  4. package/bin/_cctally_completion.py +106 -33
  5. package/bin/_cctally_core.py +69 -4
  6. package/bin/_cctally_dashboard.py +188 -13
  7. package/bin/_cctally_dashboard_conversation.py +5 -1
  8. package/bin/_cctally_dashboard_envelope.py +50 -19
  9. package/bin/_cctally_dashboard_sources.py +23 -1
  10. package/bin/_cctally_db.py +193 -153
  11. package/bin/_cctally_diagnosis_sources.py +63 -7
  12. package/bin/_cctally_doctor.py +865 -93
  13. package/bin/_cctally_five_hour.py +67 -50
  14. package/bin/_cctally_forecast.py +35 -13
  15. package/bin/_cctally_journal.py +186 -18
  16. package/bin/_cctally_milestone_history.py +859 -62
  17. package/bin/_cctally_oauth_samples.py +286 -15
  18. package/bin/_cctally_parser.py +2 -2
  19. package/bin/_cctally_quota.py +104 -12
  20. package/bin/_cctally_record.py +153 -29
  21. package/bin/_cctally_rederive.py +608 -68
  22. package/bin/_cctally_statusline.py +108 -81
  23. package/bin/_cctally_store.py +179 -38
  24. package/bin/_cctally_tui.py +892 -181
  25. package/bin/_cctally_update.py +32 -13
  26. package/bin/_lib_codex_conversation_query.py +52 -14
  27. package/bin/_lib_codex_drain_admission.py +224 -6
  28. package/bin/_lib_codex_metadata.py +39 -0
  29. package/bin/_lib_conversation_anon.py +15 -8
  30. package/bin/_lib_conversation_dispatch.py +7 -0
  31. package/bin/_lib_conversation_query.py +289 -70
  32. package/bin/_lib_dashboard_sources.py +37 -1
  33. package/bin/_lib_detector_access_registry.py +497 -26
  34. package/bin/_lib_doctor.py +85 -20
  35. package/bin/_lib_doctor_scan_index.py +321 -0
  36. package/bin/_lib_ingest_frontier.py +20 -1
  37. package/bin/_lib_oauth_samples.py +23 -7
  38. package/bin/_lib_pricing.py +146 -16
  39. package/bin/_lib_quota.py +19 -0
  40. package/bin/_lib_rederive.py +11 -6
  41. package/bin/_lib_retained_records.py +516 -0
  42. package/bin/_lib_snapshot_cache.py +10 -0
  43. package/bin/_lib_stale_proof.py +272 -43
  44. package/bin/_lib_stats_content_revision.py +831 -0
  45. package/bin/_lib_write_io.py +11 -4
  46. package/bin/cctally +4 -1
  47. package/dashboard/static/assets/ConversationsView-D4Iv4ku0.js +72 -0
  48. package/dashboard/static/assets/{DoctorModal-B0DnU9d9.js → DoctorModal-D4QSq6uz.js} +1 -1
  49. package/dashboard/static/assets/ModalRoot-C9F5dhZi.js +1 -0
  50. package/dashboard/static/assets/{ProjectsDrillPanel-DSxXQSa-.js → ProjectsDrillPanel-WxAdRDMt.js} +1 -1
  51. package/dashboard/static/assets/{SourceDetailModal-BleDpQqG.js → SourceDetailModal-NyyEfXmc.js} +1 -1
  52. package/dashboard/static/assets/{UpdateModal-CMqaAkSK.js → UpdateModal-CFLq9pT1.js} +1 -1
  53. package/dashboard/static/assets/{index-BjQV-0-D.js → index-BKTWH9JE.js} +2 -2
  54. package/dashboard/static/assets/{outlineNavigation-CO-VWBm7.js → outlineNavigation-BnGUwCS3.js} +3 -3
  55. package/dashboard/static/dashboard.html +1 -1
  56. package/package.json +4 -1
  57. package/dashboard/static/assets/ConversationsView-oqNsWzWV.js +0 -72
  58. package/dashboard/static/assets/ModalRoot-CYua2wP_.js +0 -1
@@ -31,7 +31,9 @@ may be eligible for one direction and refused for the other.
31
31
  """
32
32
  from __future__ import annotations
33
33
 
34
+ import bisect
34
35
  import dataclasses
36
+ import functools
35
37
  import hashlib
36
38
  import json
37
39
  from typing import Callable, Collection, Iterable, Literal, Mapping, Sequence
@@ -64,10 +66,12 @@ class MalformedReceipt(ValueError):
64
66
  # --- instants --------------------------------------------------------------------
65
67
 
66
68
 
67
- def _interval(text: str | None) -> tuple[int, int] | None:
68
- """The ``[start, end)`` nanosecond interval a canonical instant denotes."""
69
- if not isinstance(text, str):
70
- return None
69
+ #: #1064 (spec 5.1.1): the bound of each scalar memo below, sized for a
70
+ #: week's provider samples (two instants each) with headroom.
71
+ _MEMO_SIZE = 65536
72
+
73
+
74
+ def _parse_interval(text: str) -> tuple[int, int] | None:
71
75
  try:
72
76
  start = _activity.instant_key(text)
73
77
  digits = _activity.instant_precision(text)
@@ -76,6 +80,22 @@ def _interval(text: str | None) -> tuple[int, int] | None:
76
80
  return start, start + 10 ** (9 - min(digits, 9))
77
81
 
78
82
 
83
+ _memo_interval = functools.lru_cache(maxsize=_MEMO_SIZE)(_parse_interval)
84
+
85
+
86
+ def _interval(text: str | None) -> tuple[int, int] | None:
87
+ """The ``[start, end)`` nanosecond interval a canonical instant denotes.
88
+
89
+ #1064: a pure function of the text, memoized for exact ``str`` values
90
+ only (a subclass is parsed uncached, so only plain text keys the
91
+ memo)."""
92
+ if not isinstance(text, str):
93
+ return None
94
+ if type(text) is str:
95
+ return _memo_interval(text)
96
+ return _parse_interval(text)
97
+
98
+
79
99
  def after(a: str | None, b: str | None) -> bool:
80
100
  """Whether instant ``a`` is proven to follow instant ``b``."""
81
101
  ia, ib = _interval(a), _interval(b)
@@ -220,15 +240,33 @@ def _window_text(window: int) -> str:
220
240
  return _samples.normalize_weekly_reset(int(window))
221
241
 
222
242
 
223
- def _sample_window(sample) -> str | None:
224
- if sample.weekly_resets_at_utc is None:
225
- return None
243
+ def _parse_window_identity(value) -> str | None:
226
244
  try:
227
- return _samples.weekly_window(sample.weekly_resets_at_utc)
245
+ return _samples.weekly_window(value)
228
246
  except ValueError:
229
247
  return None
230
248
 
231
249
 
250
+ _memo_window_identity = functools.lru_cache(maxsize=_MEMO_SIZE)(_parse_window_identity)
251
+
252
+
253
+ def _window_identity(value) -> str | None:
254
+ """The weekly window identity of a raw ``weekly_resets_at_utc`` value, or
255
+ None where `weekly_window` refuses it. #1064: memoized for exact ``str``
256
+ and ``int`` values (a bool, a subclass or another type is resolved
257
+ uncached, so ``True`` never shares ``1``'s entry); any other exception
258
+ propagates, uncached, exactly as before."""
259
+ if value is None:
260
+ return None
261
+ if type(value) is str or type(value) is int:
262
+ return _memo_window_identity(value)
263
+ return _parse_window_identity(value)
264
+
265
+
266
+ def _sample_window(sample) -> str | None:
267
+ return _window_identity(sample.weekly_resets_at_utc)
268
+
269
+
232
270
  def _in_window(samples: Iterable, window: int) -> list:
233
271
  text = _window_text(window)
234
272
  return [s for s in samples
@@ -260,6 +298,136 @@ def _latest(samples: Iterable):
260
298
  return best
261
299
 
262
300
 
301
+ # --- the prepared sample view (#1064) ------------------------------------------------
302
+
303
+
304
+ class _Window:
305
+ """One ``(axis, window)`` of a `PreparedSamples`: the positions of its
306
+ entries in original order and, built on first use, the positions sorted
307
+ by `_latest`'s order with their order keys (False when the identities do
308
+ not sort) and the entries' axis values (False when one is not a plain
309
+ number)."""
310
+
311
+ __slots__ = ("positions", "ordered", "keys", "values")
312
+
313
+ def __init__(self, positions: list):
314
+ self.positions = positions
315
+ self.ordered = None
316
+ self.keys = None
317
+ self.values = None
318
+
319
+
320
+ class PreparedSamples:
321
+ """One snapshot's provider samples, prepared once for every kernel call
322
+ that reads them (#1064 spec 5.1.2).
323
+
324
+ It keeps every original sample object in its original order (``iter``
325
+ yields them; nothing is deduplicated) with its request and capture
326
+ intervals and its `_latest` order key, and memoizes per ``(axis,
327
+ window)`` the in-window entries, computed on first use with the very
328
+ expressions `_in_window` and `_five_in_window` evaluate, so a conversion
329
+ error surfaces at the reader call that evaluates that window, as before.
330
+ It caches no fact, proof, receipt or compacted set."""
331
+
332
+ __slots__ = ("_samples", "_request", "_capture", "_order", "_windows")
333
+
334
+ def __init__(self, samples: Iterable):
335
+ self._samples = tuple(samples)
336
+ self._request = [_interval(s.requested_at_utc) for s in self._samples]
337
+ self._capture = [_interval(s.captured_at_utc) for s in self._samples]
338
+ self._order = [-1 if r is None else r[0] for r in self._request]
339
+ self._windows: dict = {}
340
+
341
+ def __iter__(self):
342
+ return iter(self._samples)
343
+
344
+ def __len__(self) -> int:
345
+ return len(self._samples)
346
+
347
+ def _window(self, axis: str, window) -> _Window:
348
+ """The window `_axis_window` selects, memoized by its identity: the
349
+ normalized reset (weekly) or the exact ``int`` key (five-hour; any
350
+ other key type is filtered afresh on every call)."""
351
+ if axis == FIVE_HOUR:
352
+ if type(window) is not int:
353
+ return _Window(self._five_positions(window))
354
+ key = (FIVE_HOUR, window)
355
+ else:
356
+ key = (WEEKLY, _window_text(window))
357
+ found = self._windows.get(key)
358
+ if found is None:
359
+ positions = (self._five_positions(window) if axis == FIVE_HOUR
360
+ else self._weekly_positions(key[1]))
361
+ found = self._windows[key] = _Window(positions)
362
+ return found
363
+
364
+ def _weekly_positions(self, text: str) -> list:
365
+ return [i for i, s in enumerate(self._samples)
366
+ if s.weekly_percent is not None and _sample_window(s) == text]
367
+
368
+ def _five_positions(self, window) -> list:
369
+ return [i for i, s in enumerate(self._samples)
370
+ if s.five_hour_percent is not None and s.five_hour_window_key is not None
371
+ and int(s.five_hour_window_key) == int(window)]
372
+
373
+ def _ordered(self, w: _Window):
374
+ """``w``'s positions sorted by ``(order key, observation_id, -position)``:
375
+ scanned from its end, the first entry that qualifies is `_latest` of
376
+ the qualifying entries (its strict ``>`` keeps the earliest original
377
+ position of a complete-key tie). False when the identities do not
378
+ sort; the caller then evaluates today's expressions instead."""
379
+ if w.ordered is None:
380
+ try:
381
+ w.ordered = sorted(w.positions, key=lambda i: (
382
+ self._order[i], self._samples[i].observation_id, -i))
383
+ except Exception: # noqa: BLE001 - the unprepared path decides
384
+ w.ordered = False
385
+ else:
386
+ w.keys = [self._order[i] for i in w.ordered]
387
+ return w.ordered
388
+
389
+ def _proof_values(self, w: _Window, axis: str):
390
+ """``w``'s axis values by position when a proof may scan the sorted
391
+ entries (every value a plain number, so ``float`` cannot raise and
392
+ evaluation order is immaterial), else None."""
393
+ if self._ordered(w) is False:
394
+ return None
395
+ if w.values is None:
396
+ values = {}
397
+ for i in w.positions:
398
+ s = self._samples[i]
399
+ raw = s.five_hour_percent if axis == FIVE_HOUR else s.weekly_percent
400
+ if type(raw) not in (float, int, bool):
401
+ values = False
402
+ break
403
+ try:
404
+ values[i] = float(raw)
405
+ except OverflowError:
406
+ values = False
407
+ break
408
+ w.values = values
409
+ return None if w.values is False else w.values
410
+
411
+ def _latest_requested_after(self, w: _Window, bound_end: int, accept):
412
+ """`_latest` of ``w``'s entries whose request starts at or after
413
+ ``bound_end`` (an unparsable request never does) and that ``accept``
414
+ takes: the sorted entries from the bound on, scanned from the end."""
415
+ ordered, request = w.ordered, self._request
416
+ start = bisect.bisect_left(w.keys, bound_end)
417
+ for idx in range(len(ordered) - 1, start - 1, -1):
418
+ i = ordered[idx]
419
+ r = request[i]
420
+ if r is not None and r[0] >= bound_end and accept(i):
421
+ return self._samples[i]
422
+ return None
423
+
424
+
425
+ def prepare(samples) -> PreparedSamples:
426
+ """``samples`` as a `PreparedSamples`: itself when it already is one,
427
+ else a new view of it (the per-call fallback of every reader)."""
428
+ return samples if isinstance(samples, PreparedSamples) else PreparedSamples(samples)
429
+
430
+
263
431
  # --- the proof -----------------------------------------------------------------------
264
432
 
265
433
 
@@ -276,14 +444,31 @@ def proven_stale(sigma: SampleIdentity, samples: Iterable, *, after_utc: str,
276
444
  if sigma.axis not in AXES:
277
445
  return None
278
446
  reset = sigma.window if advertised_reset_epoch is None else int(advertised_reset_epoch)
279
- proving = [
280
- s for s in _axis_window(samples, sigma.axis, sigma.window)
281
- if _axis_value(s, sigma.axis) < sigma.percent - TOLERANCE_PP
282
- and after(s.requested_at_utc, sigma.activity_at_utc)
283
- and after(s.requested_at_utc, after_utc)
284
- and _before_epoch(s.captured_at_utc, reset)
285
- ]
286
- return _latest(proving)
447
+ view = prepare(samples)
448
+ w = view._window(sigma.axis, sigma.window)
449
+ values = view._proof_values(w, sigma.axis)
450
+ if values is None:
451
+ proving = [
452
+ s for s in (view._samples[i] for i in w.positions)
453
+ if _axis_value(s, sigma.axis) < sigma.percent - TOLERANCE_PP
454
+ and after(s.requested_at_utc, sigma.activity_at_utc)
455
+ and after(s.requested_at_utc, after_utc)
456
+ and _before_epoch(s.captured_at_utc, reset)
457
+ ]
458
+ return _latest(proving)
459
+ if not w.positions:
460
+ return None
461
+ limit = sigma.percent - TOLERANCE_PP
462
+ t_m, bound = _interval(sigma.activity_at_utc), _interval(after_utc)
463
+ if t_m is None or bound is None:
464
+ return None
465
+ reset_ns, capture = reset * _NS, view._capture
466
+
467
+ def accept(i):
468
+ c = capture[i]
469
+ return values[i] < limit and c is not None and c[1] <= reset_ns
470
+
471
+ return view._latest_requested_after(w, max(t_m[1], bound[1]), accept)
287
472
 
288
473
 
289
474
  def proven_stale_low(bound_utc: str, samples: Iterable, *, axis: str, window: int,
@@ -302,13 +487,27 @@ def proven_stale_low(bound_utc: str, samples: Iterable, *, axis: str, window: in
302
487
  if axis not in AXES or _interval(bound_utc) is None:
303
488
  return None
304
489
  reset = int(window) if advertised_reset_epoch is None else int(advertised_reset_epoch)
305
- proving = [
306
- s for s in _axis_window(samples, axis, window)
307
- if _axis_value(s, axis) > float(percent) + TOLERANCE_PP
308
- and after(s.requested_at_utc, bound_utc)
309
- and _before_epoch(s.captured_at_utc, reset)
310
- ]
311
- return _latest(proving)
490
+ view = prepare(samples)
491
+ w = view._window(axis, window)
492
+ values = view._proof_values(w, axis)
493
+ if values is None:
494
+ proving = [
495
+ s for s in (view._samples[i] for i in w.positions)
496
+ if _axis_value(s, axis) > float(percent) + TOLERANCE_PP
497
+ and after(s.requested_at_utc, bound_utc)
498
+ and _before_epoch(s.captured_at_utc, reset)
499
+ ]
500
+ return _latest(proving)
501
+ if not w.positions:
502
+ return None
503
+ limit = float(percent) + TOLERANCE_PP
504
+ reset_ns, capture = reset * _NS, view._capture
505
+
506
+ def accept(i):
507
+ c = capture[i]
508
+ return values[i] > limit and c is not None and c[1] <= reset_ns
509
+
510
+ return view._latest_requested_after(w, _interval(bound_utc)[1], accept)
312
511
 
313
512
 
314
513
  # --- accounting ------------------------------------------------------------------------
@@ -384,12 +583,29 @@ def earn_receipts(members: Sequence[Member], accounted: Accounted, samples: Iter
384
583
  no provider samples."""
385
584
  if accounted.axis not in AXES or accounted.percent is None:
386
585
  return ()
387
- segment = [s for s in _in_window(samples, accounted.window)
388
- if accounted.floor_epoch is None
389
- or _at_or_after_epoch(s.captured_at_utc, accounted.floor_epoch)]
390
- generation = tuple((c.event_id, c.event_revision) for c in accounted.credits
391
- if c.axis == accounted.axis)
392
- latest = _latest(segment)
586
+ view = prepare(samples)
587
+ w = view._window(WEEKLY, accounted.window)
588
+ ordered = view._ordered(w)
589
+ if ordered is False:
590
+ segment = [s for s in (view._samples[i] for i in w.positions)
591
+ if accounted.floor_epoch is None
592
+ or _at_or_after_epoch(s.captured_at_utc, accounted.floor_epoch)]
593
+ generation = tuple((c.event_id, c.event_revision) for c in accounted.credits
594
+ if c.axis == accounted.axis)
595
+ latest = _latest(segment)
596
+ else:
597
+ # `_latest` of the segment: the sorted window scanned from its end
598
+ # to the first entry captured at or after the floor.
599
+ latest = None
600
+ floor, capture = accounted.floor_epoch, view._capture
601
+ for idx in range(len(ordered) - 1, -1, -1):
602
+ i = ordered[idx]
603
+ c = capture[i]
604
+ if floor is None or (c is not None and c[0] >= floor * _NS):
605
+ latest = view._samples[i]
606
+ break
607
+ generation = tuple((c.event_id, c.event_revision) for c in accounted.credits
608
+ if c.axis == accounted.axis)
393
609
  earned = []
394
610
  for m in members:
395
611
  s = m.sigma
@@ -503,7 +719,7 @@ def refusal_facts(axis: str, members: Sequence[Member], samples: Iterable,
503
719
  fields of `ALL_ELIGIBLE`."""
504
720
  if axis not in AXES:
505
721
  return {}
506
- samples = list(samples)
722
+ samples = prepare(samples)
507
723
  transitions = list(transitions)
508
724
  by_sigma: dict[str, list[Receipt]] = {}
509
725
  for r in receipts:
@@ -653,21 +869,34 @@ def compact_samples(samples: Iterable, *, window: int,
653
869
  ``axis`` selects the window identity and value (five-hour: the canonical
654
870
  key and the raw five-hour percent)."""
655
871
  margin = int(window) - _RESET_MARGIN_SECONDS
656
- keep: dict[str, object] = {}
657
- latest: dict[float, object] = {}
658
- latest_floor: dict[float, object] = {}
659
- for s in _axis_window(samples, axis, window):
660
- if not _before_epoch(s.captured_at_utc, margin):
661
- keep[s.observation_id] = s
872
+ view = prepare(samples)
873
+ items, order, capture = view._samples, view._order, view._capture
874
+ # #1064: the same sequential per-value algorithm over the prepared
875
+ # positions; `_latest` of two is "the later one replaces the earlier
876
+ # only when its (order key, observation_id) is strictly greater".
877
+ keep: dict[str, int] = {}
878
+ latest: dict[float, int] = {}
879
+ latest_floor: dict[float, int] = {}
880
+
881
+ def later(prev, i) -> int:
882
+ if prev is None or (order[i], items[i].observation_id) > (
883
+ order[prev], items[prev].observation_id):
884
+ return i
885
+ return prev
886
+
887
+ for i in view._window(axis, window).positions:
888
+ s, c = items[i], capture[i]
889
+ if not (c is not None and c[1] <= margin * _NS):
890
+ keep[s.observation_id] = i
662
891
  continue
663
892
  v = _axis_value(s, axis)
664
- latest[v] = _latest([x for x in (latest.get(v), s) if x is not None])
665
- if floor_epoch is None or _at_or_after_epoch(s.captured_at_utc, floor_epoch):
666
- latest_floor[v] = _latest([x for x in (latest_floor.get(v), s) if x is not None])
667
- for s in [*latest.values(), *latest_floor.values()]:
668
- keep[s.observation_id] = s
669
- return tuple(sorted(keep.values(),
670
- key=lambda s: (_instant_order(s.requested_at_utc), s.observation_id)))
893
+ latest[v] = later(latest.get(v), i)
894
+ if floor_epoch is None or (c is not None and c[0] >= floor_epoch * _NS):
895
+ latest_floor[v] = later(latest_floor.get(v), i)
896
+ for i in [*latest.values(), *latest_floor.values()]:
897
+ keep[items[i].observation_id] = i
898
+ return tuple(items[i] for i in sorted(
899
+ keep.values(), key=lambda i: (order[i], items[i].observation_id)))
671
900
 
672
901
 
673
902
  # --- codecs ------------------------------------------------------------------------------------