cctally 1.116.1 → 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.
Files changed (76) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +4 -6
  3. package/bin/_cctally_account.py +57 -3
  4. package/bin/_cctally_cache.py +5720 -885
  5. package/bin/_cctally_claude_attribution.py +997 -0
  6. package/bin/_cctally_completion.py +101 -28
  7. package/bin/_cctally_core.py +235 -12
  8. package/bin/_cctally_dashboard.py +263 -17
  9. package/bin/_cctally_dashboard_conversation.py +107 -21
  10. package/bin/_cctally_dashboard_envelope.py +44 -0
  11. package/bin/_cctally_db.py +928 -80
  12. package/bin/_cctally_diagnosis_sources.py +78 -13
  13. package/bin/_cctally_doctor.py +211 -86
  14. package/bin/_cctally_five_hour.py +446 -22
  15. package/bin/_cctally_forecast.py +302 -6
  16. package/bin/_cctally_journal.py +2418 -158
  17. package/bin/_cctally_milestone_history.py +379 -91
  18. package/bin/_cctally_oauth_samples.py +185 -7
  19. package/bin/_cctally_parser.py +38 -0
  20. package/bin/_cctally_project.py +34 -8
  21. package/bin/_cctally_quarantine.py +821 -343
  22. package/bin/_cctally_quota.py +69 -4
  23. package/bin/_cctally_quota_calibration.py +677 -3
  24. package/bin/_cctally_quota_model.py +927 -127
  25. package/bin/_cctally_record.py +922 -445
  26. package/bin/_cctally_rederive.py +119 -15
  27. package/bin/_cctally_statusline.py +1048 -176
  28. package/bin/_cctally_store.py +544 -110
  29. package/bin/_cctally_tui.py +455 -42
  30. package/bin/_cctally_update.py +77 -14
  31. package/bin/_lib_account_protocol.py +227 -19
  32. package/bin/_lib_claude_attribution.py +421 -0
  33. package/bin/_lib_clone_copy.py +30 -0
  34. package/bin/_lib_codex_conversation_query.py +45 -9
  35. package/bin/_lib_codex_conversation_watch.py +72 -13
  36. package/bin/_lib_codex_rollout_container.py +234 -19
  37. package/bin/_lib_codex_worker_lifecycle.py +374 -0
  38. package/bin/_lib_credit.py +22 -13
  39. package/bin/_lib_dashboard_sources.py +257 -18
  40. package/bin/_lib_detector_access_registry.py +1335 -166
  41. package/bin/_lib_doctor.py +276 -92
  42. package/bin/_lib_ingest_frontier.py +269 -36
  43. package/bin/_lib_journal.py +273 -22
  44. package/bin/_lib_jsonl.py +12 -0
  45. package/bin/_lib_meter_rate_change.py +72 -0
  46. package/bin/_lib_outcome_quarantine.py +917 -554
  47. package/bin/_lib_provider_activity.py +24 -6
  48. package/bin/_lib_quota_calibration.py +57 -3
  49. package/bin/_lib_quota_copy.py +7 -2
  50. package/bin/_lib_quota_lineage.py +613 -0
  51. package/bin/_lib_quota_model.py +464 -33
  52. package/bin/_lib_rate_change_delivery.py +16 -0
  53. package/bin/_lib_reclaim_planner.py +6 -4
  54. package/bin/_lib_rederive.py +2046 -21
  55. package/bin/_lib_selector_state.py +131 -3
  56. package/bin/_lib_snapshot_cache.py +8 -0
  57. package/bin/_lib_stale_proof.py +113 -4
  58. package/bin/_lib_statusline.py +72 -3
  59. package/bin/_lib_statusline_candidates.py +246 -42
  60. package/bin/_lib_tick_stats.py +10 -5
  61. package/bin/_lib_write_io.py +419 -6
  62. package/bin/cctally +28 -11
  63. package/dashboard/static/assets/{ConversationsView-Dflb_myA.js → ConversationsView-oqNsWzWV.js} +1 -1
  64. package/dashboard/static/assets/{DoctorModal-DFX5CfIs.js → DoctorModal-B0DnU9d9.js} +1 -1
  65. package/dashboard/static/assets/ModalRoot-CYua2wP_.js +1 -0
  66. package/dashboard/static/assets/{ProjectsDrillPanel-C3PJy1xx.js → ProjectsDrillPanel-DSxXQSa-.js} +1 -1
  67. package/dashboard/static/assets/{SourceDetailModal-BczsWKhD.js → SourceDetailModal-BleDpQqG.js} +1 -1
  68. package/dashboard/static/assets/{UpdateModal-Os-Lo5cs.js → UpdateModal-CMqaAkSK.js} +1 -1
  69. package/dashboard/static/assets/{index-CYZ3NLqF.js → index-BjQV-0-D.js} +3 -3
  70. package/dashboard/static/assets/index-D34eYhAc.css +1 -0
  71. package/dashboard/static/assets/{outlineNavigation-k-zIif6W.js → outlineNavigation-CO-VWBm7.js} +1 -1
  72. package/dashboard/static/assets/{useKeymap-CW7Up0Ay.js → useKeymap-CUgEnnua.js} +1 -1
  73. package/dashboard/static/dashboard.html +3 -3
  74. package/package.json +5 -1
  75. package/dashboard/static/assets/ModalRoot-HlxoagIa.js +0 -1
  76. package/dashboard/static/assets/index-BKITk1CT.css +0 -1
