cctally 1.113.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.
Files changed (97) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +4 -6
  3. package/bin/_cctally_account.py +32 -8
  4. package/bin/_cctally_cache.py +6035 -722
  5. package/bin/_cctally_completion.py +1287 -0
  6. package/bin/_cctally_config.py +37 -0
  7. package/bin/_cctally_core.py +400 -2
  8. package/bin/_cctally_dashboard.py +98 -20
  9. package/bin/_cctally_dashboard_conversation.py +292 -23
  10. package/bin/_cctally_dashboard_envelope.py +16 -3
  11. package/bin/_cctally_dashboard_perf.py +128 -9
  12. package/bin/_cctally_dashboard_sources.py +285 -213
  13. package/bin/_cctally_db.py +1177 -23
  14. package/bin/_cctally_diagnosis_sources.py +22 -4
  15. package/bin/_cctally_doctor.py +572 -45
  16. package/bin/_cctally_five_hour.py +84 -33
  17. package/bin/_cctally_forecast.py +108 -15
  18. package/bin/_cctally_journal.py +3950 -116
  19. package/bin/_cctally_journal_repair.py +101 -12
  20. package/bin/_cctally_milestone_history.py +82 -9
  21. package/bin/_cctally_parser.py +42 -0
  22. package/bin/_cctally_percent_breakdown.py +3 -2
  23. package/bin/_cctally_project.py +8 -2
  24. package/bin/_cctally_quarantine.py +652 -0
  25. package/bin/_cctally_quota.py +227 -37
  26. package/bin/_cctally_quota_model.py +360 -83
  27. package/bin/_cctally_record.py +691 -149
  28. package/bin/_cctally_rederive.py +320 -41
  29. package/bin/_cctally_refresh.py +84 -13
  30. package/bin/_cctally_source_analytics.py +83 -17
  31. package/bin/_cctally_statusline.py +6154 -517
  32. package/bin/_cctally_store.py +367 -53
  33. package/bin/_cctally_transcript.py +8 -7
  34. package/bin/_cctally_tui.py +61 -56
  35. package/bin/_cctally_weekrefs.py +128 -21
  36. package/bin/_lib_account_binding.py +152 -0
  37. package/bin/_lib_account_protocol.py +386 -0
  38. package/bin/_lib_aggregators.py +4 -0
  39. package/bin/_lib_cache_coverage.py +13 -1
  40. package/bin/_lib_cache_report.py +35 -6
  41. package/bin/_lib_cache_writer_lock.py +19 -3
  42. package/bin/_lib_clone_copy.py +142 -0
  43. package/bin/_lib_codex_conversation.py +32 -0
  44. package/bin/_lib_codex_conversation_query.py +297 -49
  45. package/bin/_lib_codex_conversation_watch.py +59 -14
  46. package/bin/_lib_codex_drain_admission.py +214 -0
  47. package/bin/_lib_codex_metadata.py +18 -1
  48. package/bin/_lib_codex_publication_barrier.py +277 -0
  49. package/bin/_lib_codex_rollout_container.py +502 -0
  50. package/bin/_lib_conversation_dispatch.py +3 -0
  51. package/bin/_lib_conversation_query.py +106 -59
  52. package/bin/_lib_conversation_retention.py +1605 -640
  53. package/bin/_lib_cost_provenance.py +53 -2
  54. package/bin/_lib_detector_access_registry.py +3817 -0
  55. package/bin/_lib_doctor.py +714 -69
  56. package/bin/_lib_five_hour.py +126 -0
  57. package/bin/_lib_fmt.py +11 -0
  58. package/bin/_lib_ingest_frontier.py +414 -20
  59. package/bin/_lib_journal.py +141 -10
  60. package/bin/_lib_jsonl.py +5 -4
  61. package/bin/_lib_legacy_spool.py +126 -0
  62. package/bin/_lib_legacy_writer_evidence.py +136 -0
  63. package/bin/_lib_merge_gate.py +231 -86
  64. package/bin/_lib_meter_rate_change.py +10 -0
  65. package/bin/_lib_outcome_quarantine.py +690 -0
  66. package/bin/_lib_perf.py +27 -1
  67. package/bin/_lib_pricing.py +199 -9
  68. package/bin/_lib_pricing_check.py +20 -4
  69. package/bin/_lib_quarantine.py +154 -0
  70. package/bin/_lib_quota_calibration.py +53 -8
  71. package/bin/_lib_quota_copy.py +2 -1
  72. package/bin/_lib_quota_model.py +288 -46
  73. package/bin/_lib_reclaim_planner.py +792 -0
  74. package/bin/_lib_rederive.py +319 -82
  75. package/bin/_lib_segment_summary.py +35 -2
  76. package/bin/_lib_snapshot_cache.py +21 -0
  77. package/bin/_lib_sqlite_close.py +81 -0
  78. package/bin/_lib_statusline_candidates.py +292 -23
  79. package/bin/_lib_subscription_weeks.py +100 -17
  80. package/bin/_lib_tick_stats.py +217 -17
  81. package/bin/_lib_wal_checkpoint.py +1077 -0
  82. package/bin/_lib_write_budget.py +483 -0
  83. package/bin/_lib_write_io.py +703 -0
  84. package/bin/cctally +31 -1
  85. package/dashboard/static/assets/{ConversationsView-CEZ-BUyi.js → ConversationsView-Bc8LCOAZ.js} +1 -1
  86. package/dashboard/static/assets/{DoctorModal-CE1oH_Zy.js → DoctorModal-D--qVBwe.js} +1 -1
  87. package/dashboard/static/assets/ModalRoot-DLPgyAM_.js +1 -0
  88. package/dashboard/static/assets/{ProjectsDrillPanel-B3DZoCeD.js → ProjectsDrillPanel-z53a4v4M.js} +1 -1
  89. package/dashboard/static/assets/{SourceDetailModal-CHp9EgLP.js → SourceDetailModal-Bw0T8W-W.js} +1 -1
  90. package/dashboard/static/assets/{UpdateModal-5iKST6jo.js → UpdateModal-D5AKx4Yw.js} +1 -1
  91. package/dashboard/static/assets/index-BKITk1CT.css +1 -0
  92. package/dashboard/static/assets/{index-Cm0VTxJC.js → index-BXDOzSng.js} +3 -3
  93. package/dashboard/static/assets/{outlineNavigation-DkXGL8J6.js → outlineNavigation-JObY7cUY.js} +1 -1
  94. package/dashboard/static/dashboard.html +2 -2
  95. package/package.json +19 -1
  96. package/dashboard/static/assets/ModalRoot-D5Y8IFUG.js +0 -1
  97. package/dashboard/static/assets/index-Dt4tqf43.css +0 -1
