cctally 1.7.1 → 1.7.3

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.
@@ -372,6 +372,47 @@ def _normalize_percent(value: "float | int | None") -> "float | None":
372
372
  return round(float(value), _PERCENT_NORMALIZE_DECIMALS)
373
373
 
374
374
 
375
+ def _resolve_active_five_hour_reset_event_id(
376
+ conn: "sqlite3.Connection",
377
+ five_hour_window_key: int,
378
+ ) -> int:
379
+ """Return ``id`` of the most-recent ``five_hour_reset_events`` row for
380
+ ``five_hour_window_key``, else 0 (pre-credit / no-event sentinel).
381
+
382
+ Mirrors the weekly active-segment resolution pattern used by
383
+ ``maybe_record_milestone`` for ``percent_milestones.reset_event_id``.
384
+ Called once per ``maybe_update_five_hour_block`` invocation and the
385
+ return value is threaded through every read/write site that keys on
386
+ ``(five_hour_window_key, percent_threshold)`` so post-credit threshold
387
+ crossings land as a distinct row from any pre-credit one at the same
388
+ threshold. See spec
389
+ docs/superpowers/specs/2026-05-16-5h-in-place-credit-detection.md §3.3.
390
+
391
+ Returns ``0`` when:
392
+ - The window has no ``five_hour_reset_events`` row (most blocks).
393
+ - The table doesn't exist yet (DB predates this feature).
394
+
395
+ Returns the largest ``id`` matching the window otherwise; the
396
+ ``ORDER BY id DESC LIMIT 1`` clause is what *defines* "active" in
397
+ the stacked-credit case (spec §2.3 — multiple events across distinct
398
+ 10-min slots): pre-credit milestones key on ``seg=0``, milestones
399
+ between credit 1 and credit 2 key on event-1's id, and milestones
400
+ after credit 2 key on event-2's id.
401
+ """
402
+ try:
403
+ row = conn.execute(
404
+ "SELECT id FROM five_hour_reset_events "
405
+ "WHERE five_hour_window_key = ? "
406
+ "ORDER BY id DESC LIMIT 1",
407
+ (int(five_hour_window_key),),
408
+ ).fetchone()
409
+ except sqlite3.DatabaseError:
410
+ return 0
411
+ if row is None:
412
+ return 0
413
+ return int(row["id"])
414
+
415
+
375
416
  def maybe_record_milestone(
376
417
  saved: dict[str, Any],
377
418
  ) -> None:
@@ -398,7 +439,31 @@ def maybe_record_milestone(
398
439
 
399
440
  conn = open_db()
400
441
  try:
401
- max_existing = get_max_milestone_for_week(conn, week_start_date)
442
+ # Resolve the active segment for THIS captured moment. The segment
443
+ # is the latest week_reset_events row keyed on week_end_at whose
444
+ # effective_reset_at_utc <= captured_at; 0 = pre-credit / no-event
445
+ # sentinel. ``unixepoch()`` normalizes the comparison across mixed
446
+ # +00:00 / Z offsets (see precedent at bin/cctally:_compute_block_totals
447
+ # cross-reset detection; also project gotcha
448
+ # ``unixepoch_for_cross_offset_compare``).
449
+ captured_at_iso = saved.get("capturedAt") or now_utc_iso()
450
+ reset_event_id = 0
451
+ if week_end_at:
452
+ seg_row = conn.execute(
453
+ """
454
+ SELECT id FROM week_reset_events
455
+ WHERE new_week_end_at = ?
456
+ AND unixepoch(effective_reset_at_utc) <= unixepoch(?)
457
+ ORDER BY id DESC LIMIT 1
458
+ """,
459
+ (week_end_at, captured_at_iso),
460
+ ).fetchone()
461
+ if seg_row is not None:
462
+ reset_event_id = int(seg_row["id"])
463
+
464
+ max_existing = get_max_milestone_for_week(
465
+ conn, week_start_date, reset_event_id=reset_event_id,
466
+ )
402
467
  if max_existing is not None and current_floor <= max_existing:
403
468
  return
404
469
 
@@ -482,7 +547,10 @@ def maybe_record_milestone(
482
547
  pending_alerts: list[dict[str, Any]] = []
483
548
  for pct in range(start_threshold, current_floor + 1):
484
549
  if pct == start_threshold and max_existing is not None:
485
- prev_cost = get_milestone_cost_for_week(conn, week_start_date, max_existing)
550
+ prev_cost = get_milestone_cost_for_week(
551
+ conn, week_start_date, max_existing,
552
+ reset_event_id=reset_event_id,
553
+ )
486
554
  marginal = (cumulative_cost - prev_cost) if prev_cost is not None else None
487
555
  else:
488
556
  marginal = None
@@ -499,6 +567,7 @@ def maybe_record_milestone(
499
567
  cost_snapshot_id=cost_snapshot_id,
500
568
  five_hour_percent_at_crossing=five_hour_percent,
501
569
  commit=False,
570
+ reset_event_id=reset_event_id,
502
571
  )
503
572
  # ── Threshold-actions dispatch (set-then-dispatch, spec §3.2) ──
504
573
  # Only the genuine-new-crossing winner (rowcount==1) reaches this
@@ -523,17 +592,22 @@ def maybe_record_milestone(
523
592
  conn.execute(
524
593
  "UPDATE percent_milestones SET alerted_at = ? "
525
594
  "WHERE week_start_date = ? AND percent_threshold = ? "
526
- "AND alerted_at IS NULL",
527
- (crossed_at, week_start_date, pct),
595
+ " AND reset_event_id = ? "
596
+ " AND alerted_at IS NULL",
597
+ (crossed_at, week_start_date, pct, reset_event_id),
528
598
  )
529
599
  # Cheap re-read for payload context (cumulative_cost_usd
530
600
  # reflects the value persisted on insert, immune to any
531
601
  # subsequent recompute drift). SELECT inside the open
532
602
  # transaction is fine; values reflect post-INSERT state.
603
+ # Filter by reset_event_id so a credited week's
604
+ # alert payload reads the post-credit row, not a
605
+ # stale pre-credit row at the same (week, threshold).
533
606
  row = conn.execute(
534
607
  "SELECT cumulative_cost_usd FROM percent_milestones "
535
- "WHERE week_start_date = ? AND percent_threshold = ?",
536
- (week_start_date, pct),
608
+ "WHERE week_start_date = ? AND percent_threshold = ? "
609
+ " AND reset_event_id = ?",
610
+ (week_start_date, pct, reset_event_id),
537
611
  ).fetchone()
538
612
  if row is not None:
539
613
  cum = float(row["cumulative_cost_usd"])
@@ -930,7 +1004,23 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
930
1004
  # Snap-up-by-1e-9 per the gotcha: 0.50 * 100 == 49.99...9 in
931
1005
  # IEEE-754, so bare math.floor would miss the 50 threshold.
932
1006
  current_floor = math.floor(float(five_hour_percent) + 1e-9)
1007
+
1008
+ # Resolve active segment ONCE so every per-site read + write
1009
+ # below sees the same value within this transaction. Spec
1010
+ # §3.3 & §3.4 of
1011
+ # docs/superpowers/specs/2026-05-16-5h-in-place-credit-detection.md:
1012
+ # the active segment is the latest five_hour_reset_events row
1013
+ # for this window_key, else sentinel 0 (pre-credit).
1014
+ active_reset_event_id = _resolve_active_five_hour_reset_event_id(
1015
+ conn, int(five_hour_window_key)
1016
+ )
1017
+
933
1018
  if current_floor >= 1:
1019
+ # Site A — MAX(percent_threshold) scoped to active segment.
1020
+ # Without the reset_event_id filter, MAX returns the
1021
+ # pre-credit max and post-credit milestones from 1..max
1022
+ # are silently never emitted.
1023
+ #
934
1024
  # Use max(percent_threshold) directly (not prior block's
935
1025
  # final_pct) so first-observation already-mid-stream doesn't
936
1026
  # synthesize crossings 1..(current_floor - 1) we never had
@@ -938,8 +1028,8 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
938
1028
  # maybe_record_milestone's max_existing path.
939
1029
  row = conn.execute(
940
1030
  "SELECT MAX(percent_threshold) AS m FROM five_hour_milestones "
941
- "WHERE five_hour_window_key = ?",
942
- (int(five_hour_window_key),),
1031
+ "WHERE five_hour_window_key = ? AND reset_event_id = ?",
1032
+ (int(five_hour_window_key), active_reset_event_id),
943
1033
  ).fetchone()
944
1034
  max_existing = row["m"] if row and row["m"] is not None else None
945
1035
 
@@ -952,14 +1042,21 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
952
1042
  # block_id was resolved above (before the children writes) and
953
1043
  # is still in scope here.
954
1044
 
1045
+ # Site B — prior-cost lookup scoped to active segment.
955
1046
  # Marginal-cost lookup for the start_threshold milestone
956
1047
  # (only when there's a prior milestone in this block).
1048
+ # Without the reset_event_id filter, marginal could be
1049
+ # computed against a pre-credit row whose block_cost is
1050
+ # unrelated to the post-credit segment's totals.
957
1051
  prior_cost: float | None = None
958
1052
  if max_existing is not None:
959
1053
  prev_row = conn.execute(
960
1054
  "SELECT block_cost_usd FROM five_hour_milestones "
961
- "WHERE five_hour_window_key = ? AND percent_threshold = ?",
962
- (int(five_hour_window_key), int(max_existing)),
1055
+ "WHERE five_hour_window_key = ? "
1056
+ " AND percent_threshold = ? "
1057
+ " AND reset_event_id = ?",
1058
+ (int(five_hour_window_key), int(max_existing),
1059
+ active_reset_event_id),
963
1060
  ).fetchone()
964
1061
  if prev_row is not None:
965
1062
  prior_cost = float(prev_row["block_cost_usd"])
@@ -969,6 +1066,12 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
969
1066
  marginal: float | None = totals["cost_usd"] - prior_cost
970
1067
  else:
971
1068
  marginal = None
1069
+ # Site C — INSERT stamps the resolved
1070
+ # ``active_reset_event_id`` (0 = pre-credit, else
1071
+ # the latest five_hour_reset_events.id). UNIQUE
1072
+ # is now (window_key, threshold, reset_event_id)
1073
+ # so post-credit threshold crossings re-fire
1074
+ # fresh — not absorbed into the pre-credit row.
972
1075
  cur = conn.execute(
973
1076
  """
974
1077
  INSERT OR IGNORE INTO five_hour_milestones (
@@ -983,9 +1086,10 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
983
1086
  block_cache_read_tokens,
984
1087
  block_cost_usd,
985
1088
  marginal_cost_usd,
986
- seven_day_pct_at_crossing
1089
+ seven_day_pct_at_crossing,
1090
+ reset_event_id
987
1091
  )
988
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1092
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
989
1093
  """,
990
1094
  (
991
1095
  block_id,
@@ -1000,6 +1104,7 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
1000
1104
  totals["cost_usd"],
1001
1105
  marginal,
1002
1106
  weekly_percent,
1107
+ active_reset_event_id,
1003
1108
  ),
1004
1109
  )
1005
1110
  # ── Threshold-actions dispatch (set-then-dispatch, spec §3.2) ──
@@ -1028,11 +1133,19 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
1028
1133
  and pct in alerts_cfg["five_hour_thresholds"]
1029
1134
  ):
1030
1135
  crossed_at = now_utc_iso()
1136
+ # Site D — alerted_at UPDATE scoped to the
1137
+ # active segment, so the post-credit row
1138
+ # gets stamped without overwriting an
1139
+ # already-alerted pre-credit row at the
1140
+ # same threshold.
1031
1141
  conn.execute(
1032
1142
  "UPDATE five_hour_milestones SET alerted_at = ? "
1033
- "WHERE five_hour_window_key = ? AND percent_threshold = ? "
1034
- "AND alerted_at IS NULL",
1035
- (crossed_at, int(five_hour_window_key), int(pct)),
1143
+ "WHERE five_hour_window_key = ? "
1144
+ " AND percent_threshold = ? "
1145
+ " AND reset_event_id = ? "
1146
+ " AND alerted_at IS NULL",
1147
+ (crossed_at, int(five_hour_window_key),
1148
+ int(pct), active_reset_event_id),
1036
1149
  )
1037
1150
  # Cheap re-reads inside BEGIN are SELECT-only and
1038
1151
  # safe; values reflect post-INSERT state. We
@@ -1040,10 +1153,17 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
1040
1153
  # are in scope) and defer ONLY the Popen-side
1041
1154
  # _dispatch_alert_notification to after the outer
1042
1155
  # commit.
1156
+ # Site E — alert-payload reread scoped to
1157
+ # the active segment so the dispatch shows
1158
+ # post-credit cost, not the pre-credit
1159
+ # row's stale value at the same threshold.
1043
1160
  cost_row = conn.execute(
1044
1161
  "SELECT block_cost_usd FROM five_hour_milestones "
1045
- "WHERE five_hour_window_key = ? AND percent_threshold = ?",
1046
- (int(five_hour_window_key), int(pct)),
1162
+ "WHERE five_hour_window_key = ? "
1163
+ " AND percent_threshold = ? "
1164
+ " AND reset_event_id = ?",
1165
+ (int(five_hour_window_key), int(pct),
1166
+ active_reset_event_id),
1047
1167
  ).fetchone()
1048
1168
  block_row = conn.execute(
1049
1169
  "SELECT block_start_at FROM five_hour_blocks "
@@ -1305,9 +1425,9 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
1305
1425
  prior["week_end_at"], "record.prior"
1306
1426
  )
1307
1427
  prior_pct = prior["weekly_percent"]
1428
+ now_utc = dt.datetime.now(dt.timezone.utc)
1308
1429
  if prior_end_canon and prior_end_canon != cur_end_canon:
1309
1430
  prior_end_dt = parse_iso_datetime(prior_end_canon, "prior.week_end_at")
1310
- now_utc = dt.datetime.now(dt.timezone.utc)
1311
1431
  # Fire only when (a) prior window was still in the FUTURE
1312
1432
  # (Anthropic shifted the boundary before natural expiration),
1313
1433
  # AND (b) weekly_percent dropped by RESET_PCT_DROP_THRESHOLD
@@ -1331,32 +1451,429 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
1331
1451
  effective_iso),
1332
1452
  )
1333
1453
  conn.commit()
1454
+ elif prior_end_canon and prior_end_canon == cur_end_canon:
1455
+ # In-place credit branch (v1.7.2). When `resets_at` stays
1456
+ # unchanged but `weekly_percent` drops by RESET_PCT_DROP_THRESHOLD
1457
+ # or more, Anthropic has issued a goodwill in-place weekly
1458
+ # credit. Emit one week_reset_events row keyed on the
1459
+ # current end_at (old == new) so the reset-aware clamp
1460
+ # above and the milestone segment writer can pivot to
1461
+ # the post-credit segment. The seed snapshot lands via
1462
+ # the now-reset-aware clamp on this same call.
1463
+ prior_end_dt = parse_iso_datetime(prior_end_canon, "prior.week_end_at")
1464
+ if (
1465
+ prior_end_dt > now_utc
1466
+ and prior_pct is not None
1467
+ and (float(prior_pct) - float(weekly_percent)) >= c._RESET_PCT_DROP_THRESHOLD
1468
+ ):
1469
+ # Pre-check (Q5 belt-and-suspenders): suppress duplicate
1470
+ # event rows for the same new_week_end_at across
1471
+ # consecutive ticks. UNIQUE(old, new) at the DDL
1472
+ # also catches the duplicate in the (old == new) case,
1473
+ # but the pre-check avoids a useless write attempt
1474
+ # and keeps the log clean. After the seed lands at
1475
+ # post-credit %, the next tick's `prior_pct` will be
1476
+ # the post-credit value so the drop predicate alone
1477
+ # also suffices — pre-check is belt-and-suspenders.
1478
+ already = conn.execute(
1479
+ "SELECT 1 FROM week_reset_events "
1480
+ "WHERE new_week_end_at = ? LIMIT 1",
1481
+ (cur_end_canon,),
1482
+ ).fetchone()
1483
+ effective_dt = _floor_to_hour(now_utc)
1484
+ effective_iso = effective_dt.isoformat(timespec="seconds")
1485
+ if already is None:
1486
+ # Row shape: old=effective_iso, new=cur_end_canon
1487
+ # (distinct values). The previous shape stored
1488
+ # old==new==cur_end_canon, which let BOTH
1489
+ # _apply_reset_events_to_weekrefs maps
1490
+ # (pre_map[old] and post_map[new]) fire on the
1491
+ # SAME WeekRef — pre_map rewrote week_end_at to
1492
+ # effective, post_map rewrote week_start_at to
1493
+ # effective, collapsing the credited week to a
1494
+ # zero-width window in downstream renders. With
1495
+ # old==effective and new==cur_end_canon, only
1496
+ # post_map fires on the credited week (setting
1497
+ # week_start_at = effective, the intended
1498
+ # behavior); pre_map keys on effective_iso and
1499
+ # finds no matching WeekRef in practice. The
1500
+ # UNIQUE(old, new) constraint permits this
1501
+ # row, and the pre-check above keys on
1502
+ # new_week_end_at so dedup still works.
1503
+ conn.execute(
1504
+ "INSERT OR IGNORE INTO week_reset_events "
1505
+ "(detected_at_utc, old_week_end_at, new_week_end_at, "
1506
+ " effective_reset_at_utc) VALUES (?, ?, ?, ?)",
1507
+ (now_utc_iso(), effective_iso, cur_end_canon,
1508
+ effective_iso),
1509
+ )
1510
+ conn.commit()
1511
+ # Pivots fire UNCONDITIONALLY whenever a credit
1512
+ # is detected — they're NOT gated on
1513
+ # ``already is None``. Memory
1514
+ # ``project_dedup_must_not_gate_side_effects.md``:
1515
+ # "Skipping a no-op INSERT must NOT skip
1516
+ # milestones/rollups/alerts; prior run may have
1517
+ # died mid-flight." Crash scenario: tick N
1518
+ # committed the event row, then died before
1519
+ # HWM + DELETE. Tick N+1's pre-check sees
1520
+ # ``already`` non-None (the row IS in the
1521
+ # table) and would skip the pivots, leaving
1522
+ # the system wedged on pre-credit HWM + stale-
1523
+ # replica rows. Pivots are individually
1524
+ # idempotent (file overwrite + DELETE on stable
1525
+ # predicate), so re-running them is safe.
1526
+ # ``effective_iso`` is resolved above; on a
1527
+ # recovery tick it lands on the SAME 10-min
1528
+ # slot as the original (now_utc has drifted
1529
+ # only seconds), so the DELETE predicate's
1530
+ # ``unixepoch(captured_at_utc) >= unixepoch(?)``
1531
+ # still matches every stale-replica row.
1532
+ #
1533
+ # Force-write hwm-7d so the next status-line
1534
+ # render reflects the post-credit value. The
1535
+ # monotonic guard at the normal write site
1536
+ # (below) would refuse to decrease the file;
1537
+ # this write is the credit-only escape hatch.
1538
+ # Lands AFTER the conn.commit() so a concurrent
1539
+ # record-usage reader doesn't see the new HWM
1540
+ # before the event row is durable.
1541
+ try:
1542
+ (c.APP_DIR / "hwm-7d").write_text(
1543
+ f"{week_start_date} {weekly_percent}\n"
1544
+ )
1545
+ except OSError:
1546
+ pass
1547
+
1548
+ # Race-defensive cleanup. Between the moment
1549
+ # Anthropic credited the user (effective_iso)
1550
+ # and this code firing, the EXTERNAL
1551
+ # claude-statusline tool can replay stale
1552
+ # pre-credit `--percent` values (it has its
1553
+ # own in-memory HWM cache and re-runs us once
1554
+ # per status-line tick). Those replays land
1555
+ # captured_at_utc >= effective_iso with
1556
+ # weekly_percent == prior_pct (the pre-credit
1557
+ # value), and they dominate the reset-aware
1558
+ # clamp's MAX over the post-credit segment so
1559
+ # legitimate fresh OAuth values are rejected.
1560
+ # Strict equality (round(.,1)) keeps this
1561
+ # narrow: we only delete rows whose percent
1562
+ # exactly matches the pre-credit value we just
1563
+ # observed — legitimate post-credit climbs
1564
+ # past `prior_pct` (rare, but possible if the
1565
+ # credit is small + activity is heavy) stay.
1566
+ try:
1567
+ conn.execute(
1568
+ "DELETE FROM weekly_usage_snapshots "
1569
+ "WHERE week_start_date = ? "
1570
+ " AND unixepoch(captured_at_utc) >= "
1571
+ " unixepoch(?) "
1572
+ " AND round(weekly_percent, 1) = "
1573
+ " round(?, 1)",
1574
+ (week_start_date, effective_iso,
1575
+ float(prior_pct)),
1576
+ )
1577
+ conn.commit()
1578
+ except sqlite3.DatabaseError as exc:
1579
+ eprint(
1580
+ "[record-usage] post-credit cleanup "
1581
+ f"failed: {exc}"
1582
+ )
1583
+
1584
+ # ── 5h in-place credit detection (parallel to weekly above) ──
1585
+ # Spec §2.2 of
1586
+ # docs/superpowers/specs/2026-05-16-5h-in-place-credit-detection.md.
1587
+ # Slot SECOND so the weekly branch retains control-flow
1588
+ # priority — both branches are independent (they touch
1589
+ # different tables) and the order has no behavioral
1590
+ # interaction. Same outer try/except wraps both so a
1591
+ # 5h-detection failure logs but does not regress the rest
1592
+ # of cmd_record_usage.
1593
+ #
1594
+ # Diverges from weekly in three places:
1595
+ # - Threshold: 5.0pp (constant on cctally module), not 25.0pp.
1596
+ # The 5h envelope is smaller so a 5pp move is
1597
+ # proportionally larger.
1598
+ # - Effective-iso floor: 10-min (matches
1599
+ # ``_canonical_5h_window_key``'s 600s floor), not hour.
1600
+ # Up to ~30 distinct slots per 5h block; same-slot
1601
+ # collisions absorbed by UNIQUE per spec §2.3.
1602
+ # - Pre-check: pair-checks the latest event's
1603
+ # ``(prior_percent, post_percent)`` against this tick's
1604
+ # ``(prior_5h_pct, five_hour_percent)``, not
1605
+ # ``new_week_end_at`` equality. A genuine replay matches
1606
+ # BOTH fields; a NEW credit-with-idle (prior_pct equals
1607
+ # the prior credit's post_pct because the user didn't
1608
+ # move between credits) matches only one field and
1609
+ # correctly proceeds to write a second event row.
1610
+ try:
1611
+ if (
1612
+ five_hour_window_key is not None
1613
+ and five_hour_percent is not None
1614
+ ):
1615
+ prior_5h_row = conn.execute(
1616
+ "SELECT five_hour_window_key, five_hour_percent, "
1617
+ " five_hour_resets_at "
1618
+ " FROM weekly_usage_snapshots "
1619
+ " WHERE five_hour_window_key IS NOT NULL "
1620
+ " AND five_hour_percent IS NOT NULL "
1621
+ " ORDER BY captured_at_utc DESC, id DESC LIMIT 1"
1622
+ ).fetchone()
1623
+ if (
1624
+ prior_5h_row is not None
1625
+ and int(prior_5h_row["five_hour_window_key"])
1626
+ == int(five_hour_window_key)
1627
+ and prior_5h_row["five_hour_resets_at"] is not None
1628
+ ):
1629
+ prior_5h_pct = float(prior_5h_row["five_hour_percent"])
1630
+ prior_5h_resets_dt = parse_iso_datetime(
1631
+ prior_5h_row["five_hour_resets_at"],
1632
+ "prior.five_hour_resets_at",
1633
+ )
1634
+ # ``now_utc`` was bound earlier in this same
1635
+ # outer try block from
1636
+ # ``dt.datetime.now(dt.timezone.utc)``; reuse it
1637
+ # so both branches see the same instant.
1638
+ if (
1639
+ prior_5h_resets_dt > now_utc
1640
+ and (prior_5h_pct - float(five_hour_percent))
1641
+ >= c._FIVE_HOUR_RESET_PCT_DROP_THRESHOLD
1642
+ ):
1643
+ # Pair-check dedup pre-check (spec §2.2;
1644
+ # refined by Codex r4 P1 finding). The
1645
+ # round-1 predicate compared only the
1646
+ # latest event's ``post_percent`` against
1647
+ # this tick's ``prior_5h_pct``; that
1648
+ # false-positived on a legitimate 2nd
1649
+ # credit where the user was idle between
1650
+ # credits (Credit 1 lands prior=20/post=5;
1651
+ # user does nothing; Credit 2 arrives with
1652
+ # CLI percent=0 so prior_5h_pct=5 reads
1653
+ # equal to stored post_percent=5 →
1654
+ # silently swallowed). Pair-checking
1655
+ # against BOTH fields disambiguates: a
1656
+ # genuine replay matches BOTH; a new
1657
+ # credit-with-idle matches at most ONE
1658
+ # (the prior side coincides but
1659
+ # post_percent differs).
1660
+ most_recent = conn.execute(
1661
+ "SELECT prior_percent, post_percent "
1662
+ " FROM five_hour_reset_events "
1663
+ " WHERE five_hour_window_key = ? "
1664
+ " ORDER BY id DESC LIMIT 1",
1665
+ (int(five_hour_window_key),),
1666
+ ).fetchone()
1667
+ is_dup = (
1668
+ most_recent is not None
1669
+ and round(prior_5h_pct, 1)
1670
+ == round(float(most_recent["prior_percent"]), 1)
1671
+ and round(float(five_hour_percent), 1)
1672
+ == round(float(most_recent["post_percent"]), 1)
1673
+ )
1674
+ # 10-min floor (spec §2.3 — bounded
1675
+ # stacked-credit resolution; one event per
1676
+ # 10-min slot per block). Resolved BEFORE
1677
+ # the ``if not is_dup`` branch so it's in
1678
+ # scope for the pivots below (per memory
1679
+ # ``project_dedup_must_not_gate_side_effects.md``:
1680
+ # the recovery-tick path must still force
1681
+ # HWM + DELETE even when the INSERT is
1682
+ # absorbed by the pre-check or by
1683
+ # UNIQUE — see comment below for the
1684
+ # crash scenario). ``_floor_to_ten_minutes``
1685
+ # is a cctally module attribute; the
1686
+ # ``c.X`` accessor resolves at call time
1687
+ # so test ``monkeypatch.setitem(ns,
1688
+ # "_floor_to_ten_minutes", …)``
1689
+ # propagates.
1690
+ effective_dt = c._floor_to_ten_minutes(now_utc)
1691
+ effective_iso = effective_dt.isoformat(
1692
+ timespec="seconds"
1693
+ )
1694
+ if not is_dup:
1695
+ conn.execute(
1696
+ "INSERT OR IGNORE INTO five_hour_reset_events "
1697
+ "(detected_at_utc, five_hour_window_key, "
1698
+ " prior_percent, post_percent, "
1699
+ " effective_reset_at_utc) "
1700
+ "VALUES (?, ?, ?, ?, ?)",
1701
+ (
1702
+ now_utc_iso(),
1703
+ int(five_hour_window_key),
1704
+ prior_5h_pct,
1705
+ float(five_hour_percent),
1706
+ effective_iso,
1707
+ ),
1708
+ )
1709
+ conn.commit()
1710
+ # Pivots fire UNCONDITIONALLY whenever a
1711
+ # credit is detected — NOT gated on
1712
+ # ``not is_dup`` and NOT on
1713
+ # ``rowcount == 1``. Memory
1714
+ # ``project_dedup_must_not_gate_side_effects.md``:
1715
+ # "Skipping a no-op INSERT must NOT skip
1716
+ # milestones/rollups/alerts; prior run may
1717
+ # have died mid-flight." Crash scenario A:
1718
+ # tick N committed the event row, then died
1719
+ # before HWM + DELETE. Tick N+1's
1720
+ # INSERT OR IGNORE returns rowcount == 0
1721
+ # (UNIQUE absorbs) but the system is still
1722
+ # wedged on the pre-credit HWM + stale-
1723
+ # replica rows. Crash scenario B (the
1724
+ # Codex r4 finding): a recovery tick where
1725
+ # ``(prior, post)`` pair-matches the
1726
+ # already-stored event row also takes the
1727
+ # ``is_dup`` branch; without the hoist the
1728
+ # pivots would be skipped and the system
1729
+ # would stay wedged. The pivots are
1730
+ # individually idempotent (file overwrite
1731
+ # + DELETE on a stable predicate), so
1732
+ # re-running them on the recovery tick is
1733
+ # always safe. Mirrors the weekly hoist at
1734
+ # ``_cctally_record.py`` after the
1735
+ # ``if already is None`` block (grep
1736
+ # ``Force-write hwm-7d``).
1737
+ #
1738
+ # Force-write hwm-5h: bypasses the
1739
+ # monotonic guard at the normal hwm-5h
1740
+ # writer below. Lands AFTER
1741
+ # ``conn.commit()`` so a concurrent reader
1742
+ # doesn't see the new HWM before the
1743
+ # event row is durable. File format
1744
+ # matches the canonical writer:
1745
+ # ``<key> <percent>\n``.
1746
+ try:
1747
+ (c.APP_DIR / "hwm-5h").write_text(
1748
+ f"{int(five_hour_window_key)} "
1749
+ f"{float(five_hour_percent)}\n"
1750
+ )
1751
+ except OSError:
1752
+ pass
1753
+ # Stale-replica DELETE (spec §4.3).
1754
+ # Defends against claude-statusline
1755
+ # replaying the pre-credit
1756
+ # ``--five-hour-percent`` value past the
1757
+ # credit moment from its own in-memory
1758
+ # HWM cache. Strict round-1 equality
1759
+ # keeps the scope narrow — only rows
1760
+ # whose five_hour_percent exactly matches
1761
+ # the just-observed pre-credit value are
1762
+ # removed. ``unixepoch()`` on both sides
1763
+ # for offset robustness (Z vs +00:00).
1764
+ try:
1765
+ conn.execute(
1766
+ "DELETE FROM weekly_usage_snapshots "
1767
+ " WHERE five_hour_window_key = ? "
1768
+ " AND unixepoch(captured_at_utc) "
1769
+ " >= unixepoch(?) "
1770
+ " AND round(five_hour_percent, 1) "
1771
+ " = round(?, 1)",
1772
+ (
1773
+ int(five_hour_window_key),
1774
+ effective_iso,
1775
+ prior_5h_pct,
1776
+ ),
1777
+ )
1778
+ conn.commit()
1779
+ except sqlite3.DatabaseError as exc:
1780
+ eprint(
1781
+ "[record-usage] 5h post-credit "
1782
+ f"cleanup failed: {exc}"
1783
+ )
1784
+ except (sqlite3.DatabaseError, ValueError, TypeError) as exc:
1785
+ eprint(
1786
+ f"[record-usage] 5h in-place-credit detection "
1787
+ f"failed: {exc}"
1788
+ )
1334
1789
  except (sqlite3.DatabaseError, ValueError) as exc:
1335
1790
  eprint(f"[record-usage] reset-event detection failed: {exc}")
1336
1791
 
1337
- # 7-day usage is monotonically non-decreasing within a billing week.
1338
- # A lower value means stale rate-limit data from a previous API call;
1339
- # skip the insert to avoid regressing the reported usage.
1792
+ # 7-day usage is monotonically non-decreasing within a billing week
1793
+ # UNTIL Anthropic issues an in-place weekly credit. When a
1794
+ # week_reset_events row exists for THIS week_end_at, the MAX query
1795
+ # filters to samples captured at-or-after the segment's
1796
+ # effective_reset_at_utc so a fresh post-credit OAuth value (e.g.
1797
+ # 2%) lands instead of being held back by stale pre-credit history
1798
+ # (e.g. 67%). When no event row exists, COALESCE defaults to
1799
+ # epoch-zero so the filter is a no-op and legacy clamp behavior
1800
+ # is preserved byte-identically.
1801
+ # NB: comparison wrapped with ``unixepoch()`` on BOTH sides.
1802
+ # ``captured_at_utc`` is stored with `Z` suffix, but
1803
+ # ``effective_reset_at_utc`` may have a non-UTC offset on
1804
+ # historical backfill rows written before Bug 3 was fixed
1805
+ # (parse_iso_datetime returned host-local). Lex string compare
1806
+ # on mixed offsets silently mis-orders moments for non-UTC
1807
+ # hosts (CLAUDE.md gotcha: 5h-block cross-reset flag — "all
1808
+ # comparisons go through unixepoch(), NOT lex
1809
+ # BETWEEN/`<`/`>`"). Same rule applies here.
1340
1810
  max_row = conn.execute(
1341
- "SELECT MAX(weekly_percent) AS v FROM weekly_usage_snapshots WHERE week_start_date = ?",
1342
- (week_start_date,),
1811
+ """
1812
+ SELECT MAX(weekly_percent) AS v
1813
+ FROM weekly_usage_snapshots
1814
+ WHERE week_start_date = ?
1815
+ AND unixepoch(captured_at_utc) >= unixepoch(COALESCE(
1816
+ (SELECT effective_reset_at_utc
1817
+ FROM week_reset_events
1818
+ WHERE new_week_end_at = ?
1819
+ ORDER BY id DESC
1820
+ LIMIT 1),
1821
+ '1970-01-01T00:00:00Z'
1822
+ ))
1823
+ """,
1824
+ (week_start_date, week_end_at),
1343
1825
  ).fetchone()
1344
1826
  if max_row and max_row["v"] is not None and round(weekly_percent, 1) < round(float(max_row["v"]), 1):
1345
1827
  should_insert = False
1346
1828
  else:
1347
- # 5-hour usage is monotonically non-decreasing within a window.
1348
- # A lower value means stale API data; clamp to existing max.
1349
- # Joining on five_hour_window_key (canonical 10-min-floored
1829
+ # 5-hour usage is monotonically non-decreasing within a window
1830
+ # UNTIL an in-place 5h credit fires. When a
1831
+ # ``five_hour_reset_events`` row exists for THIS
1832
+ # ``five_hour_window_key``, the MAX query filters to samples
1833
+ # captured at-or-after the event's ``effective_reset_at_utc``
1834
+ # so a fresh post-credit OAuth value (e.g. 4%) lands instead
1835
+ # of being re-clamped to the pre-credit max (e.g. 28%). When
1836
+ # no event row exists, ``COALESCE`` defaults to epoch-zero so
1837
+ # the filter is a no-op and legacy clamp behavior is preserved
1838
+ # byte-identically.
1839
+ #
1840
+ # ``unixepoch()`` on BOTH sides for offset robustness — stored
1841
+ # ``captured_at_utc`` carries ``Z`` while
1842
+ # ``effective_reset_at_utc`` carries ``+00:00``. Lex compare
1843
+ # would silently mis-order moments for non-UTC hosts (same
1844
+ # gotcha as the weekly clamp / 5h-block cross-reset flag).
1845
+ #
1846
+ # Joining on ``five_hour_window_key`` (canonical 10-min-floored
1350
1847
  # epoch) absorbs Anthropic's seconds-level jitter on
1351
- # resets_at; an ISO-string equality at this site silently
1848
+ # ``resets_at``; an ISO-string equality at this site silently
1352
1849
  # skipped the clamp every time a jittered fetch landed in
1353
1850
  # the same physical 5h window (spec Bug B).
1851
+ #
1852
+ # Spec §4.1 of
1853
+ # docs/superpowers/specs/2026-05-16-5h-in-place-credit-detection.md.
1354
1854
  if five_hour_percent is not None and five_hour_window_key is not None:
1355
1855
  max_5h_row = conn.execute(
1356
- "SELECT MAX(five_hour_percent) AS v FROM weekly_usage_snapshots WHERE five_hour_window_key = ?",
1357
- (five_hour_window_key,),
1856
+ """
1857
+ SELECT MAX(five_hour_percent) AS v
1858
+ FROM weekly_usage_snapshots
1859
+ WHERE five_hour_window_key = ?
1860
+ AND unixepoch(captured_at_utc) >= unixepoch(COALESCE(
1861
+ (SELECT effective_reset_at_utc
1862
+ FROM five_hour_reset_events
1863
+ WHERE five_hour_window_key = ?
1864
+ ORDER BY id DESC
1865
+ LIMIT 1),
1866
+ '1970-01-01T00:00:00Z'
1867
+ ))
1868
+ """,
1869
+ (int(five_hour_window_key), int(five_hour_window_key)),
1358
1870
  ).fetchone()
