cctally 1.111.0 → 1.112.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 (37) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +5 -6
  3. package/bin/_cctally_cache.py +14 -7
  4. package/bin/_cctally_core.py +9 -1
  5. package/bin/_cctally_dashboard_envelope.py +7 -6
  6. package/bin/_cctally_dashboard_sources.py +90 -55
  7. package/bin/_cctally_doctor.py +73 -27
  8. package/bin/_cctally_forecast.py +2 -1
  9. package/bin/_cctally_journal.py +147 -6
  10. package/bin/_cctally_milestones.py +29 -3
  11. package/bin/_cctally_quota.py +10 -1
  12. package/bin/_cctally_record.py +136 -4
  13. package/bin/_cctally_rederive.py +311 -8
  14. package/bin/_cctally_source_analytics.py +20 -8
  15. package/bin/_cctally_tui.py +546 -45
  16. package/bin/_lib_codex_conversation_query.py +40 -4
  17. package/bin/_lib_codex_metadata.py +57 -11
  18. package/bin/_lib_cost_provenance.py +303 -0
  19. package/bin/_lib_dashboard_sources.py +263 -28
  20. package/bin/_lib_doctor.py +2 -2
  21. package/bin/_lib_ingest_frontier.py +137 -38
  22. package/bin/_lib_pricing.py +13 -1
  23. package/bin/_lib_rederive.py +704 -36
  24. package/bin/_lib_snapshot_cache.py +332 -9
  25. package/dashboard/static/assets/{ConversationsView-BB9vUiL6.js → ConversationsView-CEZ-BUyi.js} +1 -1
  26. package/dashboard/static/assets/{DoctorModal-DFY2iPDf.js → DoctorModal-CE1oH_Zy.js} +1 -1
  27. package/dashboard/static/assets/ModalRoot-D5Y8IFUG.js +1 -0
  28. package/dashboard/static/assets/{ProjectsDrillPanel-DAv39uSo.js → ProjectsDrillPanel-B3DZoCeD.js} +1 -1
  29. package/dashboard/static/assets/{SourceDetailModal-DSAWo2Fp.js → SourceDetailModal-CHp9EgLP.js} +1 -1
  30. package/dashboard/static/assets/{UpdateModal-R_9dOVnh.js → UpdateModal-5iKST6jo.js} +1 -1
  31. package/dashboard/static/assets/{index-DdU62sov.js → index-Cm0VTxJC.js} +2 -2
  32. package/dashboard/static/assets/index-Dt4tqf43.css +1 -0
  33. package/dashboard/static/assets/{outlineNavigation-BOCLcRa6.js → outlineNavigation-DkXGL8J6.js} +1 -1
  34. package/dashboard/static/dashboard.html +2 -2
  35. package/package.json +2 -1
  36. package/dashboard/static/assets/ModalRoot-DvRKHbQE.js +0 -1
  37. package/dashboard/static/assets/index-COhxe3vU.css +0 -1
@@ -16,8 +16,13 @@ import dataclasses
16
16
  import datetime as dt
17
17
  import hashlib
18
18
  import json
19
+ import math
19
20
  from collections.abc import Iterable, Mapping
20
21
 
22
+ from _lib_cost_provenance import (
23
+ HISTORICAL_CLOSE_BASIS, RECORDED_CLOSE_CORRECTION_BASIS,
24
+ )
25
+
21
26
 
22
27
  FAMILY = "claude-usage"
23
28
 
@@ -151,7 +156,11 @@ def _is_owned_event(record: Mapping) -> bool:
151
156
  }:
152
157
  return False
153
158
  if kind == "budget":
154
- return payload.get("vendor", "claude") == "claude"
159
+ return (
160
+ payload.get("vendor", "claude") == "claude"
161
+ or (payload.get("vendor") == "codex"
162
+ and (payload.get("_pricing") or {}).get("version") == 1)
163
+ )
155
164
  if kind == "projected":
156
165
  metric = str(payload.get("metric") or "")
157
166
  return not metric.startswith("codex_")