@@ -0,0 +1,386 @@
1
+ """Pure account-bound protocol kernel (#825/#832, spec sections 5.1-5.4).
2
+
3
+ One durable journal and one stats index are fenced by a durable
4
+ ``account_bound_cutover`` op and a per-record writer-generation stamp. This
5
+ module owns:
6
+
7
+ * the protocol constants (writer generation, op kinds, the cutover op id);
8
+ * the stamp the shared append preparation applies to every record the current
9
+ binary writes;
10
+ * the historical boundary the cutover op records, and its payload codec;
11
+ * the admission predicate every accounting reader applies BEFORE it interprets
12
+ a record (ingest, rebuild, rederive, journal-repair, correction selection,
13
+ cache recovery and the deep doctor scans).
14
+
15
+ "After the cutover" is a PHYSICAL journal position (segment and byte offset of
16
+ a complete line), never ``at``, capture time or provider time. A record is
17
+ historical when it lies inside the frozen complete-line extent the cutover op
18
+ recorded for its segment; a segment absent from that map (a later month, or a
19
+ bootstrap segment created after the cutover, which sorts FIRST in canonical
20
+ order) is post-boundary in its entirety.
21
+
22
+ The fence is Claude-scoped (operator Q5). Exclusion is the default: a
23
+ post-boundary record without the current writer generation is admitted only
24
+ when an explicit field its real producer writes proves it Codex-only. An
25
+ unknown or unclassifiable record is excluded, never admitted by omission.
26
+
27
+ Pure: no I/O, no clock, no SQLite.
28
+ """
29
+ from __future__ import annotations
30
+
31
+ import dataclasses
32
+ import hashlib
33
+ import re
34
+ from typing import Mapping
35
+
36
+
37
+ PROTOCOL_GENERATION = "account-bound-v1"
38
+ WRITER_GENERATION_KEY = "wpg"
39
+ CUTOVER_OP_KIND = "account_bound_cutover"
40
+ CUTOVER_OP_ID = "account-bound-cutover-v1"
41
+ LEGACY_SPOOL_ACTIVITY_KIND = "legacy_spool_activity"
42
+ #: Spec revision 5 (section 7, operator decision on Codex design-fix-round-5
43
+ #: Q4): the `legacy_spool_activity` reason of an incomplete inventory. Such an
44
+ #: op records typed failure evidence and no digest, so it anchors no baseline.
45
+ INVENTORY_INCOMPLETE_REASON = "inventory-incomplete"
46
+ #: Fix round K12 (K11R-4; spec 5.5 step 6, 5.6 item 1(b) and section 7): the
47
+ #: reason of the anchoring op the first complete inventory appends after a
48
+ #: cutover that recorded inventory uncertainty and so anchored no baseline.
49
+ #: It records the complete digest and ends that episode. Like every
50
+ #: `legacy_spool_activity` op it is a trigger, but of the same uncertainty,
51
+ #: never an observed legacy change, and it clears nothing (residual 6).
52
+ INVENTORY_COMPLETE_REASON = "inventory-complete"
53
+ #: The reasons whose unresolved markers an incomplete inventory left.
54
+ INVENTORY_REASONS = (INVENTORY_INCOMPLETE_REASON, INVENTORY_COMPLETE_REASON)
55
+ #: The operations an inventory can fail at (the evidence's ``operation``).
56
+ INVENTORY_OPERATIONS = ("list", "stat", "read")
57
+
58
+ #: The versioned successors of the two administrative op kinds whose
59
+ #: previous-release validators require exact keys (spec 5.1). The previous
60
+ #: release ignores an unknown op kind, so the current binary emits only these.
61
+ PROTOCOL_RESOLUTION_V2_KIND = "journal_protocol_resolution_v2"
62
+ WEEKLY_OBSERVATION_DECISION_V2_KIND = "claude_weekly_observation_decision_v2"
63
+
64
+ #: Admission families. ``codex`` is the only family admitted without a stamp.
65
+ FAMILIES = (
66
+ "claude-observation",
67
+ "claude-op",
68
+ "claude-event",
69
+ "administrative",
70
+ "protocol",
71
+ "codex",
72
+ "unknown",
73
+ )
74
+
75
+ ADMISSION_REASONS = ("no-cutover", "historical", "stamped", "codex-only",
76
+ "excluded")
77
+
78
+ _DIGEST_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
79
+
80
+ _CLAUDE_OP_KINDS = frozenset((
81
+ "account_observe", "account_label", "accounts_cutover",
82
+ "weekly_credit_floor", "sync_week",
83
+ ))
84
+ _CODEX_OP_KINDS = frozenset((
85
+ "codex_file_account", "codex_window_attribution",
86
+ "codex_window_attribution_retract",
87
+ ))
88
+ _ADMIN_OP_KINDS = frozenset((
89
+ "journal_protocol_resolution", "claude_weekly_observation_decision",
90
+ PROTOCOL_RESOLUTION_V2_KIND, WEEKLY_OBSERVATION_DECISION_V2_KIND,
91
+ ))
92
+ _PROTOCOL_OP_KINDS = frozenset((CUTOVER_OP_KIND, LEGACY_SPOOL_ACTIVITY_KIND))
93
+ _ADMIN_RECORD_TYPES = frozenset(("correction", "correction_batch"))
94
+ _CLAUDE_EVT_KINDS = frozenset((
95
+ "snapshot_accept", "weekly_cost_snapshot", "week_reset", "five_hour_credit",
96
+ "five_hour_block_close", "percent_milestone", "five_hour_milestone",
97
+ "weekly_credit_effects", "projected", "project_budget", "meter_rate_change",
98
+ ))
99
+ #: Evt kinds admitted without a stamp only when their payload proves a Codex
100
+ #: quota source (``payload.source == "codex"``, the source every Codex quota
101
+ #: identity carries and every Codex quota table filters on).
102
+ _QUOTA_EVT_KINDS = frozenset(("quota_alert_arming", "quota_threshold_event"))
103
+
104
+
105
+ @dataclasses.dataclass(frozen=True)
106
+ class Coordinate:
107
+ """Physical journal position of a complete line: its segment name and the
108
+ byte offset at which the line STARTS."""
109
+
110
+ segment: str
111
+ offset: int
112
+
113
+
114
+ @dataclasses.dataclass(frozen=True)
115
+ class CutoverBoundary:
116
+ """The historical boundary recorded by the ``account_bound_cutover`` op.
117
+
118
+ ``segment``/``end_offset`` name where the op itself was appended: the
119
+ target segment and its complete-line end at that moment, which is the op's
120
+ own start offset. ``frozen_extents`` maps every
121
+ segment that existed before the op to its complete-line byte length at that
122
+ moment; only bytes inside those extents are historical.
123
+
124
+ Spec revision 5: when the establishment could not complete its legacy
125
+ spool inventory, the op records the uncertainty itself —
126
+ ``legacy_spool_inventory_failure`` holds the typed failure evidence and
127
+ ``legacy_spool_baseline_digest`` is None, so the op anchors no baseline
128
+ (the next complete inventory installs one under its own anchoring op).
129
+ """
130
+
131
+ segment: str
132
+ end_offset: int
133
+ frozen_extents: Mapping[str, int]
134
+ legacy_spool_baseline_digest: "str | None"
135
+ legacy_spool_inventory_failure: "Mapping[str, object] | None" = None
136
+
137
+
138
+ @dataclasses.dataclass(frozen=True)
139
+ class Admission:
140
+ admitted: bool
141
+ family: str
142
+ reason: str
143
+
144
+
145
+ # --------------------------------------------------------------------------
146
+ # writer-generation stamp
147
+ # --------------------------------------------------------------------------
148
+
149
+ def stamp(record: dict) -> dict:
150
+ """Return a shallow copy of ``record`` carrying the current writer
151
+ generation. Idempotent; refuses a record that already names a DIFFERENT
152
+ generation (a caller must never relabel another protocol's record). The
153
+ payload is never touched: the stamp is transport metadata."""
154
+ if not isinstance(record, dict):
155
+ raise ValueError("journal record must be a dict")
156
+ existing = record.get(WRITER_GENERATION_KEY)
157
+ if existing is not None and existing != PROTOCOL_GENERATION:
158
+ raise ValueError(
159
+ f"record already carries writer generation {existing!r}")
160
+ out = dict(record)
161
+ out[WRITER_GENERATION_KEY] = PROTOCOL_GENERATION
162
+ return out
163
+
164
+
165
+ def is_stamped(record: dict) -> bool:
166
+ return (isinstance(record, dict)
167
+ and record.get(WRITER_GENERATION_KEY) == PROTOCOL_GENERATION)
168
+
169
+
170
+ def strip_stamp(record: dict) -> dict:
171
+ """``record`` without its writer-generation field — the comparison shape
172
+ for semantic event duplicate/conflict classification."""
173
+ if not isinstance(record, dict) or WRITER_GENERATION_KEY not in record:
174
+ return record
175
+ return {k: v for k, v in record.items() if k != WRITER_GENERATION_KEY}
176
+
177
+
178
+ # --------------------------------------------------------------------------
179
+ # boundary
180
+ # --------------------------------------------------------------------------
181
+
182
+ def is_historical(coord: Coordinate, boundary: CutoverBoundary) -> bool:
183
+ extent = boundary.frozen_extents.get(coord.segment)
184
+ return extent is not None and coord.offset < extent
185
+
186
+
187
+ def inventory_failure_evidence(*, path: str, operation: str,
188
+ error: str, message: str = "") -> dict:
189
+ """The typed evidence of a failed legacy spool inventory (spec revision
190
+ 5): the path, the failed operation and the error (its errno name). It is
191
+ diagnostic evidence only, never accounting input."""
192
+ evidence = {"path": str(path), "operation": str(operation),
193
+ "error": str(error)}
194
+ if message:
195
+ evidence["message"] = str(message)
196
+ if not is_inventory_failure(evidence):
197
+ raise ValueError(f"invalid inventory failure evidence {evidence!r}")
198
+ return evidence
199
+
200
+
201
+ def is_inventory_failure(value) -> bool:
202
+ """Whether ``value`` is well-formed inventory failure evidence."""
203
+ if not isinstance(value, dict):
204
+ return False
205
+ if not (isinstance(value.get("path"), str)
206
+ and value.get("operation") in INVENTORY_OPERATIONS
207
+ and isinstance(value.get("error"), str) and value["error"]):
208
+ return False
209
+ message = value.get("message")
210
+ return message is None or isinstance(message, str)
211
+
212
+
213
+ def boundary_payload(boundary: CutoverBoundary) -> dict:
214
+ """The camelCase payload fields of the cutover op (without ``kind``).
215
+ ``legacySpoolInventoryFailure`` is present only on a cutover that
216
+ recorded inventory uncertainty, so a complete cutover's bytes are
217
+ unchanged."""
218
+ payload = {
219
+ "segment": boundary.segment,
220
+ "endOffset": boundary.end_offset,
221
+ "frozenExtents": {k: boundary.frozen_extents[k]
222
+ for k in sorted(boundary.frozen_extents)},
223
+ "legacySpoolBaselineDigest": boundary.legacy_spool_baseline_digest,
224
+ }
225
+ if boundary.legacy_spool_inventory_failure is not None:
226
+ payload["legacySpoolInventoryFailure"] = dict(
227
+ boundary.legacy_spool_inventory_failure)
228
+ return payload
229
+
230
+
231
+ def _nonneg_int(value) -> bool:
232
+ return type(value) is int and value >= 0
233
+
234
+
235
+ def boundary_from_op(op_record: dict) -> CutoverBoundary:
236
+ """Decode the boundary from a cutover op. Raises ``ValueError`` on any
237
+ shape failure: a malformed cutover op never yields a boundary."""
238
+ if not isinstance(op_record, dict) or op_record.get("t") != "op":
239
+ raise ValueError("cutover boundary must come from an op record")
240
+ payload = op_record.get("payload")
241
+ if not isinstance(payload, dict) or payload.get("kind") != CUTOVER_OP_KIND:
242
+ raise ValueError("record is not an account_bound_cutover op")
243
+ segment = payload.get("segment")
244
+ end_offset = payload.get("endOffset")
245
+ extents = payload.get("frozenExtents")
246
+ digest = payload.get("legacySpoolBaselineDigest")
247
+ if not isinstance(segment, str) or not segment:
248
+ raise ValueError("cutover boundary segment is invalid")
249
+ if not _nonneg_int(end_offset):
250
+ raise ValueError("cutover boundary endOffset is invalid")
251
+ if not isinstance(extents, dict) or not all(
252
+ isinstance(k, str) and k and _nonneg_int(v)
253
+ for k, v in extents.items()):
254
+ raise ValueError("cutover boundary frozenExtents is invalid")
255
+ failure = payload.get("legacySpoolInventoryFailure")
256
+ if failure is None:
257
+ if not isinstance(digest, str) or not _DIGEST_RE.match(digest):
258
+ raise ValueError("cutover boundary legacy spool digest is invalid")
259
+ else:
260
+ # Revision 5: an uncertain inventory records its evidence and NO
261
+ # digest; a digest beside failure evidence is malformed.
262
+ if not is_inventory_failure(failure) or digest is not None:
263
+ raise ValueError(
264
+ "cutover boundary legacy spool inventory failure is invalid")
265
+ failure = dict(failure)
266
+ return CutoverBoundary(
267
+ segment=segment,
268
+ end_offset=end_offset,
269
+ frozen_extents=dict(extents),
270
+ legacy_spool_baseline_digest=digest,
271
+ legacy_spool_inventory_failure=failure,
272
+ )
273
+
274
+
275
+ # --------------------------------------------------------------------------
276
+ # admission
277
+ # --------------------------------------------------------------------------
278
+
279
+ def _family(record: dict) -> tuple[str, bool]:
280
+ """``(family, codex_only)`` from an explicit allow-list per kind. Anything
281
+ not named here is ``("unknown", False)``."""
282
+ if not isinstance(record, dict):
283
+ return ("unknown", False)
284
+ t = record.get("t")
285
+ if t in _ADMIN_RECORD_TYPES:
286
+ return ("administrative", False)
287
+ payload = record.get("payload")
288
+ if not isinstance(payload, dict):
289
+ return ("unknown", False)
290
+ kind = payload.get("kind")
291
+ if t == "obs":
292
+ provider = record.get("provider")
293
+ if provider == "claude":
294
+ return ("claude-observation", False)
295
+ if provider == "codex" and kind == "quota_window_snapshot":
296
+ return ("codex", True)
297
+ return ("unknown", False)
298
+ if t == "op":
299
+ if kind in _CODEX_OP_KINDS:
300
+ return ("codex", True)
301
+ if kind in ("account_observe", "account_label"):
302
+ if payload.get("provider") == "codex":
303
+ return ("codex", True)
304
+ return ("claude-op", False)
305
+ if kind in _CLAUDE_OP_KINDS:
306
+ return ("claude-op", False)
307
+ if kind in _ADMIN_OP_KINDS:
308
+ return ("administrative", False)
309
+ if kind in _PROTOCOL_OP_KINDS:
310
+ return ("protocol", False)
311
+ return ("unknown", False)
312
+ if t == "evt":
313
+ if kind in _CLAUDE_EVT_KINDS:
314
+ return ("claude-event", False)
315
+ if kind == "budget":
316
+ if payload.get("vendor") == "codex":
317
+ return ("codex", True)
318
+ return ("claude-event", False)
319
+ if kind in _QUOTA_EVT_KINDS:
320
+ if payload.get("source") == "codex":
321
+ return ("codex", True)
322
+ return ("claude-event", False)
323
+ return ("unknown", False)
324
+ return ("unknown", False)
325
+
326
+
327
+ def record_family(record: dict) -> str:
328
+ return _family(record)[0]
329
+
330
+
331
+ def classify_admission(record: dict, coord: Coordinate,
332
+ boundary: CutoverBoundary | None) -> Admission:
333
+ """The single admission predicate (spec 5.3).
334
+
335
+ Admitted when there is no cutover yet, when the record lies inside the
336
+ historical boundary, when it carries the current writer generation, or when
337
+ its family is proven Codex-only. Every other post-boundary record is
338
+ excluded."""
339
+ family, codex_only = _family(record)
340
+ if boundary is None:
341
+ return Admission(True, family, "no-cutover")
342
+ if is_historical(coord, boundary):
343
+ return Admission(True, family, "historical")
344
+ if is_stamped(record):
345
+ return Admission(True, family, "stamped")
346
+ if codex_only:
347
+ return Admission(True, family, "codex-only")
348
+ return Admission(False, family, "excluded")
349
+
350
+
351
+ # --------------------------------------------------------------------------
352
+ # excluded-population digest (spec section 8)
353
+ # --------------------------------------------------------------------------
354
+
355
+ _DIGEST_MODULUS = 1 << 256
356
+
357
+
358
+ def _coordinate_term(coord: Coordinate) -> int:
359
+ data = f"{coord.segment}\x00{coord.offset}".encode("utf-8")
360
+ return int.from_bytes(hashlib.sha256(data).digest(), "big")
361
+
362
+
363
+ def coordinate_population_digest(coords, base: str | None = None) -> str:
364
+ """An ORDER-INDEPENDENT digest over a set of physical coordinates: the sum
365
+ of each coordinate's sha256 modulo 2**256. ``base`` extends an earlier
366
+ digest incrementally, so an ingest cycle can add the coordinates it newly
367
+ excludes and a full scan reproduces the same value in any order."""
368
+ total = 0
369
+ if base is not None:
370
+ if not isinstance(base, str) or not _DIGEST_RE.match(base):
371
+ raise ValueError("coordinate population digest is invalid")
372
+ total = int(base[len("sha256:"):], 16)
373
+ for coord in coords:
374
+ total = (total + _coordinate_term(coord)) % _DIGEST_MODULUS
375
+ return "sha256:" + format(total, "064x")
376
+
377
+
378
+ #: The record type of an inert positional placeholder for consumers that need
379
+ #: a dict in an excluded record's slot. No reader recognises the type, so the
380
+ #: selector, the pipeline and the legacy classifier all skip it while it still
381
+ #: consumes the slot's sequence number.
382
+ EXCLUDED_PLACEHOLDER_TYPE = "excluded"
383
+
384
+
385
+ def excluded_placeholder() -> dict:
386
+ return {"t": EXCLUDED_PLACEHOLDER_TYPE}
@@ -46,6 +46,8 @@ import threading
46
46
  from dataclasses import dataclass
