cctally 1.95.0 → 1.95.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 +7 -0
- package/bin/_cctally_db.py +32 -2
- package/bin/_cctally_record.py +9 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.95.1] - 2026-08-09
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Corruption recovery now preserves both incidents when two database families are quarantined within the same second. The second incident atomically reserves a distinct retention-compatible directory instead of reusing the first incident's path and failing because the old and new `stats.db` generations both exist.
|
|
12
|
+
- The authoritative test gate now runs its rollback-contention wall-clock checks after the parallel pytest estate, alongside the existing serial rebuild benchmark. Whole-estate CPU contention can no longer consume those latency ceilings while isolated regressions still fail the release.
|
|
13
|
+
- Concurrent usage ingestion no longer derives five-hour blocks or milestones from a snapshot that a retained suppression event deliberately removed. A duplicate observation now respects the absent Model-A target instead of passing `None` into the derivation path and repeatedly failing every ingester behind it.
|
|
14
|
+
|
|
8
15
|
## [1.95.0] - 2026-08-09
|
|
9
16
|
|
|
10
17
|
### Changed
|
package/bin/_cctally_db.py
CHANGED
|
@@ -1300,6 +1300,37 @@ def _load_pending_quarantine(db_path: pathlib.Path) -> "dict[str, Any] | None":
|
|
|
1300
1300
|
_QUARANTINE_MISSING_CONTEXT_WARNED = False # one-shot warn flag
|
|
1301
1301
|
|
|
1302
1302
|
|
|
1303
|
+
def _reserve_quarantine_incident(
|
|
1304
|
+
root: pathlib.Path, db_name: str, timestamp: str
|
|
1305
|
+
) -> pathlib.Path:
|
|
1306
|
+
"""Atomically reserve a retention-compatible incident directory."""
|
|
1307
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
1308
|
+
primary = root / f"{db_name}-{timestamp}"
|
|
1309
|
+
try:
|
|
1310
|
+
primary.mkdir()
|
|
1311
|
+
return primary
|
|
1312
|
+
except FileExistsError:
|
|
1313
|
+
pass
|
|
1314
|
+
|
|
1315
|
+
match = re.fullmatch(r"(\d{8}T\d{6})(?:Z|_(\d{6}))", timestamp)
|
|
1316
|
+
if match is None:
|
|
1317
|
+
stem = dt.datetime.now(dt.timezone.utc).strftime("%Y%m%dT%H%M%S")
|
|
1318
|
+
first = 1
|
|
1319
|
+
else:
|
|
1320
|
+
stem = match.group(1)
|
|
1321
|
+
first = int(match.group(2) or "0") + 1
|
|
1322
|
+
for microsecond in range(first, 1_000_000):
|
|
1323
|
+
candidate = root / f"{db_name}-{stem}_{microsecond:06d}"
|
|
1324
|
+
try:
|
|
1325
|
+
candidate.mkdir()
|
|
1326
|
+
return candidate
|
|
1327
|
+
except FileExistsError:
|
|
1328
|
+
continue
|
|
1329
|
+
raise OSError(
|
|
1330
|
+
f"could not allocate a unique quarantine incident for {db_name}"
|
|
1331
|
+
)
|
|
1332
|
+
|
|
1333
|
+
|
|
1303
1334
|
def _warn_quarantine_created_without_context(incident: pathlib.Path) -> None:
|
|
1304
1335
|
"""Report a creation that supplied no `QuarantineContext` (#496 S6 §4.2).
|
|
1305
1336
|
|
|
@@ -1352,8 +1383,7 @@ def _quarantine_db_family_strict(
|
|
|
1352
1383
|
if state is None:
|
|
1353
1384
|
ts = ts or _db_backup_timestamp()
|
|
1354
1385
|
root = _cctally_core.APP_DIR / "quarantine"
|
|
1355
|
-
incident = root
|
|
1356
|
-
incident.mkdir(parents=True, exist_ok=True)
|
|
1386
|
+
incident = _reserve_quarantine_incident(root, db_path.name, ts)
|
|
1357
1387
|
for directory in (root, incident):
|
|
1358
1388
|
try:
|
|
1359
1389
|
os.chmod(directory, 0o700)
|
package/bin/_cctally_record.py
CHANGED
|
@@ -5812,6 +5812,15 @@ def _pipeline_claude_usage(ctx, rec):
|
|
|
5812
5812
|
columns=cols,
|
|
5813
5813
|
at=capture_at,
|
|
5814
5814
|
)
|
|
5815
|
+
# An exact retry can meet effective event metadata whose physical row a
|
|
5816
|
+
# later suppression deliberately deleted. emit_model_a must not
|
|
5817
|
+
# resurrect that row and therefore returns None; deriving milestones or
|
|
5818
|
+
# blocks from the absent target would either create dangling references
|
|
5819
|
+
# or attempt int(None). This is distinct from an ordinary fold-decision
|
|
5820
|
+
# skip, which resolves a real latest snapshot below and still runs the
|
|
5821
|
+
# idempotent side-effect chokepoints.
|
|
5822
|
+
if rowid is None:
|
|
5823
|
+
return
|
|
5815
5824
|
saved["id"] = rowid
|
|
5816
5825
|
else:
|
|
5817
5826
|
latest = conn.execute(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cctally",
|
|
3
|
-
"version": "1.95.
|
|
3
|
+
"version": "1.95.1",
|
|
4
4
|
"description": "Claude Code usage tracker and local dashboard for Pro/Max subscription limits - weekly cost-per-percent trend, quota forecasts, threshold alerts. ccusage-compatible.",
|
|
5
5
|
"homepage": "https://github.com/omrikais/cctally",
|
|
6
6
|
"repository": {
|