cctally 1.113.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 (97) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +4 -6
  3. package/bin/_cctally_account.py +32 -8
  4. package/bin/_cctally_cache.py +6035 -722
  5. package/bin/_cctally_completion.py +1287 -0
  6. package/bin/_cctally_config.py +37 -0
  7. package/bin/_cctally_core.py +400 -2
  8. package/bin/_cctally_dashboard.py +98 -20
  9. package/bin/_cctally_dashboard_conversation.py +292 -23
  10. package/bin/_cctally_dashboard_envelope.py +16 -3
  11. package/bin/_cctally_dashboard_perf.py +128 -9
  12. package/bin/_cctally_dashboard_sources.py +285 -213
  13. package/bin/_cctally_db.py +1177 -23
  14. package/bin/_cctally_diagnosis_sources.py +22 -4
  15. package/bin/_cctally_doctor.py +572 -45
  16. package/bin/_cctally_five_hour.py +84 -33
  17. package/bin/_cctally_forecast.py +108 -15
  18. package/bin/_cctally_journal.py +3950 -116
  19. package/bin/_cctally_journal_repair.py +101 -12
  20. package/bin/_cctally_milestone_history.py +82 -9
  21. package/bin/_cctally_parser.py +42 -0
  22. package/bin/_cctally_percent_breakdown.py +3 -2
  23. package/bin/_cctally_project.py +8 -2
  24. package/bin/_cctally_quarantine.py +652 -0
  25. package/bin/_cctally_quota.py +227 -37
  26. package/bin/_cctally_quota_model.py +360 -83
  27. package/bin/_cctally_record.py +691 -149
  28. package/bin/_cctally_rederive.py +320 -41
  29. package/bin/_cctally_refresh.py +84 -13
  30. package/bin/_cctally_source_analytics.py +83 -17
  31. package/bin/_cctally_statusline.py +6154 -517
  32. package/bin/_cctally_store.py +367 -53
  33. package/bin/_cctally_transcript.py +8 -7
  34. package/bin/_cctally_tui.py +61 -56
  35. package/bin/_cctally_weekrefs.py +128 -21
  36. package/bin/_lib_account_binding.py +152 -0
  37. package/bin/_lib_account_protocol.py +386 -0
  38. package/bin/_lib_aggregators.py +4 -0
  39. package/bin/_lib_cache_coverage.py +13 -1
  40. package/bin/_lib_cache_report.py +35 -6
  41. package/bin/_lib_cache_writer_lock.py +19 -3
  42. package/bin/_lib_clone_copy.py +142 -0
  43. package/bin/_lib_codex_conversation.py +32 -0
  44. package/bin/_lib_codex_conversation_query.py +297 -49
  45. package/bin/_lib_codex_conversation_watch.py +59 -14
  46. package/bin/_lib_codex_drain_admission.py +214 -0
  47. package/bin/_lib_codex_metadata.py +18 -1
  48. package/bin/_lib_codex_publication_barrier.py +277 -0
  49. package/bin/_lib_codex_rollout_container.py +502 -0
  50. package/bin/_lib_conversation_dispatch.py +3 -0
  51. package/bin/_lib_conversation_query.py +106 -59
  52. package/bin/_lib_conversation_retention.py +1605 -640
  53. package/bin/_lib_cost_provenance.py +53 -2
  54. package/bin/_lib_detector_access_registry.py +3817 -0
  55. package/bin/_lib_doctor.py +714 -69
  56. package/bin/_lib_five_hour.py +126 -0
  57. package/bin/_lib_fmt.py +11 -0
  58. package/bin/_lib_ingest_frontier.py +414 -20
  59. package/bin/_lib_journal.py +141 -10
  60. package/bin/_lib_jsonl.py +5 -4
  61. package/bin/_lib_legacy_spool.py +126 -0
  62. package/bin/_lib_legacy_writer_evidence.py +136 -0
  63. package/bin/_lib_merge_gate.py +231 -86
  64. package/bin/_lib_meter_rate_change.py +10 -0
  65. package/bin/_lib_outcome_quarantine.py +690 -0
  66. package/bin/_lib_perf.py +27 -1
  67. package/bin/_lib_pricing.py +199 -9
  68. package/bin/_lib_pricing_check.py +20 -4
  69. package/bin/_lib_quarantine.py +154 -0
  70. package/bin/_lib_quota_calibration.py +53 -8
  71. package/bin/_lib_quota_copy.py +2 -1
  72. package/bin/_lib_quota_model.py +288 -46
  73. package/bin/_lib_reclaim_planner.py +792 -0
  74. package/bin/_lib_rederive.py +319 -82
  75. package/bin/_lib_segment_summary.py +35 -2
  76. package/bin/_lib_snapshot_cache.py +21 -0
  77. package/bin/_lib_sqlite_close.py +81 -0
  78. package/bin/_lib_statusline_candidates.py +292 -23
  79. package/bin/_lib_subscription_weeks.py +100 -17
  80. package/bin/_lib_tick_stats.py +217 -17
  81. package/bin/_lib_wal_checkpoint.py +1077 -0
  82. package/bin/_lib_write_budget.py +483 -0
  83. package/bin/_lib_write_io.py +703 -0
  84. package/bin/cctally +31 -1
  85. package/dashboard/static/assets/{ConversationsView-CEZ-BUyi.js → ConversationsView-Bc8LCOAZ.js} +1 -1
  86. package/dashboard/static/assets/{DoctorModal-CE1oH_Zy.js → DoctorModal-D--qVBwe.js} +1 -1
  87. package/dashboard/static/assets/ModalRoot-DLPgyAM_.js +1 -0
  88. package/dashboard/static/assets/{ProjectsDrillPanel-B3DZoCeD.js → ProjectsDrillPanel-z53a4v4M.js} +1 -1
  89. package/dashboard/static/assets/{SourceDetailModal-CHp9EgLP.js → SourceDetailModal-Bw0T8W-W.js} +1 -1
  90. package/dashboard/static/assets/{UpdateModal-5iKST6jo.js → UpdateModal-D5AKx4Yw.js} +1 -1
  91. package/dashboard/static/assets/index-BKITk1CT.css +1 -0
  92. package/dashboard/static/assets/{index-Cm0VTxJC.js → index-BXDOzSng.js} +3 -3
  93. package/dashboard/static/assets/{outlineNavigation-DkXGL8J6.js → outlineNavigation-JObY7cUY.js} +1 -1
  94. package/dashboard/static/dashboard.html +2 -2
  95. package/package.json +19 -1
  96. package/dashboard/static/assets/ModalRoot-D5Y8IFUG.js +0 -1
  97. package/dashboard/static/assets/index-Dt4tqf43.css +0 -1
