cctally 1.114.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 +25 -0
- package/bin/_cctally_account.py +32 -8
- package/bin/_cctally_cache.py +4394 -279
- package/bin/_cctally_completion.py +1287 -0
- package/bin/_cctally_config.py +37 -0
- package/bin/_cctally_core.py +178 -2
- package/bin/_cctally_dashboard.py +2 -2
- package/bin/_cctally_dashboard_conversation.py +292 -23
- package/bin/_cctally_dashboard_envelope.py +16 -3
- package/bin/_cctally_dashboard_sources.py +18 -9
- package/bin/_cctally_db.py +316 -5
- package/bin/_cctally_doctor.py +436 -27
- package/bin/_cctally_five_hour.py +82 -31
- package/bin/_cctally_forecast.py +81 -9
- package/bin/_cctally_journal.py +3884 -112
- package/bin/_cctally_journal_repair.py +101 -12
- package/bin/_cctally_parser.py +42 -0
- package/bin/_cctally_project.py +8 -2
- package/bin/_cctally_quarantine.py +652 -0
- package/bin/_cctally_quota.py +26 -4
- package/bin/_cctally_quota_model.py +360 -83
- package/bin/_cctally_record.py +677 -149
- package/bin/_cctally_rederive.py +297 -32
- package/bin/_cctally_refresh.py +84 -13
- package/bin/_cctally_source_analytics.py +23 -2
- package/bin/_cctally_statusline.py +6137 -517
- package/bin/_cctally_store.py +287 -48
- package/bin/_cctally_transcript.py +8 -7
- package/bin/_cctally_tui.py +57 -53
- 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_writer_lock.py +19 -3
- package/bin/_lib_clone_copy.py +142 -0
- package/bin/_lib_codex_conversation_query.py +133 -19
- 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 +11 -3
- package/bin/_lib_detector_access_registry.py +3817 -0
- package/bin/_lib_doctor.py +452 -1
- package/bin/_lib_five_hour.py +126 -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 +29 -302
- package/bin/_lib_meter_rate_change.py +10 -0
- package/bin/_lib_outcome_quarantine.py +690 -0
- package/bin/_lib_pricing.py +7 -2
- 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_rederive.py +94 -5
- package/bin/_lib_segment_summary.py +35 -2
- package/bin/_lib_statusline_candidates.py +292 -23
- package/bin/cctally +23 -1
- package/package.json +14 -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(
|
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
|
|
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:
|