cctally 1.115.0 → 1.116.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 (55) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/bin/_cctally_cache.py +85 -4
  3. package/bin/_cctally_completion.py +80 -12
  4. package/bin/_cctally_core.py +106 -9
  5. package/bin/_cctally_dashboard.py +6 -9
  6. package/bin/_cctally_dashboard_envelope.py +26 -32
  7. package/bin/_cctally_db.py +85 -4
  8. package/bin/_cctally_doctor.py +38 -5
  9. package/bin/_cctally_five_hour.py +83 -1017
  10. package/bin/_cctally_journal.py +1307 -180
  11. package/bin/_cctally_journal_repair.py +9 -7
  12. package/bin/_cctally_oauth_samples.py +280 -0
  13. package/bin/_cctally_parser.py +5 -0
  14. package/bin/_cctally_quarantine.py +14 -4
  15. package/bin/_cctally_quota.py +70 -8
  16. package/bin/_cctally_quota_model.py +35 -4
  17. package/bin/_cctally_record.py +414 -123
  18. package/bin/_cctally_rederive.py +632 -235
  19. package/bin/_cctally_refresh.py +29 -0
  20. package/bin/_cctally_statusline.py +984 -47
  21. package/bin/_cctally_store.py +972 -58
  22. package/bin/_cctally_tui.py +62 -7
  23. package/bin/_cctally_update.py +25 -0
  24. package/bin/_lib_account_protocol.py +201 -18
  25. package/bin/_lib_clone_copy.py +46 -11
  26. package/bin/_lib_dashboard_sources.py +57 -15
  27. package/bin/_lib_detector_access_registry.py +273 -52
  28. package/bin/_lib_doctor.py +302 -1
  29. package/bin/_lib_journal.py +145 -25
  30. package/bin/_lib_journal_router.py +166 -48
  31. package/bin/_lib_oauth_samples.py +238 -0
  32. package/bin/_lib_pricing_check.py +0 -12
  33. package/bin/_lib_provider_activity.py +770 -0
  34. package/bin/_lib_quarantine.py +3 -2
  35. package/bin/_lib_quota_model.py +96 -6
  36. package/bin/_lib_rebuild_progress.py +146 -0
  37. package/bin/_lib_record.py +22 -0
  38. package/bin/_lib_rederive.py +299 -3
  39. package/bin/_lib_snapshot_cache.py +43 -1
  40. package/bin/_lib_stale_proof.py +708 -0
  41. package/bin/_lib_statusline_candidates.py +65 -5
  42. package/bin/_lib_worker_launches.py +315 -0
  43. package/bin/cctally +6 -11
  44. package/dashboard/static/assets/{ConversationsView-Bc8LCOAZ.js → ConversationsView-Dflb_myA.js} +1 -1
  45. package/dashboard/static/assets/DoctorModal-DFX5CfIs.js +1 -0
  46. package/dashboard/static/assets/{ModalRoot-DLPgyAM_.js → ModalRoot-HlxoagIa.js} +1 -1
  47. package/dashboard/static/assets/{ProjectsDrillPanel-z53a4v4M.js → ProjectsDrillPanel-C3PJy1xx.js} +1 -1
  48. package/dashboard/static/assets/{SourceDetailModal-Bw0T8W-W.js → SourceDetailModal-BczsWKhD.js} +1 -1
  49. package/dashboard/static/assets/{UpdateModal-D5AKx4Yw.js → UpdateModal-Os-Lo5cs.js} +1 -1
  50. package/dashboard/static/assets/{index-BXDOzSng.js → index-CYZ3NLqF.js} +3 -3
  51. package/dashboard/static/assets/{outlineNavigation-JObY7cUY.js → outlineNavigation-k-zIif6W.js} +1 -1
  52. package/dashboard/static/dashboard.html +1 -1
  53. package/package.json +7 -2
  54. package/bin/_lib_merge_gate.py +0 -452
  55. package/dashboard/static/assets/DoctorModal-D--qVBwe.js +0 -1
@@ -19,6 +19,7 @@ import json
19
19
  import math
20
20
  from collections.abc import Iterable, Mapping
21
21
 
22
+ import _lib_accounts
22
23
  from _lib_cost_provenance import (
23
24
  HISTORICAL_CLOSE_BASIS, RECORDED_CLOSE_CORRECTION_BASIS,
24
25
  )