47
47
  from typing import Any, Callable, Iterable
48
48
 
49
+ import _lib_codex_rollout_container as _rc
50
+
49
51
 
50
52
  def _cctally():
51
53
  """Resolve the current `cctally` module at call-time (spec §5.5)."""
@@ -687,6 +689,7 @@ def _session_path_parts(source_path: str) -> tuple[str, str, str]:
687
689
  keyed on ``(scope roots, source_path)``. Outside one the roots are
688
690
  resolved per call, which is the historic behaviour every CLI caller keeps.
689
691
  """
692
+ source_path = _rc.logical_path(source_path)
690
693
  scope = getattr(_CODEX_PATH_SCOPE, "scope", None)
691
694
  if scope is not None:
692
695
  return scope.parts(source_path)
@@ -713,6 +716,7 @@ def _session_path_parts_for_roots(
713
716
  files stay free of maintainer absolute paths), then basename. Direct-JSONL
714
717
  roots yield an id relative to <entry> itself (no sessions/ prefix).
715
718
  """
719
+ source_path = _rc.logical_path(source_path)
716
720
  p = pathlib.Path(source_path)
717
721
  rel: pathlib.PurePath | None = None
718
722
  for root in roots:
@@ -69,7 +69,19 @@ PROGRESS_KEY = "codex_journal_coverage_progress"
69
69
  #: `REASON_MALFORMED` and falls back to a full replay — the same outcome a bump
