cctally 1.91.0 → 1.92.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 +39 -0
- package/bin/_cctally_cache.py +863 -74
- package/bin/_cctally_config.py +57 -0
- package/bin/_cctally_core.py +39 -8
- package/bin/_cctally_dashboard.py +146 -5
- package/bin/_cctally_dashboard_conversation.py +164 -18
- package/bin/_cctally_dashboard_envelope.py +2 -0
- package/bin/_cctally_db.py +372 -10
- package/bin/_cctally_doctor.py +18 -1
- package/bin/_cctally_journal.py +535 -13
- package/bin/_cctally_journal_repair.py +6 -0
- package/bin/_cctally_parser.py +6 -0
- package/bin/_cctally_quota.py +171 -55
- package/bin/_cctally_record.py +13 -1
- package/bin/_cctally_rederive.py +4 -0
- package/bin/_cctally_store.py +311 -6
- package/bin/_cctally_transcript.py +32 -2
- package/bin/_lib_cache_report.py +8 -3
- package/bin/_lib_codex_conversation.py +851 -81
- package/bin/_lib_codex_conversation_query.py +2005 -95
- package/bin/_lib_codex_find_projection.py +370 -0
- package/bin/_lib_codex_harness_preamble.py +176 -0
- package/bin/_lib_codex_hooks.py +5 -3
- package/bin/_lib_codex_js_scan.py +254 -0
- package/bin/_lib_codex_landmarks.py +309 -0
- package/bin/_lib_codex_title_clean.py +116 -0
- package/bin/_lib_conversation_dispatch.py +153 -21
- package/bin/_lib_conversation_watch.py +4 -2
- package/bin/_lib_doctor.py +64 -0
- package/bin/_lib_quota_alert_axes.py +31 -34
- package/bin/_lib_stats_damage.py +523 -0
- package/bin/cctally +5 -0
- package/dashboard/static/assets/index-BEzzJtUd.js +97 -0
- package/dashboard/static/assets/{index-Dwirao3Y.css → index-DnWdv8um.css} +1 -1
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +7 -1
- package/dashboard/static/assets/index-CILAoEja.js +0 -90
|
@@ -332,6 +332,9 @@ def _apply(requested, initial_preview):
|
|
|
332
332
|
try:
|
|
333
333
|
_call_rebuild_error_hook()
|
|
334
334
|
rebuild = _journal.rebuild_stats_index(
|
|
335
|
+
context=_journal.RebuildContext(
|
|
336
|
+
trigger="journal-repair-acknowledge"
|
|
337
|
+
),
|
|
335
338
|
high_water=audit_high_water,
|
|
336
339
|
update_quota_cache=False,
|
|
337
340
|
before_swap=lambda: _call_crash_hook(
|
|
@@ -377,6 +380,9 @@ def _apply(requested, initial_preview):
|
|
|
377
380
|
try:
|
|
378
381
|
_call_rebuild_error_hook()
|
|
379
382
|
rebuild = _journal.rebuild_stats_index(
|
|
383
|
+
context=_journal.RebuildContext(
|
|
384
|
+
trigger="journal-repair-recovery"
|
|
385
|
+
),
|
|
380
386
|
high_water=recovery_high_water,
|
|
381
387
|
update_quota_cache=False,
|
|
382
388
|
before_swap=lambda: _call_crash_hook(
|
package/bin/_cctally_parser.py
CHANGED
|
@@ -2799,6 +2799,9 @@ def _build_transcript_parser(subparsers, name, *, help_text, xref=None):
|
|
|
2799
2799
|
help="Codex service tier for per-turn cost (default: auto). Applies only "
|
|
2800
2800
|
"to Codex (v1.) conversations; an explicit value on any other ref "
|
|
2801
2801
|
"is a usage error")
|
|
2802
|
+
t_export.add_argument(
|
|
2803
|
+
"--account", metavar="REF", default=None,
|
|
2804
|
+
help="Restrict the export to one account (label, email, or key prefix)")
|
|
2802
2805
|
t_export.add_argument(
|
|
2803
2806
|
"-o", "--output", metavar="PATH", default=None,
|
|
2804
2807
|
help="Write to PATH instead of stdout (same exact bytes)")
|
|
@@ -2811,6 +2814,9 @@ def _build_transcript_parser(subparsers, name, *, help_text, xref=None):
|
|
|
2811
2814
|
t_search.add_argument(
|
|
2812
2815
|
"--source", choices=("claude", "codex"), default="claude",
|
|
2813
2816
|
help="Which provider's conversations to search (default: claude)")
|
|
2817
|
+
t_search.add_argument(
|
|
2818
|
+
"--account", metavar="REF", default=None,
|
|
2819
|
+
help="Restrict search to one account (label, email, or key prefix)")
|
|
2814
2820
|
t_search.add_argument(
|
|
2815
2821
|
"--kind",
|
|
2816
2822
|
choices=("all", "prompts", "assistant", "tools", "thinking",
|
package/bin/_cctally_quota.py
CHANGED
|
@@ -333,7 +333,8 @@ def _ledger_state(stats_conn: sqlite3.Connection) -> dict | None:
|
|
|
333
333
|
try:
|
|
334
334
|
row = stats_conn.execute(
|
|
335
335
|
"SELECT watermark_seq, interpretation_version, alerts_enabled, "
|
|
336
|
-
" next_evaluation_at_utc, last_full_pass_at "
|
|
336
|
+
" next_evaluation_at_utc, last_full_pass_at, "
|
|
337
|
+
" next_evaluation_by_root_json "
|
|
337
338
|
" FROM quota_projection_ledger_state WHERE source='codex'"
|
|
338
339
|
).fetchone()
|
|
339
340
|
except sqlite3.Error:
|
|
@@ -341,17 +342,35 @@ def _ledger_state(stats_conn: sqlite3.Connection) -> dict | None:
|
|
|
341
342
|
if row is None:
|
|
342
343
|
return None
|
|
343
344
|
try:
|
|
345
|
+
schedule_wire = json.loads(str(row[5]))
|
|
346
|
+
if not isinstance(schedule_wire, dict):
|
|
347
|
+
return None
|
|
348
|
+
schedule: dict[str, str] = {}
|
|
349
|
+
for root_key, captured_at in schedule_wire.items():
|
|
350
|
+
if not isinstance(root_key, str) or not root_key:
|
|
351
|
+
return None
|
|
352
|
+
parsed = _parse_utc(str(captured_at), "next_evaluation_by_root_json")
|
|
353
|
+
schedule[root_key] = _utc_iso(parsed)
|
|
354
|
+
scalar_boundary = None if row[3] is None else str(row[3])
|
|
355
|
+
if schedule:
|
|
356
|
+
if scalar_boundary is None:
|
|
357
|
+
return None
|
|
358
|
+
parsed_scalar = _utc_iso(_parse_utc(
|
|
359
|
+
scalar_boundary, "next_evaluation_at_utc"))
|
|
360
|
+
if parsed_scalar != min(schedule.values()):
|
|
361
|
+
return None
|
|
344
362
|
return {
|
|
345
363
|
"watermark": int(row[0]),
|
|
346
364
|
"interpretation_version": int(row[1]),
|
|
347
365
|
"alerts_enabled": (
|
|
348
366
|
None if row[2] is None else bool(int(row[2]))),
|
|
349
367
|
"next_evaluation_at": (
|
|
350
|
-
|
|
368
|
+
scalar_boundary),
|
|
351
369
|
"last_full_pass_at": (
|
|
352
370
|
None if row[4] is None else str(row[4])),
|
|
371
|
+
"next_evaluation_by_root": schedule,
|
|
353
372
|
}
|
|
354
|
-
except (TypeError, ValueError):
|
|
373
|
+
except (json.JSONDecodeError, TypeError, ValueError):
|
|
355
374
|
return None
|
|
356
375
|
|
|
357
376
|
|
|
@@ -514,6 +533,7 @@ def _store_ledger_state(
|
|
|
514
533
|
conn: sqlite3.Connection, *, watermark: int,
|
|
515
534
|
alerts_enabled: "bool | None", next_evaluation_at: "str | None",
|
|
516
535
|
last_full_pass_at: "str | None",
|
|
536
|
+
next_evaluation_by_root: Mapping[str, str],
|
|
517
537
|
) -> None:
|
|
518
538
|
"""Stamp the consumed range, the non-dirtiness alert axes and the deadline.
|
|
519
539
|
|
|
@@ -524,18 +544,23 @@ def _store_ledger_state(
|
|
|
524
544
|
conn.execute(
|
|
525
545
|
"""INSERT INTO quota_projection_ledger_state
|
|
526
546
|
(source, watermark_seq, interpretation_version, alerts_enabled,
|
|
527
|
-
next_evaluation_at_utc, last_full_pass_at
|
|
528
|
-
|
|
547
|
+
next_evaluation_at_utc, last_full_pass_at,
|
|
548
|
+
next_evaluation_by_root_json)
|
|
549
|
+
VALUES ('codex',?,?,?,?,?,?)
|
|
529
550
|
ON CONFLICT(source) DO UPDATE SET
|
|
530
551
|
watermark_seq=excluded.watermark_seq,
|
|
531
552
|
interpretation_version=excluded.interpretation_version,
|
|
532
553
|
alerts_enabled=excluded.alerts_enabled,
|
|
533
554
|
next_evaluation_at_utc=excluded.next_evaluation_at_utc,
|
|
534
|
-
last_full_pass_at=excluded.last_full_pass_at
|
|
555
|
+
last_full_pass_at=excluded.last_full_pass_at,
|
|
556
|
+
next_evaluation_by_root_json=excluded.next_evaluation_by_root_json""",
|
|
535
557
|
(
|
|
536
558
|
int(watermark), _CODEX_QUOTA_INTERPRETATION_VERSION,
|
|
537
559
|
None if alerts_enabled is None else int(bool(alerts_enabled)),
|
|
538
560
|
next_evaluation_at, last_full_pass_at,
|
|
561
|
+
json.dumps(
|
|
562
|
+
dict(sorted(next_evaluation_by_root.items())),
|
|
563
|
+
sort_keys=True, separators=(",", ":")),
|
|
539
564
|
),
|
|
540
565
|
)
|
|
541
566
|
|
|
@@ -639,7 +664,7 @@ def _armed_identities(
|
|
|
639
664
|
def _resolve_alert_scope(
|
|
640
665
|
stats_conn: sqlite3.Connection, *, ledger_scope: str, now: dt.datetime,
|
|
641
666
|
ledger_state: "dict | None", global_enabled: bool, quota_enabled: bool,
|
|
642
|
-
rules, config, defer_scheduled: bool = False,
|
|
667
|
+
rules, config, eligible_roots: set[str], defer_scheduled: bool = False,
|
|
643
668
|
) -> _axes.AlertDirtyScope:
|
|
644
669
|
"""Feed the five-axis kernel from stats.db and the resolved configuration."""
|
|
645
670
|
armed = _armed_identities(stats_conn)
|
|
@@ -657,12 +682,22 @@ def _resolve_alert_scope(
|
|
|
657
682
|
quota_enabled=quota_enabled,
|
|
658
683
|
)
|
|
659
684
|
boundary = None
|
|
685
|
+
scheduled_roots: "frozenset[str] | None" = None
|
|
660
686
|
if ledger_state is not None and ledger_state["next_evaluation_at"]:
|
|
661
687
|
try:
|
|
662
688
|
boundary = _parse_utc(
|
|
663
689
|
ledger_state["next_evaluation_at"], "next_evaluation_at_utc")
|
|
664
690
|
except (TypeError, ValueError):
|
|
665
691
|
boundary = None
|
|
692
|
+
if ledger_state is not None:
|
|
693
|
+
schedule = ledger_state["next_evaluation_by_root"]
|
|
694
|
+
if schedule:
|
|
695
|
+
scheduled_roots = frozenset(
|
|
696
|
+
root_key for root_key, captured_at in schedule.items()
|
|
697
|
+
if root_key in eligible_roots
|
|
698
|
+
and now >= _parse_utc(
|
|
699
|
+
captured_at, "next_evaluation_by_root_json")
|
|
700
|
+
)
|
|
666
701
|
return _axes.alert_dirty_scope(
|
|
667
702
|
ledger_groups=(1,) if ledger_scope == _axes.SCOPE_GROUPS else (),
|
|
668
703
|
stored_fingerprints=stored,
|
|
@@ -672,6 +707,7 @@ def _resolve_alert_scope(
|
|
|
672
707
|
gate_after=bool(global_enabled and quota_enabled),
|
|
673
708
|
now=now,
|
|
674
709
|
next_evaluation_at=boundary,
|
|
710
|
+
scheduled_roots=scheduled_roots,
|
|
675
711
|
defer_scheduled=defer_scheduled,
|
|
676
712
|
)
|
|
677
713
|
|
|
@@ -681,7 +717,7 @@ def _blocks_missing_reverse_map(stats_conn: sqlite3.Connection) -> bool:
|
|
|
681
717
|
|
|
682
718
|
A scoped sweep matches on ``physical_group_key``, so a NULL there would
|
|
683
719
|
silently escape it and the stale block would survive indefinitely. The
|
|
684
|
-
epoch rebuild (1005
|
|
720
|
+
epoch rebuild (1005 introduced the column; current epoch 1007) stamps every row, and
|
|
685
721
|
this is the guard that turns the
|
|
686
722
|
one shape it cannot reach — a block written by an older binary against an
|
|
687
723
|
already-current index — into a full pass rather than a missed one.
|
|
@@ -1909,7 +1945,10 @@ def _apply_quota_projection_rows(
|
|
|
1909
1945
|
journal_terminal=None, holder=None, dirty_units=None,
|
|
1910
1946
|
ledger_watermark=None, alerts_enabled=None,
|
|
1911
1947
|
stored_next_evaluation_at=None, stored_last_full_pass_at=None,
|
|
1912
|
-
stored_alerts_enabled=None,
|
|
1948
|
+
stored_alerts_enabled=None, stored_next_evaluation_by_root=None,
|
|
1949
|
+
consume_alert_axes=True, reconcile_roots=None,
|
|
1950
|
+
schedule_evaluated_roots=frozenset(),
|
|
1951
|
+
prune_inactive_schedule=False,
|
|
1913
1952
|
):
|
|
1914
1953
|
"""Transaction-neutral quota projection apply (spec §5.3 "projection").
|
|
1915
1954
|
|
|
@@ -1924,17 +1963,16 @@ def _apply_quota_projection_rows(
|
|
|
1924
1963
|
live caller; rebuild passes ``None``.
|
|
1925
1964
|
|
|
1926
1965
|
``dirty_units`` (public #5) is the BOUNDED pass: a set of serialized loading
|
|
1927
|
-
units whose complete current membership ``observations`` carries.
|
|
1928
|
-
|
|
1929
|
-
|
|
1966
|
+
units whose complete current membership ``observations`` carries.
|
|
1967
|
+
``reconcile_roots`` distinguishes a complete root-scoped pass from the
|
|
1968
|
+
genuine whole-history ``None``/``None`` shape. It changes exactly two
|
|
1930
1969
|
things: the sweep is scoped to those units, and the root signature is
|
|
1931
1970
|
composed from the stored per-group digests rather than recomputed from
|
|
1932
1971
|
scratch. Everything else runs identically, which is what keeps the two paths
|
|
1933
1972
|
from drifting.
|
|
1934
1973
|
|
|
1935
|
-
``consume_alert_axes`` says whether this pass may
|
|
1936
|
-
non-dirtiness
|
|
1937
|
-
``next_evaluation_at``) or must carry the stored values through untouched,
|
|
1974
|
+
``consume_alert_axes`` says whether this pass may advance the global
|
|
1975
|
+
non-dirtiness axes or must carry the stored values through untouched,
|
|
1938
1976
|
the way ``last_full_pass_at`` is carried through by a bounded pass. False
|
|
1939
1977
|
means "this pass did not do the work those axes exist to trigger", and there
|
|
1940
1978
|
are two such passes. A REPORTING-ONLY pass (no alert-eligible roots — the
|
|
@@ -1944,8 +1982,9 @@ def _apply_quota_projection_rows(
|
|
|
1944
1982
|
stamping the gate would retire a delivery-gate ENABLE with no arming row and
|
|
1945
1983
|
no ``suppressed_backfill`` written, and the axis could never re-fire because
|
|
1946
1984
|
``gate_before`` now reads True. A hook tick that DEFERRED axis 4 is the
|
|
1947
|
-
other.
|
|
1948
|
-
|
|
1985
|
+
other. Scheduled ownership is narrower: ``schedule_evaluated_roots`` names
|
|
1986
|
+
only complete root histories this alert-eligible pass may replace. Group
|
|
1987
|
+
passes and reporting-only passes can merge new minima but retire none.
|
|
1949
1988
|
|
|
1950
1989
|
``ledger_watermark`` is stamped INSIDE this transaction. That is deliberate:
|
|
1951
1990
|
``run_stats_ingest`` is the sole stats writer, so advancing it after the
|
|
@@ -1954,7 +1993,10 @@ def _apply_quota_projection_rows(
|
|
|
1954
1993
|
group is idempotent.
|
|
1955
1994
|
"""
|
|
1956
1995
|
historic_roots = _historic_root_keys(conn)
|
|
1957
|
-
roots_to_reconcile =
|
|
1996
|
+
roots_to_reconcile = (
|
|
1997
|
+
active_roots | historic_roots
|
|
1998
|
+
if reconcile_roots is None else set(reconcile_roots)
|
|
1999
|
+
)
|
|
1958
2000
|
if not roots_to_reconcile:
|
|
1959
2001
|
return
|
|
1960
2002
|
generation = secrets.token_hex(16)
|
|
@@ -1995,12 +2037,11 @@ def _apply_quota_projection_rows(
|
|
|
1995
2037
|
# retires the right rows — and the DELETE below removes what it no longer
|
|
1996
2038
|
# names. A root with no blocks at all still stamps one `unattributed` row,
|
|
1997
2039
|
# byte-stable with the prior behaviour.
|
|
1998
|
-
|
|
1999
|
-
for root_key in sorted(
|
|
2040
|
+
roots_to_stamp = active_roots & roots_to_reconcile
|
|
2041
|
+
for root_key in sorted(roots_to_stamp):
|
|
2000
2042
|
accounts = _root_accounts(conn, root_key) or {_lib_accounts.UNATTRIBUTED}
|
|
2001
2043
|
root_signature = _ledger.compose_root_signature(
|
|
2002
2044
|
_root_group_pairs(conn, root_key))
|
|
2003
|
-
signatures[root_key] = root_signature
|
|
2004
2045
|
placeholders = ",".join("?" for _ in accounts)
|
|
2005
2046
|
conn.execute(
|
|
2006
2047
|
"DELETE FROM quota_projection_state WHERE source_root_key=? "
|
|
@@ -2019,6 +2060,15 @@ def _apply_quota_projection_rows(
|
|
|
2019
2060
|
completed_at_utc=excluded.completed_at_utc""",
|
|
2020
2061
|
(root_key, account_key, generation, root_signature, now_iso),
|
|
2021
2062
|
)
|
|
2063
|
+
# The cache-side certificate remains whole-store even when this pass was
|
|
2064
|
+
# root-scoped. Root signatures are composable from the stored group digests,
|
|
2065
|
+
# so collecting every active root here is O(groups), not O(observations),
|
|
2066
|
+
# and prevents a scoped pass from erasing untouched roots from the proof.
|
|
2067
|
+
signatures = {
|
|
2068
|
+
root_key: _ledger.compose_root_signature(
|
|
2069
|
+
_root_group_pairs(conn, root_key))
|
|
2070
|
+
for root_key in sorted(active_roots)
|
|
2071
|
+
}
|
|
2022
2072
|
# The state row is stamped even when ``ledger_watermark`` is ``None`` — a
|
|
2023
2073
|
# cache too old to carry the change log, where ``_ledger_max_seq`` cannot
|
|
2024
2074
|
# report a sequence. Guarding this whole block on it left such a store with
|
|
@@ -2045,16 +2095,61 @@ def _apply_quota_projection_rows(
|
|
|
2045
2095
|
# Recording a NEWLY seen future capture is safe from any pass, so that side
|
|
2046
2096
|
# stays unconditional; only RETIRING a matured instant is gated, because
|
|
2047
2097
|
# that is the half that claims an evaluation happened.
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2098
|
+
stored_schedule = dict(stored_next_evaluation_by_root or {})
|
|
2099
|
+
# Retire only roots whose COMPLETE history this alert-eligible pass
|
|
2100
|
+
# evaluated. A group-bounded pass may discover an earlier future capture,
|
|
2101
|
+
# but it cannot prove the absence of another capture in a clean group, so
|
|
2102
|
+
# it only merges minima and never removes a stored root deadline.
|
|
2103
|
+
for root_key in schedule_evaluated_roots:
|
|
2104
|
+
stored_schedule.pop(root_key, None)
|
|
2105
|
+
future_by_root: dict[str, dt.datetime] = {}
|
|
2106
|
+
for observation in observations:
|
|
2107
|
+
if observation.captured_at <= now:
|
|
2108
|
+
continue
|
|
2109
|
+
root_key = observation.identity.source_root_key
|
|
2110
|
+
current = future_by_root.get(root_key)
|
|
2111
|
+
if current is None or observation.captured_at < current:
|
|
2112
|
+
future_by_root[root_key] = observation.captured_at
|
|
2113
|
+
for root_key, captured_at in future_by_root.items():
|
|
2114
|
+
current_wire = stored_schedule.get(root_key)
|
|
2115
|
+
current = (
|
|
2116
|
+
None if current_wire is None
|
|
2117
|
+
else _parse_utc(current_wire, "next_evaluation_by_root_json")
|
|
2118
|
+
)
|
|
2119
|
+
if root_key in schedule_evaluated_roots or current is None or captured_at < current:
|
|
2120
|
+
stored_schedule[root_key] = _utc_iso(captured_at)
|
|
2121
|
+
# Inactive roots have no lifecycle owner and cannot dispatch. Keeping their
|
|
2122
|
+
# deadlines would create an unretirable scheduled axis.
|
|
2123
|
+
if prune_inactive_schedule:
|
|
2124
|
+
stored_schedule = {
|
|
2125
|
+
root_key: captured_at
|
|
2126
|
+
for root_key, captured_at in stored_schedule.items()
|
|
2127
|
+
if root_key in active_roots
|
|
2128
|
+
}
|
|
2129
|
+
schedule_boundary = min(stored_schedule.values(), default=None)
|
|
2130
|
+
# A scalar-only boundary is a legacy/fail-safe state with unknown ownership.
|
|
2131
|
+
# Keep the prior behavior until a qualifying whole-history pass can retire
|
|
2132
|
+
# it; epoch-1007 indexes normally never enter this branch.
|
|
2133
|
+
legacy_boundary = stored_boundary if not stored_next_evaluation_by_root else None
|
|
2134
|
+
boundary = (
|
|
2135
|
+
None if schedule_boundary is None
|
|
2136
|
+
else _parse_utc(schedule_boundary, "next_evaluation_by_root_json")
|
|
2052
2137
|
)
|
|
2138
|
+
if legacy_boundary is not None:
|
|
2139
|
+
retained_legacy = _axes.next_evaluation_boundary(
|
|
2140
|
+
capture_times=(), now=now, stored=legacy_boundary,
|
|
2141
|
+
retain_due=not consume_alert_axes,
|
|
2142
|
+
)
|
|
2143
|
+
if retained_legacy is not None and (
|
|
2144
|
+
boundary is None or retained_legacy < boundary
|
|
2145
|
+
):
|
|
2146
|
+
boundary = retained_legacy
|
|
2053
2147
|
_store_ledger_state(
|
|
2054
2148
|
conn, watermark=0 if ledger_watermark is None else ledger_watermark,
|
|
2055
2149
|
alerts_enabled=(
|
|
2056
2150
|
alerts_enabled if consume_alert_axes else stored_alerts_enabled),
|
|
2057
2151
|
next_evaluation_at=None if boundary is None else _utc_iso(boundary),
|
|
2152
|
+
next_evaluation_by_root=stored_schedule,
|
|
2058
2153
|
# Spec §2: EVERY full pass stamps the verification deadline,
|
|
2059
2154
|
# whatever triggered it — the interval itself, a rebuild, an
|
|
2060
2155
|
# interpretation bump, `force_full`, or a dirty-unit burst
|
|
@@ -2064,7 +2159,9 @@ def _apply_quota_projection_rows(
|
|
|
2064
2159
|
# untouched; overwriting it would let an install that never
|
|
2065
2160
|
# bursts postpone verification forever.
|
|
2066
2161
|
last_full_pass_at=(
|
|
2067
|
-
now_iso
|
|
2162
|
+
now_iso
|
|
2163
|
+
if dirty_units is None and reconcile_roots is None
|
|
2164
|
+
else stored_last_full_pass_at),
|
|
2068
2165
|
)
|
|
2069
2166
|
if sink is not None:
|
|
2070
2167
|
# Set-then-dispatch: all claims committed with the cycle before the
|
|
@@ -2078,7 +2175,7 @@ def _apply_quota_projection_rows(
|
|
|
2078
2175
|
milestones_upserted=sum(len(percent_milestones(b)) for b in blocks),
|
|
2079
2176
|
blocks_orphaned=blocks_orphaned,
|
|
2080
2177
|
milestones_orphaned=milestones_orphaned,
|
|
2081
|
-
roots_stamped=len(
|
|
2178
|
+
roots_stamped=len(roots_to_stamp),
|
|
2082
2179
|
alerts_dispatched=len(queued),
|
|
2083
2180
|
)
|
|
2084
2181
|
|
|
@@ -2130,6 +2227,7 @@ def rematerialize_quota_projection_for_rebuild(stats_conn, *, now=None) -> None:
|
|
|
2130
2227
|
# value — `gate_before is not True` makes the next alert-eligible pass
|
|
2131
2228
|
# widen and do the activation.
|
|
2132
2229
|
consume_alert_axes=True,
|
|
2230
|
+
prune_inactive_schedule=True,
|
|
2133
2231
|
)
|
|
2134
2232
|
|
|
2135
2233
|
|
|
@@ -2260,12 +2358,10 @@ def reconcile_codex_quota_projection(
|
|
|
2260
2358
|
delay it. Both fire on a config change the user just made, so the cost is
|
|
2261
2359
|
bounded and attributable.
|
|
2262
2360
|
|
|
2263
|
-
Axis 4
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
and apply on the blocking path with nothing to have predicted it. A BOUNDED
|
|
2268
|
-
tick carries the stored boundary through untouched instead.
|
|
2361
|
+
Axis 4 fires on wall clock. Epoch 1007 persists its owning roots, so a due
|
|
2362
|
+
hook tick performs complete passes only for the matured roots. A legacy or
|
|
2363
|
+
inconsistent scalar-only boundary still defers under ``"defer"`` because
|
|
2364
|
+
its only honest fallback is whole history.
|
|
2269
2365
|
|
|
2270
2366
|
A tick that widened to whole-history for axis 2 or 3 anyway does retire it,
|
|
2271
2367
|
because it did look: at every observation of every active root. Withholding
|
|
@@ -2274,11 +2370,8 @@ def reconcile_codex_quota_projection(
|
|
|
2274
2370
|
declining both left the same scope standing and repeated the whole-history
|
|
2275
2371
|
pass inline on every subsequent tick.
|
|
2276
2372
|
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
matured boundary is retained indefinitely. Bounded in cost (the tick stays
|
|
2280
|
-
bounded and fast; the window is re-evaluated the moment it goes
|
|
2281
|
-
ledger-dirty) and under-alerting in direction, but open.
|
|
2373
|
+
A quiet owned boundary is therefore consumed without broadening the hook to
|
|
2374
|
+
unrelated roots; the following quiet tick returns to the zero-group path.
|
|
2282
2375
|
"""
|
|
2283
2376
|
if full_pass not in ("inline", "defer"):
|
|
2284
2377
|
raise ValueError(
|
|
@@ -2341,6 +2434,7 @@ def reconcile_codex_quota_projection(
|
|
|
2341
2434
|
global_enabled=global_alerts_enabled,
|
|
2342
2435
|
quota_enabled=quota_alerts_enabled,
|
|
2343
2436
|
rules=_rules, config=_config,
|
|
2437
|
+
eligible_roots=alert_eligible_roots,
|
|
2344
2438
|
defer_scheduled=(full_pass == "defer"),
|
|
2345
2439
|
)
|
|
2346
2440
|
finally:
|
|
@@ -2430,6 +2524,10 @@ def reconcile_codex_quota_projection(
|
|
|
2430
2524
|
stale_reverse_map=stale_reverse_map,
|
|
2431
2525
|
verification_due=verification_due,
|
|
2432
2526
|
)
|
|
2527
|
+
# ``None`` means the genuine whole-history path. A set means complete
|
|
2528
|
+
# histories for only those roots — axis 2 and epoch-1007 axis 4 can both
|
|
2529
|
+
# be satisfied without scanning unrelated roots.
|
|
2530
|
+
reconcile_roots: "set[str] | None" = None
|
|
2433
2531
|
if dirty_units is None and full_pass == "defer" and not force_full:
|
|
2434
2532
|
# The rule, for every OTHER route into a whole-history pass: an
|
|
2435
2533
|
# absent or freshly rebuilt projector state, an interpretation-
|
|
@@ -2465,25 +2563,28 @@ def reconcile_codex_quota_projection(
|
|
|
2465
2563
|
# just made, so the widening is bounded and expected, and it stays
|
|
2466
2564
|
# inline even under `defer`.
|
|
2467
2565
|
#
|
|
2468
|
-
# Axis 4
|
|
2469
|
-
#
|
|
2470
|
-
#
|
|
2471
|
-
#
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
dirty_units = None
|
|
2566
|
+
# Axis 4 is root-scoped when epoch-1007 ownership is available. A
|
|
2567
|
+
# scalar-only legacy boundary still records `REASON_SCHEDULED_DEFERRED`
|
|
2568
|
+
# under `full_pass="defer"`, because whole history remains its only
|
|
2569
|
+
# honest fallback.
|
|
2570
|
+
if dirty_units is not None and alert_scope is not None:
|
|
2571
|
+
if alert_scope.scope == _axes.SCOPE_ALL:
|
|
2572
|
+
dirty_units = None
|
|
2573
|
+
elif alert_scope.scope == _axes.SCOPE_ROOTS:
|
|
2574
|
+
# Reconcile every alert-invalidated root in full. If physical
|
|
2575
|
+
# ledger work landed on the same tick, promote its roots too so
|
|
2576
|
+
# the watermark can advance without skipping those mutations.
|
|
2577
|
+
reconcile_roots = set(alert_scope.roots)
|
|
2578
|
+
reconcile_roots.update(
|
|
2579
|
+
str(group[0]) for group in dirty_units["raw_groups"])
|
|
2580
|
+
dirty_units = None
|
|
2484
2581
|
if dirty_units is None:
|
|
2485
2582
|
observations = load_codex_quota_observations(
|
|
2486
|
-
source_root_keys=
|
|
2583
|
+
source_root_keys=(
|
|
2584
|
+
active_roots if reconcile_roots is None
|
|
2585
|
+
else active_roots & reconcile_roots
|
|
2586
|
+
),
|
|
2587
|
+
cache_conn=cache,
|
|
2487
2588
|
)
|
|
2488
2589
|
else:
|
|
2489
2590
|
observations = load_codex_quota_observations(
|
|
@@ -2539,6 +2640,12 @@ def reconcile_codex_quota_projection(
|
|
|
2539
2640
|
or alert_scope is None
|
|
2540
2641
|
or _axes.REASON_SCHEDULED_DEFERRED not in alert_scope.reasons
|
|
2541
2642
|
)
|
|
2643
|
+
complete_roots = (
|
|
2644
|
+
(active_roots if reconcile_roots is None else active_roots & reconcile_roots)
|
|
2645
|
+
if dirty_units is None else set()
|
|
2646
|
+
)
|
|
2647
|
+
schedule_evaluated_roots = frozenset(
|
|
2648
|
+
complete_roots & alert_eligible_roots)
|
|
2542
2649
|
|
|
2543
2650
|
# ── Apply phase (Task 7 Item 3) ─────────────────────────────────────────
|
|
2544
2651
|
# The stats.db writes route through the single-flight ingest cycle instead
|
|
@@ -2581,7 +2688,16 @@ def reconcile_codex_quota_projection(
|
|
|
2581
2688
|
stored_alerts_enabled=(
|
|
2582
2689
|
None if ledger_state is None
|
|
2583
2690
|
else ledger_state["alerts_enabled"]),
|
|
2691
|
+
stored_next_evaluation_by_root=(
|
|
2692
|
+
{} if ledger_state is None
|
|
2693
|
+
else ledger_state["next_evaluation_by_root"]),
|
|
2584
2694
|
consume_alert_axes=consume_alert_axes,
|
|
2695
|
+
reconcile_roots=reconcile_roots,
|
|
2696
|
+
schedule_evaluated_roots=schedule_evaluated_roots,
|
|
2697
|
+
prune_inactive_schedule=(
|
|
2698
|
+
source_root_keys is None
|
|
2699
|
+
and dirty_units is None
|
|
2700
|
+
and reconcile_roots is None),
|
|
2585
2701
|
)
|
|
2586
2702
|
|
|
2587
2703
|
import _cctally_journal as _jr
|
package/bin/_cctally_record.py
CHANGED
|
@@ -4915,8 +4915,20 @@ def _cmd_hook_tick_codex(
|
|
|
4915
4915
|
{}, conn=ictx.conn, alert_sink=ictx.pending_alerts,
|
|
4916
4916
|
raise_errors=True) or 0)
|
|
4917
4917
|
|
|
4918
|
-
_jr_codex.run_stats_ingest(
|
|
4918
|
+
budget_ingest = _jr_codex.run_stats_ingest(
|
|
4919
4919
|
mode="authoritative", codex_apply=_codex_budget_leg)
|
|
4920
|
+
if not budget_ingest.ran:
|
|
4921
|
+
# A detached quota-verification worker can own the ingest lock
|
|
4922
|
+
# while applying its projection. Authoritative ingest reports
|
|
4923
|
+
# that bounded timeout as `ran=False`; acknowledging the root
|
|
4924
|
+
# here would throttle away the skipped budget evaluation. Keep
|
|
4925
|
+
# the lifecycle markers due so the first uncontended tick
|
|
4926
|
+
# retries the forward-only budget crossing.
|
|
4927
|
+
log_outcome(
|
|
4928
|
+
sync="contended", result="noop", projection=projection,
|
|
4929
|
+
backlog=int(getattr(stats, "backlog_files", 0) or 0),
|
|
4930
|
+
)
|
|
4931
|
+
return 0
|
|
4920
4932
|
budget_alerts = _budget_holder["n"]
|
|
4921
4933
|
if getattr(stats, "deferred_reason", None) == "replay_pending":
|
|
4922
4934
|
# public #5: the budgeted tick declined a byte-zero replay, and on
|
package/bin/_cctally_rederive.py
CHANGED
|
@@ -779,6 +779,7 @@ def apply_db_rederive(
|
|
|
779
779
|
try:
|
|
780
780
|
_call_crash_hook("after-batch-commit")
|
|
781
781
|
result = _journal.rebuild_stats_index(
|
|
782
|
+
context=_journal.RebuildContext(trigger="rederive-apply"),
|
|
782
783
|
high_water=batch_high_water,
|
|
783
784
|
update_quota_cache=False,
|
|
784
785
|
before_swap=lambda: _call_crash_hook(
|
|
@@ -810,6 +811,9 @@ def apply_db_rederive(
|
|
|
810
811
|
)
|
|
811
812
|
try:
|
|
812
813
|
result = _journal.rebuild_stats_index(
|
|
814
|
+
context=_journal.RebuildContext(
|
|
815
|
+
trigger="rederive-recovery"
|
|
816
|
+
),
|
|
813
817
|
high_water=preview.latest_completed_high_water,
|
|
814
818
|
update_quota_cache=False,
|
|
815
819
|
before_swap=lambda: _call_crash_hook(
|