@@ -162,6 +163,14 @@ _OP_CLASSIFICATIONS = {
162
163
  # by the selector directly and never registered).
163
164
  "journal_protocol_resolution_v2": KindClassification(
164
165
  "retained", "journal-repair acknowledgements are audit records"),
166
+ # #797/#823/#840 spec 4.6 B2, B5: the revision-2 boundary is fence
167
+ # metadata like the cutover, and the v3 acknowledgement is the v2's
168
+ # successor.
169
+ "account_bound_revision": KindClassification(
170
+ "retained", "the revision-2 journal fence is replayed, never "
171
+ "re-derived"),
172
+ "journal_protocol_resolution_v3": KindClassification(
173
+ "retained", "journal-repair acknowledgements are audit records"),
165
174
  }
166
175
 
167
176
 
@@ -912,6 +921,132 @@ _MILESTONE_BASE_DEPENDENCIES = {
912
921
  }
913
922
 
914
923
 
924
+ # ── Reviewed weekly decisions (#797/#823/#840 spec 4.7, Q5, D4, U2) ─────────
925
+ #
926
+ # Op kind `claude_weekly_observation_decision_v2` (and the previous release's
927
+ # `claude_weekly_observation_decision`) carries one reviewed manifest. Payload
928
+ # schema 1 lists `decisions`, schema 2 `weekly_axis_decisions` (and snapshot
929
+ # identity decisions), both of `hold` / `accept`. Schema 3 adds `retract`
930
+ # WITHOUT a new projection: `weekly_axis_decisions` projects each retraction as
931
+ # an ordinary `hold` (the previous release's completion, which recognizes only
932
+ # a literal `hold`, then sees a weekly exclusion) and the canonical
933
+ # `weekly_axis_retractions` list — sorted, unique, each naming a same-op
934
+ # projected hold — gives it its causal meaning. This is the ONE strict decoder
935
+ # every current consumer of a retained payload uses.
936
+
937
+ REVIEWED_WEEKLY_SCHEMA_VERSIONS = (1, 2, 3)
938
+ REVIEWED_WEEKLY_DISPOSITIONS = ("hold", "accept", "retract")
939
+ _REVIEWED_WEEKLY_IDENTITY_KEYS = frozenset({
940
+ "acceptedObservationId", "replayObservationId", "disposition",
941
+ })
942
+
943
+
944
+ @dataclasses.dataclass(frozen=True)
945
+ class ReviewedWeeklyDecision:
946
+ """One decoded reviewed weekly decision payload: its schema version, the
947
+ disposition per observation (``hold``, ``accept`` or ``retract``, in
948
+ payload order), and its snapshot identity decisions."""
949
+
950
+ version: int
951
+ dispositions: Mapping[str, str]
952
+ identity_decisions: tuple
953
+
954
+
955
+ def decode_reviewed_weekly_payload(payload) -> ReviewedWeeklyDecision:
956
+ """Strictly decode one retained reviewed weekly decision payload, or raise
957
+ :class:`RederiveConflict`."""
958
+ if not isinstance(payload, Mapping):
959
+ raise RederiveConflict("malformed retained reviewed weekly decision op")
960
+ version = payload.get("schema_version")
961
+ if type(version) is not int or version not in REVIEWED_WEEKLY_SCHEMA_VERSIONS:
962
+ raise RederiveConflict("malformed retained reviewed weekly decision op")
963
+ decisions = (payload.get("decisions") if version == 1
964
+ else payload.get("weekly_axis_decisions"))
965
+ identity = ([] if version == 1
966
+ else payload.get("snapshot_identity_decisions"))
967
+ if (not isinstance(decisions, list) or not isinstance(identity, list)
968
+ or not decisions and not identity):
969
+ raise RederiveConflict("malformed retained reviewed weekly decision op")
970
+ dispositions: dict = {}
971
+ for item in decisions:
972
+ if (not isinstance(item, dict)
973
+ or set(item) != {"observationId", "disposition"}
974
+ or not isinstance(item["observationId"], str)
975
+ or not item["observationId"]
976
+ or item["disposition"] not in ("hold", "accept")):
977
+ raise RederiveConflict(
978
+ "malformed retained reviewed weekly decision entry")
979
+ obs_id = item["observationId"]
980
+ if obs_id in dispositions:
981
+ raise RederiveConflict(
982
+ f"unknown or duplicate reviewed weekly observation: {obs_id}")
983
+ dispositions[obs_id] = item["disposition"]
984
+ retractions = payload.get("weekly_axis_retractions")
985
+ if version < 3:
986
+ if "weekly_axis_retractions" in payload:
987
+ raise RederiveConflict(
988
+ "malformed retained reviewed weekly decision op")
989
+ else:
990
+ if (not isinstance(retractions, list)
991
+ or any(not isinstance(r, str) or not r for r in retractions)
992
+ or retractions != sorted(set(retractions))):
993
+ raise RederiveConflict(
994
+ "reviewed weekly retractions must be a canonical list")
995
+ for obs_id in retractions:
996
+ if dispositions.get(obs_id) != "hold":
997
+ raise RederiveConflict(
998
+ "reviewed weekly retraction references no same-op hold: "
999
+ + obs_id)
1000
+ dispositions[obs_id] = "retract"
1001
+ seen: set = set()
1002
+ for item in identity:
1003
+ if (not isinstance(item, dict)
1004
+ or set(item) != _REVIEWED_WEEKLY_IDENTITY_KEYS
1005
+ or not all(isinstance(item.get(key), str) and item[key]
1006
+ for key in ("acceptedObservationId",
1007
+ "replayObservationId"))
1008
+ or item.get("disposition") not in ("preserve", "rederive")
1009
+ or item["acceptedObservationId"] == item["replayObservationId"]
1010
+ or item["acceptedObservationId"] in seen
1011
+ or item["replayObservationId"] in seen):
1012
+ raise RederiveConflict(
1013
+ "malformed retained snapshot identity decision")
1014
+ seen.update((item["acceptedObservationId"], item["replayObservationId"]))
1015
+ return ReviewedWeeklyDecision(version, dispositions, tuple(identity))
1016
+
1017
+
1018
+ def effective_reviewed_weekly_dispositions(payloads) -> dict:
1019
+ """The effective disposition per observation over ``payloads`` in journal
1020
+ order: the latest logical decision wins."""
1021
+ effective: dict = {}
1022
+ for payload in payloads:
1023
+ effective.update(decode_reviewed_weekly_payload(payload).dispositions)
1024
+ return effective
1025
+
1026
+
1027
+ def reviewed_weekly_payload(*, version: int, decisions, base: Mapping) -> dict:
1028
+ """The persisted payload of one reviewed manifest: ``base`` (kind, bound,
1029
+ reason and the other shared fields) plus ``decisions`` as
1030
+ ``[{"observationId", "disposition"}]``, with each retraction projected as
1031
+ an ordinary `hold` and listed in `weekly_axis_retractions` (schema 3)."""
1032
+ payload = dict(base)
1033
+ projected = sorted(
1034
+ ({"observationId": item["observationId"],
1035
+ "disposition": ("hold" if item["disposition"] == "retract"
1036
+ else item["disposition"])}
1037
+ for item in decisions),
1038
+ key=lambda item: item["observationId"])
1039
+ if version == 1:
1040
+ payload["decisions"] = projected
1041
+ else:
1042
+ payload["weekly_axis_decisions"] = projected
1043
+ if version >= 3:
1044
+ payload["weekly_axis_retractions"] = sorted(
1045
+ item["observationId"] for item in decisions
1046
+ if item["disposition"] == "retract")
1047
+ return payload
1048
+
1049
+
915
1050
  def _milestone_dependencies(kind: str) -> dict[str, str]:
916
1051
  dependencies = dict(_MILESTONE_BASE_DEPENDENCIES)
917
1052
  dependencies["reset_event_ref"] = (
@@ -1367,9 +1502,94 @@ def _without_snapshot_suppression(payload: Mapping) -> Mapping:
1367
1502
  return {key: value for key, value in payload.items() if key != "suppression"}
1368
1503
 
1369
1504
 
1505
+ def _retraction_unsupported_resets(
1506
+ current: Mapping[str, Mapping], desired: Mapping[str, Mapping],
1507
+ retracted_snapshot_ids, raw_observations: Iterable[Mapping] = (),
1508
+ ) -> frozenset:
1509
+ """#797/#823/#840 spec 4.7 (D4): the current `week_reset` events the
1510
+ corrected replay no longer supports BECAUSE of a retraction — absent from
1511
+ ``desired``, and whose causal predecessor was a retracted observation.
1512
+
1513
+ The predecessor is read from the raw journal observation stream
1514
+ (``raw_observations``), not from the current graph: the latest
1515
+ weekly-bearing Claude observation of the reset's account (attributed as
1516
+ the replay attributes it, `_cctally_record._pipeline_claude_usage`)
1517
+ captured strictly before the reset's originating low observation (whose
1518
+ identity the reset follows, `_lib_journal` `week_reset_identity_parts`),
1519
+ or before its `detected_at_utc` when it has no origin; equal instants
1520
+ break by journal order. Whether the replay accepted that observation,
1521
+ held it or stored no snapshot for it does not matter: a stale replay an
1522
+ earlier reviewed `hold` turned into a held snapshot, or never accepted,
1523
+ is still the reading the reset was detected against, and the latest
1524
+ unheld snapshot before the origin is then an unrelated genuine reading
1525
+ (review of 79847ac5a, D1). Exact identity, never a percentage
1526
+ comparison. A reset the corrected replay still produces (alternate
1527
+ support, review F4) is in ``desired`` and is never here."""
1528
+ retracted = frozenset(retracted_snapshot_ids or ())
1529
+ if not retracted:
1530
+ return frozenset()
1531
+ weekly: dict = {}
1532
+ captured: dict = {}
1533
+ for index, record in enumerate(raw_observations or ()):
1534
+ payload = record.get("payload") or {}
1535
+ observation_id = record.get("id")
1536
+ if (record.get("t") != "obs" or record.get("provider") != "claude"
1537
+ or "resets_at" not in payload
1538
+ or payload.get("weekly_percent") is None
1539
+ or not isinstance(observation_id, str) or not observation_id):
1540
+ continue
1541
+ instant = _iso_instant(payload.get("captured_at") or record.get("at"))
1542
+ if instant is None:
1543
+ continue
1544
+ account = record.get("account") or _lib_accounts.UNATTRIBUTED
1545
+ weekly.setdefault(account, []).append((instant, index, observation_id))
1546
+ captured.setdefault(observation_id, instant)
1547
+ unsupported = set()
1548
+ for event_id, record in current.items():
1549
+ payload = record.get("payload") or {}
1550
+ if payload.get("kind") != "week_reset" or event_id in desired:
1551
+ continue
1552
+ origin = payload.get("origin_observation_id")
1553
+ origin = origin if isinstance(origin, str) and origin else None
1554
+ origin_payload = (
1555
+ (current.get(f"sa:{origin}") or {}).get("payload") or {}
1556
+ if origin is not None else {})
1557
+ at = ((captured.get(origin) if origin is not None else None)
1558
+ or _iso_instant(origin_payload.get("captured_at_utc"))
1559
+ or _iso_instant(payload.get("detected_at_utc")))
1560
+ if at is None:
1561
+ continue
1562
+ prior = [item for item in weekly.get(payload.get("account_key"), ())
1563
+ if item[0] < at and item[2] != origin]
1564
+ if prior and f"sa:{max(prior)[2]}" in retracted:
1565
+ unsupported.add(event_id)
1566
+ return frozenset(unsupported)
1567
+
1568
+
1569
+ def _retraction_scope(current: Mapping[str, Mapping], retracted_snapshot_ids,
1570
+ unsupported_resets) -> frozenset:
1571
+ """The event ids whose reviewed actions a retraction itself causes and
1572
+ the reviewed delta therefore always carries (spec 4.7: explicit
1573
+ retractions survive the baseline/reviewed plan intersection): the
1574
+ retracted snapshots, the resets they leave unsupported, and every
1575
+ current milestone citing either."""
1576
+ scope = set(retracted_snapshot_ids or ()) | set(unsupported_resets or ())
1577
+ if not scope:
1578
+ return frozenset()
1579
+ for event_id, record in current.items():
1580
+ payload = record.get("payload") or {}
1581
+ if payload.get("kind") not in _SNAPSHOT_DEPENDENT_KINDS:
1582
+ continue
1583
+ if (payload.get("usage_snapshot_ref") in scope
1584
+ or payload.get("reset_event_ref") in scope):
1585
+ scope.add(event_id)
1586
+ return frozenset(scope)
1587
+
1588
+
1370
1589
  def _preserve_reviewed_hold_dependencies(
1371
1590
  current: Mapping[str, Mapping], desired: dict[str, Mapping],
1372
1591
  reviewed_weekly_hold_ids=(), historical_close_markers=None,
1592
+ unsupported_resets=frozenset(),
1373
1593
  ) -> None:
1374
1594
  """Keep frozen five-hour facts reached from exact reviewed holds.
1375
1595
 
@@ -1384,6 +1604,13 @@ def _preserve_reviewed_hold_dependencies(
1384
1604
  children stay the selected frozen fact, so that update keeps the close's
1385
1605
  money and its block stays frozen (spec §4.1). Any unfamiliar reference
1386
1606
  shape refuses rather than being guessed or rewritten.
1607
+
1608
+ #797/#823/#840 spec 4.7 (D4): ``reviewed_weekly_hold_ids`` names the
1609
+ ordinary holds only — a retraction is never preserved — and a held
1610
+ milestone whose reset a retraction left unsupported
1611
+ (``unsupported_resets``) is not kept: keeping it would restore that
1612
+ reset, and the retraction's tombstone wins over the dangling milestone,
1613
+ which then follows the corrected replay.
1387
1614
  """
1388
1615
  snapshot_ids = {
1389
1616
  f"sa:{raw_id}" for raw_id in reviewed_weekly_hold_ids
@@ -1458,6 +1685,8 @@ def _preserve_reviewed_hold_dependencies(
1458
1685
  "reviewed weekly hold dependency has an unfamiliar kind: "
1459
1686
  + event_id
1460
1687
  )
1688
+ if payload.get("reset_event_ref") in unsupported_resets:
1689
+ continue
1461
1690
  snapshot = current.get(usage_ref)
1462
1691
  if ((snapshot or {}).get("payload") or {}).get("kind") != "snapshot_accept":
1463
1692
  raise RederiveConflict(
@@ -1643,6 +1872,10 @@ class RederivePlan:
1643
1872
  action_counts_by_event_kind: Mapping[str, Mapping[str, int]] = dataclasses.field(
1644
1873
  default_factory=dict
1645
1874
  )
1875
+ #: #797/#823/#840 spec 4.7: the event ids a retraction itself acts on
1876
+ #: (`_retraction_scope`). Not part of the plan body: the retractions are
1877
+ #: already in the config fingerprint through the decision op's payload.
1878
+ retraction_event_ids: frozenset = frozenset()
1646
1879
 
1647
1880
  def _body(self) -> dict:
1648
1881
  body = {
@@ -1703,6 +1936,7 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
1703
1936
  raw_observations=(),
1704
1937
  snapshot_identity_decisions=(),
1705
1938
  reviewed_weekly_hold_ids=(),
1939
+ reviewed_weekly_retraction_ids=(),
1706
1940
  uncertain_cost_fact_ids=(),
1707
1941
  historical_close_markers=None,
1708
1942
  physical_revisions=None,
@@ -1754,12 +1988,26 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
1754
1988
  and selected.record is not None
1755
1989
  and _is_owned_event(selected.record)
1756
1990
  }
1991
+ if not isinstance(raw_observations, (list, tuple)):
1992
+ raw_observations = tuple(raw_observations)
1757
1993
  _reconcile_snapshot_identities(
1758
1994
  current, desired, selection, raw_observations,
1759
1995
  snapshot_identity_decisions,
1760
1996
  )
1997
+ # #797/#823/#840 spec 4.7 (D4): `reviewed_weekly_hold_ids` are the
1998
+ # ordinary holds; a retraction is excluded from preservation, and the
1999
+ # resets it leaves unsupported are never restored by a held milestone.
2000
+ retracted_snapshot_ids = frozenset(
2001
+ f"sa:{raw_id}" for raw_id in reviewed_weekly_retraction_ids or ()
2002
+ if isinstance(raw_id, str) and raw_id)
2003
+ unsupported_resets = _retraction_unsupported_resets(
2004
+ current, desired, retracted_snapshot_ids,
2005
+ raw_observations=raw_observations)
2006
+ retraction_event_ids = _retraction_scope(
2007
+ current, retracted_snapshot_ids, unsupported_resets)
1761
2008
  _preserve_reviewed_hold_dependencies(
1762
2009
  current, desired, reviewed_weekly_hold_ids, historical_close_markers,
2010
+ unsupported_resets=unsupported_resets,
1763
2011
  )
1764
2012
  retained_event_count = sum(
1765
2013
  1 for selected in selection.by_id.values()
@@ -1970,6 +2218,7 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
1970
2218
  preserved_event_count=len(preserved),
1971
2219
  uncertain_cost_fact_count=len(uncertain_ids),
1972
2220
  action_counts_by_event_kind=counts_by_kind,
2221
+ retraction_event_ids=retraction_event_ids,
1973
2222
  )
1974
2223
  if enforce_guard:
1975
2224
  enforce_week_reset_add_burst_guard(plan)
@@ -2162,8 +2411,14 @@ def _monetary_chain_closure(
2162
2411
  if action.event_id in required_ids]
2163
2412
 
2164
2413
 
2165
- def _causal_snapshot_closure(actions, reviewed_by_id, current_events):
2166
- """Close explicit references, keeping a selected dependency if needed."""
2414
+ def _causal_snapshot_closure(actions, reviewed_by_id, current_events,
2415
+ protected=frozenset()):
2416
+ """Close explicit references, keeping a selected dependency if needed.
2417
+
2418
+ #797/#823/#840 spec 4.7 (D4): a tombstone in ``protected`` (a retraction's
2419
+ own scope) is never cancelled to keep a milestone that still names it —
2420
+ the retraction wins, so such a milestone is a conflict here (the planner
2421
+ never pins one, `_preserve_reviewed_hold_dependencies`)."""
2167
2422
  action_by_id = {action.event_id: action for action in actions}
2168
2423
  dependency_tombstones = set()
2169
2424
  for action in actions:
@@ -2177,6 +2432,10 @@ def _causal_snapshot_closure(actions, reviewed_by_id, current_events):
2177
2432
  dependency_action = action_by_id.get(reference)
2178
2433
  if (dependency_action is not None
2179
2434
  and dependency_action.disposition == "tombstone"):
2435
+ if reference in protected:
2436
+ raise RederiveConflict(
2437
+ "a retracted dependency's tombstone would be cancelled "
2438
+ "by milestone: " + action.event_id)
2180
2439
  current = current_events.get(reference)
2181
2440
  if ((current or {}).get("payload") or {}).get("kind") \
2182
2441
  != expected_kind:
@@ -2215,6 +2474,34 @@ def _causal_snapshot_closure(actions, reviewed_by_id, current_events):
2215
2474
  return actions
2216
2475
 
2217
2476
 
2477
+ def _retained_reference_closure(actions, reviewed_by_id, current_events):
2478
+ """#797/#823/#840 spec 4.7: unchanged retained references are validated
2479
+ as well as proposed actions. A current milestone the delta does not act
2480
+ on but which names an event the delta tombstones takes its reviewed
2481
+ action; with none, the plan would leave it dangling and refuses."""
2482
+ acted = {action.event_id for action in actions}
2483
+ tombstoned = {action.event_id for action in actions
2484
+ if action.disposition == "tombstone"}
2485
+ if not tombstoned:
2486
+ return actions
2487
+ for event_id, record in sorted(current_events.items()):
2488
+ payload = (record or {}).get("payload") or {}
2489
+ kind = payload.get("kind")
2490
+ if event_id in acted or kind not in _SNAPSHOT_DEPENDENT_KINDS:
2491
+ continue
2492
+ if not any(payload.get(field) in tombstoned
2493
+ for field in _milestone_dependencies(kind)):
2494
+ continue
2495
+ reviewed = reviewed_by_id.get(event_id)
2496
+ if reviewed is None:
2497
+ raise RederiveConflict(
2498
+ "a retained milestone would name a tombstoned event: "
2499
+ + event_id)
2500
+ actions.append(reviewed)
2501
+ acted.add(event_id)
2502
+ return actions
2503
+
2504
+
2218
2505
  def causal_delta_plan(
2219
2506
  baseline: RederivePlan, reviewed: RederivePlan, *,
2220
2507
  current_events: Mapping[str, Mapping],
@@ -2250,6 +2537,7 @@ def causal_delta_plan(
2250
2537
  }
2251
2538
  authorized_week_reset_origins = frozenset(
2252
2539
  authorized_week_reset_origins or ())
2540
+ retraction_scope = frozenset(reviewed.retraction_event_ids or ())
2253
2541
  actions = [
2254
2542
  action for action in reviewed.actions
2255
2543
  if (
@@ -2261,6 +2549,9 @@ def causal_delta_plan(
2261
2549
  and (action.payload or {}).get("origin_observation_id")
2262
2550
  in authorized_week_reset_origins
2263
2551
  )
2552
+ # #797/#823/#840 spec 4.7: an explicit retraction survives the
2553
+ # baseline/reviewed intersection.
2554
+ or action.event_id in retraction_scope
2264
2555
  )
2265
2556
  ]
2266
2557
  # Close block, monetary and explicit-reference edges to a fixed point.
@@ -2272,7 +2563,11 @@ def causal_delta_plan(
2272
2563
  actions = _monetary_chain_closure(
2273
2564
  actions, reviewed.actions, current_events)
2274
2565
  actions = _causal_snapshot_closure(
2275
- actions, reviewed_by_id, current_events)
2566
+ actions, reviewed_by_id, current_events,
2567
+ protected=retraction_scope)
2568
+ if retraction_scope:
2569
+ actions = _retained_reference_closure(
2570
+ actions, reviewed_by_id, current_events)
2276
2571
  if {action.event_id for action in actions} == previous_ids:
2277
2572
  break
2278
2573
  included_ids = {action.event_id for action in actions}
@@ -2303,6 +2598,7 @@ def causal_delta_plan(
2303
2598
  preserved_event_count=reviewed.preserved_event_count,
2304
2599
  uncertain_cost_fact_count=reviewed.uncertain_cost_fact_count,
2305
2600
  action_counts_by_event_kind=counts_by_kind,
2601
+ retraction_event_ids=reviewed.retraction_event_ids,
2306
2602
  )
2307
2603
 
2308
2604
 
@@ -1887,9 +1887,51 @@ def doctor_payload_memo(
1887
1887
 
1888
1888
 
1889
1889
  def reset_doctor_memo() -> None:
1890
- """Drop the memoized doctor payload (test hook + M5 invalidation entry)."""
1890
+ """Drop the memoized doctor payload and the deferred frames' memoized
1891
+ ``rebuild_worker`` value (test hook + M5 invalidation entry)."""
1891
1892
  with _DOCTOR_MEMO_LOCK:
1892
1893
  _DOCTOR_MEMO.clear()
1894
+ _REBUILD_WORKER_MEMO.clear()
1895
+
1896
+
1897
+ # #997 D10: while an epoch rebuild is pending or running every stats open
1898
+ # defers, so the dashboard serves only the deferred startup frame and deferred
1899
+ # ticks, neither of which runs the doctor gather. Their doctor block still
1900
+ # carries the `db.rebuild_worker` aggregate, from the worker snapshots alone
1901
+ # (file reads and a `ps` probe per pid; no stats open, no cctally lock). This
1902
+ # memo keeps a deferred tick from forking `ps` every tick, on the doctor
1903
+ # payload's TTL and freshness rule. Same injected-`compute` decoupling.
1904
+ REBUILD_WORKER_MEMO_TTL_S = DOCTOR_MEMO_TTL_S
1905
+
1906
+ _REBUILD_WORKER_MEMO: "dict" = {}
1907
+
1908
+
1909
+ def rebuild_worker_memo(
1910
+ now_utc: dt.datetime,
1911
+ *,
1912
+ ttl_s: float,
1913
+ compute: "Callable[[dt.datetime], dict | None]",
1914
+ ) -> "dict | None":
1915
+ """Return the deferred frames' ``rebuild_worker`` value, recomputing via
1916
+ ``compute(now_utc)`` only when the memo is cold: never computed, older
1917
+ than ``ttl_s``, or a clock regression. ``compute`` runs outside the
1918
+ lock, exactly as in `doctor_payload_memo`."""
1919
+ with _DOCTOR_MEMO_LOCK:
1920
+ cached = _REBUILD_WORKER_MEMO
1921
+ computed_at = cached.get("computed_at")
1922
+ fresh = (
1923
+ computed_at is not None
1924
+ and now_utc >= computed_at
1925
+ and (now_utc - computed_at).total_seconds() < ttl_s
1926
+ )
1927
+ if fresh:
1928
+ return cached["value"]
1929
+ value = compute(now_utc)
1930
+ with _DOCTOR_MEMO_LOCK:
1931
+ _REBUILD_WORKER_MEMO.clear()
1932
+ _REBUILD_WORKER_MEMO["value"] = value
1933
+ _REBUILD_WORKER_MEMO["computed_at"] = now_utc
1934
+ return value
1893
1935
 
1894
1936
 
1895
1937
  # === Task 5.1 — idle-path dispatch state (last signature + snapshot) ========