cctally 1.7.2 → 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:
@@ -963,7 +1004,23 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
963
1004
  # Snap-up-by-1e-9 per the gotcha: 0.50 * 100 == 49.99...9 in
964
1005
  # IEEE-754, so bare math.floor would miss the 50 threshold.
965
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
+
966
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
+ #
967
1024
  # Use max(percent_threshold) directly (not prior block's
968
1025
  # final_pct) so first-observation already-mid-stream doesn't
969
1026
  # synthesize crossings 1..(current_floor - 1) we never had
@@ -971,8 +1028,8 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
971
1028
  # maybe_record_milestone's max_existing path.
972
1029
  row = conn.execute(
973
1030
  "SELECT MAX(percent_threshold) AS m FROM five_hour_milestones "
974
- "WHERE five_hour_window_key = ?",
975
- (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),
976
1033
  ).fetchone()
977
1034
  max_existing = row["m"] if row and row["m"] is not None else None
978
1035
 
@@ -985,14 +1042,21 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
985
1042
  # block_id was resolved above (before the children writes) and
986
1043
  # is still in scope here.
987
1044
 
1045
+ # Site B — prior-cost lookup scoped to active segment.
988
1046
  # Marginal-cost lookup for the start_threshold milestone
989
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.
990
1051
  prior_cost: float | None = None
991
1052
  if max_existing is not None:
992
1053
  prev_row = conn.execute(
993
1054
  "SELECT block_cost_usd FROM five_hour_milestones "
994
- "WHERE five_hour_window_key = ? AND percent_threshold = ?",
995
- (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),
996
1060
  ).fetchone()
997
1061
  if prev_row is not None:
998
1062
  prior_cost = float(prev_row["block_cost_usd"])
@@ -1002,6 +1066,12 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
1002
1066
  marginal: float | None = totals["cost_usd"] - prior_cost
1003
1067
  else:
1004
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.
1005
1075
  cur = conn.execute(
1006
1076
  """
1007
1077
  INSERT OR IGNORE INTO five_hour_milestones (
@@ -1016,9 +1086,10 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
1016
1086
  block_cache_read_tokens,
1017
1087
  block_cost_usd,
1018
1088
  marginal_cost_usd,
1019
- seven_day_pct_at_crossing
1089
+ seven_day_pct_at_crossing,
1090
+ reset_event_id
1020
1091
  )
1021
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1092
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1022
1093
  """,
1023
1094
  (
1024
1095
  block_id,
@@ -1033,6 +1104,7 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
1033
1104
  totals["cost_usd"],
1034
1105
  marginal,
1035
1106
  weekly_percent,
1107
+ active_reset_event_id,
1036
1108
  ),
1037
1109
  )
1038
1110
  # ── Threshold-actions dispatch (set-then-dispatch, spec §3.2) ──
@@ -1061,11 +1133,19 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
1061
1133
  and pct in alerts_cfg["five_hour_thresholds"]
1062
1134
  ):
1063
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.
1064
1141
  conn.execute(
1065
1142
  "UPDATE five_hour_milestones SET alerted_at = ? "
1066
- "WHERE five_hour_window_key = ? AND percent_threshold = ? "
1067
- "AND alerted_at IS NULL",
1068
- (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),
1069
1149
  )
1070
1150
  # Cheap re-reads inside BEGIN are SELECT-only and
1071
1151
  # safe; values reflect post-INSERT state. We
@@ -1073,10 +1153,17 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
1073
1153
  # are in scope) and defer ONLY the Popen-side
1074
1154
  # _dispatch_alert_notification to after the outer
1075
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.
1076
1160
  cost_row = conn.execute(
1077
1161
  "SELECT block_cost_usd FROM five_hour_milestones "
1078
- "WHERE five_hour_window_key = ? AND percent_threshold = ?",
1079
- (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),
1080
1167
  ).fetchone()
1081
1168
  block_row = conn.execute(
1082
1169
  "SELECT block_start_at FROM five_hour_blocks "
@@ -1393,9 +1480,9 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
1393
1480
  "WHERE new_week_end_at = ? LIMIT 1",
1394
1481
  (cur_end_canon,),
1395
1482
  ).fetchone()
1483
+ effective_dt = _floor_to_hour(now_utc)
1484
+ effective_iso = effective_dt.isoformat(timespec="seconds")
1396
1485
  if already is None:
1397
- effective_dt = _floor_to_hour(now_utc)
1398
- effective_iso = effective_dt.isoformat(timespec="seconds")
1399
1486
  # Row shape: old=effective_iso, new=cur_end_canon
1400
1487
  # (distinct values). The previous shape stored
1401
1488
  # old==new==cur_end_canon, which let BOTH