@@ -274,6 +283,13 @@ def preserved_history(records: Iterable[Mapping], *,
274
283
  continue
275
284
  if not _is_owned_event(candidate):
276
285
  continue
286
+ if _is_codex_budget(candidate):
287
+ # #869: a Codex crossing's desired state comes only from its own
288
+ # recorded provenance, never from Claude replay evidence, so it is
289
+ # never "un-re-derivable history". Holding one here would retain a
290
+ # crossing the correction retires below its threshold, and would
291
+ # revive that intentionally retired crossing on the next run.
292
+ continue
277
293
  if evidence_retained and not event_id.startswith(
278
294
  _CUTOVER_EXPORT_ID_PREFIX
279
295
  ):
@@ -287,6 +303,78 @@ def preserved_history(records: Iterable[Mapping], *,
287
303
  return {event_id: entry[2] for event_id, entry in best.items()}
288
304
 
289
305
 
306
+ def _is_codex_budget(record: Mapping) -> bool:
307
+ payload = record.get("payload") or {}
308
+ return payload.get("kind") == "budget" and payload.get("vendor") == "codex"
309
+
310
+
311
+ _BLOCK_TOKEN_KEYS = (
312
+ "input_tokens", "output_tokens", "cache_create_tokens", "cache_read_tokens",
313
+ )
314
+ #: The monetary fields a five-hour milestone snapshots from its block.
315
+ _FIVE_HOUR_MILESTONE_MONETARY_KEYS = frozenset({
316
+ "block_cost_usd", "marginal_cost_usd",
317
+ })
318
+ #: Milestone kinds whose ``alerted_at`` records an actual dispatch. Scratch
319
+ #: replay runs without alert config, so it can never reproduce that latch.
320
+ _ALERT_LATCHED_MILESTONE_KINDS = frozenset({
321
+ "percent_milestone", "five_hour_milestone",
322
+ })
323
+
324
+
325
+ def _positive_token_models(payload: Mapping) -> set:
326
+ return {
327
+ child.get("model")
328
+ for child in payload.get("_models", [])
329
+ if any(int(child.get(key) or 0) > 0 for key in _BLOCK_TOKEN_KEYS)
330
+ }
331
+
332
+
333
+ def _child_population(payload: Mapping, key: str) -> list:
334
+ """A close's child set without its monetary field, order-independent."""
335
+ return sorted(
336
+ [
337
+ {k: v for k, v in child.items() if k != "cost_usd"}
338
+ for child in payload.get(key, [])
339
+ ],
340
+ key=lambda item: json.dumps(item, sort_keys=True),
341
+ )
342
+
343
+
344
+ def _same_child_population(current: Mapping, desired: Mapping) -> bool:
345
+ return (
346
+ _child_population(current, "_models")
347
+ == _child_population(desired, "_models")
348
+ and _child_population(current, "_projects")
349
+ == _child_population(desired, "_projects")
350
+ )
351
+
352
+
353
+ def close_cost_is_uncertain(payload: Mapping) -> bool:
354
+ """A closed block's retained cost lacks proof that every model was priced.
355
+
356
+ A computation-time marker settles it except for an ambiguous model, or a
357
+ missing-card model beside a priced one (project children have no model
358
+ axis). An unmarked close is uncertain only while a positive-token model
359
+ carries a zero or unresolved cost; a historical correction marker (#869)
360
+ is proof in itself.
361
+ """
362
+ pricing = payload.get("_pricing")
363
+ positive = _positive_token_models(payload)
364
+ if pricing:
365
+ if pricing.get("basis") == HISTORICAL_CLOSE_BASIS:
366
+ return False
367
+ missing = set(pricing.get("unpricedModels") or [])
368
+ return bool(
369
+ pricing.get("uncertainModels")
370
+ or (missing and missing != positive)
371
+ )
372
+ return any(
373
+ child.get("model") in positive and not child.get("cost_usd")
374
+ for child in payload.get("_models", [])
375
+ )
376
+
377
+
290
378
  def _canonical_bytes(value) -> bytes:
291
379
  return json.dumps(
292
380
  value, sort_keys=True, separators=(",", ":"), ensure_ascii=False,
@@ -305,17 +393,43 @@ def _semantic_event(record: Mapping) -> dict:
305
393
  }
306
394
 
307
395
 
396
+ def _with_close_marker(record: Mapping, marker: Mapping) -> dict:
397
+ """A close record carrying ``marker`` in both of its provenance fields."""
398
+ corrected = dict(record)
399
+ payload = dict(record.get("payload") or {})
400
+ payload["_pricing"] = dict(marker)
401
+ payload["pricing_provenance_json"] = json.dumps(
402
+ dict(marker), sort_keys=True, separators=(",", ":"),
403
+ )
404
+ corrected["payload"] = payload
405
+ return corrected
406
+
407
+
308
408
  def _preserve_non_derivable_state(
309
- current_record: Mapping, desired_record: Mapping
409
+ current_record: Mapping, desired_record: Mapping, *,
410
+ historical_marker: "Mapping | None" = None,
411
+ drifted_ids: "set | None" = None,
310
412
  ) -> Mapping:
311
413
  """Carry durable state that scratch replay cannot truthfully reconstruct.
312
414
 
313
- Percent-milestone ``alerted_at`` is the latch recorded when the first
314
- crossing actually dispatched. Historical alert configuration is not
315
- retained, so scratch replay deliberately runs with alerts disabled and
316
- produces ``None``. Preserve the selected event's exact latch both when the
317
- rest of the milestone is retained and when another field needs an audited
318
- higher-revision correction (#410 Task B).
415
+ A percent or five-hour milestone's ``alerted_at`` is the latch recorded
416
+ when the first crossing actually dispatched. Historical alert
417
+ configuration is not retained, so scratch replay deliberately runs with
418
+ alerts disabled and produces ``None``. Preserve the selected event's exact
419
+ latch both when the rest of the milestone is retained and when another
420
+ field needs an audited higher-revision correction (#410 Task B; five-hour
421
+ milestones since #869 V3).
422
+
423
+ #869: ``historical_marker`` is the planner's proof that an UNMARKED close
424
+ priced every positive-token model at $0 only for want of a card; the close
425
+ then adopts the replay under that marker. A marked or historically proven
426
+ close whose replayed child population differs is kept unchanged and its id
427
+ added to ``drifted_ids``. A close corrected from its recorded missing-card
428
+ marker is stamped with its own basis, ``RECORDED_CLOSE_CORRECTION_BASIS``.
429
+
430
+ #875: the milestones of a frozen or replaced close are decided by
431
+ :func:`_resolve_closed_block_milestones`, not here. This function only
432
+ carries a milestone's ``alerted_at`` latch.
319
433
  """
320
434
  current_payload = current_record.get("payload") or {}
321
435
  desired_payload = desired_record.get("payload") or {}
@@ -355,7 +469,10 @@ def _preserve_non_derivable_state(
355
469
  facts = {
356
470
  key: value for key, value in payload.items()
357
471
  if not key.startswith("total_")
358
- and key not in {"_models", "_projects"}
472
+ and key not in {
473
+ "_models", "_projects", "_pricing",
474
+ "pricing_provenance_json",
475
+ }
359
476
  }
360
477
  # Live close used the display-zone offset; scratch replay writes
361
478
  # UTC. Both name one frozen boundary and must compare as an instant.
@@ -373,18 +490,354 @@ def _preserve_non_derivable_state(
373
490
  return facts
374
491
 
375
492
  if structure(current_payload) == structure(desired_payload):
493
+ current_pricing = current_payload.get("_pricing") or {}
494
+ desired_pricing = desired_payload.get("_pricing") or {}
495
+
496
+ def keep_drifted():
497
+ # #869 F3: a changed retained population cannot prove the
498
+ # correction, but it does not falsify the recorded close
499
+ # either. Keep this close and disclose it; never refuse the
500
+ # family's other corrections over it.
501
+ if drifted_ids is not None:
502
+ drifted_ids.add(current_record.get("id"))
503
+ return current_record
504
+
505
+ if (
506
+ not current_pricing
507
+ and current_payload.get("pricing_provenance_json") is None
508
+ and historical_marker is not None
509
+ ):
510
+ # #869 F2: an unmarked historical close the retained entries
511
+ # prove was $0 only because its model had no card. Adopt the
512
+ # replayed parent and BOTH child sets together, under a marker
513
+ # whose distinct basis freezes the result against later cards.
514
+ if not _same_child_population(current_payload, desired_payload):
515
+ return keep_drifted()
516
+ return _with_close_marker(desired_record, historical_marker)
517
+ missing = current_pricing.get("unpricedModels") or []
518
+ if missing:
519
+ # A closed amount is frozen unless its own durable close event
520
+ # says a model had no direct card. Verify the replay covers the
521
+ # same token population and now has direct cards for every
522
+ # missing model before allowing a monetary correction.
523
+ # The retained project children do not contain a model axis.
524
+ # With both direct and missing-card models, scratch replay
525
+ # cannot preserve the direct model's at-close contribution in
526
+ # every project. Leave that entire close unchanged.
527
+ if (
528
+ set(missing) != _positive_token_models(current_payload)
529
+ or current_pricing.get("uncertainModels")
530
+ ):
531
+ return current_record
532
+ if any(
533
+ model in (desired_pricing.get("unpricedModels") or [])
534
+ for model in missing
535
+ ):
536
+ return current_record
537
+ if not _same_child_population(current_payload, desired_payload):
538
+ return keep_drifted()
539
+ # #869 V4: record that this close was CORRECTED. The basis is
540
+ # provenance only: since #875 every frozen close freezes the
541
+ # block cost of its unchanged five-hour crossings, whatever
542
+ # its basis. A genuinely priced close's marker looks the same
543
+ # otherwise.
544
+ return _with_close_marker(desired_record, {
545
+ **desired_pricing, "basis": RECORDED_CLOSE_CORRECTION_BASIS,
546
+ })
376
547
  return current_record
377
548
  if (
378
- current_payload.get("kind") != "percent_milestone"
379
- or desired_payload.get("kind") != "percent_milestone"
380
- or "alerted_at" not in current_payload
549
+ current_payload.get("kind") in _ALERT_LATCHED_MILESTONE_KINDS
550
+ and desired_payload.get("kind") == current_payload.get("kind")
551
+ and "alerted_at" in current_payload
381
552
  ):
382
- return desired_record
383
- merged = dict(desired_record)
384
- merged_payload = dict(desired_payload)
385
- merged_payload["alerted_at"] = current_payload["alerted_at"]
386
- merged["payload"] = merged_payload
387
- return merged
553
+ # The recorded dispatch latch survives any correction of the rest of
554
+ # the milestone (#410 Task B; five-hour milestones since #869 V3).
555
+ merged = dict(desired_record)
556
+ merged_payload = dict(desired_payload)
557
+ merged_payload["alerted_at"] = current_payload["alerted_at"]
558
+ merged["payload"] = merged_payload
559
+ desired_record, desired_payload = merged, merged_payload
560
+ return desired_record
561
+
562
+
563
+ #: A resolved five-hour marginal within this many USD of its reference keeps
564
+ #: the reference's exact float, so float spelling never creates a correction
565
+ #: and a second rederive stays a no-op (#875).
566
+ _MARGINAL_TOLERANCE_USD = 1e-9
567
+ #: A dependency reference that names no event. For ``reset_event_ref`` these
568
+ #: are the spellings of the pre-credit ``"0"`` segment.
569
+ _EMPTY_REFERENCE_SENTINELS = frozenset({None, 0, "0", ""})
570
+
571
+
572
+ def _milestone_segment_key(payload: Mapping) -> tuple:
573
+ """A five-hour milestone's credit segment: account, window, reset ref.
574
+
575
+ ``"0"`` (and every empty spelling) is the pre-credit segment.
576
+ """
577
+ reset_ref = payload.get("reset_event_ref")
578
+ if reset_ref in _EMPTY_REFERENCE_SENTINELS:
579
+ reset_ref = "0"
580
+ return (
581
+ payload.get("account_key"),
582
+ payload.get("five_hour_window_key"),
583
+ str(reset_ref),
584
+ )
585
+
586
+
587
+ def _milestone_identity(payload: Mapping) -> dict:
588
+ """A five-hour milestone's non-monetary identity."""
589
+ return {
590
+ key: value for key, value in payload.items()
591
+ if key not in _FIVE_HOUR_MILESTONE_MONETARY_KEYS
592
+ }
593
+
594
+
595
+ def _five_hour_block(payload: Mapping) -> tuple:
596
+ """The ``(account_key, five_hour_window_key)`` block a payload belongs to."""
597
+ return (payload.get("account_key"), payload.get("five_hour_window_key"))
598
+
599
+
600
+ def _usable_threshold(value):
601
+ """A milestone threshold the marginal rule can order, or ``None``.
602
+
603
+ Only an ``int`` qualifies. ``int()`` would read ``True``, ``1.0`` and
604
+ ``"1"`` as threshold 1 and make a malformed row a predecessor.
605
+ """
606
+ if isinstance(value, int) and not isinstance(value, bool):
607
+ return value
608
+ return None
609
+
610
+
611
+ def _usable_amount(value) -> bool:
612
+ """True for a finite ``int`` or ``float`` that is not a ``bool``.
613
+
614
+ An ``int`` too large for a float is unusable rather than an error, so a
615
+ malformed row can never abort the plan (spec §4.3).
616
+ """
617
+ if not isinstance(value, (int, float)) or isinstance(value, bool):
618
+ return False
619
+ try:
620
+ return math.isfinite(value)
621
+ except OverflowError:
622
+ return False
623
+
624
+
625
+ #: A close correction that moves ``total_cost_usd`` by at most this many USD
626
+ #: keeps the close's money, so its block stays frozen (#875, spec §4.1).
627
+ _CLOSE_TOTAL_TOLERANCE_USD = 1e-9
628
+
629
+
630
+ def _close_keeps_its_money(current_payload: Mapping,
631
+ final_payload: Mapping) -> bool:
632
+ """#875 (spec §4.1): a close correction that leaves the block's money.
633
+
634
+ True when the current close and the one the plan leaves are both closed
635
+ and both totals are usable (a finite ``int`` or ``float`` that is not a
636
+ ``bool``) within 1e-9 USD of each other. The reviewed-hold helper's
637
+ weekly-axis update is the known case: it keeps the current payload and
638
+ replaces only the block's weekly axes. Such a correction is FROZEN like a
639
+ kept close. A correction that closes an open block, or whose totals are
640
+ unusable or differ by more, writes the close's money and REPLACES it.
641
+ """
642
+ current_total = current_payload.get("total_cost_usd")
643
+ final_total = final_payload.get("total_cost_usd")
644
+ return (
645
+ current_payload.get("is_closed") == 1
646
+ and final_payload.get("is_closed") == 1
647
+ and _usable_amount(current_total)
648
+ and _usable_amount(final_total)
649
+ and abs(final_total - current_total) <= _CLOSE_TOTAL_TOLERANCE_USD
650
+ )
651
+
652
+
653
+ def _revived_by_this_family(selected) -> bool:
654
+ """A preserved event this family's own earlier batch retired (#426)."""
655
+ return (
656
+ selected is not None
657
+ and selected.status == "tombstone"
658
+ and str(selected.batch_id or "").startswith(_FAMILY_BATCH_PREFIX)
659
+ )
660
+
661
+
662
+ def _resolve_closed_block_milestones(
663
+ current: Mapping[str, Mapping], desired: Mapping[str, Mapping],
664
+ preserved: Mapping[str, Mapping], frozen_blocks, replaced_blocks, *,
665
+ selection,
666
+ ) -> dict[str, dict]:
667
+ """#875: target records of the five-hour milestones of closed blocks.
668
+
669
+ A block is classified by the close the plan's final state holds for it
670
+ (spec §4.1), by its money rather than by whether the plan acts on it. A
671
+ FROZEN close keeps the money it already had: a kept close, including a
672
+ forced-conflict reaffirmation; preserved history the plan retains or
673
+ revives; or a correction of a closed close that leaves its usable total
674
+ within 1e-9 USD (:func:`_close_keeps_its_money`), such as the reviewed
675
+ hold's weekly-axis update. Under it, an existing crossing whose ``at``
676
+ and non-monetary identity are both unchanged keeps its current
677
+ ``block_cost_usd`` across any card edit, and a crossing whose ``at`` or
678
+ identity differs is a corrected crossing that takes replay's block cost.
679
+ A REPLACED close is one this plan adds, or corrects so that it writes the
680
+ close's money (it closes an open block, or its totals are unusable or
681
+ differ by more), and every matched milestone under it is a corrected
682
+ crossing. A block holding a frozen close and a replaced one is frozen.
683
+ Replay additions take replay's block cost under either. The milestones of
684
+ every other block follow replay and are not returned.
685
+
686
+ A present marginal is its final block cost minus the final block cost of
687
+ its predecessor: the highest lower threshold in the same (account,
688
+ window, reset_event_ref) segment of the plan's FINAL state. The final
689
+ state is every milestone the plan leaves active: these resolved records,
690
+ replay's milestones of the other blocks, and retained or preserved
691
+ current milestones. Tombstones are excluded. Block costs are settled
692
+ before any marginal, so the definition is not circular. A value within
693
+ 1e-9 USD of its reference (the current marginal, or the desired one for
694
+ an addition) keeps the reference's bytes. A reference that is not a
695
+ finite number only disables that snap; it never aborts the plan.
696
+
697
+ Marginal presence comes from the CURRENT record for an unchanged
698
+ crossing: replay never reproduces preserved ``b:`` predecessors, and a
699
+ reviewed hold can remove a desired predecessor before the diff. It comes
700
+ from the desired record for a corrected crossing or an addition. A
701
+ candidate keeps its whole chosen monetary pair (the current pair for an
702
+ unchanged crossing, the desired pair otherwise) rather than inventing a
703
+ value when it has no usable predecessor, or when its own threshold or
704
+ block cost is unusable. Only an ``int`` threshold occupies a position,
705
+ and only a finite amount is usable. A row whose threshold is usable but
706
+ whose block cost is not still occupies its threshold, so a successor
707
+ right above it fails closed and never skips to a lower row.
708
+
709
+ A second plan is a no-op for a block whose close it takes no action on.
710
+ That close is frozen in the next plan, kept or preserved, whatever role
711
+ it had in this one. Every crossing this plan wrote from replay now
712
+ carries the ``at`` and identity of the raw replay records, and a record
713
+ that reconciliation or a hold carries into ``desired`` keeps the applied
714
+ identity and resolved values. So the next plan sees unchanged crossings
715
+ whose current block cost and presence are the values this plan wrote,
716
+ and it resolves the same marginals against the same final predecessors.
717
+
718
+ Returns ``{event_id: record}`` for every matched milestone and every
719
+ replay addition of a frozen or replaced block. The ``alerted_at`` latch
720
+ is carried before the identity comparison, exactly as the ordinary path
721
+ carries it.
722
+ """
723
+ settled_blocks = frozenset(frozen_blocks) | frozenset(replaced_blocks)
724
+ resolved: dict[str, dict] = {}
725
+ roles: dict[str, str] = {}
726
+ final: dict[str, Mapping] = {}
727
+ for event_id in sorted(set(current) | set(desired) | set(preserved)):
728
+ current_record = current.get(event_id)
729
+ desired_record = desired.get(event_id)
730
+ preserved_record = preserved.get(event_id)
731
+ kind_payload = (
732
+ (desired_record or current_record or preserved_record).get(
733
+ "payload") or {}
734
+ )
735
+ if kind_payload.get("kind") != "five_hour_milestone":
736
+ continue
737
+ if preserved_record is not None:
738
+ # Held out of the diff (#426): retained as it stands, or revived
739
+ # when this family's own earlier batch retired it.
740
+ if current_record is not None:
741
+ final[event_id] = current_record
742
+ elif _revived_by_this_family(selection.by_id.get(event_id)):
743
+ final[event_id] = preserved_record
744
+ continue
745
+ block = _five_hour_block(kind_payload)
746
+ if block not in settled_blocks:
747
+ # An open block, or one whose close the plan tombstones: replay
748
+ # decides these milestones, exactly as it did before #875.
749
+ if desired_record is not None:
750
+ final[event_id] = desired_record
751
+ continue
752
+ if current_record is not None and desired_record is not None:
753
+ carried = _preserve_non_derivable_state(
754
+ current_record, desired_record)
755
+ current_payload = current_record.get("payload") or {}
756
+ payload = dict(carried.get("payload") or {})
757
+ # A moved ``at`` is a corrected crossing like a changed identity:
758
+ # copying replay's marginal for it would leave the next plan to
759
+ # re-resolve it against its final predecessor (spec §1.3).
760
+ if (
761
+ block in frozen_blocks
762
+ and current_record.get("at") == desired_record.get("at")
763
+ and _milestone_identity(current_payload)
764
+ == _milestone_identity(payload)
765
+ ):
766
+ payload["block_cost_usd"] = current_payload.get(
767
+ "block_cost_usd")
768
+ roles[event_id] = "unchanged"
769
+ else:
770
+ roles[event_id] = "corrected"
771
+ record = {**carried, "payload": payload}
772
+ elif desired_record is not None:
773
+ roles[event_id] = "added"
774
+ record = {**desired_record,
775
+ "payload": dict(desired_record.get("payload") or {})}
776
+ else:
777
+ # Current only and not preserved: the plan tombstones it.
778
+ continue
779
+ resolved[event_id] = record
780
+ final[event_id] = record
781
+
782
+ # Every row with a usable threshold occupies it, usable cost or not.
783
+ occupied: dict[tuple, dict[int, list[str]]] = {}
784
+ for event_id, record in final.items():
785
+ payload = record.get("payload") or {}
786
+ threshold = _usable_threshold(payload.get("percent_threshold"))
787
+ if threshold is not None:
788
+ occupied.setdefault(
789
+ _milestone_segment_key(payload), {}
790
+ ).setdefault(threshold, []).append(event_id)
791
+
792
+ def predecessor_cost(payload):
793
+ """The final block cost at the nearest lower threshold, or None."""
794
+ rows = occupied.get(_milestone_segment_key(payload), {})
795
+ threshold = _usable_threshold(payload.get("percent_threshold"))
796
+ lower = [value for value in rows if value < threshold]
797
+ if not lower:
798
+ return None
799
+ nearest = rows[max(lower)]
800
+ costs = {
801
+ event_id: (final[event_id].get("payload") or {}).get(
802
+ "block_cost_usd")
803
+ for event_id in nearest
804
+ }
805
+ if not all(_usable_amount(cost) for cost in costs.values()):
806
+ return None # fails closed: never skips past an unusable row
807
+ return costs[max(nearest)]
808
+
809
+ for event_id, record in resolved.items():
810
+ payload = record["payload"]
811
+ current_payload = (current.get(event_id) or {}).get("payload") or {}
812
+ desired_payload = (desired.get(event_id) or {}).get("payload") or {}
813
+ chosen = (
814
+ current_payload if roles[event_id] == "unchanged"
815
+ else desired_payload
816
+ )
817
+ if chosen.get("marginal_cost_usd") is None:
818
+ payload["marginal_cost_usd"] = None
819
+ continue
820
+ prior = None
821
+ if (
822
+ _usable_threshold(payload.get("percent_threshold")) is not None
823
+ and _usable_amount(payload.get("block_cost_usd"))
824
+ ):
825
+ prior = predecessor_cost(payload)
826
+ if prior is None:
827
+ payload["block_cost_usd"] = chosen.get("block_cost_usd")
828
+ payload["marginal_cost_usd"] = chosen.get("marginal_cost_usd")
829
+ continue
830
+ value = float(payload["block_cost_usd"]) - float(prior)
831
+ reference = (
832
+ current_payload if event_id in current else desired_payload
833
+ ).get("marginal_cost_usd")
834
+ if (
835
+ _usable_amount(reference)
836
+ and abs(value - reference) <= _MARGINAL_TOLERANCE_USD
837
+ ):
838
+ value = reference
839
+ payload["marginal_cost_usd"] = value
840
+ return resolved
388
841
 
389
842
 
390
843
  _SNAPSHOT_IDENTITY_FIELDS = (
@@ -395,7 +848,6 @@ _SNAPSHOT_IDENTITY_FIELDS = (
395
848
  _SNAPSHOT_DEPENDENT_KINDS = frozenset({
396
849
  "percent_milestone", "five_hour_milestone",
397
850
  })
398
- _EMPTY_REFERENCE_SENTINELS = frozenset({None, 0, "0", ""})
399
851
  _MILESTONE_BASE_DEPENDENCIES = {
400
852
  "usage_snapshot_ref": "snapshot_accept",
401
853
  "cost_snapshot_ref": "weekly_cost_snapshot",
@@ -825,12 +1277,16 @@ def _preserve_reviewed_hold_dependencies(
825
1277
  """Keep frozen five-hour facts reached from exact reviewed holds.
826
1278
 
827
1279
  An exact weekly-axis hold must not erase the same observation's retained
828
- five-hour crossing. Milestones are historical facts, so retain their whole
829
- accepted payload and close the usage/cost reference graph with the exact
830
- currently selected events. A closed block may update only its weekly axes;
831
- its five-hour close boundary, reading, money, tokens and children stay the
832
- selected frozen fact. Any unfamiliar reference shape refuses rather than
833
- being guessed or rewritten.
1280
+ five-hour crossing. Milestones are historical facts, so ``desired``
1281
+ receives their whole accepted payload, and the usage/cost reference graph
1282
+ is closed with the exact currently selected events. Under a frozen or a
1283
+ replaced close (#875), :func:`_resolve_closed_block_milestones` still
1284
+ settles such a milestone's marginal against its final predecessor; its
1285
+ block cost stays the accepted one. A closed block may update only its
1286
+ weekly axes; its five-hour close boundary, reading, money, tokens and
1287
+ children stay the selected frozen fact, so that update keeps the close's
1288
+ money and its block stays frozen (spec §4.1). Any unfamiliar reference
1289
+ shape refuses rather than being guessed or rewritten.
834
1290
  """
835
1291
  snapshot_ids = {
836
1292
  f"sa:{raw_id}" for raw_id in reviewed_weekly_hold_ids
@@ -1046,12 +1502,13 @@ class RederivePlan:
1046
1502
  actions: tuple[PlanAction, ...]
1047
1503
  retained_event_count: int
1048
1504
  preserved_event_count: int = 0
1505
+ uncertain_cost_fact_count: int = 0
1049
1506
  action_counts_by_event_kind: Mapping[str, Mapping[str, int]] = dataclasses.field(
1050
1507
  default_factory=dict
1051
1508
  )
1052
1509
 
1053
1510
  def _body(self) -> dict:
1054
- return {
1511
+ body = {
1055
1512
  "schemaVersion": 1,
1056
1513
  "family": self.family,
1057
1514
  "journalHighWater": (
@@ -1068,6 +1525,9 @@ class RederivePlan:
1068
1525
  "payloadHashes": sorted(action.payload_hash for action in self.actions),
1069
1526
  "actions": [action.to_dict() for action in self.actions],
1070
1527
  }
1528
+ if self.uncertain_cost_fact_count:
1529
+ body["uncertainCostFactCount"] = self.uncertain_cost_fact_count
1530
+ return body
1071
1531
 
1072
1532
  @property
1073
1533
  def plan_hash(self) -> str:
@@ -1106,6 +1566,8 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
1106
1566
  raw_observations=(),
1107
1567
  snapshot_identity_decisions=(),
1108
1568
  reviewed_weekly_hold_ids=(),
1569
+ uncertain_cost_fact_ids=(),
1570
+ historical_close_markers=None,
1109
1571
  enforce_guard=True) -> RederivePlan:
1110
1572
  """Diff current effective events against one scratch-derived desired set.
1111
1573
 
@@ -1126,7 +1588,19 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
1126
1588
  append-only journal forever, which is precisely the case the first design
1127
1589
  called already-correct. The resulting correction is a semantic no-op in
1128
1590
  content and a revision advance in effect, and the revision advance is what
1129
- suppresses the rev-0 group under the selector's revision filter."""
1591
+ suppresses the rev-0 group under the selector's revision filter.
1592
+
1593
+ ``historical_close_markers`` (#869) maps an unmarked close's event id to
1594
+ the planner's retained-entry proof that its $0 came from a missing card.
1595
+ ``uncertain_cost_fact_ids`` names the non-close monetary events the caller
1596
+ already judged unprovable; closed blocks are judged here, against the
1597
+ state this plan leaves behind.
1598
+
1599
+ #875: the milestones of a block whose final close is frozen (kept,
1600
+ preserved history retained or revived, or corrected without moving its
1601
+ money) or replaced (added, or corrected in a way that writes its money)
1602
+ are decided by :func:`_resolve_closed_block_milestones` before the diff,
1603
+ the same way closes are decided before milestones."""
1130
1604
  conflicted_event_ids = frozenset(conflicted_event_ids or ())
1131
1605
  desired = _desired_by_id(desired_events)
1132
1606
  current = {
@@ -1160,20 +1634,87 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
1160
1634
  for kind in sorted(_EVT_CLASSIFICATIONS)
1161
1635
  }
1162
1636
  actions: list[PlanAction] = []
1637
+ historical_close_markers = dict(historical_close_markers or {})
1638
+ drifted_close_ids: set = set()
1639
+
1640
+ def _kind(record):
1641
+ return (record.get("payload") or {}).get("kind")
1642
+
1643
+ # Every close is decided before any milestone, so a five-hour milestone can
1644
+ # follow the decision its block's close actually received (#869).
1645
+ close_decisions = {}
1646
+ for event_id in sorted(set(current) & set(desired)):
1647
+ if (
1648
+ _kind(current[event_id]) == "five_hour_block_close"
1649
+ and _kind(desired[event_id]) == "five_hour_block_close"
1650
+ ):
1651
+ close_decisions[event_id] = _preserve_non_derivable_state(
1652
+ current[event_id], desired[event_id],
1653
+ historical_marker=historical_close_markers.get(event_id),
1654
+ drifted_ids=drifted_close_ids,
1655
+ )
1656
+ # #875 (spec §4.1): classify every block by the closed close the plan's
1657
+ # FINAL state holds for it, whatever its pricing basis. The class depends
1658
+ # on that close's MONEY, the block total its milestones must agree with,
1659
+ # never on whether the plan acts on it: kept and preserved are transient
1660
+ # roles (a hold can inject a preserved close, and the next plan can see
1661
+ # it preserved again), so both are frozen, a revival is frozen although
1662
+ # the plan writes it, and so is a correction that leaves the total where
1663
+ # it was. A frozen close freezes the block cost of its unchanged
1664
+ # crossings; a corrected crossing under it still takes replay's.
1665
+ frozen_close_blocks = set()
1666
+ replaced_close_blocks = set()
1667
+ for event_id, decision in close_decisions.items():
1668
+ payload = decision.get("payload") or {}
1669
+ if payload.get("is_closed") != 1:
1670
+ continue
1671
+ if decision is current[event_id] or _close_keeps_its_money(
1672
+ current[event_id].get("payload") or {}, payload,
1673
+ ):
1674
+ # Kept, including a forced-conflict reaffirmation, which only
1675
+ # re-writes this same content at a higher revision; or a
1676
+ # money-preserving correction, such as the reviewed hold's
1677
+ # weekly-axis update (spec §1.7).
1678
+ frozen_close_blocks.add(_five_hour_block(payload))
1679
+ else:
1680
+ # The plan writes this close's money: it closes a block that was
1681
+ # open, or it moves or cannot compare the total.
1682
+ replaced_close_blocks.add(_five_hour_block(payload))
1683
+ for event_id in set(desired) - set(current):
1684
+ payload = desired[event_id].get("payload") or {}
1685
+ if (payload.get("kind") == "five_hour_block_close"
1686
+ and payload.get("is_closed") == 1):
1687
+ replaced_close_blocks.add(_five_hour_block(payload)) # an add
1688
+ for event_id, record in preserved.items():
1689
+ if event_id in current:
1690
+ final_close = current[event_id]
1691
+ elif _revived_by_this_family(selection.by_id.get(event_id)):
1692
+ final_close = record
1693
+ else:
1694
+ continue # stays retired: the final state holds no such close
1695
+ payload = final_close.get("payload") or {}
1696
+ if (payload.get("kind") == "five_hour_block_close"
1697
+ and payload.get("is_closed") == 1):
1698
+ frozen_close_blocks.add(_five_hour_block(payload))
1699
+ # Frozen wins (spec §4.1): a block whose final state holds a frozen close
1700
+ # and a replaced one is frozen. The causal block closure applies the same
1701
+ # rule (:func:`_replaced_block_closure`).
1702
+ replaced_close_blocks -= frozen_close_blocks
1703
+ milestone_decisions = _resolve_closed_block_milestones(
1704
+ current, desired, preserved, frozen_close_blocks,
1705
+ replaced_close_blocks, selection=selection,
1706
+ )
1163
1707
 
1164
1708
  for event_id in sorted(set(current) | set(desired) | set(preserved)):
1165
1709
  current_record = current.get(event_id)
1166
- desired_record = desired.get(event_id)
1710
+ desired_record = milestone_decisions.get(
1711
+ event_id, desired.get(event_id))
1167
1712
  selected = selection.by_id.get(event_id)
1168
1713
  preserved_record = preserved.get(event_id)
1169
1714
  if preserved_record is not None:
1170
1715
  # Un-re-derivable history: retain it, or restore it when this
1171
1716
  # family's own earlier plan retired it (#426).
1172
- revive = (
1173
- selected is not None
1174
- and selected.status == "tombstone"
1175
- and str(selected.batch_id or "").startswith(_FAMILY_BATCH_PREFIX)
1176
- )
1717
+ revive = _revived_by_this_family(selected)
1177
1718
  reaffirm = (
1178
1719
  selected is not None
1179
1720
  and selected.status == "active"
@@ -1193,9 +1734,12 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
1193
1734
  at = str(source["at"])
1194
1735
  payload = dict(source.get("payload") or {})
1195
1736
  elif current_record is not None and desired_record is not None:
1196
- desired_record = _preserve_non_derivable_state(
1197
- current_record, desired_record
1198
- )
1737
+ if event_id in close_decisions:
1738
+ desired_record = close_decisions[event_id]
1739
+ elif event_id not in milestone_decisions:
1740
+ desired_record = _preserve_non_derivable_state(
1741
+ current_record, desired_record,
1742
+ )
1199
1743
  if (
1200
1744
  _semantic_event(current_record) == _semantic_event(desired_record)
1201
1745
  and event_id not in conflicted_event_ids
@@ -1248,6 +1792,27 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
1248
1792
  kind=kind,
1249
1793
  ))
1250
1794
 
1795
+ # #869 F7/Q9: a close counts only while its cost stays unprovable in the
1796
+ # state this plan leaves behind — not a provably priced close, not one this
1797
+ # plan corrects, and not one an earlier correction already proved.
1798
+ uncertain_closes = {
1799
+ event_id for event_id, record in current.items()
1800
+ if _kind(record) == "five_hour_block_close"
1801
+ and close_cost_is_uncertain(record.get("payload") or {})
1802
+ }
1803
+ for action in actions:
1804
+ if action.kind != "five_hour_block_close":
1805
+ continue
1806
+ if action.disposition == "tombstone":
1807
+ uncertain_closes.discard(action.event_id)
1808
+ elif close_cost_is_uncertain(action.payload or {}):
1809
+ uncertain_closes.add(action.event_id)
1810
+ else:
1811
+ uncertain_closes.discard(action.event_id)
1812
+ uncertain_ids = (
1813
+ set(uncertain_cost_fact_ids or ()) | uncertain_closes
1814
+ | drifted_close_ids
1815
+ )
1251
1816
  plan = RederivePlan(
1252
1817
  family=FAMILY,
1253
1818
  journal_high_water=journal_high_water,
@@ -1257,6 +1822,7 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
1257
1822
  actions=tuple(actions),
1258
1823
  retained_event_count=retained_event_count,
1259
1824
  preserved_event_count=len(preserved),
1825
+ uncertain_cost_fact_count=len(uncertain_ids),
1260
1826
  action_counts_by_event_kind=counts_by_kind,
1261
1827
  )
1262
1828
  if enforce_guard:
@@ -1264,6 +1830,99 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
1264
1830
  return plan
1265
1831
 
1266
1832
 
1833
+ def _replaced_block_closure(
1834
+ included: list, reviewed_actions, current_events: Mapping[str, Mapping],
1835
+ ) -> list:
1836
+ """#875 (spec §4.5): keep a five-hour block whose close R replaces whole.
1837
+
1838
+ The B/R filter drops a close correction both plans make identically, yet
1839
+ a milestone under that close can still differ between them, for instance
1840
+ when R's hold keeps its predecessor whole while B reprices it. Keeping
1841
+ only the milestone would reprice it under a close the subset leaves as it
1842
+ stands (spec §1.5). So whenever ``included`` holds a five-hour milestone
1843
+ action whose block has a reviewed close action that REPLACES the close,
1844
+ the subset takes that close action and every reviewed action on the
1845
+ block's milestones, whether or not they survived the filter.
1846
+
1847
+ The classification is §4.1's, derived from R's actions and
1848
+ ``current_events`` alone. A close action replaces when it adds a closed
1849
+ close, or supersedes to a closed record whose current record is open,
1850
+ whose totals are unusable, or whose totals differ by more than 1e-9 USD.
1851
+ A block R holds FROZEN never triggers, even beside such an action
1852
+ (frozen wins): a closed current close R takes no action on (kept, or
1853
+ preserved history retained), a revival (a supersede with no active
1854
+ current record), a forced-conflict reaffirmation (a supersede to the
1855
+ current content, whatever its total), or a money-preserving correction.
1856
+ A tombstone carries no payload; its block is read from its current
1857
+ record. Only reviewed actions are ever added, each once. The reverse
1858
+ direction, a causal close action whose milestones are identical in both
1859
+ plans, is #883's.
1860
+ """
1861
+ def block(action):
1862
+ if action.disposition == "tombstone":
1863
+ record = current_events.get(action.event_id) or {}
1864
+ return _five_hour_block(record.get("payload") or {})
1865
+ return _five_hour_block(action.payload or {})
1866
+
1867
+ acted_ids = {action.event_id for action in reviewed_actions}
1868
+ frozen_blocks = set()
1869
+ for event_id, record in current_events.items():
1870
+ payload = record.get("payload") or {}
1871
+ if (
1872
+ event_id not in acted_ids
1873
+ and payload.get("kind") == "five_hour_block_close"
1874
+ and payload.get("is_closed") == 1
1875
+ ):
1876
+ frozen_blocks.add(_five_hour_block(payload))
1877
+ replacing: dict[tuple, set] = {}
1878
+ for action in reviewed_actions:
1879
+ payload = action.payload or {}
1880
+ if (
1881
+ action.kind != "five_hour_block_close"
1882
+ or action.disposition not in {"add", "supersede"}
1883
+ or payload.get("is_closed") != 1
1884
+ ):
1885
+ continue
1886
+ if action.disposition == "supersede":
1887
+ now = current_events.get(action.event_id)
1888
+ if (
1889
+ now is None # a revival of preserved history
1890
+ # A reaffirmation re-writes the kept content, whatever its
1891
+ # total, exactly as the resolver's ``decision is current``.
1892
+ or _semantic_event(now) == _semantic_event({
1893
+ "id": action.event_id, "at": action.at,
1894
+ "payload": payload,
1895
+ })
1896
+ or _close_keeps_its_money(now.get("payload") or {}, payload)
1897
+ ):
1898
+ frozen_blocks.add(block(action))
1899
+ continue
1900
+ replacing.setdefault(block(action), set()).add(action.event_id)
1901
+ for key in frozen_blocks:
1902
+ replacing.pop(key, None)
1903
+ triggered = {
1904
+ block(action) for action in included
1905
+ if action.kind == "five_hour_milestone"
1906
+ and block(action) in replacing
1907
+ }
1908
+ if not triggered:
1909
+ return included
1910
+ close_ids = set().union(*(replacing[key] for key in triggered))
1911
+ expanded = list(included)
1912
+ included_ids = {action.event_id for action in included}
1913
+ for action in reviewed_actions:
1914
+ if action.event_id in included_ids:
1915
+ continue
1916
+ if (
1917
+ action.event_id in close_ids
1918
+ or (action.kind == "five_hour_milestone"
1919
+ and block(action) in triggered)
1920
+ ):
1921
+ expanded.append(action)
1922
+ included_ids.add(action.event_id)
1923
+ return expanded
1924
+
1925
+
1267
1926
  def causal_delta_plan(
1268
1927
  baseline: RederivePlan, reviewed: RederivePlan, *,
1269
1928
  current_events: Mapping[str, Mapping],
@@ -1276,6 +1935,12 @@ def causal_delta_plan(
1276
1935
  reviewed-only or changed action is caused by the proposed operator record.
1277
1936
  A baseline-only action means the reviewed target returns to C and requires
1278
1937
  no correction action.
1938
+
1939
+ #875 (spec §4.5): a five-hour block whose close R replaces, by §4.1's
1940
+ money rule, is applied whole whenever the subset changes one of its
1941
+ milestones, and a block R holds frozen never is; see
1942
+ :func:`_replaced_block_closure`. The snapshot-dependency closure then runs
1943
+ over that expanded set.
1279
1944
  """
1280
1945
  if (
1281
1946
  baseline.journal_high_water != reviewed.journal_high_water
@@ -1306,6 +1971,8 @@ def causal_delta_plan(
1306
1971
  )
1307
1972
  )
1308
1973
  ]
1974
+ actions = _replaced_block_closure(
1975
+ actions, reviewed.actions, current_events)
1309
1976
  action_by_id = {action.event_id: action for action in actions}
1310
1977
  dependency_tombstones = set()
1311
1978
  for action in actions:
@@ -1397,6 +2064,7 @@ def causal_delta_plan(
1397
2064
  actions=actions,
1398
2065
  retained_event_count=reviewed.retained_event_count,
1399
2066
  preserved_event_count=reviewed.preserved_event_count,
2067
+ uncertain_cost_fact_count=reviewed.uncertain_cost_fact_count,
1400
2068
  action_counts_by_event_kind=counts_by_kind,
1401
2069
  )
1402
2070