cctally 1.115.0 → 1.116.1
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 +21 -0
- package/bin/_cctally_cache.py +85 -4
- package/bin/_cctally_completion.py +80 -12
- package/bin/_cctally_core.py +106 -9
- package/bin/_cctally_dashboard.py +6 -9
- package/bin/_cctally_dashboard_envelope.py +26 -32
- package/bin/_cctally_db.py +85 -4
- package/bin/_cctally_doctor.py +38 -5
- package/bin/_cctally_five_hour.py +83 -1017
- package/bin/_cctally_journal.py +1307 -180
- package/bin/_cctally_journal_repair.py +9 -7
- package/bin/_cctally_oauth_samples.py +280 -0
- package/bin/_cctally_parser.py +5 -0
- package/bin/_cctally_quarantine.py +14 -4
- package/bin/_cctally_quota.py +70 -8
- package/bin/_cctally_quota_model.py +35 -4
- package/bin/_cctally_record.py +414 -123
- package/bin/_cctally_rederive.py +632 -235
- package/bin/_cctally_refresh.py +29 -0
- package/bin/_cctally_statusline.py +984 -47
- package/bin/_cctally_store.py +972 -58
- package/bin/_cctally_tui.py +62 -7
- package/bin/_cctally_update.py +25 -0
- package/bin/_lib_account_protocol.py +201 -18
- package/bin/_lib_clone_copy.py +46 -11
- package/bin/_lib_dashboard_sources.py +57 -15
- package/bin/_lib_detector_access_registry.py +273 -52
- package/bin/_lib_doctor.py +302 -1
- package/bin/_lib_journal.py +145 -25
- package/bin/_lib_journal_router.py +166 -48
- package/bin/_lib_oauth_samples.py +238 -0
- package/bin/_lib_pricing_check.py +0 -12
- package/bin/_lib_provider_activity.py +770 -0
- package/bin/_lib_quarantine.py +3 -2
- package/bin/_lib_quota_model.py +96 -6
- package/bin/_lib_rebuild_progress.py +146 -0
- package/bin/_lib_record.py +22 -0
- package/bin/_lib_rederive.py +299 -3
- package/bin/_lib_snapshot_cache.py +43 -1
- package/bin/_lib_stale_proof.py +708 -0
- package/bin/_lib_statusline_candidates.py +65 -5
- package/bin/_lib_worker_launches.py +315 -0
- package/bin/cctally +6 -11
- 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 +7 -2
- package/bin/_lib_merge_gate.py +0 -452
- package/dashboard/static/assets/DoctorModal-D--qVBwe.js +0 -1
|
@@ -217,9 +217,11 @@ def _select_current_block_for_envelope(
|
|
|
217
217
|
Delta semantics:
|
|
218
218
|
- Non-crossed block: ``current_used_pct - seven_day_pct_at_block_start``
|
|
219
219
|
(the natural "how much 7d% has changed during this 5h block" read), where
|
|
220
|
-
that axis is the
|
|
221
|
-
|
|
222
|
-
|
|
220
|
+
that axis is the block's stored ``seven_day_pct_at_block_start``. #835's
|
|
221
|
+
retirement band, which published ``None`` for a value within a point of
|
|
222
|
+
one an in-place weekly credit retired, is removed (#840): stale
|
|
223
|
+
contributors are refused before publication, and a stored value that is
|
|
224
|
+
already wrong is corrected by ``db rederive``'s reviewed ``retract``.
|
|
223
225
|
- Crossed block (``crossed_seven_day_reset == 1``): the block straddles
|
|
224
226
|
a weekly reset, so the natural delta would be dominated by the
|
|
225
227
|
reset itself (e.g. −94pp) rather than the user's actual burn-rate.
|
|
@@ -281,21 +283,17 @@ def _select_current_block_for_envelope(
|
|
|
281
283
|
# Account-qualified selection binds the block and its downstream milestone
|
|
282
284
|
# read to the displayed weekly account; the unqualified order remains the
|
|
283
285
|
# historical <=1-real-account fallback for published byte stability.
|
|
284
|
-
# #834 S1 (#835) Gate A R8
|
|
285
|
-
#
|
|
286
|
-
#
|
|
287
|
-
#
|
|
288
|
-
#
|
|
289
|
-
#
|
|
290
|
-
#
|
|
291
|
-
#
|
|
292
|
-
#
|
|
293
|
-
#
|
|
294
|
-
#
|
|
295
|
-
# instant it feeds is discarded with the end axis. It stays because the
|
|
296
|
-
# helper's contract is a block ROW, and pruning this SELECT to the subset the
|
|
297
|
-
# helper happens to read today would couple this route to the helper's
|
|
298
|
-
# internals. Round four's comment implied necessity instead.
|
|
286
|
+
# #834 S1 (#835) Gate A R8 selected `first_observed_at_utc`,
|
|
287
|
+
# `five_hour_resets_at` and `last_observed_at_utc` for the retirement band's
|
|
288
|
+
# week resolution. Since the band's removal (#840) the axes helper below
|
|
289
|
+
# reads only the stored `seven_day_pct_at_block_start` /
|
|
290
|
+
# `seven_day_pct_at_block_end` through `block.get(...)`, so none of the three
|
|
291
|
+
# is needed by it; they stay because the helper's contract is a block ROW,
|
|
292
|
+
# and pruning this SELECT to the subset the helper happens to read today
|
|
293
|
+
# would couple this route to the helper's internals.
|
|
294
|
+
# `seven_day_pct_at_block_end` is not selected: this route publishes the
|
|
295
|
+
# start axis alone and discards the helper's second return value, which is
|
|
296
|
+
# then `None`.
|
|
299
297
|
block = conn.execute(
|
|
300
298
|
"""
|
|
301
299
|
SELECT id, account_key,
|
|
@@ -319,14 +317,14 @@ def _select_current_block_for_envelope(
|
|
|
319
317
|
|
|
320
318
|
crossed = bool(block["crossed_seven_day_reset"])
|
|
321
319
|
# #834 S1 (#835) Gate A R8: THE THIRD CONSUMER of the two derived weekly axes
|
|
322
|
-
# goes through the one
|
|
323
|
-
# `cmd_five_hour_breakdown` use.
|
|
324
|
-
# in-place weekly credit
|
|
325
|
-
#
|
|
326
|
-
#
|
|
327
|
-
#
|
|
328
|
-
# `
|
|
329
|
-
#
|
|
320
|
+
# goes through the one shared axes helper, the same one `cmd_five_hour_blocks`
|
|
321
|
+
# and `cmd_five_hour_breakdown` use. #835 made that helper a read-time filter
|
|
322
|
+
# that withheld a value within a point of one an in-place weekly credit
|
|
323
|
+
# retired; #840 removed that retirement band, so the helper now returns the
|
|
324
|
+
# block's stored axes. A stale contributor is refused before it is
|
|
325
|
+
# published, and a stored value that is already wrong is corrected by
|
|
326
|
+
# `db rederive`'s reviewed `retract`, never by a percentage heuristic here.
|
|
327
|
+
# `sqlite3.Row` has no `.get()`, so the row is converted.
|
|
330
328
|
#
|
|
331
329
|
# Neither field is rendered by any client today: both are declared in
|
|
332
330
|
# `dashboard/web/src/types/envelope.ts` and used in one vitest module,
|
|
@@ -2193,18 +2191,14 @@ def snapshot_to_envelope(snap: "DataSnapshot", *,
|
|
|
2193
2191
|
_ld = sys.modules["cctally"]._load_sibling("_lib_doctor")
|
|
2194
2192
|
_doc_state = sys.modules["cctally"].doctor_gather_state(now_utc=now_utc, runtime_bind=runtime_bind)
|
|
2195
2193
|
_doc_report = _ld.run_checks(_doc_state)
|
|
2196
|
-
doctor_envelope =
|
|
2197
|
-
"severity": _doc_report.overall_severity,
|
|
2198
|
-
"counts": dict(_doc_report.counts),
|
|
2199
|
-
"generated_at": _ld._iso_z(_doc_report.generated_at),
|
|
2200
|
-
"fingerprint": _ld.fingerprint(_doc_report),
|
|
2201
|
-
}
|
|
2194
|
+
doctor_envelope = _ld.envelope_block(_doc_report)
|
|
2202
2195
|
except Exception as exc: # noqa: BLE001 — never crash SSE on doctor failure
|
|
2203
2196
|
doctor_envelope = {
|
|
2204
2197
|
"severity": "fail",
|
|
2205
2198
|
"counts": {"ok": 0, "warn": 0, "fail": 1},
|
|
2206
2199
|
"generated_at": _iso_z(now_utc),
|
|
2207
2200
|
"fingerprint": "sha1:" + ("0" * 40),
|
|
2201
|
+
"rebuild_worker": None,
|
|
2208
2202
|
"_error": f"{type(exc).__name__}: {exc}",
|
|
2209
2203
|
}
|
|
2210
2204
|
|
package/bin/_cctally_db.py
CHANGED
|
@@ -9154,6 +9154,13 @@ def _027_codex_fork_preamble_rebuild(conn: sqlite3.Connection) -> None:
|
|
|
9154
9154
|
_release_cache_db_writer_flocks(held)
|
|
9155
9155
|
|
|
9156
9156
|
|
|
9157
|
+
#: Pages 028 returns per committed transaction when it reclaims the dropped
|
|
9158
|
+
#: transcript store. The chunk bounds each transaction's WAL growth at roughly,
|
|
9159
|
+
#: but more than, one SQLite WAL auto-checkpoint (1000 frames): each moved page
|
|
9160
|
+
#: also writes its pointer-map page, its parent and the header page.
|
|
9161
|
+
_028_VACUUM_CHUNK_PAGES = 1000
|
|
9162
|
+
|
|
9163
|
+
|
|
9157
9164
|
@cache_migration("028_split_conversation_store")
|
|
9158
9165
|
def _028_split_conversation_store(conn: sqlite3.Connection) -> None:
|
|
9159
9166
|
"""Move the re-derivable transcript corpus out of cache.db (#320).
|
|
@@ -9263,11 +9270,45 @@ def _028_split_conversation_store(conn: sqlite3.Connection) -> None:
|
|
|
9263
9270
|
raise
|
|
9264
9271
|
try:
|
|
9265
9272
|
if conn.execute("PRAGMA auto_vacuum").fetchone()[0] == 2:
|
|
9266
|
-
#
|
|
9267
|
-
#
|
|
9268
|
-
|
|
9273
|
+
# Return the dropped tables' pages one page per statement. A
|
|
9274
|
+
# bare `PRAGMA incremental_vacuum` returns no columns, and
|
|
9275
|
+
# CPython 3.11's cursor steps such a statement exactly once,
|
|
9276
|
+
# fetchall() or not, so it frees ONE page (#998). A statement
|
|
9277
|
+
# capped at one page is complete after that first step on every
|
|
9278
|
+
# Python (the `_run_incremental_vacuum_chunk` pattern). Each
|
|
9279
|
+
# chunk commits so WAL auto-checkpoints run between chunks, and
|
|
9280
|
+
# the loop stops when a chunk frees nothing, so it is bounded.
|
|
9281
|
+
remaining = conn.execute("PRAGMA freelist_count").fetchone()[0]
|
|
9282
|
+
while remaining:
|
|
9283
|
+
# Any failure inside a chunk, including a non-database
|
|
9284
|
+
# error or an interrupt, rolls that chunk back before it
|
|
9285
|
+
# propagates, so no write transaction outlives the
|
|
9286
|
+
# conversation locks released below.
|
|
9287
|
+
try:
|
|
9288
|
+
conn.execute("BEGIN IMMEDIATE")
|
|
9289
|
+
for _ in range(min(remaining, _028_VACUUM_CHUNK_PAGES)):
|
|
9290
|
+
conn.execute("PRAGMA incremental_vacuum(1)").fetchall()
|
|
9291
|
+
conn.commit()
|
|
9292
|
+
except BaseException:
|
|
9293
|
+
try:
|
|
9294
|
+
conn.rollback()
|
|
9295
|
+
except sqlite3.Error:
|
|
9296
|
+
pass
|
|
9297
|
+
raise
|
|
9298
|
+
left = conn.execute("PRAGMA freelist_count").fetchone()[0]
|
|
9299
|
+
if left >= remaining:
|
|
9300
|
+
break
|
|
9301
|
+
remaining = left
|
|
9269
9302
|
except sqlite3.DatabaseError:
|
|
9270
|
-
|
|
9303
|
+
# Intentional: the reclaim is best effort. Raising here would leave
|
|
9304
|
+
# 028 unapplied, and its next run would re-arm both conversation
|
|
9305
|
+
# store rebuild-from-scratch flags; free pages left behind are
|
|
9306
|
+
# harmless and `db vacuum` returns them. The guarded rollback keeps
|
|
9307
|
+
# a failing rollback from turning that into a handler failure.
|
|
9308
|
+
try:
|
|
9309
|
+
conn.rollback()
|
|
9310
|
+
except sqlite3.Error:
|
|
9311
|
+
pass
|
|
9271
9312
|
finally:
|
|
9272
9313
|
for fh in reversed(held):
|
|
9273
9314
|
try:
|
|
@@ -11312,6 +11353,9 @@ def cmd_db_status(args: argparse.Namespace) -> int:
|
|
|
11312
11353
|
),
|
|
11313
11354
|
},
|
|
11314
11355
|
}
|
|
11356
|
+
# #997 D10: additive — each detached rebuild worker's state and its
|
|
11357
|
+
# read-only classification (the doctor `db.rebuild_worker` kernel).
|
|
11358
|
+
payload["rebuildWorkers"] = _db_status_rebuild_workers()
|
|
11315
11359
|
if getattr(args, "json", False):
|
|
11316
11360
|
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
11317
11361
|
return 0
|
|
@@ -11329,9 +11373,46 @@ def cmd_db_status(args: argparse.Namespace) -> int:
|
|
|
11329
11373
|
line = _db_status_format_row(m)
|
|
11330
11374
|
print(line)
|
|
11331
11375
|
print() # blank line between DBs
|
|
11376
|
+
for worker in ("epoch", "heal"):
|
|
11377
|
+
info = payload["rebuildWorkers"][worker]
|
|
11378
|
+
if info["classification"]["row"] != "idle":
|
|
11379
|
+
print(info["classification"]["summary"])
|
|
11332
11380
|
return 0
|
|
11333
11381
|
|
|
11334
11382
|
|
|
11383
|
+
def _db_status_rebuild_workers() -> dict:
|
|
11384
|
+
"""``{"epoch": {...}, "heal": {...}}``: each worker's run state and its
|
|
11385
|
+
classification, gathered without any lock (#997 D8/D10)."""
|
|
11386
|
+
import _cctally_store
|
|
11387
|
+
import _lib_doctor
|
|
11388
|
+
|
|
11389
|
+
now_utc = _cctally_core._command_as_of()
|
|
11390
|
+
out: dict = {}
|
|
11391
|
+
for worker in ("epoch", "heal"):
|
|
11392
|
+
try:
|
|
11393
|
+
snapshot = _cctally_store.rebuild_worker_snapshot(worker)
|
|
11394
|
+
except Exception: # noqa: BLE001 — a diagnostic never fails status
|
|
11395
|
+
snapshot = None
|
|
11396
|
+
assessed = _lib_doctor.assess_rebuild_worker(worker, snapshot,
|
|
11397
|
+
now_utc)
|
|
11398
|
+
details = assessed["details"]
|
|
11399
|
+
out[worker] = {
|
|
11400
|
+
"state": details["state"],
|
|
11401
|
+
"stateUnreadable": details["stateUnreadable"],
|
|
11402
|
+
"ledger": details["ledger"],
|
|
11403
|
+
"liveness": details["liveness"],
|
|
11404
|
+
"pending": details["pending"],
|
|
11405
|
+
"recoveredDeaths": details["recoveredDeaths"],
|
|
11406
|
+
"classification": {
|
|
11407
|
+
"severity": assessed["severity"],
|
|
11408
|
+
"row": assessed["row"],
|
|
11409
|
+
"summary": assessed["summary"],
|
|
11410
|
+
"warnings": assessed["warnings"],
|
|
11411
|
+
},
|
|
11412
|
+
}
|
|
11413
|
+
return out
|
|
11414
|
+
|
|
11415
|
+
|
|
11335
11416
|
def _db_status_for(
|
|
11336
11417
|
db_path: pathlib.Path,
|
|
11337
11418
|
registry: list[Migration],
|
package/bin/_cctally_doctor.py
CHANGED
|
@@ -507,6 +507,31 @@ def _fetch_dashboard_write_io(descriptor: dict, *, timeout_s: float) -> dict:
|
|
|
507
507
|
return {**base, "probe": "ok", "reason": None, "write_io": write_io}
|
|
508
508
|
|
|
509
509
|
|
|
510
|
+
_REBUILD_WORKERS = ("epoch", "heal")
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def _rebuild_worker_gather_failed(worker: str, exc: BaseException) -> dict:
|
|
514
|
+
"""A snapshot that could not be gathered reads as an unreadable state
|
|
515
|
+
(the existing row), with the error in `details`, never as "has run"."""
|
|
516
|
+
return {"worker": worker, "state": None, "stateUnreadable": True,
|
|
517
|
+
"ledger": None, "ledgerUnreadable": False, "identities": {},
|
|
518
|
+
"pending": {}, "reread": False,
|
|
519
|
+
"gatherError": f"{type(exc).__name__}: {exc}"}
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _gather_rebuild_workers() -> dict:
|
|
523
|
+
"""#997 D10: each detached rebuild worker's read-only snapshot; a worker
|
|
524
|
+
whose snapshot fails is reported unreadable, the other still gathered."""
|
|
525
|
+
out: dict = {}
|
|
526
|
+
for worker in _REBUILD_WORKERS:
|
|
527
|
+
try:
|
|
528
|
+
import _cctally_store
|
|
529
|
+
out[worker] = _cctally_store.rebuild_worker_snapshot(worker)
|
|
530
|
+
except Exception as exc: # noqa: BLE001 — reported, never fatal
|
|
531
|
+
out[worker] = _rebuild_worker_gather_failed(worker, exc)
|
|
532
|
+
return out
|
|
533
|
+
|
|
534
|
+
|
|
510
535
|
def _gather_dashboard_disk_writes(
|
|
511
536
|
*, fetch=None, timeout_s: float = DASHBOARD_WRITE_IO_TIMEOUT_SECONDS,
|
|
512
537
|
discover: bool = True,
|
|
@@ -559,6 +584,7 @@ def _gather_legacy_writer(deep_excluded, deep_boundary,
|
|
|
559
584
|
says the detection pause exists only when the validated index holds the
|
|
560
585
|
durable `inventory-incomplete` marker. Read-only: the sidecar is never
|
|
561
586
|
written here, the spool is only read, and nothing is journaled."""
|
|
587
|
+
import _cctally_journal
|
|
562
588
|
import _cctally_store
|
|
563
589
|
import _lib_account_protocol
|
|
564
590
|
|
|
@@ -579,11 +605,9 @@ def _gather_legacy_writer(deep_excluded, deep_boundary,
|
|
|
579
605
|
pc.close()
|
|
580
606
|
except Exception: # noqa: BLE001 — a diagnostic read degrades to unavailable
|
|
581
607
|
row = None
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
except (OSError, ValueError):
|
|
586
|
-
evidence = None
|
|
608
|
+
# #797/#823/#840 review RV-5: the v2 sidecar, else the previous
|
|
609
|
+
# release's v1 one (read only) as its seed.
|
|
610
|
+
evidence, _seeded = _cctally_journal.read_legacy_writer_evidence_document()
|
|
587
611
|
inventory = _legacy_spool_inventory_probe(inventory_marker)
|
|
588
612
|
if inventory is not None:
|
|
589
613
|
out["legacySpoolInventory"] = inventory
|
|
@@ -3208,6 +3232,14 @@ def _doctor_gather_state_impl(
|
|
|
3208
3232
|
discover=_discover_dashboards)
|
|
3209
3233
|
except Exception: # noqa: BLE001 — a probe failure must not fail doctor
|
|
3210
3234
|
dashboard_disk_writes = {"mode": "error", "instances": []}
|
|
3235
|
+
# #997 D10: the detached rebuild workers, read-only (no worker or
|
|
3236
|
+
# admission flock is taken; a probe would make a starting worker `busy`).
|
|
3237
|
+
try:
|
|
3238
|
+
with _lib_perf.phase("doctor.rebuild_workers"):
|
|
3239
|
+
rebuild_workers = _gather_rebuild_workers()
|
|
3240
|
+
except Exception as exc: # noqa: BLE001 — a gather failure must not fail doctor
|
|
3241
|
+
rebuild_workers = {worker: _rebuild_worker_gather_failed(worker, exc)
|
|
3242
|
+
for worker in _REBUILD_WORKERS}
|
|
3211
3243
|
return _lib_doctor.DoctorState(
|
|
3212
3244
|
symlink_state=symlink_state,
|
|
3213
3245
|
path_includes_local_bin=path_includes,
|
|
@@ -3318,6 +3350,7 @@ def _doctor_gather_state_impl(
|
|
|
3318
3350
|
conversations_db_freelist_count=conversations_db_freelist_count,
|
|
3319
3351
|
conversations_db_page_size=conversations_db_page_size,
|
|
3320
3352
|
dashboard_disk_writes=dashboard_disk_writes,
|
|
3353
|
+
rebuild_workers=rebuild_workers,
|
|
3321
3354
|
conversations_reclaim_pending=conversations_reclaim_pending,
|
|
3322
3355
|
codex_quota_windows=codex_quota_windows,
|
|
3323
3356
|
codex_hook_roots=codex_hook_roots,
|