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
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,8 @@ 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
|
|
79
81
|
global OAUTH_BACKOFF_MARKER_PATH, OAUTH_BACKOFF_COUNT_PATH
|
|
80
82
|
global UPDATE_STATE_PATH, UPDATE_SUPPRESS_PATH
|
|
81
83
|
global UPDATE_LOCK_PATH, UPDATE_LOG_PATH, UPDATE_LOG_ROTATED_PATH
|
|
@@ -184,6 +186,19 @@ def _init_paths_from_env() -> None:
|
|
|
184
186
|
STATUSLINE_TRANSPORT_MARKER_PATH = APP_DIR / "statusline-transport.last"
|
|
185
187
|
STATUSLINE_AUTHORITATIVE_7D_PATH = APP_DIR / "statusline-authoritative-7d.json"
|
|
186
188
|
STATUSLINE_AUTHORITATIVE_5H_PATH = APP_DIR / "statusline-authoritative-5h.json"
|
|
189
|
+
# #825/#832 account-bound protocol (spec section 7). Every current
|
|
190
|
+
# coordination file lives under its own sibling root; the legacy paths
|
|
191
|
+
# above are never read as authority after the cutover, only compared with
|
|
192
|
+
# the journal-anchored legacy spool baseline. The coordinate cache only
|
|
193
|
+
# accelerates locating the cutover op and never confers authority; the
|
|
194
|
+
# legacy-writer evidence sidecar is diagnostic only (spec section 8).
|
|
195
|
+
STATUSLINE_ACCOUNT_BOUND_DIR = APP_DIR / "statusline-account-bound-v1"
|
|
196
|
+
LEGACY_SPOOL_BASELINE_PATH = (
|
|
197
|
+
STATUSLINE_ACCOUNT_BOUND_DIR / "legacy-spool-baseline.json")
|
|
198
|
+
ACCOUNT_BOUND_CUTOVER_CACHE_PATH = (
|
|
199
|
+
STATUSLINE_ACCOUNT_BOUND_DIR / "cutover-coordinate.json")
|
|
200
|
+
LEGACY_WRITER_EVIDENCE_PATH = (
|
|
201
|
+
STATUSLINE_ACCOUNT_BOUND_DIR / "legacy-writer-evidence.json")
|
|
187
202
|
OAUTH_BACKOFF_MARKER_PATH = APP_DIR / "oauth-backoff.until"
|
|
188
203
|
# Consecutive-429 counter (text int) driving the headerless exponential
|
|
189
204
|
# backoff (base * 2**count). Separate from the deadline marker so the
|
|
@@ -261,6 +276,18 @@ def _repo_root() -> pathlib.Path:
|
|
|
261
276
|
return pathlib.Path(__file__).resolve().parent.parent
|
|
262
277
|
|
|
263
278
|
|
|
279
|
+
def _has_git_checkout_marker() -> bool:
|
|
280
|
+
"""True iff the running code has a ``.git`` entry at its root (#965 5.1).
|
|
281
|
+
|
|
282
|
+
A main checkout has a ``.git`` directory and a linked worktree a ``.git``
|
|
283
|
+
file; both count. Eligibility follows the code's resolved location
|
|
284
|
+
(``_repo_root()``), never the current directory, and is evaluated on every
|
|
285
|
+
call. No suppressor (``CCTALLY_DISABLE_DEV_AUTODETECT``,
|
|
286
|
+
``CCTALLY_ALLOW_PROD_MIGRATION``) affects it. npm and Homebrew installs
|
|
287
|
+
ship without ``.git``, so they never read True."""
|
|
288
|
+
return (_repo_root() / ".git").exists()
|
|
289
|
+
|
|
290
|
+
|
|
264
291
|
def _is_dev_checkout() -> bool:
|
|
265
292
|
"""True iff running from a git checkout (a ``.git`` entry at the repo
|
|
266
293
|
root — a directory for a main checkout, a file for a worktree) AND the
|
|
@@ -272,7 +299,7 @@ def _is_dev_checkout() -> bool:
|
|
|
272
299
|
install copies ship without ``.git`` so they never read True."""
|
|
273
300
|
if _truthy_env("CCTALLY_DISABLE_DEV_AUTODETECT"):
|
|
274
301
|
return False
|
|
275
|
-
return (
|
|
302
|
+
return _has_git_checkout_marker()
|
|
276
303
|
|
|
277
304
|
|
|
278
305
|
def is_preview_channel() -> bool:
|
|
@@ -487,9 +514,19 @@ _init_paths_from_env()
|
|
|
487
514
|
# reason this is a bump and not a migration is unchanged: the registry is
|
|
488
515
|
# frozen at 13, and an epoch-current open returns before any schema work, so a
|
|
489
516
|
# schema helper alone would never run on an upgraded install.
|
|
490
|
-
|
|
517
|
+
# 1017 -> 1018 (#825/#832 spec section 6): the same journal now has a different
|
|
518
|
+
# authoritative interpretation — the account-bound admission predicate excludes
|
|
519
|
+
# unstamped post-cutover records before any derivation — so an index
|
|
520
|
+
# materialized under the old interpretation is not current even though its
|
|
521
|
+
# schema is usable. The epoch also carries the disposable
|
|
522
|
+
# `journal_admission_summary` table, whose presence is the protocol marker
|
|
523
|
+
# `_cctally_store.validated_read_snapshot` checks inside each decision
|
|
524
|
+
# scope's own snapshot. An epoch transition, not a numbered migration: the
|
|
525
|
+
# registry stays frozen at 13.
|
|
526
|
+
STATS_INDEX_EPOCH = 1018
|
|
491
527
|
LEGACY_STATS_HEAD = 13
|
|
492
528
|
|
|
529
|
+
|
|
493
530
|
#: #496 S1 F1. A NEW branch, for a state that cannot occur before the
|
|
494
531
|
#: publication transaction exists: a replacement index was published and then
|
|
495
532
|
#: failed validation on a fresh connection. The existing corrupt-stats text
|
|
@@ -3310,6 +3347,106 @@ def open_db(*, _target_path=None) -> sqlite3.Connection:
|
|
|
3310
3347
|
"cutover_seen INTEGER NOT NULL DEFAULT 0, "
|
|
3311
3348
|
"cutover_account_key TEXT)"
|
|
3312
3349
|
)
|
|
3350
|
+
# #825/#832 epoch 1018 (spec section 8): the bounded, disposable
|
|
3351
|
+
# summary of the complete journal records the admission predicate
|
|
3352
|
+
# EXCLUDED (post-cutover records without the current writer
|
|
3353
|
+
# generation). Ingest extends it inside its cycle transaction and a
|
|
3354
|
+
# rebuild reproduces it from the pinned prefix; `complete_through` is
|
|
3355
|
+
# NULL when the row was started mid-journal (it then describes only
|
|
3356
|
+
# what it saw). Coordinates are JSON `{"segment", "offset"}` text, and
|
|
3357
|
+
# `coordinate_digest` is the order-independent population digest of
|
|
3358
|
+
# every excluded coordinate (`_lib_account_protocol`).
|
|
3359
|
+
conn.execute(
|
|
3360
|
+
"CREATE TABLE IF NOT EXISTS journal_admission_summary ("
|
|
3361
|
+
"id INTEGER PRIMARY KEY CHECK (id = 1), "
|
|
3362
|
+
"cutover_id TEXT, "
|
|
3363
|
+
"scanned_high_water TEXT, "
|
|
3364
|
+
"excluded_total INTEGER NOT NULL DEFAULT 0, "
|
|
3365
|
+
"family_counts_json TEXT NOT NULL DEFAULT '{}', "
|
|
3366
|
+
"first_excluded_coordinate TEXT, "
|
|
3367
|
+
"latest_excluded_coordinate TEXT, "
|
|
3368
|
+
"coordinate_digest TEXT, "
|
|
3369
|
+
"complete_through TEXT, "
|
|
3370
|
+
# The boundary payload this summary classified against, so the
|
|
3371
|
+
# live ingest path resolves admission without scanning the journal.
|
|
3372
|
+
"boundary_json TEXT)"
|
|
3373
|
+
)
|
|
3374
|
+
# #825/#832 fix round A (Codex fix-round-1 Q1): the derivation
|
|
3375
|
+
# frontier. Every admitted derivable input at or before this journal
|
|
3376
|
+
# coordinate has had its decisions journaled by a current binary. Live
|
|
3377
|
+
# ingest advances it with the cursor only while the two are equal; a
|
|
3378
|
+
# rebuild carries it from a trusted predecessor index or sets it after
|
|
3379
|
+
# the shared completion step proved the window. Absent means unknown,
|
|
3380
|
+
# which the completion step reads as "replay from the cutover".
|
|
3381
|
+
# Fix round C (P1-1): the frontier counts CONSUMED INPUTS, and the
|
|
3382
|
+
# decisions a cycle derives from them land beyond it, so the row also
|
|
3383
|
+
# names the output end — the coordinate through which every decision
|
|
3384
|
+
# derived from an input at or before the frontier is journaled, with
|
|
3385
|
+
# nothing between the two derived from a later input. NULL means
|
|
3386
|
+
# nothing is owed beyond the frontier.
|
|
3387
|
+
conn.execute(
|
|
3388
|
+
"CREATE TABLE IF NOT EXISTS journal_derivation_frontier ("
|
|
3389
|
+
"id INTEGER PRIMARY KEY CHECK (id = 1), "
|
|
3390
|
+
"segment TEXT NOT NULL, "
|
|
3391
|
+
"offset INTEGER NOT NULL, "
|
|
3392
|
+
"output_segment TEXT, "
|
|
3393
|
+
"output_offset INTEGER)"
|
|
3394
|
+
)
|
|
3395
|
+
# An index an earlier build of this epoch created lacks the two
|
|
3396
|
+
# columns; additive, so it keeps its frontier row.
|
|
3397
|
+
add_column_if_missing(
|
|
3398
|
+
conn, "journal_derivation_frontier", "output_segment", "TEXT")
|
|
3399
|
+
add_column_if_missing(
|
|
3400
|
+
conn, "journal_derivation_frontier", "output_offset", "INTEGER")
|
|
3401
|
+
# Revision 2 (#946, Codex design-fix-round-3 Q5): the proof
|
|
3402
|
+
# generation. A row written under the old traversal rule (NULL here)
|
|
3403
|
+
# may name a valid endpoint past an input nothing derived, so only
|
|
3404
|
+
# `_cctally_journal.DERIVATION_PROOF_GENERATION` is trusted.
|
|
3405
|
+
add_column_if_missing(
|
|
3406
|
+
conn, "journal_derivation_frontier", "proof_generation", "INTEGER")
|
|
3407
|
+
# #825/#832 fix round A (Codex fix-round-1 Q2): the physical revision
|
|
3408
|
+
# inventory of every EXCLUDED event or correction action the index has
|
|
3409
|
+
# consumed, by logical id. Admission hides these from effective
|
|
3410
|
+
# selection, so without it a current writer would classify the id NEW
|
|
3411
|
+
# and append a revision-0 twin the previous release reads as a
|
|
3412
|
+
# same-revision conflict.
|
|
3413
|
+
conn.execute(
|
|
3414
|
+
"CREATE TABLE IF NOT EXISTS journal_excluded_event_revisions ("
|
|
3415
|
+
"event_id TEXT PRIMARY KEY, "
|
|
3416
|
+
"max_rev INTEGER NOT NULL)"
|
|
3417
|
+
)
|
|
3418
|
+
# #825/#832 epoch 1018 (spec 5.6 item 9): the outcome quarantine. A
|
|
3419
|
+
# deterministic function of journal order that ingest extends inside
|
|
3420
|
+
# its cycle transaction and a rebuild reproduces from the pinned
|
|
3421
|
+
# prefix: one row per uncertainty trigger (an excluded record or a
|
|
3422
|
+
# current `legacy_spool_activity` op, at its physical coordinate), the
|
|
3423
|
+
# whole-prefix envelope per scope ("excluded" applies to every
|
|
3424
|
+
# account), and each account's accounting and control certificates
|
|
3425
|
+
# with the trigger sequence they were granted against. A certificate
|
|
3426
|
+
# granted against an older sequence is void; a missing or
|
|
3427
|
+
# unverifiable row reads as quarantined.
|
|
3428
|
+
conn.execute(
|
|
3429
|
+
"CREATE TABLE IF NOT EXISTS outcome_quarantine_triggers ("
|
|
3430
|
+
"seq INTEGER PRIMARY KEY, "
|
|
3431
|
+
"segment TEXT, "
|
|
3432
|
+
"byte_offset INTEGER, "
|
|
3433
|
+
"kind TEXT NOT NULL, "
|
|
3434
|
+
"reason TEXT NOT NULL, "
|
|
3435
|
+
"at TEXT)"
|
|
3436
|
+
)
|
|
3437
|
+
conn.execute(
|
|
3438
|
+
"CREATE TABLE IF NOT EXISTS outcome_quarantine_envelope ("
|
|
3439
|
+
"scope TEXT PRIMARY KEY, "
|
|
3440
|
+
"envelope_json TEXT NOT NULL)"
|
|
3441
|
+
)
|
|
3442
|
+
conn.execute(
|
|
3443
|
+
"CREATE TABLE IF NOT EXISTS outcome_quarantine_certificates ("
|
|
3444
|
+
"account_key TEXT PRIMARY KEY, "
|
|
3445
|
+
"accounting_certificate_json TEXT, "
|
|
3446
|
+
"accounting_trigger_seq INTEGER, "
|
|
3447
|
+
"control_certificate_json TEXT, "
|
|
3448
|
+
"control_trigger_seq INTEGER)"
|
|
3449
|
+
)
|
|
3313
3450
|
conn.execute(
|
|
3314
3451
|
"CREATE TABLE IF NOT EXISTS journal_selector_batches ("
|
|
3315
3452
|
"batch_id TEXT PRIMARY KEY, "
|
|
@@ -3369,6 +3506,45 @@ def open_db(*, _target_path=None) -> sqlite3.Connection:
|
|
|
3369
3506
|
"started_at_utc TEXT NOT NULL, "
|
|
3370
3507
|
"stamped_at_utc TEXT NOT NULL)"
|
|
3371
3508
|
)
|
|
3509
|
+
# #825/#832 Task 4.0 (epoch 1018): a milestone whose snapshot a
|
|
3510
|
+
# credit's stale-replica removal deleted. The removal's band starts at
|
|
3511
|
+
# floor10 of the arming tick, so it also reaches the GENUINE pre-credit
|
|
3512
|
+
# peak tick when that tick shares the arming tick's ten-minute slot —
|
|
3513
|
+
# and that tick is the one that crossed the last weekly and five-hour
|
|
3514
|
+
# milestones. A rebuild folds the suppression (order 30) before the
|
|
3515
|
+
# milestones (order 60), so their journaled `usage_snapshot_ref` named
|
|
3516
|
+
# a row that no longer existed and the NOT NULL row was silently
|
|
3517
|
+
# dropped; the next tick then re-recorded the highest weekly milestone
|
|
3518
|
+
# and its conflict convergence raised on every cycle.
|
|
3519
|
+
#
|
|
3520
|
+
# The tombstone records every journaled snapshot the index deleted —
|
|
3521
|
+
# the live stale-replica removals and the suppression appliers alike,
|
|
3522
|
+
# because a trigger sees every DELETE — so the fold's ONE resolver
|
|
3523
|
+
# (`_cctally_journal._resolve_fold_ref`) can tell "a suppression
|
|
3524
|
+
# removed it" from a genuinely unresolvable reference. The trigger's
|
|
3525
|
+
# second half points every milestone that referenced the deleted row at
|
|
3526
|
+
# the 0 "no snapshot row" sentinel, which is what the rebuild resolves
|
|
3527
|
+
# such a reference to, so the live index and a rebuild agree. Both
|
|
3528
|
+
# milestone readers already treat 0 as "no row": the five-hour
|
|
3529
|
+
# milestone read LEFT JOINs (and so publishes the effective weekly
|
|
3530
|
+
# value as unavailable, as it already did for the dangling rowid), and
|
|
3531
|
+
# the observation-gap classifier never continues a run through 0.
|
|
3532
|
+
conn.execute(
|
|
3533
|
+
"CREATE TABLE IF NOT EXISTS usage_snapshot_suppressions ("
|
|
3534
|
+
"journal_id TEXT PRIMARY KEY)"
|
|
3535
|
+
)
|
|
3536
|
+
conn.execute(
|
|
3537
|
+
"CREATE TRIGGER IF NOT EXISTS trg_usage_snapshot_suppression "
|
|
3538
|
+
"AFTER DELETE ON weekly_usage_snapshots "
|
|
3539
|
+
"BEGIN "
|
|
3540
|
+
"INSERT OR IGNORE INTO usage_snapshot_suppressions (journal_id) "
|
|
3541
|
+
"SELECT OLD.journal_id WHERE OLD.journal_id IS NOT NULL; "
|
|
3542
|
+
"UPDATE percent_milestones SET usage_snapshot_id = 0 "
|
|
3543
|
+
"WHERE usage_snapshot_id = OLD.id; "
|
|
3544
|
+
"UPDATE five_hour_milestones SET usage_snapshot_id = 0 "
|
|
3545
|
+
"WHERE usage_snapshot_id = OLD.id; "
|
|
3546
|
+
"END"
|
|
3547
|
+
)
|
|
3372
3548
|
|
|
3373
3549
|
# §6.2 backfill gate (Task 8): stamp the one-shot marker AFTER the three
|
|
3374
3550
|
# 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)
|