@@ -17,6 +17,7 @@ import _cctally_journal as _journal
17
17
  import _lib_accounts
18
18
  import _lib_journal
19
19
  import _lib_journal_router
20
+ import _cctally_store
20
21
  from _lib_json_envelope import stamp_schema_version
21
22
 
22
23
 
@@ -68,11 +69,20 @@ def _read_prefix(high_water):
68
69
  )
69
70
  records = []
70
71
  evidence = []
72
+ evidence_owners: list = []
71
73
  audit_ends: dict = {}
74
+ audit_candidates: list = []
72
75
  malformed = 0
73
76
  prior_high_water = None
74
77
  cutover_captured = _journal._CUTOVER_UNSEEN
75
78
  hasher = _lib_journal_router.PrefixHashAccumulator()
79
+ # #825/#832 (spec 5.3): admission is resolved within this one pass and
80
+ # applied after it, before selection or normalization. An excluded record
81
+ # — an old process's repair acknowledgement or correction batch included —
82
+ # keeps its positional slot and is never selection or audit input (F7).
83
+ pass_boundary = _journal._PassBoundary()
84
+ coordinates: list = []
85
+ cutover_candidates: list = []
76
86
  for segment, offset, raw in _journal._iter_range_with_segments(
77
87
  None,
78
88
  high_water,
@@ -86,6 +96,7 @@ def _read_prefix(high_water):
86
96
  prior_high_water = (segment, offset + len(raw) + 1)
87
97
  continue
88
98
  record_end = (segment, offset + len(raw) + 1)
99
+ pass_boundary.observe(record)
89
100
  if record.get("t") == "op":
90
101
  _journal._capture_protocol_prefix_evidence(
91
102
  record,
@@ -93,20 +104,22 @@ def _read_prefix(high_water):
93
104
  evidence,
94
105
  hasher=hasher,
95
106
  )
107
+ if len(evidence) > len(evidence_owners):
108
+ evidence_owners.append(len(records))
96
109
  payload = record.get("payload")
97
110
  if (
98
111
  isinstance(payload, dict)
99
112
  and payload.get("kind")
100
- == _lib_journal._PROTOCOL_RESOLUTION_KIND
113
+ in _lib_journal.PROTOCOL_RESOLUTION_KINDS
101
114
  ):
102
- audit_ends[record.get("id")] = record_end
103
- # First cutover op wins, exactly as `find_accounts_cutover_op` scans.
104
- if (
105
- cutover_captured is _journal._CUTOVER_UNSEEN
106
- and record.get("id") == _journal.CUTOVER_OP_ID
107
- ):
108
- cutover_captured = _journal._cutover_value_of(record)
115
+ audit_candidates.append(
116
+ (len(records), record.get("id"), record_end))
117
+ # First ADMITTED cutover op wins, exactly as `find_accounts_cutover_op`
118
+ # scans; admission is known only after the pass.
119
+ if record.get("id") == _journal.CUTOVER_OP_ID:
120
+ cutover_candidates.append((record, (segment, offset)))
109
121
  records.append(_lib_journal_router.selector_slot(record))
122
+ coordinates.append((segment, offset))
110
123
  prior_high_water = record_end
111
124
  prefix_hash = hasher.digest_at(high_water)
112
125
  # The accumulator buffers the segment it is reading — 410 MB on the
@@ -119,6 +132,19 @@ def _read_prefix(high_water):
119
132
  raise _lib_journal.JournalProtocolError(
120
133
  f"journal prefix contains {malformed} malformed line(s)"
121
134
  )
135
+ boundary = pass_boundary.resolve(high_water)
136
+ _journal._apply_pass_admission(records, coordinates, boundary)
137
+ # #825/#832 fix round C (CD2-005): an excluded acknowledgement is absent
138
+ # for every purpose — its evidence and its audit end go with it.
139
+ evidence = _journal._admitted_prefix_evidence(
140
+ evidence, evidence_owners, records)
141
+ for index, audit_id, record_end in audit_candidates:
142
+ if records[index] is not None:
143
+ audit_ends[audit_id] = record_end
144
+ for candidate, (segment, offset) in cutover_candidates:
145
+ if _journal._is_admitted(candidate, segment, offset, boundary):
146
+ cutover_captured = _journal._cutover_value_of(candidate)
147
+ break
122
148
  # No suffix fallback, deliberately. `_read_only_high_water` pins the
123
149
  # canonically-last segment at its full size, so this prefix IS the whole
124
150
  # journal and an op the prefix does not contain is not in the journal at
@@ -255,6 +281,13 @@ def _repair_locks(timeout: float = 5.0):
255
281
  finally:
256
282
  _cctally_core.note_stats_maintenance_released()
257
283
  release_cache_writer_flocks(held)
284
+ # #825/#832 spec 4.3 revision 2: the repair's live index publication (or
285
+ # its no-op) posts a wake once its locks are released.
286
+ try:
287
+ import _cctally_statusline
288
+ _cctally_statusline.post_wake("holder:journal-repair")
289
+ except Exception:
290
+ pass
258
291
 
259
292
 
260
293
  def _iso_now() -> str:
@@ -322,6 +355,25 @@ def _post_audit_failure(requested, audit_ids, exc):
322
355
  return payload, 3
323
356
 
324
357
 
358
+ def _post_audit_obligation_lost(requested, audit_ids, exc):
359
+ """Fix round K12 (K11R-3): the rebuilt index IS published (and the
360
+ holder wake is posted once the repair locks are released); only its
361
+ residual-derivation obligation was lost. The status stays `failed`,
362
+ with the additive `indexPublished`, and the command exits 3."""
363
+ payload, returncode = _post_audit_failure(requested, audit_ids, exc)
364
+ payload["indexPublished"] = True
365
+ return payload, returncode
366
+
367
+
368
+ def _obligation_lost_message(error) -> str:
369
+ return (
370
+ "cctally: the corrected stats.db was published, but the record that "
371
+ "the inputs written during the rebuild still need deriving could not "
372
+ f"be saved ({error}). Those inputs stay in the journal and the next "
373
+ "ingest derives them; check that the data directory is writable."
374
+ )
375
+
376
+
325
377
  def _stats_has_acknowledgements(fingerprints) -> bool:
326
378
  if not _cctally_core.DB_PATH.exists():
327
379
  return False
@@ -330,9 +382,12 @@ def _stats_has_acknowledgements(fingerprints) -> bool:
330
382
  f"file:{_cctally_core.DB_PATH}?mode=ro", uri=True
331
383
  )
