cctally 1.114.0 → 1.116.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/bin/_cctally_account.py +32 -8
- package/bin/_cctally_cache.py +4491 -295
- package/bin/_cctally_completion.py +1355 -0
- package/bin/_cctally_config.py +37 -0
- package/bin/_cctally_core.py +282 -9
- package/bin/_cctally_dashboard.py +8 -11
- package/bin/_cctally_dashboard_conversation.py +292 -23
- package/bin/_cctally_dashboard_envelope.py +42 -35
- package/bin/_cctally_dashboard_sources.py +18 -9
- package/bin/_cctally_db.py +401 -9
- package/bin/_cctally_doctor.py +469 -27
- package/bin/_cctally_five_hour.py +166 -1049
- package/bin/_cctally_forecast.py +81 -9
- package/bin/_cctally_journal.py +5076 -177
- package/bin/_cctally_journal_repair.py +110 -19
- package/bin/_cctally_oauth_samples.py +280 -0
- package/bin/_cctally_parser.py +47 -0
- package/bin/_cctally_project.py +8 -2
- package/bin/_cctally_quarantine.py +662 -0
- package/bin/_cctally_quota.py +96 -12
- package/bin/_cctally_quota_model.py +391 -83
- package/bin/_cctally_record.py +1088 -269
- package/bin/_cctally_rederive.py +854 -192
- package/bin/_cctally_refresh.py +113 -13
- package/bin/_cctally_source_analytics.py +23 -2
- package/bin/_cctally_statusline.py +7062 -505
- package/bin/_cctally_store.py +1244 -91
- package/bin/_cctally_transcript.py +8 -7
- package/bin/_cctally_tui.py +119 -60
- package/bin/_cctally_update.py +25 -0
- package/bin/_lib_account_binding.py +152 -0
- package/bin/_lib_account_protocol.py +569 -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 +177 -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_dashboard_sources.py +57 -15
- package/bin/_lib_detector_access_registry.py +4038 -0
- package/bin/_lib_doctor.py +753 -1
- package/bin/_lib_five_hour.py +126 -0
- package/bin/_lib_ingest_frontier.py +414 -20
- package/bin/_lib_journal.py +261 -10
- package/bin/_lib_journal_router.py +166 -48
- 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_meter_rate_change.py +10 -0
- package/bin/_lib_oauth_samples.py +238 -0
- package/bin/_lib_outcome_quarantine.py +690 -0
- package/bin/_lib_pricing.py +7 -2
- package/bin/_lib_pricing_check.py +0 -12
- package/bin/_lib_provider_activity.py +770 -0
- package/bin/_lib_quarantine.py +155 -0
- package/bin/_lib_quota_calibration.py +53 -8
- package/bin/_lib_quota_copy.py +2 -1
- package/bin/_lib_quota_model.py +384 -52
- package/bin/_lib_rebuild_progress.py +146 -0
- package/bin/_lib_record.py +22 -0
- package/bin/_lib_rederive.py +393 -8
- package/bin/_lib_segment_summary.py +35 -2
- package/bin/_lib_snapshot_cache.py +43 -1
- package/bin/_lib_stale_proof.py +708 -0
- package/bin/_lib_statusline_candidates.py +356 -27
- package/bin/_lib_worker_launches.py +315 -0
- package/bin/cctally +29 -12
- package/dashboard/static/assets/{ConversationsView-Bc8LCOAZ.js → ConversationsView-Dflb_myA.js} +1 -1
- package/dashboard/static/assets/DoctorModal-DFX5CfIs.js +1 -0
- package/dashboard/static/assets/{ModalRoot-DLPgyAM_.js → ModalRoot-HlxoagIa.js} +1 -1
- package/dashboard/static/assets/{ProjectsDrillPanel-z53a4v4M.js → ProjectsDrillPanel-C3PJy1xx.js} +1 -1
- package/dashboard/static/assets/{SourceDetailModal-Bw0T8W-W.js → SourceDetailModal-BczsWKhD.js} +1 -1
- package/dashboard/static/assets/{UpdateModal-D5AKx4Yw.js → UpdateModal-Os-Lo5cs.js} +1 -1
- package/dashboard/static/assets/{index-BXDOzSng.js → index-CYZ3NLqF.js} +3 -3
- package/dashboard/static/assets/{outlineNavigation-JObY7cUY.js → outlineNavigation-k-zIif6W.js} +1 -1
- package/dashboard/static/dashboard.html +1 -1
- package/package.json +20 -2
- package/bin/_lib_merge_gate.py +0 -725
- package/dashboard/static/assets/DoctorModal-D--qVBwe.js +0 -1
package/bin/_cctally_config.py
CHANGED
|
@@ -529,6 +529,43 @@ def resolve_retention_days(config: dict) -> int:
|
|
|
529
529
|
return default
|
|
530
530
|
|
|
531
531
|
|
|
532
|
+
#: The test-only switch (#965). Honored only at exactly "1", only when no
|
|
533
|
+
#: retention setting exists, and only for code running from a git checkout.
|
|
534
|
+
CONVERSATION_RETENTION_TEST_SWITCH = "CCTALLY_TEST_CONVERSATION_RETENTION_DEFAULT_OFF"
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def _conversation_retention_setting_absent(config) -> bool:
|
|
538
|
+
"""True when no ``conversation.retention_days`` setting exists (#965 5.2).
|
|
539
|
+
|
|
540
|
+
Absent: no ``conversation`` key, or a ``conversation`` dictionary without
|
|
541
|
+
``retention_days``. A present key of any value (``null`` and malformed
|
|
542
|
+
values included), a non-dictionary ``conversation`` and a non-dictionary
|
|
543
|
+
config are not absent: they keep ``resolve_retention_days``'s meaning."""
|
|
544
|
+
if not isinstance(config, dict):
|
|
545
|
+
return False
|
|
546
|
+
if "conversation" not in config:
|
|
547
|
+
return True
|
|
548
|
+
block = config["conversation"]
|
|
549
|
+
return isinstance(block, dict) and "retention_days" not in block
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def resolve_prune_retention_days(config: dict) -> int:
|
|
553
|
+
"""Operational transcript-retention window of the three prune paths (#965).
|
|
554
|
+
|
|
555
|
+
``resolve_retention_days`` stays the reporting resolver (``config get``,
|
|
556
|
+
the settings manifest, docs). This one differs in exactly one case: with
|
|
557
|
+
no retention setting, the test switch at exactly ``"1"`` and a ``.git``
|
|
558
|
+
entry at the running code's root, it returns ``0`` (keep forever). The
|
|
559
|
+
switch and the marker are read on every call; nothing is cached, written
|
|
560
|
+
or logged. docs/fixtures-gotchas.md, "Conversation retention is off in
|
|
561
|
+
test environments (#965)"."""
|
|
562
|
+
if (_conversation_retention_setting_absent(config)
|
|
563
|
+
and os.environ.get(CONVERSATION_RETENTION_TEST_SWITCH) == "1"
|
|
564
|
+
and _cctally_core._has_git_checkout_marker()):
|
|
565
|
+
return 0
|
|
566
|
+
return resolve_retention_days(config)
|
|
567
|
+
|
|
568
|
+
|
|
532
569
|
def _config_codex_leaf_value(config: dict, key: str) -> object:
|
|
533
570
|
"""Read one nested Codex budget leaf with its public default.
|
|
534
571
|
|
package/bin/_cctally_core.py
CHANGED
|
@@ -76,6 +76,9 @@ def _init_paths_from_env() -> None:
|
|
|
76
76
|
global STATUSLINE_CANDIDATE_DIR, STATUSLINE_SELECTED_PATH
|
|
77
77
|
global STATUSLINE_TRANSPORT_MARKER_PATH
|
|
78
78
|
global STATUSLINE_AUTHORITATIVE_7D_PATH, STATUSLINE_AUTHORITATIVE_5H_PATH
|
|
79
|
+
global STATUSLINE_ACCOUNT_BOUND_DIR, LEGACY_SPOOL_BASELINE_PATH
|
|
80
|
+
global ACCOUNT_BOUND_CUTOVER_CACHE_PATH, LEGACY_WRITER_EVIDENCE_PATH
|
|
81
|
+
global STATUSLINE_ACCOUNT_BOUND_V1_DIR, ACCOUNT_BOUND_REVISION_CACHE_PATH
|
|
79
82
|
global OAUTH_BACKOFF_MARKER_PATH, OAUTH_BACKOFF_COUNT_PATH
|
|
80
83
|
global UPDATE_STATE_PATH, UPDATE_SUPPRESS_PATH
|
|
81
84
|
global UPDATE_LOCK_PATH, UPDATE_LOG_PATH, UPDATE_LOG_ROTATED_PATH
|
|
@@ -184,6 +187,33 @@ def _init_paths_from_env() -> None:
|
|
|
184
187
|
STATUSLINE_TRANSPORT_MARKER_PATH = APP_DIR / "statusline-transport.last"
|
|
185
188
|
STATUSLINE_AUTHORITATIVE_7D_PATH = APP_DIR / "statusline-authoritative-7d.json"
|
|
186
189
|
STATUSLINE_AUTHORITATIVE_5H_PATH = APP_DIR / "statusline-authoritative-5h.json"
|
|
190
|
+
# #825/#832 account-bound protocol (spec section 7). Every current
|
|
191
|
+
# coordination file lives under its own sibling root; the legacy paths
|
|
192
|
+
# above are never read as authority after the cutover, only compared with
|
|
193
|
+
# the journal-anchored legacy spool baseline. The coordinate cache only
|
|
194
|
+
# accelerates locating the cutover op and never confers authority; the
|
|
195
|
+
# legacy-writer evidence sidecar is diagnostic only (spec section 8).
|
|
196
|
+
#
|
|
197
|
+
# #797/#823/#840 (spec 4.6 B4): revision 2 of the fence moves every
|
|
198
|
+
# current coordination file — candidates, intake, evaluations, control,
|
|
199
|
+
# pending and retry state, acknowledgements, tombstones, frozen plans,
|
|
200
|
+
# locks, transition and recovery metadata, and the protocol files below
|
|
201
|
+
# — to the v2 root. No v1 state is imported. The v1 root is retired: its
|
|
202
|
+
# `accounts/` tree and `transport.last` join the legacy spool inventory
|
|
203
|
+
# (`_cctally_journal._legacy_spool_paths`), and this binary never writes,
|
|
204
|
+
# prunes or deletes anything under it.
|
|
205
|
+
STATUSLINE_ACCOUNT_BOUND_V1_DIR = APP_DIR / "statusline-account-bound-v1"
|
|
206
|
+
STATUSLINE_ACCOUNT_BOUND_DIR = APP_DIR / "statusline-account-bound-v2"
|
|
207
|
+
LEGACY_SPOOL_BASELINE_PATH = (
|
|
208
|
+
STATUSLINE_ACCOUNT_BOUND_DIR / "legacy-spool-baseline.json")
|
|
209
|
+
ACCOUNT_BOUND_CUTOVER_CACHE_PATH = (
|
|
210
|
+
STATUSLINE_ACCOUNT_BOUND_DIR / "cutover-coordinate.json")
|
|
211
|
+
#: The revision boundary's coordinate cache; like the cutover's, it only
|
|
212
|
+
#: accelerates the lookup and never confers authority.
|
|
213
|
+
ACCOUNT_BOUND_REVISION_CACHE_PATH = (
|
|
214
|
+
STATUSLINE_ACCOUNT_BOUND_DIR / "revision-coordinate.json")
|
|
215
|
+
LEGACY_WRITER_EVIDENCE_PATH = (
|
|
216
|
+
STATUSLINE_ACCOUNT_BOUND_DIR / "legacy-writer-evidence.json")
|
|
187
217
|
OAUTH_BACKOFF_MARKER_PATH = APP_DIR / "oauth-backoff.until"
|
|
188
218
|
# Consecutive-429 counter (text int) driving the headerless exponential
|
|
189
219
|
# backoff (base * 2**count). Separate from the deadline marker so the
|
|
@@ -261,6 +291,18 @@ def _repo_root() -> pathlib.Path:
|
|
|
261
291
|
return pathlib.Path(__file__).resolve().parent.parent
|
|
262
292
|
|
|
263
293
|
|
|
294
|
+
def _has_git_checkout_marker() -> bool:
|
|
295
|
+
"""True iff the running code has a ``.git`` entry at its root (#965 5.1).
|
|
296
|
+
|
|
297
|
+
A main checkout has a ``.git`` directory and a linked worktree a ``.git``
|
|
298
|
+
file; both count. Eligibility follows the code's resolved location
|
|
299
|
+
(``_repo_root()``), never the current directory, and is evaluated on every
|
|
300
|
+
call. No suppressor (``CCTALLY_DISABLE_DEV_AUTODETECT``,
|
|
301
|
+
``CCTALLY_ALLOW_PROD_MIGRATION``) affects it. npm and Homebrew installs
|
|
302
|
+
ship without ``.git``, so they never read True."""
|
|
303
|
+
return (_repo_root() / ".git").exists()
|
|
304
|
+
|
|
305
|
+
|
|
264
306
|
def _is_dev_checkout() -> bool:
|
|
265
307
|
"""True iff running from a git checkout (a ``.git`` entry at the repo
|
|
266
308
|
root — a directory for a main checkout, a file for a worktree) AND the
|
|
@@ -272,7 +314,7 @@ def _is_dev_checkout() -> bool:
|
|
|
272
314
|
install copies ship without ``.git`` so they never read True."""
|
|
273
315
|
if _truthy_env("CCTALLY_DISABLE_DEV_AUTODETECT"):
|
|
274
316
|
return False
|
|
275
|
-
return (
|
|
317
|
+
return _has_git_checkout_marker()
|
|
276
318
|
|
|
277
319
|
|
|
278
320
|
def is_preview_channel() -> bool:
|
|
@@ -487,9 +529,35 @@ _init_paths_from_env()
|
|
|
487
529
|
# reason this is a bump and not a migration is unchanged: the registry is
|
|
488
530
|
# frozen at 13, and an epoch-current open returns before any schema work, so a
|
|
489
531
|
# schema helper alone would never run on an upgraded install.
|
|
490
|
-
|
|
532
|
+
# 1017 -> 1018 (#825/#832 spec section 6): the same journal now has a different
|
|
533
|
+
# authoritative interpretation — the account-bound admission predicate excludes
|
|
534
|
+
# unstamped post-cutover records before any derivation — so an index
|
|
535
|
+
# materialized under the old interpretation is not current even though its
|
|
536
|
+
# schema is usable. The epoch also carries the disposable
|
|
537
|
+
# `journal_admission_summary` table, whose presence is the protocol marker
|
|
538
|
+
# `_cctally_store.validated_read_snapshot` checks inside each decision
|
|
539
|
+
# scope's own snapshot. An epoch transition, not a numbered migration: the
|
|
540
|
+
# registry stays frozen at 13.
|
|
541
|
+
# 1018 -> 1019 (#797/#823/#840 spec 4.6 B6): revision 2 of the fence changes
|
|
542
|
+
# the authoritative interpretation of retained journal records again — after
|
|
543
|
+
# the `account_bound_revision` boundary a Claude-scoped record without
|
|
544
|
+
# `wpr == 2` is excluded — so an index, a derivation-completeness proof or a
|
|
545
|
+
# selector continuation produced under revision 1 is not current. The #825
|
|
546
|
+
# section 6 precedent; still no numbered migration and no accounting column.
|
|
547
|
+
# The same epoch carries the provider-sample index state of stage 1 (#797
|
|
548
|
+
# Task 2, spec 4.3 D8 and D11), so 1019 is introduced once: the
|
|
549
|
+
# `claude_oauth_samples` projection of raw api observations carrying
|
|
550
|
+
# `oauth_request_started_at`, the `claude_weekly_observation_decisions`
|
|
551
|
+
# projection of reviewed weekly decision ops that resolves a sample's weekly
|
|
552
|
+
# eligibility at read time, and four NULLABLE identity columns on
|
|
553
|
+
# `five_hour_credit_confirmation_state` (`baseline_observation_id`,
|
|
554
|
+
# `baseline_generation`, `pending_generation`, `pending_position`) that the
|
|
555
|
+
# five-hour credit generation (Task 5) writes. Both projections are rebuilt
|
|
556
|
+
# from the journal; the confirmation state stays disposable.
|
|
557
|
+
STATS_INDEX_EPOCH = 1019
|
|
491
558
|
LEGACY_STATS_HEAD = 13
|
|
492
559
|
|
|
560
|
+
|
|
493
561
|
#: #496 S1 F1. A NEW branch, for a state that cannot occur before the
|
|
494
562
|
#: publication transaction exists: a replacement index was published and then
|
|
495
563
|
#: failed validation on a fresh connection. The existing corrupt-stats text
|
|
@@ -2654,20 +2722,86 @@ def open_db(*, _target_path=None) -> sqlite3.Connection:
|
|
|
2654
2722
|
# above: a rebuild legitimately loses it, and the cold-start
|
|
2655
2723
|
# consequence is stated at `_read_five_hour_source_state` in
|
|
2656
2724
|
# `bin/_cctally_record.py`.
|
|
2725
|
+
#
|
|
2726
|
+
# #797/#823/#840 epoch 1019 (spec 4.9 D11): the four identity columns
|
|
2727
|
+
# the five-hour credit generation writes, all NULL until it does.
|
|
2728
|
+
# `baseline_observation_id` names the observation that set the
|
|
2729
|
+
# baseline; `baseline_generation` / `pending_generation` are the
|
|
2730
|
+
# effective five-hour credit generation the baseline and the armed
|
|
2731
|
+
# descent were observed under, encoded `"<event_id>@<revision>"` with
|
|
2732
|
+
# the no-credit sentinel `"none"`; `pending_position` is the arming
|
|
2733
|
+
# observation's logical position. Columns of the CREATE, not an
|
|
2734
|
+
# `add_column_if_missing`: an epoch-current open returns before any
|
|
2735
|
+
# schema work, so only the epoch's fresh CREATE can carry them.
|
|
2657
2736
|
conn.execute(
|
|
2658
2737
|
"""
|
|
2659
2738
|
CREATE TABLE IF NOT EXISTS five_hour_credit_confirmation_state (
|
|
2660
|
-
account_key
|
|
2661
|
-
five_hour_window_key
|
|
2662
|
-
source
|
|
2663
|
-
baseline_pct
|
|
2664
|
-
pending_low_pct
|
|
2665
|
-
pending_at_utc
|
|
2666
|
-
pending_observation_id
|
|
2739
|
+
account_key TEXT NOT NULL,
|
|
2740
|
+
five_hour_window_key INTEGER NOT NULL,
|
|
2741
|
+
source TEXT NOT NULL,
|
|
2742
|
+
baseline_pct REAL NOT NULL,
|
|
2743
|
+
pending_low_pct REAL,
|
|
2744
|
+
pending_at_utc TEXT,
|
|
2745
|
+
pending_observation_id TEXT,
|
|
2746
|
+
baseline_observation_id TEXT,
|
|
2747
|
+
baseline_generation TEXT,
|
|
2748
|
+
pending_generation TEXT,
|
|
2749
|
+
pending_position TEXT,
|
|
2667
2750
|
PRIMARY KEY (account_key, five_hour_window_key, source)
|
|
2668
2751
|
)
|
|
2669
2752
|
"""
|
|
2670
2753
|
)
|
|
2754
|
+
# #797/#823/#840 epoch 1019 (spec 4.3 D8, D9): provider samples. One
|
|
2755
|
+
# row per ADMITTED Claude api observation carrying a valid
|
|
2756
|
+
# `oauth_request_started_at` (`requested_at_utc`, canonical
|
|
2757
|
+
# full-precision UTC, so text order is time order), projected from
|
|
2758
|
+
# the RAW record by `_cctally_oauth_samples.project_sample` on every
|
|
2759
|
+
# derivation path, independently of the snapshot fold, the five-hour
|
|
2760
|
+
# adjustment and credit suppression or retirement. Values are the raw
|
|
2761
|
+
# readings, never the MAX-clamped stored ones; `weekly_resets_at_utc`
|
|
2762
|
+
# is nearest-hour normalized and `five_hour_window_key` is the
|
|
2763
|
+
# authoritative canonical key. Journal truth (rebuilt, never pruned),
|
|
2764
|
+
# indexed by account, axis window and `q` for the exact-window read.
|
|
2765
|
+
conn.execute(
|
|
2766
|
+
"""
|
|
2767
|
+
CREATE TABLE IF NOT EXISTS claude_oauth_samples (
|
|
2768
|
+
account_key TEXT NOT NULL,
|
|
2769
|
+
observation_id TEXT NOT NULL,
|
|
2770
|
+
requested_at_utc TEXT NOT NULL,
|
|
2771
|
+
captured_at_utc TEXT NOT NULL,
|
|
2772
|
+
weekly_percent REAL,
|
|
2773
|
+
weekly_resets_at_utc TEXT,
|
|
2774
|
+
five_hour_percent REAL,
|
|
2775
|
+
five_hour_resets_at_utc TEXT,
|
|
2776
|
+
five_hour_window_key INTEGER,
|
|
2777
|
+
PRIMARY KEY (account_key, observation_id)
|
|
2778
|
+
)
|
|
2779
|
+
"""
|
|
2780
|
+
)
|
|
2781
|
+
conn.execute(
|
|
2782
|
+
"CREATE INDEX IF NOT EXISTS idx_claude_oauth_samples_weekly "
|
|
2783
|
+
"ON claude_oauth_samples "
|
|
2784
|
+
"(account_key, weekly_resets_at_utc, requested_at_utc)"
|
|
2785
|
+
)
|
|
2786
|
+
conn.execute(
|
|
2787
|
+
"CREATE INDEX IF NOT EXISTS idx_claude_oauth_samples_five_hour "
|
|
2788
|
+
"ON claude_oauth_samples "
|
|
2789
|
+
"(account_key, five_hour_window_key, requested_at_utc)"
|
|
2790
|
+
)
|
|
2791
|
+
# The effective reviewed weekly decision per observation (latest
|
|
2792
|
+
# `claude_weekly_observation_decision[_v2]` op wins, journal order),
|
|
2793
|
+
# projected by that op's fold applier so a sample's weekly
|
|
2794
|
+
# eligibility is resolved at read time: `hold` excludes the weekly
|
|
2795
|
+
# sample only, a later `accept` restores it.
|
|
2796
|
+
conn.execute(
|
|
2797
|
+
"""
|
|
2798
|
+
CREATE TABLE IF NOT EXISTS claude_weekly_observation_decisions (
|
|
2799
|
+
observation_id TEXT PRIMARY KEY,
|
|
2800
|
+
disposition TEXT NOT NULL,
|
|
2801
|
+
decision_op_id TEXT NOT NULL
|
|
2802
|
+
)
|
|
2803
|
+
"""
|
|
2804
|
+
)
|
|
2671
2805
|
_backfill_week_reset_events(conn)
|
|
2672
2806
|
|
|
2673
2807
|
# ── five_hour_reset_events (Anthropic-issued in-place 5h credits) ──
|
|
@@ -3310,6 +3444,106 @@ def open_db(*, _target_path=None) -> sqlite3.Connection:
|
|
|
3310
3444
|
"cutover_seen INTEGER NOT NULL DEFAULT 0, "
|
|
3311
3445
|
"cutover_account_key TEXT)"
|
|
3312
3446
|
)
|
|
3447
|
+
# #825/#832 epoch 1018 (spec section 8): the bounded, disposable
|
|
3448
|
+
# summary of the complete journal records the admission predicate
|
|
3449
|
+
# EXCLUDED (post-cutover records without the current writer
|
|
3450
|
+
# generation). Ingest extends it inside its cycle transaction and a
|
|
3451
|
+
# rebuild reproduces it from the pinned prefix; `complete_through` is
|
|
3452
|
+
# NULL when the row was started mid-journal (it then describes only
|
|
3453
|
+
# what it saw). Coordinates are JSON `{"segment", "offset"}` text, and
|
|
3454
|
+
# `coordinate_digest` is the order-independent population digest of
|
|
3455
|
+
# every excluded coordinate (`_lib_account_protocol`).
|
|
3456
|
+
conn.execute(
|
|
3457
|
+
"CREATE TABLE IF NOT EXISTS journal_admission_summary ("
|
|
3458
|
+
"id INTEGER PRIMARY KEY CHECK (id = 1), "
|
|
3459
|
+
"cutover_id TEXT, "
|
|
3460
|
+
"scanned_high_water TEXT, "
|
|
3461
|
+
"excluded_total INTEGER NOT NULL DEFAULT 0, "
|
|
3462
|
+
"family_counts_json TEXT NOT NULL DEFAULT '{}', "
|
|
3463
|
+
"first_excluded_coordinate TEXT, "
|
|
3464
|
+
"latest_excluded_coordinate TEXT, "
|
|
3465
|
+
"coordinate_digest TEXT, "
|
|
3466
|
+
"complete_through TEXT, "
|
|
3467
|
+
# The boundary payload this summary classified against, so the
|
|
3468
|
+
# live ingest path resolves admission without scanning the journal.
|
|
3469
|
+
"boundary_json TEXT)"
|
|
3470
|
+
)
|
|
3471
|
+
# #825/#832 fix round A (Codex fix-round-1 Q1): the derivation
|
|
3472
|
+
# frontier. Every admitted derivable input at or before this journal
|
|
3473
|
+
# coordinate has had its decisions journaled by a current binary. Live
|
|
3474
|
+
# ingest advances it with the cursor only while the two are equal; a
|
|
3475
|
+
# rebuild carries it from a trusted predecessor index or sets it after
|
|
3476
|
+
# the shared completion step proved the window. Absent means unknown,
|
|
3477
|
+
# which the completion step reads as "replay from the cutover".
|
|
3478
|
+
# Fix round C (P1-1): the frontier counts CONSUMED INPUTS, and the
|
|
3479
|
+
# decisions a cycle derives from them land beyond it, so the row also
|
|
3480
|
+
# names the output end — the coordinate through which every decision
|
|
3481
|
+
# derived from an input at or before the frontier is journaled, with
|
|
3482
|
+
# nothing between the two derived from a later input. NULL means
|
|
3483
|
+
# nothing is owed beyond the frontier.
|
|
3484
|
+
conn.execute(
|
|
3485
|
+
"CREATE TABLE IF NOT EXISTS journal_derivation_frontier ("
|
|
3486
|
+
"id INTEGER PRIMARY KEY CHECK (id = 1), "
|
|
3487
|
+
"segment TEXT NOT NULL, "
|
|
3488
|
+
"offset INTEGER NOT NULL, "
|
|
3489
|
+
"output_segment TEXT, "
|
|
3490
|
+
"output_offset INTEGER)"
|
|
3491
|
+
)
|
|
3492
|
+
# An index an earlier build of this epoch created lacks the two
|
|
3493
|
+
# columns; additive, so it keeps its frontier row.
|
|
3494
|
+
add_column_if_missing(
|
|
3495
|
+
conn, "journal_derivation_frontier", "output_segment", "TEXT")
|
|
3496
|
+
add_column_if_missing(
|
|
3497
|
+
conn, "journal_derivation_frontier", "output_offset", "INTEGER")
|
|
3498
|
+
# Revision 2 (#946, Codex design-fix-round-3 Q5): the proof
|
|
3499
|
+
# generation. A row written under the old traversal rule (NULL here)
|
|
3500
|
+
# may name a valid endpoint past an input nothing derived, so only
|
|
3501
|
+
# `_cctally_journal.DERIVATION_PROOF_GENERATION` is trusted.
|
|
3502
|
+
add_column_if_missing(
|
|
3503
|
+
conn, "journal_derivation_frontier", "proof_generation", "INTEGER")
|
|
3504
|
+
# #825/#832 fix round A (Codex fix-round-1 Q2): the physical revision
|
|
3505
|
+
# inventory of every EXCLUDED event or correction action the index has
|
|
3506
|
+
# consumed, by logical id. Admission hides these from effective
|
|
3507
|
+
# selection, so without it a current writer would classify the id NEW
|
|
3508
|
+
# and append a revision-0 twin the previous release reads as a
|
|
3509
|
+
# same-revision conflict.
|
|
3510
|
+
conn.execute(
|
|
3511
|
+
"CREATE TABLE IF NOT EXISTS journal_excluded_event_revisions ("
|
|
3512
|
+
"event_id TEXT PRIMARY KEY, "
|
|
3513
|
+
"max_rev INTEGER NOT NULL)"
|
|
3514
|
+
)
|
|
3515
|
+
# #825/#832 epoch 1018 (spec 5.6 item 9): the outcome quarantine. A
|
|
3516
|
+
# deterministic function of journal order that ingest extends inside
|
|
3517
|
+
# its cycle transaction and a rebuild reproduces from the pinned
|
|
3518
|
+
# prefix: one row per uncertainty trigger (an excluded record or a
|
|
3519
|
+
# current `legacy_spool_activity` op, at its physical coordinate), the
|
|
3520
|
+
# whole-prefix envelope per scope ("excluded" applies to every
|
|
3521
|
+
# account), and each account's accounting and control certificates
|
|
3522
|
+
# with the trigger sequence they were granted against. A certificate
|
|
3523
|
+
# granted against an older sequence is void; a missing or
|
|
3524
|
+
# unverifiable row reads as quarantined.
|
|
3525
|
+
conn.execute(
|
|
3526
|
+
"CREATE TABLE IF NOT EXISTS outcome_quarantine_triggers ("
|
|
3527
|
+
"seq INTEGER PRIMARY KEY, "
|
|
3528
|
+
"segment TEXT, "
|
|
3529
|
+
"byte_offset INTEGER, "
|
|
3530
|
+
"kind TEXT NOT NULL, "
|
|
3531
|
+
"reason TEXT NOT NULL, "
|
|
3532
|
+
"at TEXT)"
|
|
3533
|
+
)
|
|
3534
|
+
conn.execute(
|
|
3535
|
+
"CREATE TABLE IF NOT EXISTS outcome_quarantine_envelope ("
|
|
3536
|
+
"scope TEXT PRIMARY KEY, "
|
|
3537
|
+
"envelope_json TEXT NOT NULL)"
|
|
3538
|
+
)
|
|
3539
|
+
conn.execute(
|
|
3540
|
+
"CREATE TABLE IF NOT EXISTS outcome_quarantine_certificates ("
|
|
3541
|
+
"account_key TEXT PRIMARY KEY, "
|
|
3542
|
+
"accounting_certificate_json TEXT, "
|
|
3543
|
+
"accounting_trigger_seq INTEGER, "
|
|
3544
|
+
"control_certificate_json TEXT, "
|
|
3545
|
+
"control_trigger_seq INTEGER)"
|
|
3546
|
+
)
|
|
3313
3547
|
conn.execute(
|
|
3314
3548
|
"CREATE TABLE IF NOT EXISTS journal_selector_batches ("
|
|
3315
3549
|
"batch_id TEXT PRIMARY KEY, "
|
|
@@ -3369,6 +3603,45 @@ def open_db(*, _target_path=None) -> sqlite3.Connection:
|
|
|
3369
3603
|
"started_at_utc TEXT NOT NULL, "
|
|
3370
3604
|
"stamped_at_utc TEXT NOT NULL)"
|
|
3371
3605
|
)
|
|
3606
|
+
# #825/#832 Task 4.0 (epoch 1018): a milestone whose snapshot a
|
|
3607
|
+
# credit's stale-replica removal deleted. The removal's band starts at
|
|
3608
|
+
# floor10 of the arming tick, so it also reaches the GENUINE pre-credit
|
|
3609
|
+
# peak tick when that tick shares the arming tick's ten-minute slot —
|
|
3610
|
+
# and that tick is the one that crossed the last weekly and five-hour
|
|
3611
|
+
# milestones. A rebuild folds the suppression (order 30) before the
|
|
3612
|
+
# milestones (order 60), so their journaled `usage_snapshot_ref` named
|
|
3613
|
+
# a row that no longer existed and the NOT NULL row was silently
|
|
3614
|
+
# dropped; the next tick then re-recorded the highest weekly milestone
|
|
3615
|
+
# and its conflict convergence raised on every cycle.
|
|
3616
|
+
#
|
|
3617
|
+
# The tombstone records every journaled snapshot the index deleted —
|
|
3618
|
+
# the live stale-replica removals and the suppression appliers alike,
|
|
3619
|
+
# because a trigger sees every DELETE — so the fold's ONE resolver
|
|
3620
|
+
# (`_cctally_journal._resolve_fold_ref`) can tell "a suppression
|
|
3621
|
+
# removed it" from a genuinely unresolvable reference. The trigger's
|
|
3622
|
+
# second half points every milestone that referenced the deleted row at
|
|
3623
|
+
# the 0 "no snapshot row" sentinel, which is what the rebuild resolves
|
|
3624
|
+
# such a reference to, so the live index and a rebuild agree. Both
|
|
3625
|
+
# milestone readers already treat 0 as "no row": the five-hour
|
|
3626
|
+
# milestone read LEFT JOINs (and so publishes the effective weekly
|
|
3627
|
+
# value as unavailable, as it already did for the dangling rowid), and
|
|
3628
|
+
# the observation-gap classifier never continues a run through 0.
|
|
3629
|
+
conn.execute(
|
|
3630
|
+
"CREATE TABLE IF NOT EXISTS usage_snapshot_suppressions ("
|
|
3631
|
+
"journal_id TEXT PRIMARY KEY)"
|
|
3632
|
+
)
|
|
3633
|
+
conn.execute(
|
|
3634
|
+
"CREATE TRIGGER IF NOT EXISTS trg_usage_snapshot_suppression "
|
|
3635
|
+
"AFTER DELETE ON weekly_usage_snapshots "
|
|
3636
|
+
"BEGIN "
|
|
3637
|
+
"INSERT OR IGNORE INTO usage_snapshot_suppressions (journal_id) "
|
|
3638
|
+
"SELECT OLD.journal_id WHERE OLD.journal_id IS NOT NULL; "
|
|
3639
|
+
"UPDATE percent_milestones SET usage_snapshot_id = 0 "
|
|
3640
|
+
"WHERE usage_snapshot_id = OLD.id; "
|
|
3641
|
+
"UPDATE five_hour_milestones SET usage_snapshot_id = 0 "
|
|
3642
|
+
"WHERE usage_snapshot_id = OLD.id; "
|
|
3643
|
+
"END"
|
|
3644
|
+
)
|
|
3372
3645
|
|
|
3373
3646
|
# §6.2 backfill gate (Task 8): stamp the one-shot marker AFTER the three
|
|
3374
3647
|
# open-time backfills ran, so the next open skips them (and their probes)
|
|
@@ -2586,9 +2586,9 @@ def _dashboard_maybe_prune_retention() -> None:
|
|
|
2586
2586
|
crash the sync thread)."""
|
|
2587
2587
|
try:
|
|
2588
2588
|
c = sys.modules["cctally"]
|
|
2589
|
-
from _cctally_config import
|
|
2589
|
+
from _cctally_config import resolve_prune_retention_days
|
|
2590
2590
|
import _lib_conversation_retention as retention
|
|
2591
|
-
retention_days =
|
|
2591
|
+
retention_days = resolve_prune_retention_days(c.load_config())
|
|
2592
2592
|
if retention_days <= 0:
|
|
2593
2593
|
return
|
|
2594
2594
|
conn = c.open_conversations_db(attach_cache=False)
|
|
@@ -11999,15 +11999,12 @@ def _dashboard_stats_deferred_snapshot(args, *, pinned_now, exc):
|
|
|
11999
11999
|
# worker owns maintenance-exclusive, doing that before HTTP bind reintroduces
|
|
12000
12000
|
# the exact multi-second startup stall this path exists to remove. Supply a
|
|
12001
12001
|
# schema-valid, privacy-safe warning block; the first successful background
|
|
12002
|
-
# snapshot replaces it with a real doctor payload.
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
),
|
|
12009
|
-
"fingerprint": "sha1:" + ("0" * 40),
|
|
12010
|
-
}
|
|
12002
|
+
# snapshot replaces it with a real doctor payload. #997 D10: the block still
|
|
12003
|
+
# carries the rebuild-worker aggregate, from the worker snapshots alone
|
|
12004
|
+
# (lock-free, never opens stats), so the chip names the running or dead
|
|
12005
|
+
# rebuild from the first frame.
|
|
12006
|
+
doctor_payload = tui._deferred_doctor_placeholder(now_utc)
|
|
12007
|
+
doctor_payload["rebuild_worker"] = tui._tui_rebuild_worker_value(now_utc)
|
|
12011
12008
|
replacements = {
|
|
12012
12009
|
# #583 S2 §6.1: this frame is degraded by construction — it carries a
|
|
12013
12010
|
# `stats-open` error and no successful build ran, so it must not stamp
|