cctally 1.115.0 → 1.116.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 (55) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/bin/_cctally_cache.py +85 -4
  3. package/bin/_cctally_completion.py +80 -12
  4. package/bin/_cctally_core.py +106 -9
  5. package/bin/_cctally_dashboard.py +6 -9
  6. package/bin/_cctally_dashboard_envelope.py +26 -32
  7. package/bin/_cctally_db.py +85 -4
  8. package/bin/_cctally_doctor.py +38 -5
  9. package/bin/_cctally_five_hour.py +83 -1017
  10. package/bin/_cctally_journal.py +1307 -180
  11. package/bin/_cctally_journal_repair.py +9 -7
  12. package/bin/_cctally_oauth_samples.py +280 -0
  13. package/bin/_cctally_parser.py +5 -0
  14. package/bin/_cctally_quarantine.py +14 -4
  15. package/bin/_cctally_quota.py +70 -8
  16. package/bin/_cctally_quota_model.py +35 -4
  17. package/bin/_cctally_record.py +414 -123
  18. package/bin/_cctally_rederive.py +632 -235
  19. package/bin/_cctally_refresh.py +29 -0
  20. package/bin/_cctally_statusline.py +984 -47
  21. package/bin/_cctally_store.py +972 -58
  22. package/bin/_cctally_tui.py +62 -7
  23. package/bin/_cctally_update.py +25 -0
  24. package/bin/_lib_account_protocol.py +201 -18
  25. package/bin/_lib_clone_copy.py +46 -11
  26. package/bin/_lib_dashboard_sources.py +57 -15
  27. package/bin/_lib_detector_access_registry.py +273 -52
  28. package/bin/_lib_doctor.py +302 -1
  29. package/bin/_lib_journal.py +145 -25
  30. package/bin/_lib_journal_router.py +166 -48
  31. package/bin/_lib_oauth_samples.py +238 -0
  32. package/bin/_lib_pricing_check.py +0 -12
  33. package/bin/_lib_provider_activity.py +770 -0
  34. package/bin/_lib_quarantine.py +3 -2
  35. package/bin/_lib_quota_model.py +96 -6
  36. package/bin/_lib_rebuild_progress.py +146 -0
  37. package/bin/_lib_record.py +22 -0
  38. package/bin/_lib_rederive.py +299 -3
  39. package/bin/_lib_snapshot_cache.py +43 -1
  40. package/bin/_lib_stale_proof.py +708 -0
  41. package/bin/_lib_statusline_candidates.py +65 -5
  42. package/bin/_lib_worker_launches.py +315 -0
  43. package/bin/cctally +6 -11
  44. package/dashboard/static/assets/{ConversationsView-Bc8LCOAZ.js → ConversationsView-Dflb_myA.js} +1 -1
  45. package/dashboard/static/assets/DoctorModal-DFX5CfIs.js +1 -0
  46. package/dashboard/static/assets/{ModalRoot-DLPgyAM_.js → ModalRoot-HlxoagIa.js} +1 -1
  47. package/dashboard/static/assets/{ProjectsDrillPanel-z53a4v4M.js → ProjectsDrillPanel-C3PJy1xx.js} +1 -1
  48. package/dashboard/static/assets/{SourceDetailModal-Bw0T8W-W.js → SourceDetailModal-BczsWKhD.js} +1 -1
  49. package/dashboard/static/assets/{UpdateModal-D5AKx4Yw.js → UpdateModal-Os-Lo5cs.js} +1 -1
  50. package/dashboard/static/assets/{index-BXDOzSng.js → index-CYZ3NLqF.js} +3 -3
  51. package/dashboard/static/assets/{outlineNavigation-JObY7cUY.js → outlineNavigation-k-zIif6W.js} +1 -1
  52. package/dashboard/static/dashboard.html +1 -1
  53. package/package.json +7 -2
  54. package/bin/_lib_merge_gate.py +0 -452
  55. package/dashboard/static/assets/DoctorModal-D--qVBwe.js +0 -1