332
384
  try:
333
- rows = conn.execute(
334
- "SELECT violation_json FROM journal_protocol_violations"
335
- )
385
+ # #825/#832 spec 6: completion evidence only from a validated
386
+ # current index; anything else fails closed.
387
+ with _cctally_store.current_stats_snapshot(conn):
388
+ rows = conn.execute(
389
+ "SELECT violation_json FROM journal_protocol_violations"
390
+ ).fetchall()
336
391
  stored = {
337
392
  item.get("fingerprint")
338
393
  for item in (json.loads(str(row[0])) for row in rows)
@@ -341,7 +396,8 @@ def _stats_has_acknowledgements(fingerprints) -> bool:
341
396
  return set(fingerprints) <= stored
342
397
  finally:
343
398
  conn.close()
344
- except (OSError, sqlite3.Error, ValueError, TypeError):
399
+ except (OSError, sqlite3.Error, ValueError, TypeError,
400
+ _cctally_store.StatsIndexNotCurrent):
345
401
  return False
346
402
 
347
403
 
@@ -368,6 +424,8 @@ def _recovery_high_water(audit_ids, audit_ends):
368
424
 
369
425
 
370
426
  def _apply(requested, initial_preview):
427
+ import _cctally_completion
428
+
371
429
  _call_pause_hook("before-lock")
372
430
  with _repair_locks():
373
431
  preview, snapshot = _preview_payload(requested)
@@ -429,10 +487,17 @@ def _apply(requested, initial_preview):
429
487
  ),