70
70
  #: would produce. A bump would additionally invalidate certificates written by
71
71
  #: an in-development binary that already carries the field, for no gain.
72
- COVERAGE_VERSION = 1
72
+ #:
73
+ #: Version 2 (#946; #825/#832 spec 4.7 revision 2, Codex design-fix-round-3
74
+ #: Q5): every earlier writer took `appliedThrough` from an ingest cursor that
75
+ #: could advance past a torn suffix. `_repair_torn_tail` then wrote a record
76
+ #: across that coordinate, the next ingest started inside it and skipped it,
77
+ #: and a contiguous advance certified it covered — a version-1 certificate can
78
+ #: therefore claim coverage of an unapplied record at an endpoint that follows
79
+ #: a newline, which no endpoint check can see. `prior_is_extendable` and
80
+ #: `certificate_is_valid` both reject version 1, so the first current binary
81
+ #: forces the full journal recovery and never extends or restamps one. A
82
+ #: previous release reading a version-2 certificate rejects it the same way
83
+ #: and replays (C2).
84
+ COVERAGE_VERSION = 2
73
85
 
74
86
  #: Bumped whenever the journal-record-to-cache-row materialization changes —
75
87
  #: `_apply_quota_records`, `_apply_file_account_records`, or the §3.5 precedence