@@ -72,6 +72,7 @@ from _cctally_core import (
72
72
  now_utc_iso,
73
73
  _iso_to_epoch,
74
74
  _format_short_duration,
75
+ _command_as_of,
75
76
  )
76
77
 
77
78
 
@@ -645,6 +646,8 @@ def _refresh_usage_inproc_locked(
645
646
 
646
647
  ``binding`` was captured together with ``token`` before the fetch. The
647
648
  response is revalidated against it before ANY mutation (spec 4.6)."""
649
+ # #797/#823/#840 spec 4.3 D8: `q`, taken immediately before the request.
650
+ request_started = _oauth_request_started_now(c)
648
651
  try:
649
652
  api = c._fetch_oauth_usage(token=token, timeout_seconds=timeout_seconds)
650
653
  except RefreshUsageRateLimitError as exc:
@@ -730,6 +733,9 @@ def _refresh_usage_inproc_locked(
730
733
  # This is the OAuth feeder — label the row "api" (spec §5). Without
731
734
  # this, cmd_record_usage would fall back to its "statusline" default.
732
735
  source="api",
736
+ # #797/#823/#840 spec 4.3 D8: this response's own request-start
737
+ # instant, carried with it and its captured binding to publication.
738
+ oauth_request_started_at=request_started,
733
739
  )
734
740
  authoritative = c._authoritative_record_usage(
735
741
  record_args,
@@ -974,6 +980,24 @@ def _revalidate_selected_credential(c, binding) -> bool:
974
980
  return current is not None and bool(token) and current == binding
975
981
 
976
982
 
983
+ def _oauth_request_started_now(c) -> str:
984
+ """#797/#823/#840 spec 4.3 D8: the request-start instant `q` of an OAuth
985
+ fetch — the wall clock immediately before `_fetch_oauth_usage` is called,
986
+ at full clock precision, as the raw key `oauth_request_started_at` records
987
+ it. The provider sampled the response at or after it (assumption A-Q).
988
+
989
+ It reads the same clock as the observation's capture stamp
990
+ (`record_usage_bound`): the wall clock, or the harness's pinned
991
+ `CCTALLY_AS_OF` under `CCTALLY_TEST_PIN_CAPTURE`, so `q` never follows the
992
+ capture of its own response."""
993
+ import _lib_oauth_samples
994
+ if os.environ.get("CCTALLY_TEST_PIN_CAPTURE"):
995
+ instant = _command_as_of()
996
+ else:
997
+ instant = dt.datetime.now(dt.timezone.utc)
998
+ return _lib_oauth_samples.format_request_started(instant)
999
+
1000
+
977
1001
  def _hook_tick_oauth_skip_status(c, *, throttle_seconds: float, binding) -> str | None:
978
1002
  """Return the automatic-refresh suppression reason under selected lock.
979
1003
  Repair need and selected freshness are the captured account's own."""
@@ -1080,6 +1104,10 @@ def _hook_tick_oauth_refresh(
1080
1104
  if skipped is not None:
1081
1105
  return skipped, None
1082
1106
 
1107
+ # #797/#823/#840 spec 4.3 D8: `q`, taken immediately before the request.
1108
+ # It travels only with this response: a failure or a discard below drops
1109
+ # both together.
1110
+ request_started = _oauth_request_started_now(c)
1083
1111
  try:
1084
1112
  api = c._fetch_oauth_usage(token=token, timeout_seconds=timeout_seconds)
1085
1113
  except RefreshUsageRateLimitError as exc:
@@ -1105,6 +1133,7 @@ def _hook_tick_oauth_refresh(
1105
1133
  if parsed is None:
1106
1134
  return "err(parse)", None
1107
1135
  record_args, axes, parts = parsed
1136
+ record_args.oauth_request_started_at = request_started
1108
1137
 
1109
1138
  # #583 S2: the nudge remains deferred until after the publication lock is
1110
1139
  # released. The sink contract is now enforced by the authority kernel, so