430
488
  high_water=audit_high_water,
431
489
  update_quota_cache=False,
490
+ complete_derivation=True,
432
491
  before_swap=lambda: _call_crash_hook(
433
492
  "before-rebuild-swap"
434
493
  ),
435
494
  )
495
+ except _cctally_completion.ResidualObligationNotDurable as exc:
496
+ return _post_audit_obligation_lost(
497
+ requested,
498
+ {audit["id"]},
499
+ exc,
500
+ )
436
501
  except Exception as exc:
437
502
  return _post_audit_failure(
438
503
  requested,
@@ -477,10 +542,17 @@ def _apply(requested, initial_preview):
477
542
  ),
478
543
  high_water=recovery_high_water,
479
544
  update_quota_cache=False,
545
+ complete_derivation=True,
480
546
  before_swap=lambda: _call_crash_hook(
481
547
  "before-rebuild-swap"
482
548
  ),
483
549
  )
550
+ except _cctally_completion.ResidualObligationNotDurable as exc:
551
+ return _post_audit_obligation_lost(
552
+ requested,
553
+ audit_ids,
554
+ exc,
555
+ )
484
556
  except Exception as exc:
485
557
  return _post_audit_failure(
486
558
  requested,
@@ -498,6 +570,18 @@ def _apply(requested, initial_preview):
498
570
 
499
571
  def cmd_db_journal_repair(args) -> int:
500
572
  """Preview structural violations without mutating the journal or indexes."""
573
+ if (bool(getattr(args, "yes", False))
574
+ and list(getattr(args, "violation", ()) or ())
575
+ and _journal.account_bound_cutover_enforced()):
576
+ # #825/#832 spec 5.5: establish the account-bound cutover before the
577
+ # apply's prefix-bound planning (the preview below is that plan), so
578
+ # the plan never races the boundary's own append.
579
+ try:
580
+ _journal.ensure_account_bound_cutover()
581
+ except _journal.AccountBoundCutoverDeferred:
582
+ pass
583
+ except OSError:
584
+ pass
501
585
  try:
502
586
  requested = list(getattr(args, "violation", ()) or ())
503
587
  payload, _snapshot = _preview_payload(requested)
@@ -585,6 +669,11 @@ def cmd_db_journal_repair(args) -> int:
585
669
  if returncode != 0:
586
670
  if bool(getattr(args, "json", False)):
587
671
  print(json.dumps(payload, separators=(",", ":")))
672
+ elif payload.get("indexPublished"):
673
+ print(
674
+ _obligation_lost_message(payload["errors"][0]),
675
+ file=sys.stderr,
676
+ )
588
677
  else:
589
678
  for error in payload["errors"]:
590
679
  print(
@@ -817,22 +817,75 @@ def _load_codex_cycles(stats_conn, root_keys, *, include_orphaned=False,
817
817
  if not roots:
818
818
  return []
819
819
  placeholders = ",".join("?" for _ in roots)
820
- orphan_clause = "" if include_orphaned else "AND orphaned_at IS NULL "
821
- # `quota_window_blocks.account_key` is NOT NULL DEFAULT 'unattributed'.
822
- account_clause = "" if account_key is None else "AND account_key=? "
823
- account_params = () if account_key is None else (account_key,)
824
820
  # BEFORE the SQL, per #496 S5b section 4.7.
825
821
  assert_projection_readable(stats_conn)
822
+ # #901 (I1/G1, Amendment 1 item 2, 1b and 1c): stream every weekly block
823
+ # in reset order through an epoch-1017 partial expression index. Unpinned,
824
+ # the planner seeks a root-leading index and sorts the whole weekly
825
+ # history through a temp b-tree (measured for every root/account/orphan
826
+ # variant).
827
+ #
828
+ # The ORDER BY names the tie order explicitly: `_canonicalize_codex_
829
+ # cluster` keeps the first maximal member, so rows tied on one reset
830
+ # second must arrive in the order the pre-#901 sorter received them, and
831
+ # that order came from the legacy plan, which differs by root count
832
+ # (Amendment 1c, measured on SQLite 3.53.4). With two or more roots the
833
+ # legacy plan scanned `idx_quota_blocks_active`, whose order includes
834
+ # orphaned_at; with exactly one it seeked the UNIQUE autoindex, which has
835
+ # no orphaned_at (its one exception, an account-scoped live-only read, has
836
+ # a constant orphaned_at, so the two orders agree there). The branch
837
+ # counts the NORMALIZED roots above, so duplicate or empty arguments take
838
+ # the single-root order, exactly as they took the single-root plan.
839
+ #
840
+ # Each branch pins its own index only when that index exists; on an
841
+ # older index the statement stays unpinned with ordinary predicates and
842
+ # the same explicit order. Under the pin, the root, account and orphan
843
+ # predicates carry a unary `+`: SQLite treats a column that a WHERE term
844
+ # fixes but the scan does not seek as satisfying its ORDER BY term and
845
+ # then cannot match that index column, so the later ORDER BY terms fell
846
+ # back to a tie-run sorter ("USE TEMP B-TREE FOR LAST n TERMS OF ORDER
847
+ # BY", measured). Limitation: a unary `+` keeps the operand's value but
848
+ # removes its column affinity. That is equivalent here ONLY because these
849
+ # comparisons are TEXT against TEXT: the root keys are filtered to `str`
850
+ # above, `account_key` is a TEXT key bound as a string, and `IS NULL`
851
+ # involves no affinity. It is not an equivalence for numeric parameters or
852
+ # any other affinity-dependent comparison; a caller binding a non-string
853
+ # account key would need the unpinned predicate.
854
+ if len(roots) == 1:
855
+ order_index = "idx_quota_blocks_weekly_single_root_order"
856
+ order_by = (
857
+ "ORDER BY unixepoch(resets_at_utc) DESC, source_root_key ASC, "
858
+ "account_key ASC, logical_limit_key ASC, observed_slot ASC, "
859
+ "resets_at_utc ASC")
860
+ else:
861
+ order_index = "idx_quota_blocks_weekly_reset_order"
862
+ order_by = (
863
+ "ORDER BY unixepoch(resets_at_utc) DESC, source_root_key ASC, "
864
+ "account_key ASC, orphaned_at ASC, logical_limit_key ASC, "
865
+ "observed_slot ASC, resets_at_utc ASC")
866
+ pinned = stats_conn.execute(
867
+ "SELECT 1 FROM sqlite_master WHERE type='index' AND name=?",
868
+ (order_index,),
869
+ ).fetchone() is not None
870
+ order_hint = f"INDEXED BY {order_index} " if pinned else ""
871
+ plus = "+" if pinned else ""
872
+ orphan_clause = (
873
+ "" if include_orphaned else f"AND {plus}orphaned_at IS NULL ")
874
+ # `quota_window_blocks.account_key` is NOT NULL DEFAULT 'unattributed'.
875
+ account_clause = (
876
+ "" if account_key is None else f"AND {plus}account_key=? ")
877
+ account_params = () if account_key is None else (account_key,)
826
878
  rows = stats_conn.execute(
827
879
  "SELECT source_root_key, logical_limit_key, observed_slot, "
828
880
  " window_minutes, limit_id, limit_name, account_key, "
829
881
  " resets_at_utc, nominal_start_at_utc, current_percent "
830
882
  "FROM quota_window_blocks "
883
+ f"{order_hint}"
831
884
  "WHERE source='codex' AND window_minutes=10080 "
832
885
  f"{orphan_clause}"
833
886
  f"{account_clause}"
834
- f"AND source_root_key IN ({placeholders}) "
835
- "ORDER BY unixepoch(resets_at_utc) DESC",
887
+ f"AND {plus}source_root_key IN ({placeholders}) "
888
+ f"{order_by}",
836
889
  (*account_params, *roots),
837
890
  ).fetchall()
838
891
 
@@ -952,10 +1005,20 @@ def _codex_five_hour_rows(stats_conn, cyc, *, include_orphaned=False) -> list:
952
1005
  The widening is one-directional on purpose: an unattributed CYCLE never
953
1006
  picks up another account's identified 5h rows, which would violate #341's
954
1007
  never-combine rule.
1008
+
1009
+ #901 Amendment 19 T1 (spec I1): the rows are returned whole, so they are
1010
+ ordered here, where ``ORDER BY unixepoch(nominal_start_at_utc)`` sorted
1011
+ them through a temp b-tree. Each physical 5h block surfaces as many
1012
+ jittered rows, so one weekly cycle is not a bound. The key is SQLite's own
1013
+ ``unixepoch()`` of each distinct start spelling (a table-free scalar
1014
+ query, memoized), so it is the old key exactly, and the rows keep the
1015
+ caller's row factory. ``list.sort`` is stable over the same scan SQLite's
1016
+ in-memory sorter consumed, so rows tied on one start second keep their
1017
+ order. The WHERE clause admits no NULL start.
955
1018
  """
956
1019
  orphan_clause = "" if include_orphaned else "AND orphaned_at IS NULL "
957
1020
  assert_projection_readable(stats_conn)
958
- return stats_conn.execute(
1021
+ rows = stats_conn.execute(
959
1022
  "SELECT source_root_key, logical_limit_key, observed_slot, "
960
1023
  " window_minutes, limit_id, limit_name, account_key, "
961
1024
  " resets_at_utc, nominal_start_at_utc, current_percent "
@@ -964,12 +1027,22 @@ def _codex_five_hour_rows(stats_conn, cyc, *, include_orphaned=False) -> list:
964
1027
  f"{orphan_clause}"
965
1028
  "AND source_root_key=? AND account_key IN (?,?) "
966
1029
  "AND unixepoch(nominal_start_at_utc) < unixepoch(?) "
967
- "AND unixepoch(resets_at_utc) > unixepoch(?) "
968
- "ORDER BY unixepoch(nominal_start_at_utc) ASC",
1030
+ "AND unixepoch(resets_at_utc) > unixepoch(?)",
969
1031
  (cyc.root, cyc.account_key, UNATTRIBUTED,
970
1032
  cyc.end.astimezone(UTC).isoformat(),
971
1033
  cyc.start.astimezone(UTC).isoformat()),
972
1034
  ).fetchall()
1035
+ epochs: dict = {}
1036
+
1037
+ def start_epoch(row):
1038
+ text = row[8]
1039
+ if text not in epochs:
1040
+ epochs[text] = stats_conn.execute(
1041
+ "SELECT unixepoch(?)", (text,)).fetchone()[0]
1042
+ return epochs[text]
1043
+
1044
+ rows.sort(key=start_epoch)
1045
+ return rows
973
1046
 
974
1047
 
975
1048
  def _codex_five_hour_clusters(stats_conn, cyc, *, include_orphaned=False) -> list:
@@ -3787,6 +3787,28 @@ def _build_stats_corruption_heal_parser(subparsers, name, *, help_text, xref=Non
3787
3787
  )
3788
3788
  worker.set_defaults(func=c.cmd_stats_corruption_heal_internal)
3789
3789
 
3790
+ def _build_statusline_continue_parser(subparsers, name, *, help_text, xref=None):
3791
+ """Build the #825/#832 detached statusline continuation worker parser."""
3792
+ c = _cctally()
3793
+ worker = subparsers.add_parser(
3794
+ name,
3795
+ help=help_text,
3796
+ formatter_class=CLIHelpFormatter,
3797
+ description=textwrap.dedent(
3798
+ """\
3799
+ Internal subcommand: continue deferred statusline work
3800
+ under the one reducer reservation it inherits, or (with
3801
+ --wait) observe one blocking lock's release and post one
3802
+ wake. Spawned by the event-driven continuation; never
3803
+ polls. Always returns 0.
3804
+ """
3805
+ ),
3806
+ )
3807
+ worker.add_argument("--reservation-fd", type=int, required=True,
3808
+ help=argparse.SUPPRESS)
3809
+ worker.add_argument("--wait", default=None, help=argparse.SUPPRESS)
3810
+ worker.set_defaults(func=c.cmd_statusline_continue_internal)
3811
+
3790
3812
  def _build_artifact_retention_parser(subparsers, name, *, help_text, xref=None):
3791
3813
  """Build the #496 S6 detached retained-artifact reclamation worker parser."""
3792
3814
  c = _cctally()
@@ -3827,6 +3849,24 @@ def _build_codex_replay_drain_parser(subparsers, name, *, help_text, xref=None):
3827
3849
  )
3828
3850
  rd.set_defaults(func=c.cmd_codex_replay_drain_internal)
3829
3851
 
3852
+ def _build_codex_prune_drain_parser(subparsers, name, *, help_text, xref=None):
3853
+ """Build the `_codex-prune-drain` parser (#968 spec 5.7)."""
3854
+ c = _cctally()
3855
+ pd = subparsers.add_parser(
3856
+ name,
3857
+ help=help_text,
3858
+ formatter_class=CLIHelpFormatter,
3859
+ description=textwrap.dedent(
3860
+ """\
3861
+ Internal subcommand: detached worker that drains recorded
3862
+ Codex prune debt one source identity per transaction, off
3863
+ the hook. Always returns 0; failures are written to
3864
+ hook-tick.log.
3865
+ """
3866
+ ),
3867
+ )
3868
+ pd.set_defaults(func=c.cmd_codex_prune_drain_internal)
3869
+
3830
3870
  def _build_repair_symlinks_parser(subparsers, name, *, help_text, xref=None):
3831
3871
  """Build the `repair-symlinks` parser (registered via _REGISTRATION; #279 S6 W3).
3832
3872
 
@@ -3913,8 +3953,10 @@ _REGISTRATION = (
3913
3953
  _Reg('_codex-quota-verify', _build_codex_quota_verify_parser, argparse.SUPPRESS, None, None),
3914
3954
  _Reg('_stats-epoch-rebuild', _build_stats_epoch_rebuild_parser, argparse.SUPPRESS, None, None),
3915
3955
  _Reg('_stats-corruption-heal', _build_stats_corruption_heal_parser, argparse.SUPPRESS, None, None),
3956
+ _Reg('_statusline-continue', _build_statusline_continue_parser, argparse.SUPPRESS, None, None),
3916
3957
  _Reg('_artifact-retention', _build_artifact_retention_parser, argparse.SUPPRESS, None, None),
3917
3958
  _Reg('_codex-replay-drain', _build_codex_replay_drain_parser, argparse.SUPPRESS, None, None),
3959
+ _Reg('_codex-prune-drain', _build_codex_prune_drain_parser, argparse.SUPPRESS, None, None),
3918
3960
  _Reg('repair-symlinks', _build_repair_symlinks_parser, argparse.SUPPRESS, None, None),
3919
3961
  )
3920
3962
 
@@ -262,7 +262,8 @@ def observation_gap_marginal_cell(marginal_value: Any, *, withheld: bool) -> str
262
262
  table already says what the code means.
263
263
  """
264
264
  if marginal_value is not None:
265
- return f"${float(marginal_value):.6f}"
265
+ c = _cctally()
266
+ return c._fmt_usd_accounting(float(marginal_value), 6)
266
267
  return OBSERVATION_GAP_CAUSE if withheld else "n/a"
267
268
 
268
269
 
@@ -315,7 +316,7 @@ def _render_percent_breakdown_terminal(
315
316
  rows.append([
316
317
  str(idx),
317
318
  f"{percent}%",
318
- f"${cumulative:.6f}",
319
+ c._fmt_usd_accounting(cumulative, 6),
319
320
  c.observation_gap_marginal_cell(
320
321
  marginal_value, withheld=(idx - 1) in withheld),
321
322
  f"{float(five_hour_value):.0f}%" if five_hour_value is not None else "n/a",
@@ -58,9 +58,11 @@ def _cctally():
58
58
  #: Why modelled quota is not published for a week or a whole run. A closed
59
59
  #: set. `account-not-resolved` is the run-level one (§5.3); the rest are
60
60
  #: per-week and are the reasons that week fell back to the cost share.
61
+ #: `unvalidated-coefficient-era` is `apply_regime`'s rejection of a week
62
+ #: outside the stored regime's coefficient era (#978 spec 4.6).
61
63
  ATTRIBUTION_CAUSES = ("account-not-resolved", "calibration-absent",
62
64
  "regime-boundary", "unsupported-composition",
63
- "no-local-history")
65
+ "no-local-history", "unvalidated-coefficient-era")
64
66
 
65
67
  #: The three bases a row or a run can carry.
66
68
  ATTRIBUTION_BASES = ("modelled", "cost-share", "withheld")
@@ -219,7 +221,8 @@ def resolve_week_attribution(regime, records, *, week_start, week_end):
219
221
  and week_end > regime.effective_until:
220
222
  return WeekAttribution("cost-share", "regime-boundary", None)
221
223
  qcg = _cctally()._load_sibling("_cctally_quota_calibration")
222
- applied = qcg.apply_regime(regime, list(records))
224
+ applied = qcg.apply_regime(regime, list(records),
225
+ interval=(week_start, week_end))
223
226
  if isinstance(applied, qcg.ApplyRejection):
224
227
  return WeekAttribution("cost-share", applied.value, None)
225
228
  return WeekAttribution("modelled", None, regime.units_per_point)
@@ -282,6 +285,9 @@ _ATTRIBUTION_CAUSE_COPY: dict = {
282
285
  "unsupported-composition":
283
286
  "the week's model mix sits outside the calibration's support",
284
287
  "no-local-history": "no local entries to model",
288
+ "unvalidated-coefficient-era":
289
+ "the week is not inside the coefficient era the calibration's token "
290
+ "weights are validated for",
285
291
  RESIDUAL_MISALIGNED:
286
292
  "the account, the window or the population does not align",
287
293
  RESIDUAL_OBSERVED_ABSENT: