cctally 1.108.0 → 1.110.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.
- package/CHANGELOG.md +147 -0
- package/README.md +6 -6
- package/bin/_cctally_account.py +4 -0
- package/bin/_cctally_alerts.py +6 -2
- package/bin/_cctally_cache.py +1652 -166
- package/bin/_cctally_config.py +3 -2
- package/bin/_cctally_core.py +594 -33
- package/bin/_cctally_dashboard.py +1585 -201
- package/bin/_cctally_dashboard_conversation.py +494 -48
- package/bin/_cctally_dashboard_envelope.py +167 -11
- package/bin/_cctally_dashboard_share.py +154 -36
- package/bin/_cctally_dashboard_sources.py +2615 -422
- package/bin/_cctally_db.py +678 -5
- package/bin/_cctally_diagnosis_sources.py +238 -26
- package/bin/_cctally_doctor.py +99 -18
- package/bin/_cctally_five_hour.py +1295 -40
- package/bin/_cctally_forecast.py +124 -28
- package/bin/_cctally_journal.py +408 -38
- package/bin/_cctally_milestone_history.py +150 -14
- package/bin/_cctally_parser.py +5 -0
- package/bin/_cctally_percent_breakdown.py +6 -1
- package/bin/_cctally_project.py +48 -76
- package/bin/_cctally_quota.py +39 -7
- package/bin/_cctally_quota_model.py +17 -2
- package/bin/_cctally_record.py +1647 -443
- package/bin/_cctally_rederive.py +899 -30
- package/bin/_cctally_refresh.py +2 -1
- package/bin/_cctally_share.py +24 -5
- package/bin/_cctally_source_analytics.py +1035 -78
- package/bin/_cctally_statusline.py +83 -19
- package/bin/_cctally_store.py +61 -4
- package/bin/_cctally_transcript.py +27 -2
- package/bin/_cctally_tui.py +264 -76
- package/bin/_cctally_weekrefs.py +28 -5
- package/bin/_lib_blocks.py +127 -20
- package/bin/_lib_cache_report.py +20 -5
- package/bin/_lib_codex_conversation_query.py +297 -64
- package/bin/_lib_codex_hooks.py +102 -0
- package/bin/_lib_codex_metadata.py +207 -0
- package/bin/_lib_conversation.py +119 -0
- package/bin/_lib_conversation_dispatch.py +31 -1
- package/bin/_lib_conversation_query.py +315 -51
- package/bin/_lib_conversation_retention.py +413 -25
- package/bin/_lib_dashboard_sources.py +267 -7
- package/bin/_lib_diagnosis.py +10 -0
- package/bin/_lib_diff_kernel.py +12 -1
- package/bin/_lib_doctor.py +230 -8
- package/bin/_lib_ingest_frontier.py +1366 -49
- package/bin/_lib_journal.py +19 -2
- package/bin/_lib_merge_gate.py +307 -0
- package/bin/_lib_pricing.py +588 -29
- package/bin/_lib_record.py +130 -12
- package/bin/_lib_rederive.py +903 -2
- package/bin/_lib_render.py +9 -3
- package/bin/_lib_retained_size.py +27 -2
- package/bin/_lib_segment_summary.py +66 -8
- package/bin/_lib_share_templates.py +28 -18
- package/bin/_lib_snapshot_cache.py +115 -1
- package/bin/_lib_source_retry.py +320 -0
- package/bin/_lib_statusline_candidates.py +400 -36
- package/bin/_lib_tick_stats.py +119 -0
- package/bin/_lib_view_models.py +256 -76
- package/bin/cctally +69 -0
- package/dashboard/static/assets/ConversationsView-BB9vUiL6.js +72 -0
- package/dashboard/static/assets/{DoctorModal-CWn3U4Wl.js → DoctorModal-DFY2iPDf.js} +1 -1
- package/dashboard/static/assets/ModalRoot-DvRKHbQE.js +1 -0
- package/dashboard/static/assets/ProjectsDrillPanel-DAv39uSo.js +1 -0
- package/dashboard/static/assets/SourceDetailModal-DSAWo2Fp.js +1 -0
- package/dashboard/static/assets/{UpdateModal-D3m8GG6V.js → UpdateModal-R_9dOVnh.js} +3 -3
- package/dashboard/static/assets/dashboardStream.shared-worker-DbG6Ef0T.js +1 -0
- package/dashboard/static/assets/index-COhxe3vU.css +1 -0
- package/dashboard/static/assets/index-DdU62sov.js +13 -0
- package/dashboard/static/assets/outlineNavigation-BOCLcRa6.js +9 -0
- package/dashboard/static/assets/useKeymap-CW7Up0Ay.js +1 -0
- package/dashboard/static/dashboard.html +3 -3
- package/package.json +4 -1
- package/dashboard/static/assets/ConversationsView-BOSaBRtu.js +0 -72
- package/dashboard/static/assets/ModalRoot-SR070V6c.js +0 -1
- package/dashboard/static/assets/ProjectsDrillPanel-QLI9i5mZ.js +0 -1
- package/dashboard/static/assets/SourceDetailModal-pv0wlFxl.js +0 -1
- package/dashboard/static/assets/dashboardStream.shared-worker-1XTMV3nr.js +0 -1
- package/dashboard/static/assets/index-BHu4mxd8.css +0 -1
- package/dashboard/static/assets/index-CIWsbux3.js +0 -13
- package/dashboard/static/assets/outlineNavigation-CJvKqmLV.js +0 -9
- package/dashboard/static/assets/useKeymap-ffqsS5G0.js +0 -1
package/bin/_lib_rederive.py
CHANGED
|
@@ -13,6 +13,7 @@ No filesystem, SQLite, provider, alert, or journal operation belongs here.
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
15
|
import dataclasses
|
|
16
|
+
import datetime as dt
|
|
16
17
|
import hashlib
|
|
17
18
|
import json
|
|
18
19
|
from collections.abc import Iterable, Mapping
|
|
@@ -45,6 +46,19 @@ class RederiveConflict(RederiveError):
|
|
|
45
46
|
"""The desired event set is internally inconsistent."""
|
|
46
47
|
|
|
47
48
|
|
|
49
|
+
class RederivePlanGuardConflict(RederiveConflict):
|
|
50
|
+
"""A fully assembled plan is unsafe to preview or apply."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, plan: "RederivePlan", plan_guard: dict):
|
|
53
|
+
super().__init__(
|
|
54
|
+
"week-reset add burst: "
|
|
55
|
+
f"{len(plan_guard['violations'])} group(s) exceed "
|
|
56
|
+
f"the limit of {plan_guard['limit']}"
|
|
57
|
+
)
|
|
58
|
+
self.plan = plan
|
|
59
|
+
self.plan_guard = plan_guard
|
|
60
|
+
|
|
61
|
+
|
|
48
62
|
@dataclasses.dataclass(frozen=True)
|
|
49
63
|
class KindClassification:
|
|
50
64
|
mode: str
|
|
@@ -98,6 +112,8 @@ _EVT_CLASSIFICATIONS = {
|
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
_OP_CLASSIFICATIONS = {
|
|
115
|
+
"claude_weekly_observation_decision": KindClassification(
|
|
116
|
+
"retained_input", "reviewed weekly-axis decisions are replayed into scratch"),
|
|
101
117
|
"weekly_credit_floor": KindClassification(
|
|
102
118
|
"retained_input", "operator credit truth is replayed into scratch"),
|
|
103
119
|
"account_observe": KindClassification(
|
|
@@ -303,6 +319,61 @@ def _preserve_non_derivable_state(
|
|
|
303
319
|
"""
|
|
304
320
|
current_payload = current_record.get("payload") or {}
|
|
305
321
|
desired_payload = desired_record.get("payload") or {}
|
|
322
|
+
if (
|
|
323
|
+
current_payload.get("kind") == "weekly_cost_snapshot"
|
|
324
|
+
and desired_payload.get("kind") == "weekly_cost_snapshot"
|
|
325
|
+
and current_record.get("at") == desired_record.get("at")
|
|
326
|
+
):
|
|
327
|
+
# A live ingest writes range bounds in the configured display zone,
|
|
328
|
+
# while scratch replay can recover the same instants with the source
|
|
329
|
+
# session's offset. Offset spelling is not a cost correction. Keep the
|
|
330
|
+
# selected payload byte-for-byte when both bounds name the same
|
|
331
|
+
# instants and every other priced fact agrees.
|
|
332
|
+
def cost_structure(payload):
|
|
333
|
+
facts = dict(payload)
|
|
334
|
+
for field in ("range_start_iso", "range_end_iso"):
|
|
335
|
+
instant = _iso_instant(facts.get(field))
|
|
336
|
+
if instant is not None:
|
|
337
|
+
facts[field] = instant.isoformat()
|
|
338
|
+
return facts
|
|
339
|
+
|
|
340
|
+
if cost_structure(current_payload) == cost_structure(desired_payload):
|
|
341
|
+
return current_record
|
|
342
|
+
if (
|
|
343
|
+
current_payload.get("kind") == "five_hour_block_close"
|
|
344
|
+
and desired_payload.get("kind") == "five_hour_block_close"
|
|
345
|
+
and current_record.get("at") == desired_record.get("at")
|
|
346
|
+
and current_payload.get("is_closed") == 1
|
|
347
|
+
and desired_payload.get("is_closed") == 1
|
|
348
|
+
):
|
|
349
|
+
# The close event is the monetary freeze boundary. A newer ownership
|
|
350
|
+
# rule or a later cache prefix may reprice its scratch projection, but
|
|
351
|
+
# cannot revise the already-journaled parent/child totals. Compare the
|
|
352
|
+
# closure's non-monetary evidence first so a changed boundary or final
|
|
353
|
+
# reading still reaches the normal correction path.
|
|
354
|
+
def structure(payload):
|
|
355
|
+
facts = {
|
|
356
|
+
key: value for key, value in payload.items()
|
|
357
|
+
if not key.startswith("total_")
|
|
358
|
+
and key not in {"_models", "_projects"}
|
|
359
|
+
}
|
|
360
|
+
# Live close used the display-zone offset; scratch replay writes
|
|
361
|
+
# UTC. Both name one frozen boundary and must compare as an instant.
|
|
362
|
+
start = facts.get("block_start_at")
|
|
363
|
+
if isinstance(start, str):
|
|
364
|
+
try:
|
|
365
|
+
parsed = dt.datetime.fromisoformat(
|
|
366
|
+
start.replace("Z", "+00:00"))
|
|
367
|
+
except ValueError:
|
|
368
|
+
pass
|
|
369
|
+
else:
|
|
370
|
+
if parsed.tzinfo is not None:
|
|
371
|
+
facts["block_start_at"] = parsed.astimezone(
|
|
372
|
+
dt.timezone.utc).isoformat()
|
|
373
|
+
return facts
|
|
374
|
+
|
|
375
|
+
if structure(current_payload) == structure(desired_payload):
|
|
376
|
+
return current_record
|
|
306
377
|
if (
|
|
307
378
|
current_payload.get("kind") != "percent_milestone"
|
|
308
379
|
or desired_payload.get("kind") != "percent_milestone"
|
|
@@ -316,6 +387,615 @@ def _preserve_non_derivable_state(
|
|
|
316
387
|
return merged
|
|
317
388
|
|
|
318
389
|
|
|
390
|
+
_SNAPSHOT_IDENTITY_FIELDS = (
|
|
391
|
+
"account_key", "week_start_date", "week_start_at", "week_end_at",
|
|
392
|
+
"weekly_percent", "five_hour_percent", "five_hour_window_key",
|
|
393
|
+
"weekly_observation_held", "source",
|
|
394
|
+
)
|
|
395
|
+
_SNAPSHOT_DEPENDENT_KINDS = frozenset({
|
|
396
|
+
"percent_milestone", "five_hour_milestone",
|
|
397
|
+
})
|
|
398
|
+
_EMPTY_REFERENCE_SENTINELS = frozenset({None, 0, "0", ""})
|
|
399
|
+
_MILESTONE_BASE_DEPENDENCIES = {
|
|
400
|
+
"usage_snapshot_ref": "snapshot_accept",
|
|
401
|
+
"cost_snapshot_ref": "weekly_cost_snapshot",
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _milestone_dependencies(kind: str) -> dict[str, str]:
|
|
406
|
+
dependencies = dict(_MILESTONE_BASE_DEPENDENCIES)
|
|
407
|
+
dependencies["reset_event_ref"] = (
|
|
408
|
+
"week_reset" if kind == "percent_milestone" else "five_hour_credit"
|
|
409
|
+
)
|
|
410
|
+
return dependencies
|
|
411
|
+
|
|
412
|
+
_BLOCK_WEEKLY_AXIS_FIELDS = frozenset({
|
|
413
|
+
"seven_day_pct_at_block_start",
|
|
414
|
+
"seven_day_pct_at_block_end",
|
|
415
|
+
"crossed_seven_day_reset",
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _observation_identity(record: Mapping):
|
|
420
|
+
"""Comparable reading, only when every discriminator is retained."""
|
|
421
|
+
payload = record.get("payload") or {}
|
|
422
|
+
if payload.get("kind") != "snapshot_accept":
|
|
423
|
+
return None
|
|
424
|
+
if any(field not in payload for field in _SNAPSHOT_IDENTITY_FIELDS):
|
|
425
|
+
return None
|
|
426
|
+
reading = {
|
|
427
|
+
key: value for key, value in payload.items()
|
|
428
|
+
if key not in {"captured_at_utc", "payload_json"}
|
|
429
|
+
}
|
|
430
|
+
if "payload_json" in payload:
|
|
431
|
+
try:
|
|
432
|
+
raw = json.loads(payload["payload_json"])
|
|
433
|
+
except (TypeError, ValueError):
|
|
434
|
+
raw = payload["payload_json"]
|
|
435
|
+
if isinstance(raw, dict):
|
|
436
|
+
raw = dict(raw)
|
|
437
|
+
raw.pop("capturedAt", None)
|
|
438
|
+
raw.pop("captured_at", None)
|
|
439
|
+
reading["payload_json"] = raw
|
|
440
|
+
return _canonical_bytes(reading)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _iso_instant(value):
|
|
444
|
+
if not isinstance(value, str):
|
|
445
|
+
return None
|
|
446
|
+
try:
|
|
447
|
+
parsed = dt.datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
448
|
+
except ValueError:
|
|
449
|
+
return None
|
|
450
|
+
return parsed.astimezone(dt.timezone.utc) if parsed.tzinfo else None
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _raw_supports_snapshot(raw: Mapping, event: Mapping, *, held: bool) -> bool:
|
|
454
|
+
"""Bind a journaled acceptance to its exact retained origin observation."""
|
|
455
|
+
raw_payload = raw.get("payload") or {}
|
|
456
|
+
event_payload = event.get("payload") or {}
|
|
457
|
+
if (
|
|
458
|
+
raw.get("t") != "obs" or raw.get("provider") != "claude"
|
|
459
|
+
or raw.get("src") != "record-usage"
|
|
460
|
+
or event.get("id") != f"sa:{raw.get('id')}"
|
|
461
|
+
or event_payload.get("kind") != "snapshot_accept"
|
|
462
|
+
or raw.get("account") != event_payload.get("account_key")
|
|
463
|
+
or raw.get("at") != event.get("at")
|
|
464
|
+
or raw_payload.get("captured_at") != event_payload.get("captured_at_utc")
|
|
465
|
+
or raw_payload.get("source") != event_payload.get("source")
|
|
466
|
+
or raw_payload.get("five_hour_percent")
|
|
467
|
+
!= event_payload.get("five_hour_percent")
|
|
468
|
+
or raw_payload.get("five_hour_resets_at")
|
|
469
|
+
!= event_payload.get("five_hour_resets_at")
|
|
470
|
+
):
|
|
471
|
+
return False
|
|
472
|
+
try:
|
|
473
|
+
encoded = json.loads(event_payload["payload_json"])
|
|
474
|
+
except (KeyError, TypeError, ValueError):
|
|
475
|
+
return False
|
|
476
|
+
if not isinstance(encoded, dict) or (
|
|
477
|
+
encoded.get("source") != raw_payload.get("source")
|
|
478
|
+
or encoded.get("capturedAt") != raw_payload.get("captured_at")
|
|
479
|
+
or encoded.get("fiveHourPercent")
|
|
480
|
+
!= raw_payload.get("five_hour_percent")
|
|
481
|
+
):
|
|
482
|
+
return False
|
|
483
|
+
if held:
|
|
484
|
+
return (
|
|
485
|
+
event_payload.get("weekly_observation_held") == 1
|
|
486
|
+
and encoded.get("weeklyObservationHeld") is True
|
|
487
|
+
and encoded.get("rawWeeklyPercent")
|
|
488
|
+
== raw_payload.get("weekly_percent")
|
|
489
|
+
and encoded.get("weeklyPercent")
|
|
490
|
+
== event_payload.get("weekly_percent")
|
|
491
|
+
)
|
|
492
|
+
return (
|
|
493
|
+
event_payload.get("weekly_observation_held") in (None, 0)
|
|
494
|
+
and encoded.get("weeklyObservationHeld") is not True
|
|
495
|
+
and encoded.get("weeklyPercent") == raw_payload.get("weekly_percent")
|
|
496
|
+
== event_payload.get("weekly_percent")
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def _same_acceptance_transition(
|
|
501
|
+
old: Mapping, new: Mapping, old_raw: Mapping, new_raw: Mapping,
|
|
502
|
+
between: Iterable[Mapping], *,
|
|
503
|
+
allow_intervening_same_account_observations: bool = False,
|
|
504
|
+
) -> bool:
|
|
505
|
+
"""Recognize a replay-only candidate ahead of one retained acceptance.
|
|
506
|
+
|
|
507
|
+
The old non-held event remains the durable decision. The held candidate
|
|
508
|
+
carries a lower raw weekly reading into that same effective percentage;
|
|
509
|
+
it is not an equivalent raw observation. This check uses the exact two
|
|
510
|
+
retained origins and their order, with no elapsed-time or source-blind
|
|
511
|
+
pairing. If any causal discriminator differs, the normal diff remains.
|
|
512
|
+
"""
|
|
513
|
+
old_payload = old.get("payload") or {}
|
|
514
|
+
new_payload = new.get("payload") or {}
|
|
515
|
+
old_raw_payload = old_raw.get("payload") or {}
|
|
516
|
+
new_raw_payload = new_raw.get("payload") or {}
|
|
517
|
+
if not (
|
|
518
|
+
_raw_supports_snapshot(old_raw, old, held=False)
|
|
519
|
+
and old_raw.get("account") == new_raw.get("account")
|
|
520
|
+
and _iso_instant(new.get("at")) is not None
|
|
521
|
+
and _iso_instant(old.get("at")) is not None
|
|
522
|
+
and _iso_instant(new["at"]) < _iso_instant(old["at"])
|
|
523
|
+
):
|
|
524
|
+
return False
|
|
525
|
+
axes = (
|
|
526
|
+
"account_key", "week_start_date", "week_end_date",
|
|
527
|
+
"week_start_at", "week_end_at", "weekly_percent",
|
|
528
|
+
"five_hour_percent", "five_hour_window_key", "page_url",
|
|
529
|
+
)
|
|
530
|
+
if any(old_payload.get(axis) != new_payload.get(axis) for axis in axes):
|
|
531
|
+
return False
|
|
532
|
+
old_reset = _iso_instant(old_payload.get("five_hour_resets_at"))
|
|
533
|
+
new_reset = _iso_instant(new_payload.get("five_hour_resets_at"))
|
|
534
|
+
if (old_reset is None) != (new_reset is None):
|
|
535
|
+
return False
|
|
536
|
+
if old_reset is not None and abs(
|
|
537
|
+
(old_reset - new_reset).total_seconds()
|
|
538
|
+
) > 1:
|
|
539
|
+
return False
|
|
540
|
+
try:
|
|
541
|
+
weekly_reset_gap = abs(
|
|
542
|
+
float(old_raw_payload["resets_at"])
|
|
543
|
+
- float(new_raw_payload["resets_at"])
|
|
544
|
+
)
|
|
545
|
+
except (KeyError, TypeError, ValueError):
|
|
546
|
+
return False
|
|
547
|
+
if weekly_reset_gap > 1:
|
|
548
|
+
return False
|
|
549
|
+
|
|
550
|
+
same_raw_reading = (
|
|
551
|
+
_raw_supports_snapshot(new_raw, new, held=False)
|
|
552
|
+
and old_raw_payload.get("weekly_percent")
|
|
553
|
+
== new_raw_payload.get("weekly_percent")
|
|
554
|
+
and old_raw_payload.get("source") == new_raw_payload.get("source")
|
|
555
|
+
and _observation_identity(old) == _observation_identity(new)
|
|
556
|
+
)
|
|
557
|
+
held_predecessor = (
|
|
558
|
+
_raw_supports_snapshot(new_raw, new, held=True)
|
|
559
|
+
and new_raw_payload.get("source") == "statusline"
|
|
560
|
+
and isinstance(old_raw_payload.get("weekly_percent"), (int, float))
|
|
561
|
+
and isinstance(new_raw_payload.get("weekly_percent"), (int, float))
|
|
562
|
+
and abs(
|
|
563
|
+
float(old_raw_payload["weekly_percent"])
|
|
564
|
+
- float(new_raw_payload["weekly_percent"]) - 1.0
|
|
565
|
+
) < 1e-9
|
|
566
|
+
and new_payload.get("weekly_percent")
|
|
567
|
+
== old_raw_payload["weekly_percent"]
|
|
568
|
+
)
|
|
569
|
+
if not (same_raw_reading or held_predecessor):
|
|
570
|
+
return False
|
|
571
|
+
|
|
572
|
+
# The two origins must be consecutive within this account's complete
|
|
573
|
+
# replay input. Any retained operator record can change a floor, account,
|
|
574
|
+
# or cost dependency, and any intervening Claude observation for this
|
|
575
|
+
# account can change reset/credit/clamp state even when its displayed axes
|
|
576
|
+
# differ. Other providers and other Claude accounts cannot affect this
|
|
577
|
+
# account's fold.
|
|
578
|
+
for record in between:
|
|
579
|
+
if record.get("t") == "op":
|
|
580
|
+
return False
|
|
581
|
+
if (
|
|
582
|
+
record.get("t") == "obs"
|
|
583
|
+
and record.get("provider") == "claude"
|
|
584
|
+
and record.get("account") == old_raw.get("account")
|
|
585
|
+
and not allow_intervening_same_account_observations
|
|
586
|
+
):
|
|
587
|
+
return False
|
|
588
|
+
return True
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def _contains_value(value, wanted: str) -> bool:
|
|
592
|
+
if value == wanted:
|
|
593
|
+
return True
|
|
594
|
+
if isinstance(value, Mapping):
|
|
595
|
+
return any(_contains_value(item, wanted) for item in value.values())
|
|
596
|
+
if isinstance(value, (list, tuple)):
|
|
597
|
+
return any(_contains_value(item, wanted) for item in value)
|
|
598
|
+
return False
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _contains_any_value(value, wanted: set[str]) -> bool:
|
|
602
|
+
if isinstance(value, str):
|
|
603
|
+
return value in wanted
|
|
604
|
+
if isinstance(value, Mapping):
|
|
605
|
+
return any(_contains_any_value(item, wanted) for item in value.values())
|
|
606
|
+
if isinstance(value, (list, tuple)):
|
|
607
|
+
return any(_contains_any_value(item, wanted) for item in value)
|
|
608
|
+
return False
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def _reconcile_snapshot_identities(
|
|
612
|
+
current: Mapping[str, Mapping], desired: dict[str, Mapping], selection,
|
|
613
|
+
raw_observations: Iterable[Mapping], snapshot_identity_decisions=(),
|
|
614
|
+
) -> None:
|
|
615
|
+
"""Keep an existing accepted decision across a replay policy change.
|
|
616
|
+
|
|
617
|
+
A held candidate may copy an already-observed weekly percent from scratch
|
|
618
|
+
state and be mistaken for the later genuine crossing that the journal
|
|
619
|
+
actually accepted. The two raw weekly readings differ. Keep the selected
|
|
620
|
+
decision only when exact retained origins prove their causal order, axes,
|
|
621
|
+
and dependency graph; otherwise leave the ordinary correction diff intact.
|
|
622
|
+
"""
|
|
623
|
+
if not isinstance(raw_observations, (list, tuple)):
|
|
624
|
+
raw_observations = tuple(raw_observations)
|
|
625
|
+
reviewed_pairs = tuple(snapshot_identity_decisions or ())
|
|
626
|
+
if not raw_observations and not reviewed_pairs:
|
|
627
|
+
return
|
|
628
|
+
reviewed_set = set()
|
|
629
|
+
identity_ids = set()
|
|
630
|
+
for pair in reviewed_pairs:
|
|
631
|
+
if (not isinstance(pair, (list, tuple)) or len(pair) != 2
|
|
632
|
+
or not all(isinstance(item, str) and item for item in pair)
|
|
633
|
+
or pair[0] == pair[1]
|
|
634
|
+
or pair[0] in identity_ids or pair[1] in identity_ids):
|
|
635
|
+
raise RederiveConflict(
|
|
636
|
+
"malformed or ambiguous snapshot identity decision"
|
|
637
|
+
)
|
|
638
|
+
identity_ids.update(pair)
|
|
639
|
+
reviewed_set.add(tuple(pair))
|
|
640
|
+
for raw_id in identity_ids:
|
|
641
|
+
if sum(record.get("id") == raw_id for record in raw_observations) != 1:
|
|
642
|
+
raise RederiveConflict(
|
|
643
|
+
"snapshot identity decision raw ids must occur exactly once: "
|
|
644
|
+
+ raw_id
|
|
645
|
+
)
|
|
646
|
+
needed_raw_ids = {
|
|
647
|
+
event_id[3:] for event_id, record in current.items()
|
|
648
|
+
if event_id.startswith("sa:") and event_id not in desired
|
|
649
|
+
and (record.get("payload") or {}).get("kind") == "snapshot_accept"
|
|
650
|
+
} | {
|
|
651
|
+
event_id[3:] for event_id, record in desired.items()
|
|
652
|
+
if event_id.startswith("sa:") and event_id not in current
|
|
653
|
+
and event_id not in selection.by_id
|
|
654
|
+
and (record.get("payload") or {}).get("kind") == "snapshot_accept"
|
|
655
|
+
}
|
|
656
|
+
if not needed_raw_ids:
|
|
657
|
+
return
|
|
658
|
+
raw_positions = {}
|
|
659
|
+
repeated_raw_ids = set()
|
|
660
|
+
for index, record in enumerate(raw_observations):
|
|
661
|
+
raw_id = record.get("id")
|
|
662
|
+
if (
|
|
663
|
+
record.get("t") != "obs"
|
|
664
|
+
or record.get("provider") != "claude"
|
|
665
|
+
or raw_id not in needed_raw_ids
|
|
666
|
+
):
|
|
667
|
+
continue
|
|
668
|
+
if raw_id in raw_positions:
|
|
669
|
+
repeated_raw_ids.add(raw_id)
|
|
670
|
+
else:
|
|
671
|
+
raw_positions[raw_id] = (index, record)
|
|
672
|
+
# A repeated physical raw id has no unique order position. Leave its
|
|
673
|
+
# accepted decision to the normal diff instead of choosing an occurrence.
|
|
674
|
+
for raw_id in repeated_raw_ids:
|
|
675
|
+
del raw_positions[raw_id]
|
|
676
|
+
unmatched_current = {
|
|
677
|
+
event_id: record for event_id, record in current.items()
|
|
678
|
+
if event_id not in desired
|
|
679
|
+
and event_id.startswith("sa:")
|
|
680
|
+
and event_id[3:] in raw_positions
|
|
681
|
+
and (record.get("payload") or {}).get("kind") == "snapshot_accept"
|
|
682
|
+
}
|
|
683
|
+
unmatched_desired = {
|
|
684
|
+
event_id: record for event_id, record in desired.items()
|
|
685
|
+
if event_id not in current
|
|
686
|
+
and event_id not in selection.by_id
|
|
687
|
+
and event_id.startswith("sa:")
|
|
688
|
+
and event_id[3:] in raw_positions
|
|
689
|
+
and (record.get("payload") or {}).get("kind") == "snapshot_accept"
|
|
690
|
+
}
|
|
691
|
+
candidates: dict[str, list[str]] = {}
|
|
692
|
+
for old_id, old in unmatched_current.items():
|
|
693
|
+
old_index, old_raw = raw_positions[old_id[3:]]
|
|
694
|
+
candidates[old_id] = []
|
|
695
|
+
for new_id, new in unmatched_desired.items():
|
|
696
|
+
new_index, new_raw = raw_positions[new_id[3:]]
|
|
697
|
+
if (
|
|
698
|
+
new_index < old_index
|
|
699
|
+
and _same_acceptance_transition(
|
|
700
|
+
old, new, old_raw, new_raw,
|
|
701
|
+
raw_observations[new_index + 1:old_index],
|
|
702
|
+
allow_intervening_same_account_observations=(
|
|
703
|
+
(old_id[3:], new_id[3:]) in reviewed_set
|
|
704
|
+
),
|
|
705
|
+
)
|
|
706
|
+
):
|
|
707
|
+
candidates[old_id].append(new_id)
|
|
708
|
+
|
|
709
|
+
consumed_reviewed = set()
|
|
710
|
+
for old_id, matches in sorted(candidates.items()):
|
|
711
|
+
if len(matches) != 1:
|
|
712
|
+
continue
|
|
713
|
+
new_id = matches[0]
|
|
714
|
+
if sum(new_id in group for group in candidates.values()) != 1:
|
|
715
|
+
continue
|
|
716
|
+
old = unmatched_current[old_id]
|
|
717
|
+
new = unmatched_desired[new_id]
|
|
718
|
+
# Implicit sync-week events are keyed by the originating raw
|
|
719
|
+
# observation too. A percent milestone can reference both the usage
|
|
720
|
+
# snapshot and the cost snapshot from that observation. Preserve the
|
|
721
|
+
# cost identity only when its complete priced content agrees; the two
|
|
722
|
+
# capture bounds may differ with the accepted origins.
|
|
723
|
+
old_cost_prefix = f"wcs:{old_id[3:]}:"
|
|
724
|
+
new_cost_prefix = f"wcs:{new_id[3:]}:"
|
|
725
|
+
cost_pairs = {}
|
|
726
|
+
for old_cost_id, old_cost in current.items():
|
|
727
|
+
if not old_cost_id.startswith(old_cost_prefix):
|
|
728
|
+
continue
|
|
729
|
+
new_cost_id = new_cost_prefix + old_cost_id[len(old_cost_prefix):]
|
|
730
|
+
new_cost = desired.get(new_cost_id)
|
|
731
|
+
if new_cost is None or new_cost_id in current:
|
|
732
|
+
continue
|
|
733
|
+
old_cost_payload = old_cost.get("payload") or {}
|
|
734
|
+
new_cost_payload = new_cost.get("payload") or {}
|
|
735
|
+
if (
|
|
736
|
+
old_cost_payload.get("kind") != "weekly_cost_snapshot"
|
|
737
|
+
or new_cost_payload.get("kind") != "weekly_cost_snapshot"
|
|
738
|
+
):
|
|
739
|
+
continue
|
|
740
|
+
def priced_content(payload):
|
|
741
|
+
return {
|
|
742
|
+
key: value for key, value in payload.items()
|
|
743
|
+
if key not in {"captured_at_utc", "range_end_iso"}
|
|
744
|
+
}
|
|
745
|
+
if priced_content(old_cost_payload) == priced_content(new_cost_payload):
|
|
746
|
+
cost_pairs[new_cost_id] = (old_cost_id, old_cost)
|
|
747
|
+
dependent_updates = {}
|
|
748
|
+
safe = True
|
|
749
|
+
for event_id in set(current) | set(desired):
|
|
750
|
+
now = current.get(event_id)
|
|
751
|
+
proposed = desired.get(event_id)
|
|
752
|
+
now_payload = (now or {}).get("payload") or {}
|
|
753
|
+
proposed_payload = (proposed or {}).get("payload") or {}
|
|
754
|
+
refers_old = _contains_value(now_payload, old_id) or any(
|
|
755
|
+
_contains_value(now_payload, old_cost_id)
|
|
756
|
+
for old_cost_id, _ in cost_pairs.values()
|
|
757
|
+
)
|
|
758
|
+
refers_new = _contains_value(proposed_payload, new_id) or any(
|
|
759
|
+
_contains_value(proposed_payload, new_cost_id)
|
|
760
|
+
for new_cost_id in cost_pairs
|
|
761
|
+
)
|
|
762
|
+
if not (refers_old or refers_new):
|
|
763
|
+
continue
|
|
764
|
+
if (
|
|
765
|
+
now is None or proposed is None
|
|
766
|
+
or now_payload.get("kind") not in _SNAPSHOT_DEPENDENT_KINDS
|
|
767
|
+
or proposed_payload.get("kind") != now_payload.get("kind")
|
|
768
|
+
or now_payload.get("usage_snapshot_ref") != old_id
|
|
769
|
+
or proposed_payload.get("usage_snapshot_ref") != new_id
|
|
770
|
+
or any(
|
|
771
|
+
now_payload.get(field) != proposed_payload.get(field)
|
|
772
|
+
for field in (
|
|
773
|
+
"account_key", "week_start_date",
|
|
774
|
+
"five_hour_window_key", "reset_event_ref",
|
|
775
|
+
"percent_threshold",
|
|
776
|
+
)
|
|
777
|
+
)
|
|
778
|
+
or _contains_value(
|
|
779
|
+
{k: v for k, v in proposed_payload.items()
|
|
780
|
+
if k != "usage_snapshot_ref"}, new_id,
|
|
781
|
+
)
|
|
782
|
+
):
|
|
783
|
+
safe = False
|
|
784
|
+
break
|
|
785
|
+
new_cost_ref = proposed_payload.get("cost_snapshot_ref")
|
|
786
|
+
if new_cost_ref in cost_pairs:
|
|
787
|
+
old_cost_ref = cost_pairs[new_cost_ref][0]
|
|
788
|
+
if now_payload.get("cost_snapshot_ref") != old_cost_ref:
|
|
789
|
+
safe = False
|
|
790
|
+
break
|
|
791
|
+
elif new_cost_ref != now_payload.get("cost_snapshot_ref"):
|
|
792
|
+
safe = False
|
|
793
|
+
break
|
|
794
|
+
# A crossing is a retained fact of the selected acceptance,
|
|
795
|
+
# including observed weekly axis, capture clock and money. The
|
|
796
|
+
# scratch candidate's ref/clock/cost belong to the rejected held
|
|
797
|
+
# origin. Keep the whole selected dependent event.
|
|
798
|
+
dependent_updates[event_id] = now
|
|
799
|
+
if not safe:
|
|
800
|
+
continue
|
|
801
|
+
del desired[new_id]
|
|
802
|
+
desired[old_id] = old
|
|
803
|
+
for new_cost_id, (old_cost_id, old_cost) in cost_pairs.items():
|
|
804
|
+
del desired[new_cost_id]
|
|
805
|
+
desired[old_cost_id] = old_cost
|
|
806
|
+
desired.update(dependent_updates)
|
|
807
|
+
pair = (old_id[3:], new_id[3:])
|
|
808
|
+
if pair in reviewed_set:
|
|
809
|
+
consumed_reviewed.add(pair)
|
|
810
|
+
|
|
811
|
+
unused = reviewed_set - consumed_reviewed
|
|
812
|
+
if unused:
|
|
813
|
+
accepted, replay = sorted(unused)[0]
|
|
814
|
+
raise RederiveConflict(
|
|
815
|
+
"snapshot identity decision failed automatic account, window, "
|
|
816
|
+
"axis, binding, dependency, or uniqueness checks: "
|
|
817
|
+
f"{accepted} -> {replay}"
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
def _preserve_reviewed_hold_dependencies(
|
|
822
|
+
current: Mapping[str, Mapping], desired: dict[str, Mapping],
|
|
823
|
+
reviewed_weekly_hold_ids=(),
|
|
824
|
+
) -> None:
|
|
825
|
+
"""Keep frozen five-hour facts reached from exact reviewed holds.
|
|
826
|
+
|
|
827
|
+
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.
|
|
834
|
+
"""
|
|
835
|
+
snapshot_ids = {
|
|
836
|
+
f"sa:{raw_id}" for raw_id in reviewed_weekly_hold_ids
|
|
837
|
+
if isinstance(raw_id, str) and raw_id
|
|
838
|
+
}
|
|
839
|
+
if not snapshot_ids:
|
|
840
|
+
return
|
|
841
|
+
suppressed_snapshot_ids = set()
|
|
842
|
+
for record in (*current.values(), *desired.values()):
|
|
843
|
+
suppression = (record.get("payload") or {}).get("suppression")
|
|
844
|
+
if suppression is None:
|
|
845
|
+
continue
|
|
846
|
+
if not isinstance(suppression, list) or any(
|
|
847
|
+
not isinstance(item, str) for item in suppression
|
|
848
|
+
):
|
|
849
|
+
raise RederiveConflict(
|
|
850
|
+
"reviewed weekly hold suppression has an unfamiliar shape: "
|
|
851
|
+
+ str(record.get("id"))
|
|
852
|
+
)
|
|
853
|
+
suppressed_snapshot_ids.update(snapshot_ids & set(suppression))
|
|
854
|
+
if suppressed_snapshot_ids:
|
|
855
|
+
for event_id, record in (*current.items(), *desired.items()):
|
|
856
|
+
payload = record.get("payload") or {}
|
|
857
|
+
if (payload.get("kind") in _SNAPSHOT_DEPENDENT_KINDS
|
|
858
|
+
and payload.get("usage_snapshot_ref")
|
|
859
|
+
in suppressed_snapshot_ids):
|
|
860
|
+
raise RederiveConflict(
|
|
861
|
+
"reviewed weekly hold suppression would leave a dependent "
|
|
862
|
+
"milestone: " + event_id
|
|
863
|
+
)
|
|
864
|
+
# A retained reset or credit suppression is the historical fact that
|
|
865
|
+
# this snapshot was deleted. Make that destructive effect explicit in
|
|
866
|
+
# the target graph instead of resurrecting the held observation's
|
|
867
|
+
# snapshot while silently retaining the suppression beside it.
|
|
868
|
+
for snapshot_id in suppressed_snapshot_ids:
|
|
869
|
+
desired.pop(snapshot_id, None)
|
|
870
|
+
snapshot_ids -= suppressed_snapshot_ids
|
|
871
|
+
for event_id, record in current.items():
|
|
872
|
+
payload = record.get("payload") or {}
|
|
873
|
+
usage_ref = payload.get("usage_snapshot_ref")
|
|
874
|
+
if usage_ref not in snapshot_ids:
|
|
875
|
+
if _contains_any_value(payload, snapshot_ids):
|
|
876
|
+
raise RederiveConflict(
|
|
877
|
+
"reviewed weekly hold dependency has an unfamiliar shape: "
|
|
878
|
+
+ event_id
|
|
879
|
+
)
|
|
880
|
+
continue
|
|
881
|
+
if payload.get("kind") not in _SNAPSHOT_DEPENDENT_KINDS:
|
|
882
|
+
raise RederiveConflict(
|
|
883
|
+
"reviewed weekly hold dependency has an unfamiliar kind: "
|
|
884
|
+
+ event_id
|
|
885
|
+
)
|
|
886
|
+
snapshot = current.get(usage_ref)
|
|
887
|
+
if ((snapshot or {}).get("payload") or {}).get("kind") != "snapshot_accept":
|
|
888
|
+
raise RederiveConflict(
|
|
889
|
+
"reviewed weekly hold dependency lacks its accepted snapshot: "
|
|
890
|
+
+ event_id
|
|
891
|
+
)
|
|
892
|
+
desired[event_id] = record
|
|
893
|
+
desired[usage_ref] = snapshot
|
|
894
|
+
for field, expected_kind in _milestone_dependencies(
|
|
895
|
+
payload.get("kind")
|
|
896
|
+
).items():
|
|
897
|
+
if field == "usage_snapshot_ref":
|
|
898
|
+
continue
|
|
899
|
+
reference = payload.get(field)
|
|
900
|
+
if reference in _EMPTY_REFERENCE_SENTINELS:
|
|
901
|
+
continue
|
|
902
|
+
dependency = current.get(reference)
|
|
903
|
+
if ((dependency or {}).get("payload") or {}).get("kind") \
|
|
904
|
+
!= expected_kind:
|
|
905
|
+
raise RederiveConflict(
|
|
906
|
+
f"reviewed weekly hold dependency lacks its {field}: "
|
|
907
|
+
f"{event_id}"
|
|
908
|
+
)
|
|
909
|
+
desired[reference] = dependency
|
|
910
|
+
|
|
911
|
+
# The reviewed replay can introduce a milestone for an accepted reset
|
|
912
|
+
# while the exact snapshot it cites is independently held. Close those
|
|
913
|
+
# newly introduced edges against the currently selected graph too; looking
|
|
914
|
+
# only at current milestones leaves the new event pointing at a snapshot
|
|
915
|
+
# the same plan tombstones.
|
|
916
|
+
for event_id, record in tuple(desired.items()):
|
|
917
|
+
payload = record.get("payload") or {}
|
|
918
|
+
usage_ref = payload.get("usage_snapshot_ref")
|
|
919
|
+
if usage_ref not in snapshot_ids:
|
|
920
|
+
if _contains_any_value(payload, snapshot_ids):
|
|
921
|
+
raise RederiveConflict(
|
|
922
|
+
"reviewed weekly hold dependency has an unfamiliar shape: "
|
|
923
|
+
+ event_id
|
|
924
|
+
)
|
|
925
|
+
continue
|
|
926
|
+
if payload.get("kind") not in _SNAPSHOT_DEPENDENT_KINDS:
|
|
927
|
+
raise RederiveConflict(
|
|
928
|
+
"reviewed weekly hold dependency has an unfamiliar kind: "
|
|
929
|
+
+ event_id
|
|
930
|
+
)
|
|
931
|
+
snapshot = current.get(usage_ref)
|
|
932
|
+
if ((snapshot or {}).get("payload") or {}).get("kind") \
|
|
933
|
+
!= "snapshot_accept":
|
|
934
|
+
# A fresh replay can derive a crossing from the same observation
|
|
935
|
+
# whose weekly axis the review holds. With no selected snapshot to
|
|
936
|
+
# preserve, that crossing has no valid historical dependency and
|
|
937
|
+
# must leave the reviewed target with the held snapshot.
|
|
938
|
+
desired.pop(event_id, None)
|
|
939
|
+
continue
|
|
940
|
+
desired[usage_ref] = snapshot
|
|
941
|
+
for field, expected_kind in _milestone_dependencies(
|
|
942
|
+
payload.get("kind")
|
|
943
|
+
).items():
|
|
944
|
+
if field == "usage_snapshot_ref":
|
|
945
|
+
continue
|
|
946
|
+
reference = payload.get(field)
|
|
947
|
+
if reference in _EMPTY_REFERENCE_SENTINELS:
|
|
948
|
+
continue
|
|
949
|
+
dependency = current.get(reference)
|
|
950
|
+
if ((dependency or {}).get("payload") or {}).get("kind") \
|
|
951
|
+
!= expected_kind:
|
|
952
|
+
raise RederiveConflict(
|
|
953
|
+
f"reviewed weekly hold dependency lacks its {field}: "
|
|
954
|
+
f"{event_id}"
|
|
955
|
+
)
|
|
956
|
+
desired[reference] = dependency
|
|
957
|
+
|
|
958
|
+
held_windows = set()
|
|
959
|
+
for snapshot_id in snapshot_ids:
|
|
960
|
+
payload = (current.get(snapshot_id) or {}).get("payload") or {}
|
|
961
|
+
if payload.get("kind") != "snapshot_accept":
|
|
962
|
+
continue
|
|
963
|
+
account_key = payload.get("account_key")
|
|
964
|
+
window_key = payload.get("five_hour_window_key")
|
|
965
|
+
if account_key is not None and window_key is not None:
|
|
966
|
+
held_windows.add((account_key, window_key))
|
|
967
|
+
for event_id, record in current.items():
|
|
968
|
+
payload = record.get("payload") or {}
|
|
969
|
+
if (
|
|
970
|
+
payload.get("kind") != "five_hour_block_close"
|
|
971
|
+
or (payload.get("account_key"), payload.get("five_hour_window_key"))
|
|
972
|
+
not in held_windows
|
|
973
|
+
):
|
|
974
|
+
continue
|
|
975
|
+
proposed = desired.get(event_id)
|
|
976
|
+
if proposed is None:
|
|
977
|
+
desired[event_id] = record
|
|
978
|
+
continue
|
|
979
|
+
proposed_payload = proposed.get("payload") or {}
|
|
980
|
+
if (
|
|
981
|
+
proposed_payload.get("kind") != "five_hour_block_close"
|
|
982
|
+
or proposed_payload.get("account_key") != payload.get("account_key")
|
|
983
|
+
or proposed_payload.get("five_hour_window_key")
|
|
984
|
+
!= payload.get("five_hour_window_key")
|
|
985
|
+
):
|
|
986
|
+
raise RederiveConflict(
|
|
987
|
+
"reviewed weekly hold block has an unfamiliar shape: "
|
|
988
|
+
+ event_id
|
|
989
|
+
)
|
|
990
|
+
merged_payload = dict(payload)
|
|
991
|
+
for field in _BLOCK_WEEKLY_AXIS_FIELDS:
|
|
992
|
+
if field in proposed_payload:
|
|
993
|
+
merged_payload[field] = proposed_payload[field]
|
|
994
|
+
merged = dict(record)
|
|
995
|
+
merged["payload"] = merged_payload
|
|
996
|
+
desired[event_id] = merged
|
|
997
|
+
|
|
998
|
+
|
|
319
999
|
@dataclasses.dataclass(frozen=True)
|
|
320
1000
|
class PlanAction:
|
|
321
1001
|
disposition: str
|
|
@@ -324,6 +1004,7 @@ class PlanAction:
|
|
|
324
1004
|
at: str
|
|
325
1005
|
payload: "dict | None"
|
|
326
1006
|
payload_hash: str
|
|
1007
|
+
kind: str
|
|
327
1008
|
|
|
328
1009
|
def to_dict(self) -> dict:
|
|
329
1010
|
out = {
|
|
@@ -365,6 +1046,9 @@ class RederivePlan:
|
|
|
365
1046
|
actions: tuple[PlanAction, ...]
|
|
366
1047
|
retained_event_count: int
|
|
367
1048
|
preserved_event_count: int = 0
|
|
1049
|
+
action_counts_by_event_kind: Mapping[str, Mapping[str, int]] = dataclasses.field(
|
|
1050
|
+
default_factory=dict
|
|
1051
|
+
)
|
|
368
1052
|
|
|
369
1053
|
def _body(self) -> dict:
|
|
370
1054
|
return {
|
|
@@ -418,7 +1102,11 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
418
1102
|
cache_fingerprint: str,
|
|
419
1103
|
config_fingerprint: str,
|
|
420
1104
|
preserved_events: Iterable[Mapping],
|
|
421
|
-
conflicted_event_ids=frozenset()
|
|
1105
|
+
conflicted_event_ids=frozenset(),
|
|
1106
|
+
raw_observations=(),
|
|
1107
|
+
snapshot_identity_decisions=(),
|
|
1108
|
+
reviewed_weekly_hold_ids=(),
|
|
1109
|
+
enforce_guard=True) -> RederivePlan:
|
|
422
1110
|
"""Diff current effective events against one scratch-derived desired set.
|
|
423
1111
|
|
|
424
1112
|
``preserved_events`` (#426) names the owned events the re-derivation cannot
|
|
@@ -448,6 +1136,13 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
448
1136
|
and selected.record is not None
|
|
449
1137
|
and _is_owned_event(selected.record)
|
|
450
1138
|
}
|
|
1139
|
+
_reconcile_snapshot_identities(
|
|
1140
|
+
current, desired, selection, raw_observations,
|
|
1141
|
+
snapshot_identity_decisions,
|
|
1142
|
+
)
|
|
1143
|
+
_preserve_reviewed_hold_dependencies(
|
|
1144
|
+
current, desired, reviewed_weekly_hold_ids,
|
|
1145
|
+
)
|
|
451
1146
|
retained_event_count = sum(
|
|
452
1147
|
1 for selected in selection.by_id.values()
|
|
453
1148
|
if selected.status == "active"
|
|
@@ -460,6 +1155,10 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
460
1155
|
if isinstance(event_id, str) and event_id and event_id not in desired:
|
|
461
1156
|
preserved[event_id] = record
|
|
462
1157
|
counts = {"retain": 0, "supersede": 0, "tombstone": 0, "add": 0}
|
|
1158
|
+
counts_by_kind = {
|
|
1159
|
+
kind: dict.fromkeys(counts, 0)
|
|
1160
|
+
for kind in sorted(_EVT_CLASSIFICATIONS)
|
|
1161
|
+
}
|
|
463
1162
|
actions: list[PlanAction] = []
|
|
464
1163
|
|
|
465
1164
|
for event_id in sorted(set(current) | set(desired) | set(preserved)):
|
|
@@ -482,6 +1181,9 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
482
1181
|
)
|
|
483
1182
|
if not (revive or reaffirm):
|
|
484
1183
|
counts["retain"] += 1
|
|
1184
|
+
counts_by_kind[(preserved_record.get("payload") or {})["kind"]][
|
|
1185
|
+
"retain"
|
|
1186
|
+
] += 1
|
|
485
1187
|
continue
|
|
486
1188
|
source = (
|
|
487
1189
|
selected.record if reaffirm else preserved_record
|
|
@@ -499,6 +1201,9 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
499
1201
|
and event_id not in conflicted_event_ids
|
|
500
1202
|
):
|
|
501
1203
|
counts["retain"] += 1
|
|
1204
|
+
counts_by_kind[(desired_record.get("payload") or {})["kind"]][
|
|
1205
|
+
"retain"
|
|
1206
|
+
] += 1
|
|
502
1207
|
continue
|
|
503
1208
|
revision = int(selected.rev) + 1
|
|
504
1209
|
disposition = "supersede"
|
|
@@ -519,6 +1224,13 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
519
1224
|
at = str(desired_record["at"])
|
|
520
1225
|
payload = dict(desired_record.get("payload") or {})
|
|
521
1226
|
counts[disposition] += 1
|
|
1227
|
+
kind_record = (
|
|
1228
|
+
current_record if disposition == "tombstone"
|
|
1229
|
+
else desired_record if preserved_record is None
|
|
1230
|
+
else preserved_record
|
|
1231
|
+
)
|
|
1232
|
+
kind = (kind_record.get("payload") or {})["kind"]
|
|
1233
|
+
counts_by_kind[kind][disposition] += 1
|
|
522
1234
|
action_shape = {
|
|
523
1235
|
"disposition": disposition,
|
|
524
1236
|
"eventId": event_id,
|
|
@@ -533,9 +1245,10 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
533
1245
|
at=at,
|
|
534
1246
|
payload=payload,
|
|
535
1247
|
payload_hash=_sha256(action_shape),
|
|
1248
|
+
kind=kind,
|
|
536
1249
|
))
|
|
537
1250
|
|
|
538
|
-
|
|
1251
|
+
plan = RederivePlan(
|
|
539
1252
|
family=FAMILY,
|
|
540
1253
|
journal_high_water=journal_high_water,
|
|
541
1254
|
cache_fingerprint=cache_fingerprint,
|
|
@@ -544,4 +1257,192 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
544
1257
|
actions=tuple(actions),
|
|
545
1258
|
retained_event_count=retained_event_count,
|
|
546
1259
|
preserved_event_count=len(preserved),
|
|
1260
|
+
action_counts_by_event_kind=counts_by_kind,
|
|
1261
|
+
)
|
|
1262
|
+
if enforce_guard:
|
|
1263
|
+
enforce_week_reset_add_burst_guard(plan)
|
|
1264
|
+
return plan
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
def causal_delta_plan(
|
|
1268
|
+
baseline: RederivePlan, reviewed: RederivePlan, *,
|
|
1269
|
+
current_events: Mapping[str, Mapping],
|
|
1270
|
+
authorized_week_reset_origins=frozenset(),
|
|
1271
|
+
) -> RederivePlan:
|
|
1272
|
+
"""Return only C->R actions whose B and R target states differ.
|
|
1273
|
+
|
|
1274
|
+
Both inputs are diffs from the same effective graph C. Therefore an action
|
|
1275
|
+
with the same correction shape in both plans is baseline drift, while a
|
|
1276
|
+
reviewed-only or changed action is caused by the proposed operator record.
|
|
1277
|
+
A baseline-only action means the reviewed target returns to C and requires
|
|
1278
|
+
no correction action.
|
|
1279
|
+
"""
|
|
1280
|
+
if (
|
|
1281
|
+
baseline.journal_high_water != reviewed.journal_high_water
|
|
1282
|
+
or baseline.cache_fingerprint != reviewed.cache_fingerprint
|
|
1283
|
+
):
|
|
1284
|
+
raise RederiveConflict(
|
|
1285
|
+
"baseline and reviewed plans do not share stable inputs"
|
|
1286
|
+
)
|
|
1287
|
+
baseline_by_id = {
|
|
1288
|
+
action.event_id: action.to_correction_action()
|
|
1289
|
+
for action in baseline.actions
|
|
1290
|
+
}
|
|
1291
|
+
reviewed_by_id = {
|
|
1292
|
+
action.event_id: action for action in reviewed.actions
|
|
1293
|
+
}
|
|
1294
|
+
authorized_week_reset_origins = frozenset(
|
|
1295
|
+
authorized_week_reset_origins or ())
|
|
1296
|
+
actions = [
|
|
1297
|
+
action for action in reviewed.actions
|
|
1298
|
+
if (
|
|
1299
|
+
baseline_by_id.get(action.event_id)
|
|
1300
|
+
!= action.to_correction_action()
|
|
1301
|
+
or (
|
|
1302
|
+
action.kind == "week_reset"
|
|
1303
|
+
and action.disposition != "tombstone"
|
|
1304
|
+
and (action.payload or {}).get("origin_observation_id")
|
|
1305
|
+
in authorized_week_reset_origins
|
|
1306
|
+
)
|
|
1307
|
+
)
|
|
1308
|
+
]
|
|
1309
|
+
action_by_id = {action.event_id: action for action in actions}
|
|
1310
|
+
dependency_tombstones = set()
|
|
1311
|
+
for action in actions:
|
|
1312
|
+
if (action.disposition == "tombstone"
|
|
1313
|
+
or action.kind not in _SNAPSHOT_DEPENDENT_KINDS):
|
|
1314
|
+
continue
|
|
1315
|
+
payload = action.payload or {}
|
|
1316
|
+
for field, expected_kind in _milestone_dependencies(
|
|
1317
|
+
action.kind
|
|
1318
|
+
).items():
|
|
1319
|
+
reference = payload.get(field)
|
|
1320
|
+
if reference in _EMPTY_REFERENCE_SENTINELS:
|
|
1321
|
+
continue
|
|
1322
|
+
dependency_action = action_by_id.get(reference)
|
|
1323
|
+
if (dependency_action is not None
|
|
1324
|
+
and dependency_action.disposition == "tombstone"):
|
|
1325
|
+
current = current_events.get(reference)
|
|
1326
|
+
if ((current or {}).get("payload") or {}).get("kind") \
|
|
1327
|
+
!= expected_kind:
|
|
1328
|
+
raise RederiveConflict(
|
|
1329
|
+
"causal milestone dependency cannot be retained: "
|
|
1330
|
+
+ action.event_id
|
|
1331
|
+
)
|
|
1332
|
+
dependency_tombstones.add(reference)
|
|
1333
|
+
if dependency_tombstones:
|
|
1334
|
+
actions = [
|
|
1335
|
+
action for action in actions
|
|
1336
|
+
if action.event_id not in dependency_tombstones
|
|
1337
|
+
]
|
|
1338
|
+
|
|
1339
|
+
target_events = dict(current_events)
|
|
1340
|
+
for action in actions:
|
|
1341
|
+
if action.disposition == "tombstone":
|
|
1342
|
+
target_events.pop(action.event_id, None)
|
|
1343
|
+
else:
|
|
1344
|
+
target_events[action.event_id] = {"payload": action.payload or {}}
|
|
1345
|
+
for action in actions:
|
|
1346
|
+
if (action.disposition == "tombstone"
|
|
1347
|
+
or action.kind not in _SNAPSHOT_DEPENDENT_KINDS):
|
|
1348
|
+
continue
|
|
1349
|
+
payload = action.payload or {}
|
|
1350
|
+
for field, expected_kind in _milestone_dependencies(
|
|
1351
|
+
action.kind
|
|
1352
|
+
).items():
|
|
1353
|
+
reference = payload.get(field)
|
|
1354
|
+
if reference in _EMPTY_REFERENCE_SENTINELS:
|
|
1355
|
+
continue
|
|
1356
|
+
target = target_events.get(reference)
|
|
1357
|
+
if ((target or {}).get("payload") or {}).get("kind") \
|
|
1358
|
+
!= expected_kind:
|
|
1359
|
+
dependency = reviewed_by_id.get(reference)
|
|
1360
|
+
if (
|
|
1361
|
+
dependency is None
|
|
1362
|
+
or dependency.disposition == "tombstone"
|
|
1363
|
+
or dependency.kind != expected_kind
|
|
1364
|
+
or (dependency.payload or {}).get("kind") != expected_kind
|
|
1365
|
+
):
|
|
1366
|
+
raise RederiveConflict(
|
|
1367
|
+
"causal milestone dependency is missing: "
|
|
1368
|
+
+ action.event_id
|
|
1369
|
+
)
|
|
1370
|
+
actions.append(dependency)
|
|
1371
|
+
target_events[reference] = {
|
|
1372
|
+
"payload": dependency.payload or {},
|
|
1373
|
+
}
|
|
1374
|
+
included_ids = {action.event_id for action in actions}
|
|
1375
|
+
actions = tuple(
|
|
1376
|
+
action for action in reviewed.actions
|
|
1377
|
+
if action.event_id in included_ids
|
|
547
1378
|
)
|
|
1379
|
+
removed = [action for action in reviewed.actions if action not in actions]
|
|
1380
|
+
counts = dict(reviewed.counts)
|
|
1381
|
+
counts_by_kind = {
|
|
1382
|
+
kind: dict(values)
|
|
1383
|
+
for kind, values in reviewed.action_counts_by_event_kind.items()
|
|
1384
|
+
}
|
|
1385
|
+
for action in removed:
|
|
1386
|
+
counts[action.disposition] -= 1
|
|
1387
|
+
counts_by_kind[action.kind][action.disposition] -= 1
|
|
1388
|
+
if action.disposition != "add":
|
|
1389
|
+
counts["retain"] += 1
|
|
1390
|
+
counts_by_kind[action.kind]["retain"] += 1
|
|
1391
|
+
return RederivePlan(
|
|
1392
|
+
family=reviewed.family,
|
|
1393
|
+
journal_high_water=reviewed.journal_high_water,
|
|
1394
|
+
cache_fingerprint=reviewed.cache_fingerprint,
|
|
1395
|
+
config_fingerprint=reviewed.config_fingerprint,
|
|
1396
|
+
counts=counts,
|
|
1397
|
+
actions=actions,
|
|
1398
|
+
retained_event_count=reviewed.retained_event_count,
|
|
1399
|
+
preserved_event_count=reviewed.preserved_event_count,
|
|
1400
|
+
action_counts_by_event_kind=counts_by_kind,
|
|
1401
|
+
)
|
|
1402
|
+
|
|
1403
|
+
|
|
1404
|
+
def week_reset_add_burst_guard(plan: RederivePlan):
|
|
1405
|
+
"""Return the unconditional reset-add guard finding for one plan."""
|
|
1406
|
+
# The guard checks the completed deterministic action set, not candidate
|
|
1407
|
+
# observations or retained resets. A second reset of one physical window
|
|
1408
|
+
# remains admissible; three additions for the same exact evidence do not.
|
|
1409
|
+
grouped: dict[tuple, int] = {}
|
|
1410
|
+
for action in plan.actions:
|
|
1411
|
+
payload = action.payload or {}
|
|
1412
|
+
if action.disposition != "add" or payload.get("kind") != "week_reset":
|
|
1413
|
+
continue
|
|
1414
|
+
key = (
|
|
1415
|
+
payload.get("account_key"),
|
|
1416
|
+
payload.get("new_week_end_at"),
|
|
1417
|
+
payload.get("observed_pre_credit_pct"),
|
|
1418
|
+
)
|
|
1419
|
+
if any(value is None for value in key):
|
|
1420
|
+
continue
|
|
1421
|
+
grouped[key] = grouped.get(key, 0) + 1
|
|
1422
|
+
violations = [
|
|
1423
|
+
{
|
|
1424
|
+
"accountKey": account_key,
|
|
1425
|
+
"newWeekEndAt": week_end,
|
|
1426
|
+
"observedPreCreditPct": percent,
|
|
1427
|
+
"addCount": count,
|
|
1428
|
+
}
|
|
1429
|
+
for (account_key, week_end, percent), count in grouped.items()
|
|
1430
|
+
if count > 2
|
|
1431
|
+
]
|
|
1432
|
+
violations.sort(key=lambda item: (
|
|
1433
|
+
str(item["accountKey"]), str(item["newWeekEndAt"]),
|
|
1434
|
+
repr(item["observedPreCreditPct"]),
|
|
1435
|
+
))
|
|
1436
|
+
if violations:
|
|
1437
|
+
return {
|
|
1438
|
+
"code": "week-reset-add-burst",
|
|
1439
|
+
"limit": 2,
|
|
1440
|
+
"violations": violations,
|
|
1441
|
+
}
|
|
1442
|
+
return None
|
|
1443
|
+
|
|
1444
|
+
|
|
1445
|
+
def enforce_week_reset_add_burst_guard(plan: RederivePlan) -> None:
|
|
1446
|
+
finding = week_reset_add_burst_guard(plan)
|
|
1447
|
+
if finding is not None:
|
|
1448
|
+
raise RederivePlanGuardConflict(plan, finding)
|