@@ -59,9 +59,13 @@ stable_sum = _import_stable_sum()
59
59
 
60
60
 
61
61
  def _import_pricing_kernel():
62
- """Resolve the ``_lib_pricing`` symbols this kernel needs (#195/#413):
63
- ``CACHE_WRITE_1H_MULTIPLIER`` (the derived 1-hour cache-write rate) and
64
- ``claude_usage_dict`` (the single cost-feeding usage-dict constructor).
62
+ """Resolve the ``_lib_pricing`` symbols this kernel needs, returned in
63
+ this order: ``CACHE_WRITE_1H_MULTIPLIER`` (the derived 1-hour cache-write
64
+ rate, #195), ``_claude_fast_multiplier`` (the Fast-mode multiplier for a
65
+ model), ``claude_usage_dict`` (the single cost-feeding usage-dict
66
+ constructor, #413), ``TIERED_THRESHOLD`` (the legacy 200K marginal-tier
67
+ boundary) and ``_select_claude_request_card`` (the whole-request card
68
+ selector, #929).
65
69
 
66
70
  Same shape and same justification as ``_import_stable_sum`` above:
67
71
  ``_lib_pricing`` is a PURE stdlib leaf with no sibling imports, so binding
@@ -79,6 +83,7 @@ def _import_pricing_kernel():
79
83
  m._claude_fast_multiplier,
80
84
  m.claude_usage_dict,
81
85
  m.TIERED_THRESHOLD,
86
+ m._select_claude_request_card,
82
87
  )