1359
- if max_5h_row and max_5h_row["v"] is not None and round(five_hour_percent, 1) < round(float(max_5h_row["v"]), 1):
1871
+ if (
1872
+ max_5h_row
1873
+ and max_5h_row["v"] is not None
1874
+ and round(five_hour_percent, 1)
1875
+ < round(float(max_5h_row["v"]), 1)
1876
+ ):
1360
1877
  five_hour_percent = float(max_5h_row["v"])
1361
1878
 
1362
1879
  # Dedup vs last snapshot: if BOTH weekly_percent and
@@ -1426,11 +1943,29 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
1426
1943
  )
1427
1944
  need_milestone_heal = False
1428
1945
  if latest_floor >= 1:
1946
+ # v1.7.2: scope the heal probe to the ACTIVE segment.
1947
+ # Without this, a credited week's MAX over the whole
1948
+ # ledger would still read the pre-credit ceiling
1949
+ # (e.g. 67%) and silently suppress the post-credit
1950
+ # ledger's heal even though it has zero rows.
1951
+ captured_at_for_probe = latest_row["captured_at_utc"]
1952
+ week_end_at_for_probe = latest_row["week_end_at"]
1953
+ heal_segment = 0
1954
+ if week_end_at_for_probe and captured_at_for_probe:
1955
+ seg = heal_conn.execute(
1956
+ "SELECT id FROM week_reset_events "
1957
+ "WHERE new_week_end_at = ? "
1958
+ " AND unixepoch(effective_reset_at_utc) <= unixepoch(?) "
1959
+ "ORDER BY id DESC LIMIT 1",
1960
+ (week_end_at_for_probe, captured_at_for_probe),
1961
+ ).fetchone()
1962
+ if seg is not None:
1963
+ heal_segment = int(seg["id"])
1429
1964
  max_existing = heal_conn.execute(
1430
1965
  "SELECT MAX(percent_threshold) AS m "
1431
1966
  "FROM percent_milestones "
1432
- "WHERE week_start_date = ?",
1433
- (week_start_date,),
1967
+ "WHERE week_start_date = ? AND reset_event_id = ?",
1968
+ (week_start_date, heal_segment),
1434
1969
  ).fetchone()
