cctally 1.117.2 → 1.118.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +7 -3
  3. package/bin/_cctally_cache.py +2783 -175
  4. package/bin/_cctally_completion.py +106 -33
  5. package/bin/_cctally_core.py +69 -4
  6. package/bin/_cctally_dashboard.py +188 -13
  7. package/bin/_cctally_dashboard_conversation.py +5 -1
  8. package/bin/_cctally_dashboard_envelope.py +50 -19
  9. package/bin/_cctally_dashboard_sources.py +23 -1
  10. package/bin/_cctally_db.py +193 -153
  11. package/bin/_cctally_diagnosis_sources.py +63 -7
  12. package/bin/_cctally_doctor.py +865 -93
  13. package/bin/_cctally_five_hour.py +67 -50
  14. package/bin/_cctally_forecast.py +35 -13
  15. package/bin/_cctally_journal.py +186 -18
  16. package/bin/_cctally_milestone_history.py +859 -62
  17. package/bin/_cctally_oauth_samples.py +286 -15
  18. package/bin/_cctally_parser.py +2 -2
  19. package/bin/_cctally_quota.py +104 -12
  20. package/bin/_cctally_record.py +153 -29
  21. package/bin/_cctally_rederive.py +608 -68
  22. package/bin/_cctally_statusline.py +108 -81
  23. package/bin/_cctally_store.py +179 -38
  24. package/bin/_cctally_tui.py +892 -181
  25. package/bin/_cctally_update.py +32 -13
  26. package/bin/_lib_codex_conversation_query.py +52 -14
  27. package/bin/_lib_codex_drain_admission.py +224 -6
  28. package/bin/_lib_codex_metadata.py +39 -0
  29. package/bin/_lib_conversation_anon.py +15 -8
  30. package/bin/_lib_conversation_dispatch.py +7 -0
  31. package/bin/_lib_conversation_query.py +289 -70
  32. package/bin/_lib_dashboard_sources.py +37 -1
  33. package/bin/_lib_detector_access_registry.py +497 -26
  34. package/bin/_lib_doctor.py +85 -20
  35. package/bin/_lib_doctor_scan_index.py +321 -0
  36. package/bin/_lib_ingest_frontier.py +20 -1
  37. package/bin/_lib_oauth_samples.py +23 -7
  38. package/bin/_lib_pricing.py +146 -16
  39. package/bin/_lib_quota.py +19 -0
  40. package/bin/_lib_rederive.py +11 -6
  41. package/bin/_lib_retained_records.py +516 -0
  42. package/bin/_lib_snapshot_cache.py +10 -0
  43. package/bin/_lib_stale_proof.py +272 -43
  44. package/bin/_lib_stats_content_revision.py +831 -0
  45. package/bin/_lib_write_io.py +11 -4
  46. package/bin/cctally +4 -1
  47. package/dashboard/static/assets/ConversationsView-D4Iv4ku0.js +72 -0
  48. package/dashboard/static/assets/{DoctorModal-B0DnU9d9.js → DoctorModal-D4QSq6uz.js} +1 -1
  49. package/dashboard/static/assets/ModalRoot-C9F5dhZi.js +1 -0
  50. package/dashboard/static/assets/{ProjectsDrillPanel-DSxXQSa-.js → ProjectsDrillPanel-WxAdRDMt.js} +1 -1
  51. package/dashboard/static/assets/{SourceDetailModal-BleDpQqG.js → SourceDetailModal-NyyEfXmc.js} +1 -1
  52. package/dashboard/static/assets/{UpdateModal-CMqaAkSK.js → UpdateModal-CFLq9pT1.js} +1 -1
  53. package/dashboard/static/assets/{index-BjQV-0-D.js → index-BKTWH9JE.js} +2 -2
  54. package/dashboard/static/assets/{outlineNavigation-CO-VWBm7.js → outlineNavigation-BnGUwCS3.js} +3 -3
  55. package/dashboard/static/dashboard.html +1 -1
  56. package/package.json +4 -1
  57. package/dashboard/static/assets/ConversationsView-oqNsWzWV.js +0 -72
  58. package/dashboard/static/assets/ModalRoot-CYua2wP_.js +0 -1
