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.
- package/CHANGELOG.md +47 -0
- package/README.md +4 -6
- package/bin/_cctally_account.py +32 -8
- package/bin/_cctally_cache.py +6035 -722
- package/bin/_cctally_completion.py +1287 -0
- package/bin/_cctally_config.py +37 -0
- package/bin/_cctally_core.py +400 -2
- package/bin/_cctally_dashboard.py +98 -20
- package/bin/_cctally_dashboard_conversation.py +292 -23
- package/bin/_cctally_dashboard_envelope.py +16 -3
- package/bin/_cctally_dashboard_perf.py +128 -9
- package/bin/_cctally_dashboard_sources.py +285 -213
- package/bin/_cctally_db.py +1177 -23
- package/bin/_cctally_diagnosis_sources.py +22 -4
- package/bin/_cctally_doctor.py +572 -45
- package/bin/_cctally_five_hour.py +84 -33
- package/bin/_cctally_forecast.py +108 -15
- package/bin/_cctally_journal.py +3950 -116
- package/bin/_cctally_journal_repair.py +101 -12
- package/bin/_cctally_milestone_history.py +82 -9
- package/bin/_cctally_parser.py +42 -0
- package/bin/_cctally_percent_breakdown.py +3 -2
- package/bin/_cctally_project.py +8 -2
- package/bin/_cctally_quarantine.py +652 -0
- package/bin/_cctally_quota.py +227 -37
- package/bin/_cctally_quota_model.py +360 -83
- package/bin/_cctally_record.py +691 -149
- package/bin/_cctally_rederive.py +320 -41
- package/bin/_cctally_refresh.py +84 -13
- package/bin/_cctally_source_analytics.py +83 -17
- package/bin/_cctally_statusline.py +6154 -517
- package/bin/_cctally_store.py +367 -53
- package/bin/_cctally_transcript.py +8 -7
- package/bin/_cctally_tui.py +61 -56
- package/bin/_cctally_weekrefs.py +128 -21
- package/bin/_lib_account_binding.py +152 -0
- package/bin/_lib_account_protocol.py +386 -0
- package/bin/_lib_aggregators.py +4 -0
- package/bin/_lib_cache_coverage.py +13 -1
- package/bin/_lib_cache_report.py +35 -6
- package/bin/_lib_cache_writer_lock.py +19 -3
- package/bin/_lib_clone_copy.py +142 -0
- package/bin/_lib_codex_conversation.py +32 -0
- package/bin/_lib_codex_conversation_query.py +297 -49
- package/bin/_lib_codex_conversation_watch.py +59 -14
- package/bin/_lib_codex_drain_admission.py +214 -0
- package/bin/_lib_codex_metadata.py +18 -1
- package/bin/_lib_codex_publication_barrier.py +277 -0
- package/bin/_lib_codex_rollout_container.py +502 -0
- package/bin/_lib_conversation_dispatch.py +3 -0
- package/bin/_lib_conversation_query.py +106 -59
- package/bin/_lib_conversation_retention.py +1605 -640
- package/bin/_lib_cost_provenance.py +53 -2
- package/bin/_lib_detector_access_registry.py +3817 -0
- package/bin/_lib_doctor.py +714 -69
- package/bin/_lib_five_hour.py +126 -0
- package/bin/_lib_fmt.py +11 -0
- package/bin/_lib_ingest_frontier.py +414 -20
- package/bin/_lib_journal.py +141 -10
- package/bin/_lib_jsonl.py +5 -4
- package/bin/_lib_legacy_spool.py +126 -0
- package/bin/_lib_legacy_writer_evidence.py +136 -0
- package/bin/_lib_merge_gate.py +231 -86
- package/bin/_lib_meter_rate_change.py +10 -0
- package/bin/_lib_outcome_quarantine.py +690 -0
- package/bin/_lib_perf.py +27 -1
- package/bin/_lib_pricing.py +199 -9
- package/bin/_lib_pricing_check.py +20 -4
- package/bin/_lib_quarantine.py +154 -0
- package/bin/_lib_quota_calibration.py +53 -8
- package/bin/_lib_quota_copy.py +2 -1
- package/bin/_lib_quota_model.py +288 -46
- package/bin/_lib_reclaim_planner.py +792 -0
- package/bin/_lib_rederive.py +319 -82
- package/bin/_lib_segment_summary.py +35 -2
- package/bin/_lib_snapshot_cache.py +21 -0
- package/bin/_lib_sqlite_close.py +81 -0
- package/bin/_lib_statusline_candidates.py +292 -23
- package/bin/_lib_subscription_weeks.py +100 -17
- package/bin/_lib_tick_stats.py +217 -17
- package/bin/_lib_wal_checkpoint.py +1077 -0
- package/bin/_lib_write_budget.py +483 -0
- package/bin/_lib_write_io.py +703 -0
- package/bin/cctally +31 -1
- package/dashboard/static/assets/{ConversationsView-CEZ-BUyi.js → ConversationsView-Bc8LCOAZ.js} +1 -1
- package/dashboard/static/assets/{DoctorModal-CE1oH_Zy.js → DoctorModal-D--qVBwe.js} +1 -1
- package/dashboard/static/assets/ModalRoot-DLPgyAM_.js +1 -0
- package/dashboard/static/assets/{ProjectsDrillPanel-B3DZoCeD.js → ProjectsDrillPanel-z53a4v4M.js} +1 -1
- package/dashboard/static/assets/{SourceDetailModal-CHp9EgLP.js → SourceDetailModal-Bw0T8W-W.js} +1 -1
- package/dashboard/static/assets/{UpdateModal-5iKST6jo.js → UpdateModal-D5AKx4Yw.js} +1 -1
- package/dashboard/static/assets/index-BKITk1CT.css +1 -0
- package/dashboard/static/assets/{index-Cm0VTxJC.js → index-BXDOzSng.js} +3 -3
- package/dashboard/static/assets/{outlineNavigation-DkXGL8J6.js → outlineNavigation-JObY7cUY.js} +1 -1
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +19 -1
- package/dashboard/static/assets/ModalRoot-D5Y8IFUG.js +0 -1
- 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
|
-
|
|
113
|
+
in _lib_journal.PROTOCOL_RESOLUTION_KINDS
|
|
101
114
|
):
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
334
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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:
|
package/bin/_cctally_parser.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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",
|
package/bin/_cctally_project.py
CHANGED
|
@@ -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:
|