@@ -627,15 +627,23 @@ def _witness(status, instant, reason, total, examined, digest) -> ActivityWitnes
627
627
  files_total=total, files_examined=examined, digest=digest)
628
628
 
629
629
 
630
- def capture_activity(transcript_path: str | None, *, cache_path: pathlib.Path) -> ActivityWitness:
630
+ def capture_activity(transcript_path: str | None, *, cache_path: pathlib.Path,
631
+ seed_path: pathlib.Path | None = None) -> ActivityWitness:
631
632
  """Capture one arrival's provider-activity evidence (spec 4.1). Called
632
633
  once per arrival, before the intake commit and outside the intake
633
634
  critical section; never raises for anything a transcript or the cache
634
- can contain."""
635
+ can contain.
636
+
637
+ ``seed_path`` is the starting state of an absent cache (#1019 C4): the
638
+ same contributor's document in a previous spool, read without its lock
639
+ and never written. Its records are keyed by file incarnation, so a seed
640
+ is as valid as a document this arrival would write; an unreadable or
641
+ invalid seed is an ordinary first arrival."""
635
642
  if not isinstance(transcript_path, str) or not transcript_path:
636
643
  return _witness("unknown", None, "no-transcript-path", 0, 0, _digest([], True, True))
637
644
  try:
638
- return _capture_locked(transcript_path, pathlib.Path(cache_path))
645
+ return _capture_locked(transcript_path, pathlib.Path(cache_path),
646
+ None if seed_path is None else pathlib.Path(seed_path))
639
647
  except Exception:
640
648
  # Every transcript and cache condition is classified above; anything
641
649
  # else is still an explicit, eligible unknown capture, never a
@@ -643,18 +651,28 @@ def capture_activity(transcript_path: str | None, *, cache_path: pathlib.Path) -
643
651
  return _witness("unknown", None, "unreadable", 0, 0, _digest([], False, False))
644
652
 
645
653
 
646
- def _capture_locked(transcript_path: str, cache_path: pathlib.Path) -> ActivityWitness:
654
+ def _capture_locked(transcript_path: str, cache_path: pathlib.Path,
655
+ seed_path: pathlib.Path | None) -> ActivityWitness:
647
656
  lock = _lock_cache(cache_path)
648
657
  try:
658
+ seeded = False
649
659
  if lock is None:
650
660
  cache, cache_valid, contended = _Cache(), True, True
651
661
  else:
652
- (cache, cache_valid), contended = _load_cache(cache_path), False
662
+ contended = False
663
+ # Under the lock no other arrival writes this cache, so an absent
664
+ # document stays absent until this arrival stores one.
665
+ seeded = seed_path is not None and not os.path.lexists(cache_path)
666
+ if seeded:
667
+ seed, seed_valid = _load_cache(seed_path)
668
+ cache, cache_valid = (seed if seed_valid else _Cache()), True
669
+ else:
670
+ cache, cache_valid = _load_cache(cache_path)
653
671
  before = None if contended else _cache_document(cache)
654
672
  result = _capture(transcript_path, cache, cache_valid=cache_valid)
655
673
  if lock is not None:
656
674
  after = _cache_document(cache)
657
- if after != before or not cache_valid:
675
+ if after != before or not cache_valid or seeded:
658
676
  _store_cache(cache_path, after)
659
677
  return result
660
678
  finally:
@@ -48,6 +48,16 @@ class RegimeRejection(str, enum.Enum):
48
48
  #: gate. The mark is S1 stating that the regime must not be used to
49
49
  #: predict, so it is refused here rather than applied.
50
50
  DETECTION_ONLY = "calibration-detection-only"
51
+ #: #1015 spec 5.4: the open regime was fitted before a reviewed
52
+ #: correction to its inputs was materialized (its lineage assesses
53
+ #: `tainted`, or a sweep already annotated it). Wire code
54
+ #: `recalibration-needed`.
55
+ RECALIBRATION_NEEDED = "recalibration-needed"
56
+ #: #1015 spec 5.4: the regime's correction lineage could not be read
57
+ #: (no published facts, a missing projection, a read error) or holds an
58
+ #: unresolved reference. Never an unverified calibrated number; the wire
59
+ #: states `unavailable`.
60
+ LINEAGE_UNAVAILABLE = "calibration-lineage-unavailable"
51
61
 
52
62
 
53
63
  #: The qualification S1 writes onto a successor fit that is below its
@@ -96,6 +106,12 @@ class ValidatedRegime:
96
106
  #: member of `qm.COEFFICIENT_ERAS` (#978).
97
107
  coefficient_era: str
98
108
  qualifications: tuple = ()
109
+ #: The fit-weighted family composition of the days the regime's fit was
110
+ #: taken over (#991 spec 4.5), the reference the evidence-bounded family
111
+ #: test measures a consumer population against. None when the stored
112
+ #: record carries no valid one, which disables that test for the regime
113
+ #: and leaves the radius rule alone deciding.
114
+ fit_family_shares: "dict | None" = None
99
115
 
100
116
 
101
117
  @dataclasses.dataclass(frozen=True)
@@ -162,6 +178,36 @@ def _shares(value) -> "dict | None":
162
178
  return out
163
179
 
164
180
 
181
+ def _fit_reference_shares(value) -> "dict | None":
182
+ """A stored `fitFamilyShares`, or None when it is unusable (#991).
183
+
184
+ Stricter than `_shares`, which accepts `{}`, negative shares and any sum
185
+ and stays as it is for the TV references: a non-empty dict of string keys
186
+ and finite, non-negative numbers whose `math.fsum` is within 1e-9 of 1,
187
+ returned exactly as stored, never renormalized or rounded. Anything else
188
+ is None, not a rejection, because the regime's fit and its TV references
189
+ remain usable.
190
+ """
191
+ if not isinstance(value, dict) or not value:
192
+ return None
193
+ out = {}
194
+ for name, share in value.items():
195
+ if not isinstance(name, str) or isinstance(share, bool) \
196
+ or not isinstance(share, (int, float)) \
197
+ or not _finite(share) or share < 0.0:
198
+ return None
199
+ out[name] = float(share)
200
+ try:
201
+ total = math.fsum(out[k] for k in sorted(out))
202
+ except OverflowError:
203
+ # Finite shares whose sum leaves the float range are no
204
+ # distribution; raising would take the whole calibration with it.
205
+ return None
206
+ if abs(total - 1.0) > 1e-9:
207
+ return None
208
+ return out
209
+
210
+
165
211
  def _optional_radius(value):
166
212
  """`(ok, radius)` — a radius is absent, or a finite non-negative float."""
167
213
  if value is None:
@@ -272,6 +318,7 @@ def validate_regime(raw, *, account_key, expected_fingerprint,
272
318
  class_radius=class_radius,
273
319
  coefficient_era=era.id,
274
320
  qualifications=marks,
321
+ fit_family_shares=_fit_reference_shares(raw.get("fitFamilyShares")),
275
322
  )
276
323
 
277
324
 
@@ -338,9 +385,16 @@ def apply_regime(regime: ValidatedRegime, entries, *, interval
338
385
  if aggregated is None:
339
386
  return ApplyRejection.EMPTY_POPULATION
340
387
  family_shares, class_shares = aggregated
341
- if not qm.is_supported(family_shares, class_shares,
342
- regime.family_shares, regime.family_radius,
343
- regime.class_shares, regime.class_radius):
388
+ # #991 spec 4.6: the same evaluator as the forecast probe, against the
389
+ # regime's OWN fit-weighted family composition and era.
390
+ support = qm.evaluate_prediction_support(
391
+ family_shares, class_shares,
392
+ family_centre=regime.family_shares,
393
+ family_radius=regime.family_radius,
394
+ class_centre=regime.class_shares,
395
+ class_radius=regime.class_radius,
396
+ fit_family_shares=regime.fit_family_shares, era=era)
397
+ if not support.supported:
344
398
  return ApplyRejection.UNSUPPORTED_COMPOSITION
345
399
 
346
400
  units = population_units(entries, era=era)
@@ -1,6 +1,7 @@
1
1
  """#661 S2 Task D1 — the ONE cause-code copy table (spec section 8).
2
2
 
3
- S1 publishes fifteen withheld-figure codes in `EVIDENCE_CODES`. Section 8
3
+ S1 publishes fifteen withheld-figure codes in `EVIDENCE_CODES`, and #1015
4
+ adds a sixteenth, `recalibration-needed`. Section 8
4
5
  renders them through one table in two registers, and this module is that
5
6
  table.
6
7
 
@@ -27,7 +28,7 @@ matters because a dashboard tab can outlive a server restart through
27
28
  `execvp` and will meet an older server's envelope.
28
29
 
29
30
  Pure: no I/O, no clock, no store. The cause vocabulary is the closed
30
- 15-member `EVIDENCE_CODES` union, and the separate basis vocabulary names
31
+ 16-member `EVIDENCE_CODES` union, and the separate basis vocabulary names
31
32
  which measurement produced a projection. They share this rendering boundary
32
33
  but never one semantic union: a basis is not a withholding cause.
33
34
  """
@@ -79,6 +80,10 @@ _LONG_FORM: dict = {
79
80
  "the stored calibration came from a newer cctally than this one",
80
81
  "unavailable":
81
82
  "no usable calibration could be read for this account",
83
+ # --- #1015: the stored calibration's own refusal (U3, spec 2) ---
84
+ "recalibration-needed":
85
+ "the stored calibration was fitted before a correction to its "
86
+ "inputs; run cctally quota to recalibrate",
82
87
  }
83
88
 
84
89
  #: Projection-basis copy in the same two registers, deliberately separate