1435
1970
  if max_existing is None or max_existing["m"] is None:
1436
1971
  need_milestone_heal = True
@@ -1442,6 +1977,16 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
1442
1977
  # last_observed_at_utc is stale relative to the latest
1443
1978
  # snapshot's captured_at_utc (the kill landed between
1444
1979
  # insert_usage_snapshot and maybe_update_five_hour_block).
1980
+ #
1981
+ # Round-3: ALSO scope the milestone-coverage half of
1982
+ # this probe by ACTIVE 5h SEGMENT. Without this, a
1983
+ # credited block's MAX over the whole milestone ledger
1984
+ # would still read the pre-credit ceiling (e.g. 28%) and
1985
+ # silently suppress the post-credit ledger's heal even
1986
+ # though it has zero rows. Mirrors weekly Probe 1's
1987
+ # segment-aware fix above. Uses
1988
+ # ``_resolve_active_five_hour_reset_event_id`` to find
1989
+ # the active segment for the latest snapshot's window.
1445
1990
  need_5h_heal = False
1446
1991
  window_key = latest_row["five_hour_window_key"]
1447
1992
  if window_key is not None:
@@ -1458,6 +2003,45 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
1458
2003
  < latest_row["captured_at_utc"]
1459
2004
  ):
1460
2005
  need_5h_heal = True
2006
+ else:
2007
+ # Block row exists AND last_observed is fresh
2008
+ # — but the post-credit milestone segment may
2009
+ # still owe rows. Scope MAX(percent_threshold)
2010
+ # by the active reset_event_id segment so
2011
+ # post-credit climbs from threshold 1 trigger
2012
+ # heal even when the pre-credit segment already
2013
+ # crossed higher thresholds. Probe shape mirrors
2014
+ # weekly Probe 1 (lines 1922-1956).
2015
+ five_hour_percent_for_probe = latest_row[
2016
+ "five_hour_percent"
2017
+ ]
2018
+ if five_hour_percent_for_probe is not None:
2019
+ latest_5h_floor = math.floor(
2020
+ float(five_hour_percent_for_probe) + 1e-9
2021
+ )
2022
+ if latest_5h_floor >= 1:
2023
+ heal_5h_segment = (
2024
+ _resolve_active_five_hour_reset_event_id(
2025
+ heal_conn, int(window_key)
2026
+ )
2027
+ )
2028
+ max_5h_existing = heal_conn.execute(
2029
+ "SELECT MAX(percent_threshold) AS m "
2030
+ "FROM five_hour_milestones "
2031
+ "WHERE five_hour_window_key = ? "
2032
+ " AND reset_event_id = ?",
2033
+ (int(window_key), heal_5h_segment),
2034
+ ).fetchone()
2035
+ if (
2036
+ max_5h_existing is None
2037
+ or max_5h_existing["m"] is None
2038
+ ):
2039
+ need_5h_heal = True
2040
+ elif (
2041
+ int(max_5h_existing["m"])
2042
+ < latest_5h_floor
2043
+ ):
2044
+ need_5h_heal = True
1461
2045
  finally:
1462
2046
  heal_conn.close()
1463
2047