@@ -281,7 +281,7 @@ class DoctorState:
281
281
  conversations_db_quick_check: Optional[str] = None
282
282
  # #279 S2 (F5c): non-blocking flock probes on the two sync lock files
283
283
  # (name -> True held / False free / None unreadable). Probe never
284
- # creates files (doctor read-only contract). None = probe errored.
284
+ # creates files (doctor never changes your data). None = probe errored.
285
285
  locks_held: Optional[dict] = None
286
286
  # #297: size in bytes of cache.db-wal, gathered read-only in
287
287
  # doctor_gather_state (getsize, OSError/absent -> None). Warns above
@@ -485,6 +485,14 @@ class DoctorState:
485
485
  # launch ledger, process identities probed before a re-read, pending
486
486
  # marker). None = not gathered; the check then reads both as never run.
487
487
  rebuild_workers: Optional[dict] = None
488
+ # #1018: the newest admitted Claude weekly reading (the observation
489
+ # index), no later than `now_utc`. `latest_snapshot_at` keeps its
490
+ # held-inclusive materialized meaning; `data.latest_snapshot_age` takes
491
+ # the newer of the two.
492
+ latest_reading_at: Optional[dt.datetime] = None
493
+ # #1018 C6: why freshness evidence could not be read — None,
494
+ # "stats_maintenance" or "stats_unreadable".
495
+ freshness_unavailable_reason: Optional[str] = None
488
496
 
489
497
 
490
498
  @dataclasses.dataclass(frozen=True)
@@ -1059,7 +1067,7 @@ def _check_db_version_ahead(s: DoctorState) -> CheckResult:
1059
1067
  # current index as a mismatch; keep this in lockstep with core
1060
1068
  # (#496 S5b §6.1). It stays a literal because this kernel is pure and
1061
1069
  # must not import `_cctally_core`.
1062
- epoch = 1023
1070
+ epoch = 1024
1063
1071
  mismatch = uv > legacy_head and uv != epoch
1064
1072
  return {"user_version": uv, "legacy_head": legacy_head, "epoch": epoch,
1065
1073
  "mismatch": mismatch}
@@ -1376,29 +1384,86 @@ def rebuild_worker_check(rebuild_workers, now_utc) -> CheckResult:
1376
1384
  )
1377
1385
 
1378
1386
 
1387
+ _FRESHNESS_UNAVAILABLE = {
1388
+ "stats_maintenance": (
1389
+ "unavailable (stats.db maintenance in progress)",
1390
+ "Retry after the stats.db maintenance finishes.",
1391
+ ),
1392
+ "stats_unreadable": (
1393
+ "unavailable (stats.db could not be read)",
1394
+ "See the stats.db check.",
1395
+ ),
1396
+ }
1397
+
1398
+
1379
1399
  def _check_data_latest_snapshot_age(s: DoctorState) -> CheckResult:
1380
- if s.latest_snapshot_at is None:
1381
- return CheckResult(
1382
- id="data.latest_snapshot_age", title="Latest snapshot",
1383
- severity="fail", summary="never",
1384
- remediation="Check hooks are installed and Claude Code is running",
1385
- details={"latest_snapshot_at": None},
1386
- )
1387
- age_s = int((s.now_utc - s.latest_snapshot_at).total_seconds())
1388
- if age_s <= 300:
1389
- sev, rem = "ok", None
1390
- elif age_s <= 3600:
1400
+ """Is the usage write path alive? (#1018, spec 5.4.)
1401
+
1402
+ The evidence is the newer of the held-inclusive materialized snapshot and
1403
+ the newest admitted reading, each counted only when no later than now.
1404
+ Fresh positive evidence decides even when a source failed; stale or absent
1405
+ evidence with a failed source is "unavailable", never silence.
1406
+ """
1407
+ now = s.now_utc
1408
+
1409
+ def _valid(moment):
1410
+ return moment if moment is not None and moment <= now else None
1411
+
1412
+ def _age(moment):
1413
+ return int((now - moment).total_seconds())
1414
+
1415
+ snapshot = _valid(s.latest_snapshot_at)
1416
+ reading = _valid(s.latest_reading_at)
1417
+ valid = [moment for moment in (snapshot, reading) if moment is not None]
1418
+ candidate = max(valid) if valid else None
1419
+ cand_age = _age(candidate) if candidate is not None else None
1420
+ reason = s.freshness_unavailable_reason
1421
+
1422
+ effective = candidate
1423
+ if candidate is not None and cand_age <= 300:
1424
+ sev, summary, rem = "ok", f"{cand_age}s ago", None
1425
+ elif reason:
1391
1426
  sev = "warn"
1427
+ summary, rem = _FRESHNESS_UNAVAILABLE.get(
1428
+ reason, _FRESHNESS_UNAVAILABLE["stats_unreadable"])
1429
+ effective = None
1430
+ elif candidate is not None and cand_age <= 3600:
1431
+ sev, summary = "warn", f"{cand_age}s ago"
1392
1432
  rem = "Recent but not current. Check Claude Code session is active."
1393
- else:
1394
- sev = "fail"
1433
+ elif candidate is not None:
1434
+ sev, summary = "fail", f"{cand_age}s ago"
1395
1435
  rem = "No snapshot in >1h. Hooks may be broken — check `cctally setup --status`."
1436
+ else:
1437
+ sev, summary = "fail", "never"
1438
+ rem = "Check hooks are installed and Claude Code is running"
1439
+
1440
+ details: dict = {
1441
+ "latest_snapshot_at": (
1442
+ s.latest_snapshot_at.isoformat()
1443
+ if s.latest_snapshot_at is not None else None
1444
+ ),
1445
+ }
1446
+ if s.latest_snapshot_at is not None:
1447
+ details["latest_snapshot_age_s"] = (
1448
+ _age(snapshot) if snapshot is not None else None
1449
+ )
1450
+ details.update({
1451
+ "latest_reading_at": (
1452
+ s.latest_reading_at.isoformat()
1453
+ if s.latest_reading_at is not None else None
1454
+ ),
1455
+ "latest_reading_age_s": _age(reading) if reading is not None else None,
1456
+ "effective_freshness_at": (
1457
+ effective.isoformat() if effective is not None else None
1458
+ ),
1459
+ "effective_freshness_age_s": (
1460
+ _age(effective) if effective is not None else None
1461
+ ),
1462
+ "freshness_unavailable_reason": reason,
1463
+ })
1396
1464
  return CheckResult(
1397
1465
  id="data.latest_snapshot_age", title="Latest snapshot",
1398
- severity=sev, summary=f"{age_s}s ago",
1399
- remediation=rem,
1400
- details={"latest_snapshot_at": s.latest_snapshot_at.isoformat(),
1401
- "latest_snapshot_age_s": age_s},
1466
+ severity=sev, summary=summary, remediation=rem, details=details,
1402
1467
  )
1403
1468
 
1404
1469
 
@@ -2386,7 +2451,7 @@ def _check_data_conversation_sessions_rollup(s: DoctorState) -> CheckResult:
2386
2451
  ``doctor_gather_state`` from a NON-BLOCKING ``conversations.db.lock`` flock probe
2387
2452
  (lock held ⇒ a writer is mid-flight) AND the presence of any pending
2388
2453
  ``cache_meta`` reingest/split/backfill flag; if either says in-progress, this
2389
- stays OK. Doctor remains read-only and never blocks on the lock.
2454
+ stays OK. Doctor never changes your data and never blocks on the lock.
2390
2455
 