@@ -1421,56 +1508,284 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
1421
1508
  effective_iso),
1422
1509
  )
1423
1510
  conn.commit()
1424
- # Force-write hwm-7d so the next status-line
1425
- # render reflects the post-credit value. The
1426
- # monotonic guard at the normal write site (below)
1427
- # would refuse to decrease the file; this write
1428
- # is the credit-only escape hatch. Lands AFTER
1429
- # the conn.commit() so a concurrent record-usage
1430
- # reader doesn't see the new HWM before the
1431
- # event row is durable.
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``.
1432
1746
  try:
1433
- (c.APP_DIR / "hwm-7d").write_text(
1434
- f"{week_start_date} {weekly_percent}\n"
1747
+ (c.APP_DIR / "hwm-5h").write_text(
1748
+ f"{int(five_hour_window_key)} "
1749
+ f"{float(five_hour_percent)}\n"
1435
1750
  )
1436
1751
  except OSError:
1437
1752
  pass
1438
-
1439
- # Race-defensive cleanup. Between the moment
1440
- # Anthropic credited the user (effective_iso)
1441
- # and this code firing, the EXTERNAL
1442
- # claude-statusline tool can replay stale
1443
- # pre-credit `--percent` values (it has its
1444
- # own in-memory HWM cache and re-runs us once
1445
- # per status-line tick). Those replays land
1446
- # captured_at_utc >= effective_iso with
1447
- # weekly_percent == prior_pct (the pre-credit
1448
- # value), and they dominate the reset-aware
1449
- # clamp's MAX over the post-credit segment so
1450
- # legitimate fresh OAuth values are rejected.
1451
- # Strict equality (round(.,1)) keeps this
1452
- # narrow: we only delete rows whose percent
1453
- # exactly matches the pre-credit value we just
1454
- # observed — legitimate post-credit climbs
1455
- # past `prior_pct` (rare, but possible if the
1456
- # credit is small + activity is heavy) stay.
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).
1457
1764
  try:
1458
1765
  conn.execute(
1459
1766
  "DELETE FROM weekly_usage_snapshots "
1460
- "WHERE week_start_date = ? "
1461
- " AND unixepoch(captured_at_utc) >= "
1462
- " unixepoch(?) "
1463
- " AND round(weekly_percent, 1) = "
1464
- " round(?, 1)",
1465
- (week_start_date, effective_iso,
1466
- float(prior_pct)),
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
+ ),
1467
1777
  )
1468
1778
  conn.commit()
1469
1779
  except sqlite3.DatabaseError as exc:
1470
1780
  eprint(
1471
- "[record-usage] post-credit cleanup "
1472
- f"failed: {exc}"
1781
+ "[record-usage] 5h post-credit "
1782
+ f"cleanup failed: {exc}"
1473
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
+ )
1474
1789
  except (sqlite3.DatabaseError, ValueError) as exc:
1475
1790
  eprint(f"[record-usage] reset-event detection failed: {exc}")
1476
1791
 
@@ -1511,19 +1826,54 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
1511
1826
  if max_row and max_row["v"] is not None and round(weekly_percent, 1) < round(float(max_row["v"]), 1):
1512
1827
  should_insert = False
1513
1828
  else:
1514
- # 5-hour usage is monotonically non-decreasing within a window.
1515
- # A lower value means stale API data; clamp to existing max.
1516
- # 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
1517
1847
  # epoch) absorbs Anthropic's seconds-level jitter on
1518
- # resets_at; an ISO-string equality at this site silently
1848
+ # ``resets_at``; an ISO-string equality at this site silently
1519
1849
  # skipped the clamp every time a jittered fetch landed in
1520
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.
1521
1854
  if five_hour_percent is not None and five_hour_window_key is not None:
1522
1855
  max_5h_row = conn.execute(
1523
- "SELECT MAX(five_hour_percent) AS v FROM weekly_usage_snapshots WHERE five_hour_window_key = ?",
1524
- (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)),
1525
1870
  ).fetchone()
1526
- 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
+ ):
1527
1877
  five_hour_percent = float(max_5h_row["v"])
1528
1878
 
1529
1879
  # Dedup vs last snapshot: if BOTH weekly_percent and
@@ -1627,6 +1977,16 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
1627
1977
  # last_observed_at_utc is stale relative to the latest
1628
1978
  # snapshot's captured_at_utc (the kill landed between
1629
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.
1630
1990
  need_5h_heal = False
1631
1991
  window_key = latest_row["five_hour_window_key"]
1632
1992
  if window_key is not None:
@@ -1643,6 +2003,45 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
1643
2003
  < latest_row["captured_at_utc"]
1644
2004
  ):
1645
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
1646
2045
  finally:
1647
2046
  heal_conn.close()
1648
2047