83
88
  from pathlib import Path
84
89
  import importlib.util
@@ -99,11 +104,13 @@ def _import_pricing_kernel():
99
104
  m._claude_fast_multiplier,
100
105
  m.claude_usage_dict,
101
106
  m.TIERED_THRESHOLD,
107
+ m._select_claude_request_card,
102
108
  )
103
109
 
104
110
 
105
111
  (CACHE_WRITE_1H_MULTIPLIER, _claude_fast_multiplier,
106
- claude_usage_dict, DEFAULT_TIERED_THRESHOLD) = _import_pricing_kernel()
112
+ claude_usage_dict, DEFAULT_TIERED_THRESHOLD,
113
+ _select_claude_request_card) = _import_pricing_kernel()
107
114
 
108
115
 
109
116
  # ``DEFAULT_TIERED_THRESHOLD`` above is Anthropic's per-call >200K-tokens tier,
@@ -353,6 +360,7 @@ def _compute_entry_cache_dollars(
353
360
  cache_creation_tokens: int,
354
361
  cache_read_tokens: int,
355
362
  *,
363
+ input_tokens: int,
356
364
  pricing: dict,
357
365
  tiered_threshold: "int | None" = None,
358
366
  cache_1h_tokens: int | None = None,
@@ -384,12 +392,27 @@ def _compute_entry_cache_dollars(
384
392
  than in the signature. A default argument is evaluated once, at
385
393
  function-definition time, so a change to the module global would never
386
394
  reach the calls.
395
+
396
+ ``input_tokens`` (REQUIRED keyword, #929) is the entry's uncached input.
397
+ A whole-request card (Claude Haiku 5.5) selects ONE card per request from
398
+ its full prompt — input + cache creation + cache read — exactly as the
399
+ cost kernel does, so the card is selected once here, before any rate is
400
+ read. Selecting from the cache tokens alone would price a long request at
401
+ the base card. A legacy card is returned unchanged and every expression
402
+ below runs exactly as before.
387
403
  """
388
404
  if tiered_threshold is None:
389
405
  tiered_threshold = DEFAULT_TIERED_THRESHOLD
390
406
  p = _lookup_pricing(model, pricing) or {}
391
407
  if not p:
392
408
  return (0.0, 0.0, 0.0)
409
+ # The selected card carries no `_above_200k_tokens` key, so
410
+ # `_tiered_rate` returns its flat rates, and the wasted expression below
411
+ # is already the direct TTL form on the selected input rate.
412
+ p = _select_claude_request_card(
413
+ p, input_tokens=input_tokens,
414
+ cache_creation_tokens=cache_creation_tokens,
415
+ cache_read_tokens=cache_read_tokens)
393
416
 
394
417
  def _tiered_rate(tokens: int, base_key: str, tiered_key: str) -> float:
395
418
  """Blended $/token rate for a single-call token count under tiered pricing."""
@@ -527,7 +550,9 @@ def _aggregate_cache_by_day(
527
550
  )
528
551
  else:
529
552
  saved, wasted, net = _compute_entry_cache_dollars(
530
- entry.model, create_tok, read_tok, pricing=pricing,
553
+ entry.model, create_tok, read_tok,
554
+ input_tokens=entry.usage.get("input_tokens", 0), # #929
555
+ pricing=pricing,
531
556
  # #195: the normalized flat key the ingest chokepoint writes;
532
557
  # absent on a pre-split row -> None -> unchanged pricing.
533
558
  cache_1h_tokens=entry.usage.get("cache_creation_1h_input_tokens"),
@@ -703,6 +728,7 @@ def _aggregate_cache_by_session(
703
728
  entry.model,
704
729
  entry.cache_creation_tokens,
705
730
  entry.cache_read_tokens,
731
+ input_tokens=entry.input_tokens, # #929
706
732
  pricing=pricing,
707
733
  # #195: getattr, not attribute access. This is a PURE kernel with
708
734
  # a duck-typed entry contract — callers pass `_JoinedClaudeEntry`
@@ -1156,6 +1182,7 @@ def _aggregate_cache_breakdown(
1156
1182
  getattr(e, "model", ""),
1157
1183
  getattr(e, "cache_creation_tokens", 0),
1158
1184
  getattr(e, "cache_read_tokens", 0),
1185
+ input_tokens=getattr(e, "input_tokens", 0), # #929
1159
1186
  pricing=pricing,
1160
1187
  # #195: getattr default None == split unknown, so an entry type
1161
1188
  # that does not carry the split prices exactly as before.
@@ -1270,7 +1297,9 @@ def aggregate_by_day_project(
1270
1297
  create_tok = getattr(e, "cache_creation_tokens", 0)
1271
1298
  read_tok = getattr(e, "cache_read_tokens", 0)
1272
1299
  _s, _w, net = _compute_entry_cache_dollars(
1273
- model, create_tok, read_tok, pricing=pricing,
1300
+ model, create_tok, read_tok,
1301
+ input_tokens=getattr(e, "input_tokens", 0), # #929
1302
+ pricing=pricing,
1274
1303
  cache_1h_tokens=getattr(e, "cache_1h_tokens", None), # #195
1275
1304
  speed=getattr(e, "speed", None),
1276
1305
  )
@@ -12,18 +12,28 @@ import fcntl
12
12
  import os
13
13
  import time
14
14
  from pathlib import Path
15
+ from typing import Callable
15
16
 
16
17
 
17
18
  def acquire_ordered_flocks(
18
19
  locks: list[tuple[Path, int]],
19
20
  *,
20
21
  timeout: float | None = None,
22
+ on_failure: Callable[[Path, BaseException], None] | None = None,
21
23
  ) -> list[int] | None:
22
- """Acquire ``(path, LOCK_SH|LOCK_EX)`` entries in caller-supplied order."""
24
+ """Acquire ``(path, LOCK_SH|LOCK_EX)`` entries in caller-supplied order.
25
+
26
+ ``on_failure(path, exc)``, when given, is told which lock the set lost to
27
+ before this returns None (``exc`` a ``BlockingIOError``: the lock was
28
+ held) or re-raises an ``OSError`` (an I/O error no holder caused), so a
29
+ caller can wait for exactly that lock's release (#825/#832).
30
+ """
23
31
  deadline = None if timeout is None else time.monotonic() + max(timeout, 0.0)
24
32
  held: list[int] = []
33
+ current: Path | None = None
25
34
  try:
26
35
  for path, mode in locks:
36
+ current = path
27
37
  fd = os.open(str(path), os.O_RDWR | os.O_CREAT, 0o600)
28
38
  os.fchmod(fd, 0o600)
29
39
  held.append(fd)
@@ -31,14 +41,18 @@ def acquire_ordered_flocks(
31
41
  try:
32
42
  fcntl.flock(fd, mode | fcntl.LOCK_NB)
33
43
  break
34
- except BlockingIOError:
44
+ except BlockingIOError as exc:
35
45
  if deadline is None or time.monotonic() >= deadline:
36
46
  release_cache_writer_flocks(held)
47
+ if on_failure is not None:
48
+ on_failure(path, exc)
37
49
  return None
38
50
  time.sleep(min(0.2, max(0.0, deadline - time.monotonic())))
39
51
  return held
40
- except BaseException:
52
+ except BaseException as exc:
41
53
  release_cache_writer_flocks(held)
54
+ if on_failure is not None and current is not None and isinstance(exc, OSError):
55
+ on_failure(current, exc)
42
56
  raise
43
57
 
44
58
 
@@ -47,6 +61,7 @@ def acquire_cache_writer_flocks(
47
61
  provider_path: Path | None = None,
48
62
  *,
49
63
  timeout: float | None = None,
64
+ on_failure: Callable[[Path, BaseException], None] | None = None,
50
65
  ) -> list[int] | None:
51
66
  """Acquire global then optional provider flock, or return ``None``.
52
67
 
@@ -61,6 +76,7 @@ def acquire_cache_writer_flocks(
61
76
  return acquire_ordered_flocks(
62
77
  [(path, fcntl.LOCK_EX) for path in paths],
63
78
  timeout=timeout,
79
+ on_failure=on_failure,
64
80
  )
65
81
 
66
82