2391
2456
  Informational only (no remediation): the next conversation sync re-derives
2392
2457
  the rollup via its incremental DELETE+INSERT.
@@ -0,0 +1,321 @@
1
+ """The deep doctor journal scan's checkpoint — the pure kernel (#1054).
2
+
3
+ `cctally doctor` decodes every journal line on every deep run to compute its
4
+ journal legs: the malformed and torn counts, the same-revision conflicts, the
5
+ structural protocol violations and acknowledgements, the cutover value and the
6
+ excluded population. On a production-sized journal that is about 2.6 million
7
+ decodes, of which roughly 5 % are records the scan keeps; the rest are
8
+ observations that only consume a selector position.
9
+
10
+ Doctor therefore keeps one disposable file beside the segments,
11
+ ``JOURNAL_DIR/.doctor-scan-index``. Per segment it records a SHA-256 of the
12
+ complete-line prefix it scanned, the number of decoded records and malformed
13
+ lines in that prefix, and the line offset and decoded ordinal of every record
14
+ that matters to the scan (a "hit"). A later run proves the prefix against the
15
+ current bytes, decodes only the hits, fills every other position with the
16
+ ``None`` placeholder the selector expects, and decodes the fresh suffix. The
17
+ excluded population is cached the same way, under its own digest of the range
18
+ it classified and an explicit fingerprint of the admission boundary.
19
+
20
+ **Nothing here is trusted without a content proof.** There is no stat-identity
21
+ shortcut: every reuse hashes the bytes it skips. A document whose version or
22
+ checksum does not match is ignored as a whole, and an entry that fails the
23
+ structural rules below is dropped while the others are kept. Torn status is
24
+ never stored, because a torn tail can be repaired and re-torn at the same size.
25
+
26
+ **The version is derived, not stamped.** `index_version` hashes `SHAPE_VERSION`
27
+ with the descriptors the glue supplies (the retained record types, the special
28
+ ids, the protocol-resolution kinds, every constant the admission predicate
29
+ reads and the stats epoch), so a change to any of those invalidates every
30
+ checkpoint. `SHAPE_VERSION` is bumped by hand for any change those inputs do
31
+ not capture: the meaning of a stored field, the decoder, or the scan's
32
+ per-record steps.
33
+
34
+ This module performs no I/O and imports nothing from the journal modules, the
35
+ same rule `bin/_lib_segment_summary.py` and `bin/_lib_journal_router.py` follow,
36
+ so every rule here is unit-testable without a journal on disk. The glue — the
37
+ read, the per-record step, the exclusion read and the atomic publication —
38
+ lives in `bin/_cctally_doctor.py`.
39
+ """
40
+ from __future__ import annotations
41
+
42
+ import dataclasses
43
+ import hashlib
44
+ import json
45
+ import re
46
+ from typing import Iterable
47
+
48
+ #: Bumped by hand whenever a stored field's meaning, the decoder semantics or
49
+ #: the scan's per-record steps change in a way the derived descriptors do not
50
+ #: capture (see `docs/journal-gotchas.md`).
51
+ SHAPE_VERSION = 1
52
+
53
+ _HEX_DIGEST = re.compile(r"\A[0-9a-f]{64}\Z")
54
+ _ENTRY_KEYS = frozenset(
55
+ ("name", "covered", "sha256", "decoded", "malformed", "hits", "exclusion"))
56
+ _EXCLUSION_KEYS = frozenset(("boundary", "lo", "covered", "sha256", "excluded"))
57
+
58
+
59
+ @dataclasses.dataclass(frozen=True)
60
+ class Exclusion:
61
+ """One segment's cached excluded population over ``[lo, covered)``.
62
+
63
+ ``boundary`` is the `boundary_fingerprint` it was computed under and
64
+ ``sha256`` the hex digest of exactly those bytes; ``excluded`` lists each
65
+ excluded record's line-start offset and admission family, in offset
66
+ order."""
67
+
68
+ boundary: str
69
+ lo: int
70
+ covered: int
71
+ sha256: str
72
+ excluded: tuple
73
+
74
+
75
+ @dataclasses.dataclass(frozen=True)
76
+ class Entry:
77
+ """One segment's main-scan checkpoint over ``[0, covered)``.
78
+
79
+ ``covered`` is a complete-line end or 0; ``sha256`` the hex digest of
80
+ those bytes; ``decoded`` and ``malformed`` the decoded records and the
81
+ malformed non-blank complete lines in that prefix; ``hits`` the
82
+ ``(line-start offset, 0-based decoded ordinal)`` of every record that
83
+ matters to the scan, in offset order."""
84
+
85
+ name: str
86
+ covered: int
87
+ sha256: str
88
+ decoded: int
89
+ malformed: int
90
+ hits: tuple
91
+ exclusion: "Exclusion | None"
92
+
93
+
94
+ def _canonical(obj) -> bytes:
95
+ return json.dumps(obj, sort_keys=True, separators=(",", ":"),
96
+ allow_nan=False).encode("utf-8")
97
+
98
+
99
+ def _digest(obj) -> str:
100
+ return "sha256:" + hashlib.sha256(_canonical(obj)).hexdigest()
101
+
102
+
103
+ def index_version(descriptors: dict) -> str:
104
+ """The checkpoint version: `SHAPE_VERSION` plus every derived input."""
105
+ return _digest({"shape": SHAPE_VERSION, "descriptors": descriptors})
106
+
107
+
108
+ def boundary_fingerprint(fields: dict) -> str:
109
+ """The digest of the admission-boundary fields the glue serializes."""
110
+ return _digest(fields)
111
+
112
+
113
+ def _exclusion_document(exclusion: "Exclusion | None"):
114
+ if exclusion is None:
115
+ return None
116
+ return {"boundary": exclusion.boundary, "lo": exclusion.lo,
117
+ "covered": exclusion.covered, "sha256": exclusion.sha256,
118
+ "excluded": [[offset, family]
119
+ for offset, family in exclusion.excluded]}
120
+
121
+
122
+ def _entry_document(entry: Entry) -> dict:
123
+ return {"name": entry.name, "covered": entry.covered,
124
+ "sha256": entry.sha256, "decoded": entry.decoded,
125
+ "malformed": entry.malformed,
126
+ "hits": [[offset, ordinal] for offset, ordinal in entry.hits],
127
+ "exclusion": _exclusion_document(entry.exclusion)}
128
+
129
+
130
+ def encode(version: str, entries: Iterable[Entry]) -> bytes:
131
+ """The canonical checkpoint document: segments in the given order."""
132
+ segments = [_entry_document(entry) for entry in entries]
133
+ return _canonical({"version": version, "checksum": _digest(segments),
134
+ "segments": segments})
135
+
136
+
137
+ def _count(value) -> bool:
138
+ return type(value) is int and value >= 0
139
+
140
+
141
+ def _hex(value) -> bool:
142
+ return isinstance(value, str) and _HEX_DIGEST.match(value) is not None
143
+
144
+
145
+ def _pairs(value, second_ok, lo: int, hi: int, second_hi=None):
146
+ """``value`` as a tuple of ``(offset, second)`` pairs with offsets
147
+ strictly increasing in ``[lo, hi)`` — and, when ``second_hi`` is given,
148
+ integer seconds strictly increasing in ``[0, second_hi)`` — else None."""
149
+ if not isinstance(value, list):
150
+ return None
151
+ out = []
152
+ last_offset = lo - 1
153
+ last_second = -1
154
+ for item in value:
155
+ if not isinstance(item, list) or len(item) != 2:
156
+ return None
157
+ offset, second = item
158
+ if not _count(offset) or not (last_offset < offset < hi):
159
+ return None
160
+ if not second_ok(second):
161
+ return None
162
+ if second_hi is not None:
163
+ if not (last_second < second < second_hi):
164
+ return None
165
+ last_second = second
166
+ last_offset = offset
167
+ out.append((offset, second))
168
+ return tuple(out)
169
+
170
+
171
+ def _exclusion_from_document(doc):
172
+ """``(valid, Exclusion | None)``."""
173
+ if doc is None:
174
+ return True, None
175
+ if not isinstance(doc, dict) or set(doc) != _EXCLUSION_KEYS:
176
+ return False, None
177
+ boundary, lo, covered = doc["boundary"], doc["lo"], doc["covered"]
178
+ if not isinstance(boundary, str) or not boundary:
179
+ return False, None
180
+ if not (_count(lo) and _count(covered) and lo <= covered):
181
+ return False, None
182
+ if not _hex(doc["sha256"]):
183
+ return False, None
184
+ excluded = _pairs(doc["excluded"], lambda family: isinstance(family, str),
185
+ lo, covered)
186
+ if excluded is None:
187
+ return False, None
188
+ return True, Exclusion(boundary, lo, covered, doc["sha256"], excluded)
189
+
190
+
191
+ def _entry_from_document(doc) -> "Entry | None":
192
+ if not isinstance(doc, dict) or set(doc) != _ENTRY_KEYS:
193
+ return None
194
+ name, covered = doc["name"], doc["covered"]
195
+ decoded, malformed = doc["decoded"], doc["malformed"]
196
+ if not isinstance(name, str) or not name:
197
+ return None
198
+ if not (_count(covered) and _count(decoded) and _count(malformed)):
199
+ return None
200
+ # Each counted line occupies at least one byte of `[0, covered)`: its
201
+ # newline.
202
+ if malformed + decoded > covered:
203
+ return None
204
+ if not _hex(doc["sha256"]):
205
+ return None
206
+ hits = _pairs(doc["hits"], _count, 0, covered, second_hi=decoded)
207
+ if hits is None:
208
+ return None
209
+ valid, exclusion = _exclusion_from_document(doc["exclusion"])
210
+ if not valid:
211
+ return None
212
+ return Entry(name, covered, doc["sha256"], decoded, malformed, hits,
213
+ exclusion)
214
+
215
+
216
+ def decode(raw: bytes, version: str) -> "dict[str, Entry] | None":
217
+ """The valid entries of a checkpoint document, keyed by segment name.
218
+
219
+ None when the document does not parse, names another version or fails
220
+ its checksum. A structurally invalid entry is dropped and the others are
221
+ kept; a segment named twice keeps neither entry."""
222
+ return decode_document(raw, version)[0]
223
+
224
+
225
+ def decode_document(raw: bytes, version: str):
226
+ """``(decode(raw, version), clean)``: ``clean`` is True only when the
227
+ document is valid and every one of its entries was kept, so a writer
228
+ holding the same entries has nothing to rewrite."""
229
+ try:
230
+ doc = json.loads(raw)
231
+ except (ValueError, TypeError, RecursionError):
232
+ return None, False
233
+ if not isinstance(doc, dict) or doc.get("version") != version:
234
+ return None, False
235
+ segments = doc.get("segments")
236
+ if not isinstance(segments, list):
237
+ return None, False
238
+ try:
239
+ if doc.get("checksum") != _digest(segments):
240
+ return None, False
241
+ except (ValueError, TypeError, RecursionError):
242
+ return None, False
243
+ out: dict = {}
244
+ duplicated = set()
245
+ for item in segments:
246
+ entry = _entry_from_document(item)
247
+ if entry is None:
248
+ continue
249
+ if entry.name in out:
250
+ duplicated.add(entry.name)
251
+ out[entry.name] = entry
252
+ for name in duplicated:
253
+ del out[name]
254
+ return out, len(out) == len(segments)
255
+
256
+
257
+ def verify_prefix(entry_sha: str, covered: int, data: bytes):
258
+ """The running SHA-256 after ``data[:covered]`` when that prefix is a
259
+ complete-line prefix of ``data`` whose digest is ``entry_sha``, else None.
260
+
261
+ The caller continues the returned object over the fresh suffix, so every
262
+ byte is hashed once."""
263
+ if len(data) < covered:
264
+ return None
265
+ if covered and data[covered - 1] != 0x0A:
266
+ return None
267
+ hasher = hashlib.sha256()
268
+ hasher.update(memoryview(data)[:covered])
269
+ if hasher.hexdigest() != entry_sha:
270
+ return None
271
+ return hasher
272
+
273
+
274
+ class RangeHasher:
275
+ """An ``on_bytes`` callback hashing every chunk of one ``[lo, hi)`` read.
276
+
277
+ With ``covered`` set it also proves the cached prefix ``[lo, covered)``:
278
+ the chunk that crosses ``covered`` is split, the prefix digest compared
279
+ with ``expected_prefix``, and ``prefix_state`` settled before the call
280
+ returns — so before the reader yields any line starting at or after
281
+ ``covered``. With ``covered == lo`` the empty prefix is compared at
282
+ construction. ``prefix_state`` is ``"none"`` without ``covered``,
283
+ otherwise ``"pending"`` until settled as ``"match"`` or ``"mismatch"``;
284
+ a read that ends while it is still pending never reached ``covered``."""
285
+
286
+ def __init__(self, lo: int, covered: "int | None",
287
+ expected_prefix: "str | None"):
288
+ if covered is not None and covered < lo:
289
+ raise ValueError("covered precedes lo")
290
+ self._lo = lo
291
+ self._covered = covered
292
+ self._expected = expected_prefix
293
+ self._hash = hashlib.sha256()
294
+ self.consumed = 0
295
+ if covered is None:
296
+ self.prefix_state = "none"
297
+ elif covered == lo:
298
+ self.prefix_state = self._settle()
299
+ else:
300
+ self.prefix_state = "pending"
301
+
302
+ def _settle(self) -> str:
303
+ return ("match" if self._hash.hexdigest() == self._expected
304
+ else "mismatch")
305
+
306
+ def __call__(self, chunk: bytes) -> None:
307
+ if self.prefix_state == "pending":
308
+ need = self._covered - self._lo - self.consumed
309
+ if len(chunk) >= need:
310
+ view = memoryview(chunk)
311
+ self._hash.update(view[:need])
312
+ self.prefix_state = self._settle()
313
+ self._hash.update(view[need:])
314
+ self.consumed += len(chunk)
315
+ return
316
+ self._hash.update(chunk)
317
+ self.consumed += len(chunk)
318
+
319
+ def digest(self) -> str:
320
+ """The hex digest of every byte consumed so far."""
321
+ return self._hash.hexdigest()
@@ -79,6 +79,13 @@ _COMPLETE_KEYS = {
79
79
  #: clean except for pending prune debt writes a fresh one; any other
80
80
  #: unbudgeted or certifying full walk deletes it.
81
81
  CODEX_FULL_WALK_DEBT_ONLY_KEY = "dashboard_codex_full_walk_debt_only"
82
+ #: #916. cache.db's residual-only token: the most recent exhaustive Claude
83
+ #: walk visited every on-disk source cleanly and its only defect was tracked
84
+ #: rows whose transcripts no longer exist (orphans the prune gates refused).
85
+ #: Mutually exclusive with ``claude_ingest_walk_complete``, which keeps its
86
+ #: D5a meaning for the upgrade gate; only this frontier reads the token, as a
87
+ #: second way for the Claude store to be complete. Its JSON value is debug-only.
88
+ CLAUDE_WALK_ORPHAN_RESIDUAL_KEY = "claude_ingest_walk_orphan_residual"
82
89
  CODEX_CONVERSATION_WALK_DEBT_ONLY_KEY = "dashboard_codex_conversation_walk_debt_only"
83
90
  #: How long a frontier holds a debt-only store before walking it again: the
84
91
  #: certificate's own staleness bound, so a store whose certificate is
@@ -1872,6 +1879,15 @@ def _pending_identity(conn) -> tuple[str, ...]:
1872
1879
 
1873
1880
 
1874
1881
  def _provider_complete(conn, provider: str) -> bool:
1882
+ if provider == "claude":
1883
+ # #916: a residual-only walk certifies every on-disk source as fully
1884
+ # as a clean one; the orphan rows it leaves are never read or written
1885
+ # by a walk, so they cannot hide pending work.
1886
+ row = conn.execute(
1887
+ "SELECT 1 FROM cache_meta WHERE key IN (?, ?) LIMIT 1",
1888
+ (_COMPLETE_KEYS[provider], CLAUDE_WALK_ORPHAN_RESIDUAL_KEY),
1889
+ ).fetchone()
1890
+ return row is not None
1875
1891
  row = conn.execute(
1876
1892
  "SELECT 1 FROM cache_meta WHERE key=? LIMIT 1",
1877
1893
  (_COMPLETE_KEYS[provider],),
@@ -2228,7 +2244,10 @@ def provider_sync_certifiable(mode: str, stats: object) -> bool:
2228
2244
  if mode == "targeted":
2229
2245
  return bool(getattr(stats, "targeted_clean", True))
2230
2246
  if mode == "full":
2231
- return bool(getattr(stats, "full_walk_complete", False))
2247
+ # #916: ``full_walk_residual_only`` exists only on the Claude
2248
+ # ``IngestStats``; Codex stats and ``_DebtCleared`` read False.
2249
+ return bool(getattr(stats, "full_walk_complete", False)
2250
+ or getattr(stats, "full_walk_residual_only", False))
2232
2251
  return False
2233
2252
 
2234
2253
 
@@ -164,6 +164,28 @@ def _percent(value) -> "float | None":
164
164
  return value if math.isfinite(value) else None
165
165
 
166
166
 
167
+ def finite_percent(value) -> "float | None":
168
+ """A raw payload percent as a finite float, or None (a bool, a string, a
169
+ missing or non-finite value). The one parse both the samples projection
170
+ and the weekly observation index (#1018 twin spec 5.1) apply."""
171
+ return _percent(value)
172
+
173
+
174
+ def weekly_reset_identity(resets_at) -> "str | None":
175
+ """The normalized weekly window identity of a raw payload ``resets_at``
176
+ (:func:`normalize_weekly_reset`), or None when it is missing, a bool or
177
+ unparsable. `claude_oauth_samples.weekly_resets_at_utc` and
178
+ `claude_weekly_observation_index.weekly_resets_at_utc` (#1018 twin spec
179
+ 5.1) are both spelled by this one helper, so window equality holds
180
+ between them."""
181
+ if resets_at is None or isinstance(resets_at, bool):
182
+ return None
183
+ try:
184
+ return normalize_weekly_reset(int(resets_at))
185
+ except (TypeError, ValueError, OverflowError, OSError):
186
+ return None
187
+
188
+
167
189
  def _bound_account(payload: dict) -> "str | None":
168
190
  binding = payload.get("account_binding")
169
191
  if not isinstance(binding, dict):
@@ -200,13 +222,7 @@ def sample_from_observation(payload: dict, *, account_key: str,
200
222
  return None
201
223
 
202
224
  weekly_percent = _percent(payload.get("weekly_percent"))
203
- weekly_reset = None
204
- resets_at = payload.get("resets_at")
205
- if not isinstance(resets_at, bool):
206
- try:
207
- weekly_reset = normalize_weekly_reset(int(resets_at))
208
- except (TypeError, ValueError, OverflowError, OSError):
209
- weekly_reset = None
225
+ weekly_reset = weekly_reset_identity(payload.get("resets_at"))
210
226
  if weekly_reset is None:
211
227
  weekly_percent = None
212
228