cctally 1.83.0 → 1.84.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 +34 -0
- package/README.md +2 -4
- package/bin/_cctally_account.py +144 -11
- package/bin/_cctally_alerts.py +3 -1
- package/bin/_cctally_cache.py +1396 -33
- package/bin/_cctally_dashboard.py +46 -2
- package/bin/_cctally_dashboard_conversation.py +16 -7
- package/bin/_cctally_dashboard_envelope.py +12 -0
- package/bin/_cctally_dashboard_share.py +58 -5
- package/bin/_cctally_dashboard_sources.py +641 -38
- package/bin/_cctally_db.py +173 -7
- package/bin/_cctally_doctor.py +112 -27
- package/bin/_cctally_journal.py +761 -74
- package/bin/_cctally_milestone_history.py +73 -16
- package/bin/_cctally_quota.py +313 -15
- package/bin/_cctally_source_analytics.py +4 -1
- package/bin/_cctally_tui.py +7 -3
- package/bin/_lib_doctor.py +67 -2
- package/bin/_lib_journal.py +48 -0
- package/bin/_lib_jsonl.py +119 -0
- package/bin/_lib_quota.py +222 -13
- package/bin/_lib_rederive.py +12 -0
- package/bin/_lib_source_analytics.py +13 -0
- package/bin/_lib_source_identity.py +24 -0
- package/dashboard/static/assets/index-DlVVJeS4.js +92 -0
- package/dashboard/static/assets/index-OYBkyglj.css +1 -0
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +1 -1
- package/dashboard/static/assets/index-3bgCMVHb.js +0 -92
- package/dashboard/static/assets/index-D27EIHEI.css +0 -1
|
@@ -299,6 +299,7 @@ def resolve_codex_cycle_detail_identity(
|
|
|
299
299
|
*,
|
|
300
300
|
source_root_keys: Iterable[str],
|
|
301
301
|
now_utc: dt.datetime,
|
|
302
|
+
account_key: str | None = None,
|
|
302
303
|
):
|
|
303
304
|
"""The live-cycle identity for a per-request Codex cycle-DETAIL read (#373).
|
|
304
305
|
|
|
@@ -314,6 +315,21 @@ def resolve_codex_cycle_detail_identity(
|
|
|
314
315
|
the live boundary itself is resolved from the active roots' observations,
|
|
315
316
|
exactly as the source build does.
|
|
316
317
|
|
|
318
|
+
``account_key`` (#416 QA sweep) picks the LIVE boundary belonging to the
|
|
319
|
+
account the route is focused on. ``_resolve_codex_weekly_cycle`` returns one
|
|
320
|
+
boundary per account and this function used ``cycles[0]`` — the first
|
|
321
|
+
account by sorted key — unconditionally, while ``build_codex_cycle_detail``
|
|
322
|
+
was already given the account predicate. So a focused read enumerated
|
|
323
|
+
account B's cycles and judged them against account A's reset: no candidate
|
|
324
|
+
falls inside ``CODEX_CYCLE_JITTER_FLOOR_SECONDS`` of a foreign boundary, so
|
|
325
|
+
``_select_live_physical_cycle`` returned ``None`` and B's own live cycle
|
|
326
|
+
lost both its ``is_current`` flag and the §7.4 no-clip guard — the exact
|
|
327
|
+
index/detail disagreement #373 closed, re-opened one account over.
|
|
328
|
+
|
|
329
|
+
An account with no live weekly cycle resolves to NO boundary rather than a
|
|
330
|
+
sibling's: an unarmed guard is today's honest degrade, a foreign boundary is
|
|
331
|
+
a wrong answer. ``None`` keeps the merged representative and is byte-stable.
|
|
332
|
+
|
|
317
333
|
Degrades to a bare-roots identity — today's behaviour — whenever no live
|
|
318
334
|
cycle resolves. The clip guard stays unarmed on that identity by design
|
|
319
335
|
(``_boundary_has_live_reset``), so the detail keeps clipping as it did
|
|
@@ -348,7 +364,22 @@ def resolve_codex_cycle_detail_identity(
|
|
|
348
364
|
return identity
|
|
349
365
|
if not cycles:
|
|
350
366
|
return identity
|
|
351
|
-
|
|
367
|
+
if account_key is None:
|
|
368
|
+
boundary = cycles[0]
|
|
369
|
+
else:
|
|
370
|
+
boundary = next(
|
|
371
|
+
(
|
|
372
|
+
cyc for cyc in cycles
|
|
373
|
+
if (
|
|
374
|
+
cyc.quota_identity.account_key
|
|
375
|
+
if cyc.quota_identity is not None
|
|
376
|
+
else _lib_accounts.UNATTRIBUTED
|
|
377
|
+
) == account_key
|
|
378
|
+
),
|
|
379
|
+
None,
|
|
380
|
+
)
|
|
381
|
+
if boundary is None:
|
|
382
|
+
return identity
|
|
352
383
|
identity.resets_at = boundary.resets_at
|
|
353
384
|
identity.quota_identity = boundary.quota_identity
|
|
354
385
|
return identity
|
|
@@ -450,6 +481,7 @@ def _codex_weekly_periods(
|
|
|
450
481
|
*,
|
|
451
482
|
source_root_keys: Iterable[str],
|
|
452
483
|
active_cycle: CodexCycleBoundary | None,
|
|
484
|
+
account_key: str | None = None,
|
|
453
485
|
) -> tuple[CodexWeeklyPeriod, ...]:
|
|
454
486
|
"""Read durable 10,080-minute boundaries and clip early re-anchors.
|
|
455
487
|
|
|
@@ -457,6 +489,24 @@ def _codex_weekly_periods(
|
|
|
457
489
|
the prior seven-day deadline. Sorting those nominal starts and ending the
|
|
458
490
|
prior segment at the next start preserves the actual quota-cycle boundary
|
|
459
491
|
without double-counting the overlapping nominal windows.
|
|
492
|
+
|
|
493
|
+
``account_key`` (#416 Slice 3A review B1) scopes the read to ONE account.
|
|
494
|
+
``quota_window_blocks`` is ``UNIQUE(source, source_root_key, account_key,
|
|
495
|
+
logical_limit_key, observed_slot, window_minutes, resets_at_utc)``, so two
|
|
496
|
+
accounts on one root genuinely produce two weekly rows; without the
|
|
497
|
+
predicate the jitter merge below pools their ``current_percent`` values and
|
|
498
|
+
``max(...)`` hands the focused account the OTHER account's percentage —
|
|
499
|
+
the never-combine violation D6 forbids — while ``end_at = min(resets_at,
|
|
500
|
+
next_start)`` clips one account's week at the other's start. ``None`` keeps
|
|
501
|
+
the merged "All accounts" read, which is byte-stable and is what the parent
|
|
502
|
+
still uses.
|
|
503
|
+
|
|
504
|
+
The predicate is strict equality, deliberately NOT the one-directional
|
|
505
|
+
``(account, unattributed)`` widening ``_codex_five_hour_rows`` uses: that
|
|
506
|
+
widening produces a LISTING whose members each keep their own percentage,
|
|
507
|
+
whereas the merge here ADOPTS a pooled percentage onto one account's row.
|
|
508
|
+
Unattributed weekly boundaries are already rendered by the ``unattributed``
|
|
509
|
+
child, which is a first-class scope after D1.
|
|
460
510
|
"""
|
|
461
511
|
roots = tuple(sorted({
|
|
462
512
|
root for root in source_root_keys if isinstance(root, str) and root
|
|
@@ -464,6 +514,10 @@ def _codex_weekly_periods(
|
|
|
464
514
|
if not roots:
|
|
465
515
|
return ()
|
|
466
516
|
placeholders = ",".join("?" for _ in roots)
|
|
517
|
+
# `quota_window_blocks.account_key` is `NOT NULL DEFAULT 'unattributed'`,
|
|
518
|
+
# so the sentinel needs no NULL branch here (unlike the cache tables).
|
|
519
|
+
account_predicate = "" if account_key is None else "AND account_key = ? "
|
|
520
|
+
account_params: tuple = () if account_key is None else (account_key,)
|
|
467
521
|
try:
|
|
468
522
|
rows = stats_conn.execute(
|
|
469
523
|
"SELECT source_root_key, logical_limit_key, limit_name, resets_at_utc, "
|
|
@@ -471,9 +525,10 @@ def _codex_weekly_periods(
|
|
|
471
525
|
"FROM quota_window_blocks "
|
|
472
526
|
"WHERE source='codex' AND window_minutes=10080 "
|
|
473
527
|
f"AND source_root_key IN ({placeholders}) AND orphaned_at IS NULL "
|
|
528
|
+
f"{account_predicate}"
|
|
474
529
|
"ORDER BY nominal_start_at_utc DESC, resets_at_utc DESC, source_root_key "
|
|
475
530
|
"LIMIT ?",
|
|
476
|
-
(*roots, SOURCE_HISTORY_LIMIT),
|
|
531
|
+
(*roots, *account_params, SOURCE_HISTORY_LIMIT),
|
|
477
532
|
).fetchall()
|
|
478
533
|
except sqlite3.Error:
|
|
479
534
|
rows = ()
|
|
@@ -1294,6 +1349,8 @@ def _quota_wire(
|
|
|
1294
1349
|
cycle: CodexCycleBoundary | None = None,
|
|
1295
1350
|
now_utc: dt.datetime | None = None,
|
|
1296
1351
|
display_tz_name: str | None = None,
|
|
1352
|
+
account_key: str | None = None,
|
|
1353
|
+
decorated: bool = False,
|
|
1297
1354
|
) -> tuple[dict[str, object], ...]:
|
|
1298
1355
|
"""Build current-cycle Codex 5-hour activity rows from durable windows.
|
|
1299
1356
|
|
|
@@ -1301,13 +1358,33 @@ def _quota_wire(
|
|
|
1301
1358
|
tokens, and model splits come from root-qualified accounting inside each
|
|
1302
1359
|
half-open 300-minute interval. Weekly quota summaries are deliberately not
|
|
1303
1360
|
activity blocks and never enter this wire.
|
|
1361
|
+
|
|
1362
|
+
#416 spec §5.2 (review F9): blocks were filtered by `source_root_key` and
|
|
1363
|
+
time ONLY, against a single `cycle` that is `cycles_all[0]` — the FIRST
|
|
1364
|
+
account's. Two accounts sharing one physical root therefore saw each other's
|
|
1365
|
+
5h blocks. `account_key` scopes both the durable block row and the
|
|
1366
|
+
accounting inside it to the block identity's account; `None` keeps the
|
|
1367
|
+
merged read, which is byte-stable. `decorated` (R8) serializes the block's
|
|
1368
|
+
own account so the client can label it; below two REAL accounts no key is
|
|
1369
|
+
added at all.
|
|
1370
|
+
|
|
1371
|
+
The account predicate here is STRICT and deliberately diverges from
|
|
1372
|
+
`_codex_five_hour_rows`, which widens the same table (#416 closeout F2).
|
|
1373
|
+
Both reads are selection reads, but the rule turns on the stamping
|
|
1374
|
+
mechanism, not on the verb: `_codex_five_hour_rows` asks "is this block
|
|
1375
|
+
inside the focused CYCLE" — a different physical-window group than the
|
|
1376
|
+
weekly key it is scoped by, so a still-`unattributed` 5h block genuinely
|
|
1377
|
+
belongs and must be admitted. This is a LISTING of the block rows
|
|
1378
|
+
themselves, keyed by the very column it filters, so widening would render
|
|
1379
|
+
one unattributed block twice, once under each real account.
|
|
1304
1380
|
"""
|
|
1305
1381
|
if cycle is None or now_utc is None:
|
|
1306
1382
|
return ()
|
|
1307
1383
|
try:
|
|
1308
1384
|
rows = stats_conn.execute(
|
|
1309
1385
|
"SELECT source_root_key, logical_limit_key, observed_slot, window_minutes, "
|
|
1310
|
-
"limit_name, resets_at_utc, nominal_start_at_utc, current_percent, orphaned_at "
|
|
1386
|
+
"limit_name, resets_at_utc, nominal_start_at_utc, current_percent, orphaned_at, "
|
|
1387
|
+
"account_key "
|
|
1311
1388
|
"FROM quota_window_blocks WHERE source='codex' AND window_minutes=300 "
|
|
1312
1389
|
"ORDER BY resets_at_utc DESC, source_root_key, logical_limit_key, observed_slot "
|
|
1313
1390
|
"LIMIT ?",
|
|
@@ -1323,7 +1400,11 @@ def _quota_wire(
|
|
|
1323
1400
|
for (
|
|
1324
1401
|
root_key, logical_limit_key, observed_slot, window_minutes,
|
|
1325
1402
|
_limit_name, resets_at_raw, nominal_start_raw, current_percent, orphaned_at,
|
|
1403
|
+
block_account,
|
|
1326
1404
|
) in rows:
|
|
1405
|
+
block_account = str(block_account or _lib_accounts.UNATTRIBUTED)
|
|
1406
|
+
if account_key is not None and block_account != account_key:
|
|
1407
|
+
continue
|
|
1327
1408
|
if orphaned_at is not None or str(root_key) not in cycle.source_root_keys:
|
|
1328
1409
|
continue
|
|
1329
1410
|
try:
|
|
@@ -1337,7 +1418,14 @@ def _quota_wire(
|
|
|
1337
1418
|
resets_at = resets_at.astimezone(UTC)
|
|
1338
1419
|
if resets_at <= cycle.start_at or start_at >= cycle.resets_at:
|
|
1339
1420
|
continue
|
|
1340
|
-
|
|
1421
|
+
# The account joins the physical dedup key only under decoration: two
|
|
1422
|
+
# accounts sharing one physical 5h window are two windows (never-combine
|
|
1423
|
+
# extends to accounts), but a <=1-real-account install must keep exactly
|
|
1424
|
+
# today's key so its wire is byte-identical.
|
|
1425
|
+
physical_key = (
|
|
1426
|
+
(str(root_key), start_at, resets_at, block_account) if decorated
|
|
1427
|
+
else (str(root_key), start_at, resets_at)
|
|
1428
|
+
)
|
|
1341
1429
|
if physical_key in seen_windows:
|
|
1342
1430
|
continue
|
|
1343
1431
|
seen_windows.add(physical_key)
|
|
@@ -1378,6 +1466,10 @@ def _quota_wire(
|
|
|
1378
1466
|
observed_slot, window_minutes, resets_at_raw,
|
|
1379
1467
|
),
|
|
1380
1468
|
"source": "codex",
|
|
1469
|
+
# R8: snake_case to match the sibling `quota.history[].account_key`
|
|
1470
|
+
# rows in this same subtree (#341 Task 4), NOT the camelCase
|
|
1471
|
+
# `accounts[].accountKey` hero-card surface.
|
|
1472
|
+
**({"account_key": block_account} if decorated else {}),
|
|
1381
1473
|
"label": c.format_display_dt(
|
|
1382
1474
|
start_at, display_tz, fmt="%H:%M %b %d", suffix=True,
|
|
1383
1475
|
),
|
|
@@ -1394,11 +1486,13 @@ def _quota_wire(
|
|
|
1394
1486
|
return tuple(wired)
|
|
1395
1487
|
|
|
1396
1488
|
|
|
1397
|
-
def _budget_wire(
|
|
1489
|
+
def _budget_wire(
|
|
1490
|
+
stats_conn: sqlite3.Connection, *, decorated: bool = False,
|
|
1491
|
+
) -> tuple[dict[str, object], ...]:
|
|
1398
1492
|
try:
|
|
1399
1493
|
rows = stats_conn.execute(
|
|
1400
1494
|
"SELECT period_start_at, period, threshold, budget_usd, spent_usd, "
|
|
1401
|
-
"consumption_pct FROM budget_milestones WHERE vendor='codex' "
|
|
1495
|
+
"consumption_pct, account_key FROM budget_milestones WHERE vendor='codex' "
|
|
1402
1496
|
"ORDER BY period_start_at DESC, threshold DESC LIMIT ?",
|
|
1403
1497
|
(SOURCE_HISTORY_LIMIT,),
|
|
1404
1498
|
).fetchall()
|
|
@@ -1411,13 +1505,21 @@ def _budget_wire(stats_conn: sqlite3.Connection) -> tuple[dict[str, object], ...
|
|
|
1411
1505
|
"budget_usd": budget_usd,
|
|
1412
1506
|
"spent_usd": spent_usd,
|
|
1413
1507
|
"consumption_pct": consumption_pct,
|
|
1414
|
-
|
|
1508
|
+
# R8 (#416 §5.5): the per-account ladder needs the key to scope a child;
|
|
1509
|
+
# below two REAL accounts nothing is added.
|
|
1510
|
+
**({"account_key": str(account_key or _CODEX_VENDOR_WIDE_ACCOUNT)}
|
|
1511
|
+
if decorated else {}),
|
|
1512
|
+
} for period_start_at, period, threshold, budget_usd, spent_usd,
|
|
1513
|
+
consumption_pct, account_key in rows)
|
|
1415
1514
|
|
|
1416
1515
|
|
|
1417
|
-
def _projected_budget_wire(
|
|
1516
|
+
def _projected_budget_wire(
|
|
1517
|
+
stats_conn: sqlite3.Connection, *, decorated: bool = False,
|
|
1518
|
+
) -> tuple[dict[str, object], ...]:
|
|
1418
1519
|
try:
|
|
1419
1520
|
rows = stats_conn.execute(
|
|
1420
|
-
"SELECT period, threshold, projected_value, denominator, crossed_at_utc,
|
|
1521
|
+
"SELECT period, threshold, projected_value, denominator, crossed_at_utc, "
|
|
1522
|
+
"alerted_at, account_key "
|
|
1421
1523
|
"FROM projected_milestones WHERE metric='codex_budget_usd' "
|
|
1422
1524
|
"ORDER BY crossed_at_utc DESC, threshold DESC LIMIT ?",
|
|
1423
1525
|
(SOURCE_HISTORY_LIMIT,),
|
|
@@ -1431,7 +1533,10 @@ def _projected_budget_wire(stats_conn: sqlite3.Connection) -> tuple[dict[str, ob
|
|
|
1431
1533
|
"denominator": denominator,
|
|
1432
1534
|
"crossed_at": crossed_at,
|
|
1433
1535
|
"alerted_at": alerted_at,
|
|
1434
|
-
|
|
1536
|
+
**({"account_key": str(account_key or _CODEX_VENDOR_WIDE_ACCOUNT)}
|
|
1537
|
+
if decorated else {}),
|
|
1538
|
+
} for period, threshold, projected_value, denominator, crossed_at, alerted_at,
|
|
1539
|
+
account_key in rows)
|
|
1435
1540
|
|
|
1436
1541
|
|
|
1437
1542
|
def _configured_codex_budget_status(
|
|
@@ -1439,6 +1544,7 @@ def _configured_codex_budget_status(
|
|
|
1439
1544
|
entries: Iterable[object],
|
|
1440
1545
|
*,
|
|
1441
1546
|
cost_events: tuple[tuple[dt.datetime, float], ...] | None = None,
|
|
1547
|
+
account_key: str | None = None,
|
|
1442
1548
|
) -> dict[str, object] | None:
|
|
1443
1549
|
"""Compute the live configured Codex budget from the coordinated entries.
|
|
1444
1550
|
|
|
@@ -1446,10 +1552,23 @@ def _configured_codex_budget_status(
|
|
|
1446
1552
|
This reuses the CLI's calendar-window and ``BudgetInputs``/status kernels
|
|
1447
1553
|
while deliberately keeping the accounting read on the caller-owned cache
|
|
1448
1554
|
snapshot.
|
|
1555
|
+
|
|
1556
|
+
``account_key`` (#416 §5.5) scopes the status to ONE account: the target is
|
|
1557
|
+
that account's own configured budget from ``budget.codex.accounts`` — never a
|
|
1558
|
+
share of the vendor amount, which would be an invented number. An account
|
|
1559
|
+
with no configured budget therefore has no budget status at all (``None``),
|
|
1560
|
+
exactly as an unconfigured vendor does; the merged vendor status stays on the
|
|
1561
|
+
parent. ``None`` keeps the merged behaviour and is byte-stable.
|
|
1449
1562
|
"""
|
|
1450
1563
|
config = context.codex_budget
|
|
1451
1564
|
if config is None:
|
|
1452
1565
|
return None
|
|
1566
|
+
amount_usd = config.get("amount_usd")
|
|
1567
|
+
if account_key is not None:
|
|
1568
|
+
per_account = config.get("accounts")
|
|
1569
|
+
if not isinstance(per_account, Mapping) or account_key not in per_account:
|
|
1570
|
+
return None
|
|
1571
|
+
amount_usd = per_account[account_key]
|
|
1453
1572
|
c = sys.modules["cctally"]
|
|
1454
1573
|
period, start_at, end_at = _configured_codex_budget_window(context)
|
|
1455
1574
|
|
|
@@ -1462,7 +1581,7 @@ def _configured_codex_budget_status(
|
|
|
1462
1581
|
|
|
1463
1582
|
recent_start = max(start_at, context.now_utc - dt.timedelta(hours=24))
|
|
1464
1583
|
inputs = c.BudgetInputs(
|
|
1465
|
-
target_usd=float(
|
|
1584
|
+
target_usd=float(amount_usd),
|
|
1466
1585
|
spent_usd=_sum_cost(start_at, context.now_utc),
|
|
1467
1586
|
recent_24h_usd=_sum_cost(recent_start, context.now_utc),
|
|
1468
1587
|
week_start_at=start_at,
|
|
@@ -1512,13 +1631,52 @@ def _configured_codex_budget_window(
|
|
|
1512
1631
|
return period, start_at.astimezone(UTC), end_at.astimezone(UTC)
|
|
1513
1632
|
|
|
1514
1633
|
|
|
1634
|
+
def _codex_account_admits(scope_key: str | None, row_key: object) -> bool:
|
|
1635
|
+
"""Whether an account-scoped quota read admits ``row_key`` (#416 B2).
|
|
1636
|
+
|
|
1637
|
+
One-directional widening, identical to ``_codex_five_hour_rows``
|
|
1638
|
+
(``bin/_cctally_milestone_history.py``): a REAL account admits its own rows
|
|
1639
|
+
plus the ``unattributed`` sentinel, because
|
|
1640
|
+
``adopt_unidentified_observations`` resolves attribution PER physical-window
|
|
1641
|
+
group — a decorated install can legitimately carry the weekly window under a
|
|
1642
|
+
real account while its 5h windows are still unattributed, and strict
|
|
1643
|
+
equality would then correlate nothing. The widening is one-directional on
|
|
1644
|
+
purpose: an ``unattributed`` scope never picks up another account's
|
|
1645
|
+
identified rows, so no REAL account's number ever reaches another's row.
|
|
1646
|
+
|
|
1647
|
+
``scope_key is None`` is the merged read and admits everything.
|
|
1648
|
+
"""
|
|
1649
|
+
if scope_key is None:
|
|
1650
|
+
return True
|
|
1651
|
+
key = str(row_key or _lib_accounts.UNATTRIBUTED)
|
|
1652
|
+
if scope_key == _lib_accounts.UNATTRIBUTED:
|
|
1653
|
+
return key == _lib_accounts.UNATTRIBUTED
|
|
1654
|
+
return key in (scope_key, _lib_accounts.UNATTRIBUTED)
|
|
1655
|
+
|
|
1656
|
+
|
|
1515
1657
|
def _quota_read_model(
|
|
1516
1658
|
context: DashboardReadContext,
|
|
1517
1659
|
observations: Iterable[object],
|
|
1518
1660
|
*,
|
|
1519
1661
|
accounting_entries: Iterable[object] = (),
|
|
1662
|
+
account_key: str | None = None,
|
|
1520
1663
|
) -> dict[str, object]:
|
|
1521
|
-
"""Use S2's pure history/block/forecast kernels over cache evidence.
|
|
1664
|
+
"""Use S2's pure history/block/forecast kernels over cache evidence.
|
|
1665
|
+
|
|
1666
|
+
``account_key`` (#416 Slice 3A review B2/F4) scopes the two reads this
|
|
1667
|
+
function reaches that the observation partition does NOT already cover: the
|
|
1668
|
+
durable milestone breakdown (``codex_quota_breakdown``, whose accounting and
|
|
1669
|
+
block-start reads filter by root and time only) and the 5h correlation load
|
|
1670
|
+
below. ``None`` keeps the merged parent read, byte-stable.
|
|
1671
|
+
|
|
1672
|
+
The key passed on is POST-fold — it names a registry account or an
|
|
1673
|
+
``obs_partition`` bucket, and ``load_codex_quota_observations`` applies
|
|
1674
|
+
``adopt_unidentified_observations`` before returning — while
|
|
1675
|
+
``codex_quota_breakdown``'s block-start boundary reads the PRE-fold
|
|
1676
|
+
``quota_window_snapshots``. That read widens (#416 closeout F1); its
|
|
1677
|
+
accounting read stays strict so the children keep partitioning the parent's
|
|
1678
|
+
spend. Neither is elected here — the kernel settles both.
|
|
1679
|
+
"""
|
|
1522
1680
|
quota_observations = tuple(observations)
|
|
1523
1681
|
cost_entries = tuple(accounting_entries)
|
|
1524
1682
|
histories = build_history(quota_observations)
|
|
@@ -1628,6 +1786,7 @@ def _quota_read_model(
|
|
|
1628
1786
|
speed=context.speed,
|
|
1629
1787
|
cache_conn=context.cache_conn,
|
|
1630
1788
|
stats_conn=context.stats_conn,
|
|
1789
|
+
account_key=account_key,
|
|
1631
1790
|
)
|
|
1632
1791
|
except sqlite3.Error:
|
|
1633
1792
|
# Older or partially migrated stores retain the bounded
|
|
@@ -1636,6 +1795,9 @@ def _quota_read_model(
|
|
|
1636
1795
|
canonical_rows = ()
|
|
1637
1796
|
if canonical_rows:
|
|
1638
1797
|
try:
|
|
1798
|
+
# #416 Slice 3A review F4: this load is bounded by root, slot
|
|
1799
|
+
# and `limit_id` only, so under focus the crossing was annotated
|
|
1800
|
+
# with whichever ACCOUNT's 5h observation happened to sort last.
|
|
1639
1801
|
correlated_five_hour = tuple(
|
|
1640
1802
|
observation
|
|
1641
1803
|
for observation in load_codex_quota_observations(
|
|
@@ -1646,6 +1808,8 @@ def _quota_read_model(
|
|
|
1646
1808
|
if observation.identity.window_minutes == 300
|
|
1647
1809
|
and observation.identity.observed_slot == identity.observed_slot
|
|
1648
1810
|
and observation.identity.limit_id == identity.limit_id
|
|
1811
|
+
and _codex_account_admits(
|
|
1812
|
+
account_key, observation.identity.account_key)
|
|
1649
1813
|
)
|
|
1650
1814
|
except sqlite3.Error:
|
|
1651
1815
|
correlated_five_hour = ()
|
|
@@ -2038,12 +2202,30 @@ def refresh_codex_source_clock(
|
|
|
2038
2202
|
return state if refreshed_state == state else refreshed_state
|
|
2039
2203
|
|
|
2040
2204
|
|
|
2041
|
-
def _alerts_wire(
|
|
2042
|
-
|
|
2205
|
+
def _alerts_wire(
|
|
2206
|
+
stats_conn: sqlite3.Connection, *, decorated: bool = False,
|
|
2207
|
+
) -> tuple[dict[str, object], ...]:
|
|
2208
|
+
"""Return only safe, source-owned Codex alert context in newest-first order.
|
|
2209
|
+
|
|
2210
|
+
#416 spec §5.4 (review F14): the underlying tables all carry an account key —
|
|
2211
|
+
including the vendor-wide ``*`` rows — but this wire neither selected nor
|
|
2212
|
+
emitted it, so removing the `alerts-unfiltered-note` disclaimer badge without
|
|
2213
|
+
this would silently show one account another's alerts. `account_key` is now
|
|
2214
|
+
selected on all three legs and serialized under decoration (R8: below two
|
|
2215
|
+
REAL accounts no key is added, so the envelope is byte-identical).
|
|
2216
|
+
|
|
2217
|
+
Vendor-wide ``*`` rows keep that literal key rather than being dropped or
|
|
2218
|
+
reassigned: a vendor-wide budget crossing is not attributable to one account,
|
|
2219
|
+
so it stays visible under focus and the client labels it as vendor-wide.
|
|
2220
|
+
"""
|
|
2043
2221
|
rows: list[dict[str, object]] = []
|
|
2222
|
+
|
|
2223
|
+
def _account(value: object) -> dict[str, object]:
|
|
2224
|
+
return {"account_key": str(value or _CODEX_VENDOR_WIDE_ACCOUNT)} if decorated else {}
|
|
2225
|
+
|
|
2044
2226
|
try:
|
|
2045
|
-
for period, threshold, consumption_pct, crossed_at in stats_conn.execute(
|
|
2046
|
-
"SELECT period, threshold, consumption_pct, crossed_at_utc "
|
|
2227
|
+
for period, threshold, consumption_pct, crossed_at, account_key in stats_conn.execute(
|
|
2228
|
+
"SELECT period, threshold, consumption_pct, crossed_at_utc, account_key "
|
|
2047
2229
|
"FROM budget_milestones WHERE vendor='codex' AND alerted_at IS NOT NULL "
|
|
2048
2230
|
"ORDER BY crossed_at_utc DESC, threshold DESC LIMIT ?",
|
|
2049
2231
|
(SOURCE_HISTORY_LIMIT,),
|
|
@@ -2053,9 +2235,10 @@ def _alerts_wire(stats_conn: sqlite3.Connection) -> tuple[dict[str, object], ...
|
|
|
2053
2235
|
"source": "codex",
|
|
2054
2236
|
"axis": "codex_budget", "period": period, "threshold": threshold,
|
|
2055
2237
|
"value": consumption_pct, "created_at": crossed_at,
|
|
2238
|
+
**_account(account_key),
|
|
2056
2239
|
})
|
|
2057
|
-
for period, threshold, projected_value, crossed_at in stats_conn.execute(
|
|
2058
|
-
"SELECT period, threshold, projected_value, crossed_at_utc "
|
|
2240
|
+
for period, threshold, projected_value, crossed_at, account_key in stats_conn.execute(
|
|
2241
|
+
"SELECT period, threshold, projected_value, crossed_at_utc, account_key "
|
|
2059
2242
|
"FROM projected_milestones WHERE metric='codex_budget_usd' AND alerted_at IS NOT NULL "
|
|
2060
2243
|
"ORDER BY crossed_at_utc DESC, threshold DESC LIMIT ?",
|
|
2061
2244
|
(SOURCE_HISTORY_LIMIT,),
|
|
@@ -2065,10 +2248,12 @@ def _alerts_wire(stats_conn: sqlite3.Connection) -> tuple[dict[str, object], ...
|
|
|
2065
2248
|
"source": "codex",
|
|
2066
2249
|
"axis": "projected", "period": period, "threshold": threshold,
|
|
2067
2250
|
"value": projected_value, "created_at": crossed_at,
|
|
2251
|
+
**_account(account_key),
|
|
2068
2252
|
})
|
|
2069
|
-
for root_key, logical_key, observed_slot, window_minutes, resets_at,
|
|
2253
|
+
for (root_key, logical_key, observed_slot, window_minutes, resets_at,
|
|
2254
|
+
threshold, severity, created_at, account_key) in stats_conn.execute(
|
|
2070
2255
|
"SELECT source_root_key, logical_limit_key, observed_slot, window_minutes, resets_at_utc, "
|
|
2071
|
-
"threshold, severity, created_at_utc FROM quota_threshold_events "
|
|
2256
|
+
"threshold, severity, created_at_utc, account_key FROM quota_threshold_events "
|
|
2072
2257
|
"WHERE source='codex' AND disposition='alerted' AND orphaned_at IS NULL "
|
|
2073
2258
|
"ORDER BY created_at_utc DESC, source_root_key, logical_limit_key, observed_slot, threshold "
|
|
2074
2259
|
"LIMIT ?",
|
|
@@ -2082,6 +2267,7 @@ def _alerts_wire(stats_conn: sqlite3.Connection) -> tuple[dict[str, object], ...
|
|
|
2082
2267
|
"source": "codex",
|
|
2083
2268
|
"axis": "quota", "threshold": threshold, "severity": severity,
|
|
2084
2269
|
"created_at": created_at,
|
|
2270
|
+
**_account(account_key),
|
|
2085
2271
|
})
|
|
2086
2272
|
except sqlite3.Error:
|
|
2087
2273
|
return ()
|
|
@@ -2276,12 +2462,18 @@ def _build_codex_native_weekly_view(
|
|
|
2276
2462
|
now_utc: dt.datetime,
|
|
2277
2463
|
display_tz_name: str | None,
|
|
2278
2464
|
speed: str,
|
|
2465
|
+
account_key: str | None = None,
|
|
2279
2466
|
) -> CodexWeeklyView:
|
|
2280
|
-
"""Aggregate Codex cost into observed native quota-cycle segments.
|
|
2467
|
+
"""Aggregate Codex cost into observed native quota-cycle segments.
|
|
2468
|
+
|
|
2469
|
+
``account_key`` scopes the durable boundary read to one account (#416
|
|
2470
|
+
Slice 3A review B1); ``None`` is the merged parent read and is byte-stable.
|
|
2471
|
+
"""
|
|
2281
2472
|
periods = _codex_weekly_periods(
|
|
2282
2473
|
stats_conn,
|
|
2283
2474
|
source_root_keys=source_root_keys,
|
|
2284
2475
|
active_cycle=active_cycle,
|
|
2476
|
+
account_key=account_key,
|
|
2285
2477
|
)
|
|
2286
2478
|
converted: list[CodexEntry] = []
|
|
2287
2479
|
bucket_by_entry: dict[int, str] = {}
|
|
@@ -2416,6 +2608,11 @@ def _codex_accounts_wire(
|
|
|
2416
2608
|
or _lib_accounts.UNATTRIBUTED in five_hour
|
|
2417
2609
|
):
|
|
2418
2610
|
ordered_keys.append(_lib_accounts.UNATTRIBUTED)
|
|
2611
|
+
# #416 §6: population-aware labels, so two Codex accounts that auto-label to
|
|
2612
|
+
# one email do not render two identical chips. Collision-only (D5): a
|
|
2613
|
+
# non-colliding label is untouched.
|
|
2614
|
+
_codex_label_map = _cctally_account.display_label_map(
|
|
2615
|
+
context.stats_conn, "codex")
|
|
2419
2616
|
|
|
2420
2617
|
def _totals(rows: tuple[object, ...]) -> dict[str, object]:
|
|
2421
2618
|
entries = _codex_entries_from_accounting(rows)
|
|
@@ -2457,7 +2654,7 @@ def _codex_accounts_wire(
|
|
|
2457
2654
|
totals = _totals(rows)
|
|
2458
2655
|
card: dict[str, object] = {
|
|
2459
2656
|
"accountKey": key,
|
|
2460
|
-
"label": _cctally_account.account_label(context.stats_conn, key),
|
|
2657
|
+
"label": _codex_label_map.get(key) or _cctally_account.account_label(context.stats_conn, key),
|
|
2461
2658
|
"plan": plan_by_key.get(key),
|
|
2462
2659
|
"active": key in active_keys,
|
|
2463
2660
|
"weeklyPercent": (
|
|
@@ -2486,6 +2683,270 @@ def _codex_accounts_wire(
|
|
|
2486
2683
|
return accounts_wire, hero_cycles_wire
|
|
2487
2684
|
|
|
2488
2685
|
|
|
2686
|
+
def _codex_partition_by_account(
|
|
2687
|
+
entries: Iterable[object],
|
|
2688
|
+
) -> dict[str, tuple[object, ...]]:
|
|
2689
|
+
"""Partition already-loaded Codex accounting rows by their stamped account.
|
|
2690
|
+
|
|
2691
|
+
#416 spec §5.2/§5.3 (review F9/F10). The account axis is added by splitting
|
|
2692
|
+
rows that are ALREADY in memory and re-running the SHIPPED builders per
|
|
2693
|
+
partition — not by threading an account through `CodexEntry`,
|
|
2694
|
+
`QualifiedCodexEntry`'s grouping keys, or the shared aggregator kernels. Two
|
|
2695
|
+
consequences, both load-bearing:
|
|
2696
|
+
|
|
2697
|
+
* the merged parent is byte-identical BY CONSTRUCTION, because the parent's
|
|
2698
|
+
code path is literally unchanged; and
|
|
2699
|
+
* `_aggregate_codex_buckets` accumulates in encounter order and preserves
|
|
2700
|
+
first-seen model order plus merged `model_breakdowns`, so ENCOUNTER ORDER
|
|
2701
|
+
IS PRESERVED within each partition here. Sorting, or partitioning through
|
|
2702
|
+
a set, would move a bucket's `models` order for free.
|
|
2703
|
+
|
|
2704
|
+
`NULL ≡ unattributed` — a row with no stamp lands in the reserved sentinel
|
|
2705
|
+
bucket, which stays selectable because after D1 it holds the bulk of Codex
|
|
2706
|
+
history.
|
|
2707
|
+
"""
|
|
2708
|
+
buckets: dict[str, list[object]] = {}
|
|
2709
|
+
for entry in entries:
|
|
2710
|
+
key = str(
|
|
2711
|
+
getattr(entry, "account_key", "") or _lib_accounts.UNATTRIBUTED)
|
|
2712
|
+
buckets.setdefault(key, []).append(entry)
|
|
2713
|
+
return {key: tuple(values) for key, values in buckets.items()}
|
|
2714
|
+
|
|
2715
|
+
|
|
2716
|
+
def _codex_account_scopes_wire(
|
|
2717
|
+
context: DashboardReadContext,
|
|
2718
|
+
*,
|
|
2719
|
+
account_keys: Iterable[str],
|
|
2720
|
+
quota_observations: Iterable[object],
|
|
2721
|
+
cycle_by_account: Mapping[str, "CodexCycleBoundary"],
|
|
2722
|
+
visible_accounting_entries: Iterable[object],
|
|
2723
|
+
active_roots: Iterable[str],
|
|
2724
|
+
accounting_end: dt.datetime,
|
|
2725
|
+
metadata_incomplete: bool,
|
|
2726
|
+
conversation_metadata: Mapping[tuple[str, str], Mapping[str, object]],
|
|
2727
|
+
alerts: Iterable[Mapping[str, object]],
|
|
2728
|
+
budget_milestones: Iterable[Mapping[str, object]],
|
|
2729
|
+
projected_budget_milestones: Iterable[Mapping[str, object]],
|
|
2730
|
+
budget_cost_events_by_account: Mapping[str, tuple[tuple[dt.datetime, float], ...]],
|
|
2731
|
+
private_session_labels: dict[str, str],
|
|
2732
|
+
hero_failure: bool = False,
|
|
2733
|
+
) -> dict[str, dict[str, object]]:
|
|
2734
|
+
"""The per-account CHILDREN of the merged Codex read model (spec §5.3).
|
|
2735
|
+
|
|
2736
|
+
Caller must gate on `provider_is_decorated(stats_conn, "codex")` — this
|
|
2737
|
+
builds nothing for a <=1-real-account install, so the whole surface is ABSENT
|
|
2738
|
+
rather than present-and-empty and the envelope stays byte-identical (R8).
|
|
2739
|
+
|
|
2740
|
+
Each child mirrors the parent's own key shape (`periods` / `sessions` /
|
|
2741
|
+
`projects` / `cache_report` / `budget` / `quota` / `alerts`) so one client
|
|
2742
|
+
selector can return a structurally identical object for "All accounts" (the
|
|
2743
|
+
parent) and for a focused account (its child). Nothing is summed on the
|
|
2744
|
+
client: §5.3 established that scalar summation cannot reconstruct `models` /
|
|
2745
|
+
`model_breakdowns` and that weekly `used_pct` / `dollar_per_pct` are not
|
|
2746
|
+
additive at all.
|
|
2747
|
+
|
|
2748
|
+
`is_empty` is the explicit empty state from §6 and acceptance criterion 2 —
|
|
2749
|
+
an account with no evidence renders blank rather than the PREVIOUS account's
|
|
2750
|
+
numbers, which is the literal reported symptom.
|
|
2751
|
+
|
|
2752
|
+
Every read a child reaches that is NOT already covered by the in-memory
|
|
2753
|
+
partition is account-scoped explicitly (#416 Slice 3A review B1/B2/B3):
|
|
2754
|
+
`_codex_weekly_periods`, `_quota_wire`, `codex_quota_breakdown` (its block
|
|
2755
|
+
boundary AND its accounting), the 5h correlation load, and the cycle index.
|
|
2756
|
+
A read that filters by root, time or slot but not by account is the defect
|
|
2757
|
+
CLASS this section exists to close — `quota_window_blocks` and
|
|
2758
|
+
`quota_window_snapshots` both carry two rows when two accounts share one
|
|
2759
|
+
physical root.
|
|
2760
|
+
"""
|
|
2761
|
+
visible = tuple(visible_accounting_entries)
|
|
2762
|
+
observations = tuple(quota_observations)
|
|
2763
|
+
partition = _codex_partition_by_account(visible)
|
|
2764
|
+
obs_partition: dict[str, list[object]] = {}
|
|
2765
|
+
for observation in observations:
|
|
2766
|
+
obs_partition.setdefault(
|
|
2767
|
+
observation.identity.account_key, []).append(observation)
|
|
2768
|
+
alert_rows = tuple(alerts)
|
|
2769
|
+
budget_rows = tuple(budget_milestones)
|
|
2770
|
+
projected_rows = tuple(projected_budget_milestones)
|
|
2771
|
+
roots = tuple(active_roots)
|
|
2772
|
+
|
|
2773
|
+
def _for_account(key: str) -> dict[str, object]:
|
|
2774
|
+
rows = partition.get(key, ())
|
|
2775
|
+
account_observations = tuple(obs_partition.get(key, ()))
|
|
2776
|
+
entries = _codex_entries_from_accounting(rows)
|
|
2777
|
+
cycle = cycle_by_account.get(key)
|
|
2778
|
+
sessions_view = (
|
|
2779
|
+
build_rooted_codex_session_view(
|
|
2780
|
+
rows, now_utc=context.now_utc,
|
|
2781
|
+
tz_name=context.display_tz_name, speed=context.speed,
|
|
2782
|
+
)
|
|
2783
|
+
if metadata_incomplete else build_codex_session_view(
|
|
2784
|
+
entries, now_utc=context.now_utc,
|
|
2785
|
+
tz_name=context.display_tz_name, speed=context.speed,
|
|
2786
|
+
)
|
|
2787
|
+
)
|
|
2788
|
+
quota = _quota_read_model(
|
|
2789
|
+
context, account_observations, accounting_entries=rows,
|
|
2790
|
+
account_key=key,
|
|
2791
|
+
)
|
|
2792
|
+
# #416 Slice 3A review B3. The parent sets `quota.cycle_index` and the
|
|
2793
|
+
# client reads `codex.quota.cycle_index`, so a child without the key
|
|
2794
|
+
# forces the client into a fallback — and the tempting one (reuse the
|
|
2795
|
+
# parent's) would render account A's milestone HISTORY on account B's
|
|
2796
|
+
# hero. The index is derivable per account from what the child already
|
|
2797
|
+
# has (its own `CodexCycleBoundary` plus `stats_conn`), so it is built
|
|
2798
|
+
# genuinely rather than declared parent-only. No cycle => `()`, an
|
|
2799
|
+
# honest empty state, never another account's ledger.
|
|
2800
|
+
cycle_index: tuple = ()
|
|
2801
|
+
if cycle is not None and not hero_failure:
|
|
2802
|
+
try:
|
|
2803
|
+
cycle_index = tuple(
|
|
2804
|
+
sys.modules["cctally"].build_codex_cycle_index(
|
|
2805
|
+
context.stats_conn, identity=cycle,
|
|
2806
|
+
now_utc=context.now_utc, account_key=key,
|
|
2807
|
+
)
|
|
2808
|
+
)
|
|
2809
|
+
except sqlite3.Error:
|
|
2810
|
+
cycle_index = ()
|
|
2811
|
+
quota = {
|
|
2812
|
+
**quota,
|
|
2813
|
+
"blocks": _quota_wire(
|
|
2814
|
+
context.stats_conn, accounting_entries=rows, cycle=cycle,
|
|
2815
|
+
now_utc=context.now_utc,
|
|
2816
|
+
display_tz_name=context.display_tz_name,
|
|
2817
|
+
account_key=key, decorated=True,
|
|
2818
|
+
),
|
|
2819
|
+
"cycle_index": cycle_index,
|
|
2820
|
+
}
|
|
2821
|
+
return {
|
|
2822
|
+
# An account is empty when it owns neither accounting rows nor quota
|
|
2823
|
+
# evidence. Both axes matter: a brand-new account can carry a live
|
|
2824
|
+
# quota window with no spend yet, and a retired one the reverse.
|
|
2825
|
+
"is_empty": not rows and not account_observations,
|
|
2826
|
+
"periods": {
|
|
2827
|
+
"daily": _period_wire(build_codex_daily_view(
|
|
2828
|
+
entries, now_utc=context.now_utc,
|
|
2829
|
+
tz_name=context.display_tz_name, speed=context.speed,
|
|
2830
|
+
)),
|
|
2831
|
+
"monthly": _period_wire(build_codex_monthly_view(
|
|
2832
|
+
entries, now_utc=context.now_utc,
|
|
2833
|
+
tz_name=context.display_tz_name, speed=context.speed,
|
|
2834
|
+
)),
|
|
2835
|
+
"weekly": _period_wire(_build_codex_native_weekly_view(
|
|
2836
|
+
context.stats_conn, rows, source_root_keys=roots,
|
|
2837
|
+
active_cycle=cycle, now_utc=context.now_utc,
|
|
2838
|
+
display_tz_name=context.display_tz_name, speed=context.speed,
|
|
2839
|
+
account_key=key,
|
|
2840
|
+
)),
|
|
2841
|
+
},
|
|
2842
|
+
"sessions": _session_wire(
|
|
2843
|
+
sessions_view, metadata=conversation_metadata,
|
|
2844
|
+
private_labels=private_session_labels,
|
|
2845
|
+
),
|
|
2846
|
+
"projects": (
|
|
2847
|
+
_partial_projects_wire(rows, conversation_metadata)
|
|
2848
|
+
if metadata_incomplete else _projects_wire(
|
|
2849
|
+
context, account_observations, rows,
|
|
2850
|
+
accounting_end=accounting_end,
|
|
2851
|
+
)
|
|
2852
|
+
),
|
|
2853
|
+
"cache_report": _codex_cache_report_wire(
|
|
2854
|
+
rows, metadata=conversation_metadata, now_utc=context.now_utc,
|
|
2855
|
+
display_tz_name=context.display_tz_name, speed=context.speed,
|
|
2856
|
+
anomaly_threshold_pp=context.cache_report_anomaly_threshold_pp,
|
|
2857
|
+
),
|
|
2858
|
+
"budget": {
|
|
2859
|
+
"status": _configured_codex_budget_status(
|
|
2860
|
+
context, rows,
|
|
2861
|
+
cost_events=budget_cost_events_by_account.get(key, ()),
|
|
2862
|
+
account_key=key,
|
|
2863
|
+
),
|
|
2864
|
+
"milestones": tuple(_codex_account_scoped_rows(budget_rows, key)),
|
|
2865
|
+
"projected": tuple(_codex_account_scoped_rows(projected_rows, key)),
|
|
2866
|
+
},
|
|
2867
|
+
"quota": quota,
|
|
2868
|
+
"alerts": {
|
|
2869
|
+
"rows": tuple(_codex_account_scoped_rows(alert_rows, key)),
|
|
2870
|
+
"actual_thresholds": context.codex_quota_actual_thresholds,
|
|
2871
|
+
"projected_thresholds": context.codex_quota_projected_thresholds,
|
|
2872
|
+
},
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
# #416 Slice 3A review B4. The requested key set comes from the stats
|
|
2876
|
+
# `accounts` REGISTRY (the hero cards) while the two partitions above key
|
|
2877
|
+
# off the DATA (`codex_session_entries.account_key` and each observation's
|
|
2878
|
+
# identity). Cache/stats drift — a stats rebuild that has not re-registered
|
|
2879
|
+
# an account, or a key stamped by a newer binary — therefore left rows in a
|
|
2880
|
+
# bucket with NO scope, so the union of the children was silently LESS than
|
|
2881
|
+
# the parent with no warning. Residual data keys become scopes of their own:
|
|
2882
|
+
# nothing is folded into `unattributed` (that would misattribute a KNOWN
|
|
2883
|
+
# key, which D1 forbids) and nothing is dropped. Such a scope has no hero
|
|
2884
|
+
# card, so it is simply not chip-selectable until the registry catches up —
|
|
2885
|
+
# a safe degrade, never a silent loss.
|
|
2886
|
+
#
|
|
2887
|
+
# #416 closeout F2: the durable projection is the THIRD axis. Those two
|
|
2888
|
+
# partitions cover only the rows this build loaded, and both loads are
|
|
2889
|
+
# bounded (`DASHBOARD_QUOTA_RECENT_DAYS` / `DASHBOARD_QUOTA_OBSERVATION_-
|
|
2890
|
+
# LIMIT`, and the visible accounting window) while `quota_window_blocks`
|
|
2891
|
+
# retains its account stamp indefinitely. A key that survives only there —
|
|
2892
|
+
# an older cycle, or a cache pruned behind a retained projection — was in no
|
|
2893
|
+
# bucket at all, which is B4's own failure on the axis B4 missed.
|
|
2894
|
+
ordered_keys = list(dict.fromkeys(str(key) for key in account_keys))
|
|
2895
|
+
residual_keys = sorted(
|
|
2896
|
+
(set(partition) | set(obs_partition) | _codex_block_account_keys(
|
|
2897
|
+
context.stats_conn, roots)) - set(ordered_keys)
|
|
2898
|
+
)
|
|
2899
|
+
return {key: _for_account(key) for key in ordered_keys + residual_keys}
|
|
2900
|
+
|
|
2901
|
+
|
|
2902
|
+
def _codex_block_account_keys(
|
|
2903
|
+
stats_conn: sqlite3.Connection, roots: Iterable[str],
|
|
2904
|
+
) -> set[str]:
|
|
2905
|
+
"""Every account stamped on a retained Codex block over ``roots`` (#416 F2).
|
|
2906
|
+
|
|
2907
|
+
Scoped to the child-visible cycle roots, so an unrelated root's account
|
|
2908
|
+
cannot manufacture a scope. `quota_window_blocks.account_key` is NOT NULL
|
|
2909
|
+
DEFAULT `unattributed`, and the `or UNATTRIBUTED` is belt-and-suspenders for
|
|
2910
|
+
a store written before that default landed. A read failure degrades to the
|
|
2911
|
+
two in-memory axes rather than failing the whole child build.
|
|
2912
|
+
"""
|
|
2913
|
+
root_keys = tuple(dict.fromkeys(str(root) for root in roots))
|
|
2914
|
+
if not root_keys:
|
|
2915
|
+
return set()
|
|
2916
|
+
placeholders = ",".join("?" for _ in root_keys)
|
|
2917
|
+
try:
|
|
2918
|
+
return {
|
|
2919
|
+
str(row[0] or _lib_accounts.UNATTRIBUTED)
|
|
2920
|
+
for row in stats_conn.execute(
|
|
2921
|
+
"SELECT DISTINCT account_key FROM quota_window_blocks "
|
|
2922
|
+
"WHERE source='codex' AND orphaned_at IS NULL "
|
|
2923
|
+
f"AND source_root_key IN ({placeholders})",
|
|
2924
|
+
root_keys,
|
|
2925
|
+
)
|
|
2926
|
+
}
|
|
2927
|
+
except sqlite3.Error:
|
|
2928
|
+
return set()
|
|
2929
|
+
|
|
2930
|
+
|
|
2931
|
+
def _codex_account_scoped_rows(
|
|
2932
|
+
rows: Iterable[Mapping[str, object]], account_key: str,
|
|
2933
|
+
) -> list[Mapping[str, object]]:
|
|
2934
|
+
"""Rows this account owns, PLUS the vendor-wide ``*`` rows (spec §5.4).
|
|
2935
|
+
|
|
2936
|
+
A vendor-wide budget crossing is not attributable to one account, so hiding
|
|
2937
|
+
it under focus would silently drop a real alert. It stays visible and keeps
|
|
2938
|
+
its ``account_key == "*"`` so the client can label it as vendor-wide rather
|
|
2939
|
+
than as this account's.
|
|
2940
|
+
"""
|
|
2941
|
+
return [
|
|
2942
|
+
row for row in rows
|
|
2943
|
+
if row.get("account_key") in (account_key, _CODEX_VENDOR_WIDE_ACCOUNT)
|
|
2944
|
+
]
|
|
2945
|
+
|
|
2946
|
+
|
|
2947
|
+
_CODEX_VENDOR_WIDE_ACCOUNT = "*"
|
|
2948
|
+
|
|
2949
|
+
|
|
2489
2950
|
def _claude_accounts_wire(
|
|
2490
2951
|
stats_conn: sqlite3.Connection,
|
|
2491
2952
|
*,
|
|
@@ -2527,6 +2988,8 @@ def _claude_accounts_wire(
|
|
|
2527
2988
|
).fetchone()
|
|
2528
2989
|
return float(row[0]) if row is not None and row[0] is not None else 0.0
|
|
2529
2990
|
|
|
2991
|
+
_claude_label_map = _cctally_account.display_label_map(stats_conn, "claude")
|
|
2992
|
+
|
|
2530
2993
|
# Include the unattributed bucket last iff it retained any snapshot.
|
|
2531
2994
|
unattr_usage = _latest_usage(_lib_accounts.UNATTRIBUTED)
|
|
2532
2995
|
unattr_cost = stats_conn.execute(
|
|
@@ -2545,7 +3008,7 @@ def _claude_accounts_wire(
|
|
|
2545
3008
|
resets_at = usage[2] if usage is not None else None
|
|
2546
3009
|
card: dict[str, object] = {
|
|
2547
3010
|
"accountKey": key,
|
|
2548
|
-
"label": _cctally_account.account_label(stats_conn, key),
|
|
3011
|
+
"label": _claude_label_map.get(key) or _cctally_account.account_label(stats_conn, key),
|
|
2549
3012
|
"plan": plan_by_key.get(key),
|
|
2550
3013
|
"active": key in active_keys,
|
|
2551
3014
|
"weeklyPercent": None if is_unattributed else weekly_pct,
|
|
@@ -2715,12 +3178,24 @@ def build_codex_source_state(
|
|
|
2715
3178
|
quota_observations,
|
|
2716
3179
|
accounting_entries=visible_accounting_entries,
|
|
2717
3180
|
)
|
|
3181
|
+
# R8 gate, resolved ONCE and threaded (#341 Task 4 / #416 §5.8). Every
|
|
3182
|
+
# per-account decoration below — block/alert/budget `account_key`, the
|
|
3183
|
+
# `accounts[]` cards, `hero.cycles[]`, and the `account_scopes` children —
|
|
3184
|
+
# hangs off this single boolean, so a <=1-real-account install is provably
|
|
3185
|
+
# byte-identical by construction rather than by golden observation.
|
|
3186
|
+
try:
|
|
3187
|
+
import _cctally_account
|
|
3188
|
+
_codex_decorated = _cctally_account.provider_is_decorated(
|
|
3189
|
+
context.stats_conn, "codex")
|
|
3190
|
+
except Exception:
|
|
3191
|
+
_codex_decorated = False
|
|
2718
3192
|
quota_blocks = _quota_wire(
|
|
2719
3193
|
context.stats_conn,
|
|
2720
3194
|
accounting_entries=visible_accounting_entries,
|
|
2721
3195
|
cycle=cycle,
|
|
2722
3196
|
now_utc=context.now_utc,
|
|
2723
3197
|
display_tz_name=context.display_tz_name,
|
|
3198
|
+
decorated=_codex_decorated,
|
|
2724
3199
|
)
|
|
2725
3200
|
# Hero-modal historical-milestone navigation index (spec §1c, §3). Built
|
|
2726
3201
|
# here on the non-idle codex source rebuild (idle ticks reuse the stored
|
|
@@ -2737,8 +3212,9 @@ def build_codex_source_state(
|
|
|
2737
3212
|
except sqlite3.Error:
|
|
2738
3213
|
cycle_index = ()
|
|
2739
3214
|
quota = {**quota, "blocks": quota_blocks, "cycle_index": cycle_index}
|
|
2740
|
-
budget_rows = _budget_wire(context.stats_conn)
|
|
2741
|
-
projected_budget_rows = _projected_budget_wire(
|
|
3215
|
+
budget_rows = _budget_wire(context.stats_conn, decorated=_codex_decorated)
|
|
3216
|
+
projected_budget_rows = _projected_budget_wire(
|
|
3217
|
+
context.stats_conn, decorated=_codex_decorated)
|
|
2742
3218
|
budget_cost_events = _codex_budget_cost_events(context, budget_entries)
|
|
2743
3219
|
configured_budget = _configured_codex_budget_status(
|
|
2744
3220
|
context, budget_entries, cost_events=budget_cost_events,
|
|
@@ -2761,7 +3237,16 @@ def build_codex_source_state(
|
|
|
2761
3237
|
accounting_end=accounting_end,
|
|
2762
3238
|
)
|
|
2763
3239
|
)
|
|
2764
|
-
alerts = _alerts_wire(context.stats_conn)
|
|
3240
|
+
alerts = _alerts_wire(context.stats_conn, decorated=_codex_decorated)
|
|
3241
|
+
# Built here, BEFORE the children, so the parent's session wire is the first
|
|
3242
|
+
# writer into `private_session_labels` and the children (strict subsets of
|
|
3243
|
+
# the parent's rows) can only ever re-derive the same entries.
|
|
3244
|
+
private_session_labels: dict[str, str] = {}
|
|
3245
|
+
sessions_wire = _session_wire(
|
|
3246
|
+
sessions,
|
|
3247
|
+
metadata=conversation_metadata,
|
|
3248
|
+
private_labels=private_session_labels,
|
|
3249
|
+
)
|
|
2765
3250
|
availability = (
|
|
2766
3251
|
"partial" if metadata_incomplete or hero_failure
|
|
2767
3252
|
else ("ok" if (entries or quota_blocks or budget_rows) else "empty")
|
|
@@ -2796,12 +3281,7 @@ def build_codex_source_state(
|
|
|
2796
3281
|
# projection (client-side chip filter); the hero renders per-account cards.
|
|
2797
3282
|
accounts_wire: list[dict[str, object]] = []
|
|
2798
3283
|
hero_cycles_wire: list[dict[str, object]] = []
|
|
2799
|
-
|
|
2800
|
-
import _cctally_account
|
|
2801
|
-
_codex_decorated = _cctally_account.provider_is_decorated(
|
|
2802
|
-
context.stats_conn, "codex")
|
|
2803
|
-
except Exception:
|
|
2804
|
-
_codex_decorated = False
|
|
3284
|
+
account_scopes: dict[str, dict[str, object]] = {}
|
|
2805
3285
|
if _codex_decorated:
|
|
2806
3286
|
try:
|
|
2807
3287
|
accounts_wire, hero_cycles_wire = _codex_accounts_wire(
|
|
@@ -2811,12 +3291,134 @@ def build_codex_source_state(
|
|
|
2811
3291
|
accounting_start=accounting_start,
|
|
2812
3292
|
accounting_end=accounting_end,
|
|
2813
3293
|
)
|
|
3294
|
+
# #416 §5.3: the per-account CHILDREN beside the merged parent. The
|
|
3295
|
+
# scope set is exactly the card set, so every chip the client can
|
|
3296
|
+
# focus resolves to a scope (an account with no evidence gets an
|
|
3297
|
+
# explicit `is_empty` child, never a missing key the client would
|
|
3298
|
+
# have to fall back from).
|
|
3299
|
+
cycle_by_account: dict[str, CodexCycleBoundary] = {}
|
|
3300
|
+
for cyc in cycles_all:
|
|
3301
|
+
cycle_by_account.setdefault(
|
|
3302
|
+
(
|
|
3303
|
+
cyc.quota_identity.account_key
|
|
3304
|
+
if cyc.quota_identity is not None
|
|
3305
|
+
else _lib_accounts.UNATTRIBUTED
|
|
3306
|
+
),
|
|
3307
|
+
cyc,
|
|
3308
|
+
)
|
|
3309
|
+
# Budget cost events are frozen per account over the CONFIGURED
|
|
3310
|
+
# budget window, which can start before `range_start` — so they come
|
|
3311
|
+
# from the full `accounting_entries`, not the visible slice.
|
|
3312
|
+
budget_events_by_account = {
|
|
3313
|
+
key: _codex_budget_cost_events(context, rows)
|
|
3314
|
+
for key, rows in _codex_partition_by_account(
|
|
3315
|
+
accounting_entries).items()
|
|
3316
|
+
} if context.codex_budget is not None else {}
|
|
3317
|
+
account_scopes = _codex_account_scopes_wire(
|
|
3318
|
+
context,
|
|
3319
|
+
account_keys=[str(card["accountKey"]) for card in accounts_wire],
|
|
3320
|
+
quota_observations=quota_observations,
|
|
3321
|
+
cycle_by_account=cycle_by_account,
|
|
3322
|
+
visible_accounting_entries=visible_accounting_entries,
|
|
3323
|
+
active_roots=active_roots,
|
|
3324
|
+
accounting_end=accounting_end,
|
|
3325
|
+
metadata_incomplete=metadata_incomplete,
|
|
3326
|
+
conversation_metadata=conversation_metadata,
|
|
3327
|
+
alerts=alerts,
|
|
3328
|
+
budget_milestones=budget_rows,
|
|
3329
|
+
projected_budget_milestones=projected_budget_rows,
|
|
3330
|
+
budget_cost_events_by_account=budget_events_by_account,
|
|
3331
|
+
private_session_labels=private_session_labels,
|
|
3332
|
+
hero_failure=hero_failure,
|
|
3333
|
+
)
|
|
3334
|
+
# #416 QA P1-A — the "All accounts" Blocks panel is the UNION of
|
|
3335
|
+
# every account's 5-hour blocks. `_quota_wire` filters
|
|
3336
|
+
# `str(root_key) not in cycle.source_root_keys` against a single
|
|
3337
|
+
# `cycle` that is `cycles_all[0]`, so in the production shape (one
|
|
3338
|
+
# Codex root per account) every SIBLING account's live block is
|
|
3339
|
+
# dropped: the merged panel read "1 blocks · $0.86" while focusing
|
|
3340
|
+
# the sibling revealed a second live block the merged view never
|
|
3341
|
+
# showed. That is an UNDERCOUNT, not a misattribution — the strict
|
|
3342
|
+
# account predicate `_quota_wire` applies under focus is correct and
|
|
3343
|
+
# is untouched; the defect is on the separate CYCLE-ROOT axis.
|
|
3344
|
+
#
|
|
3345
|
+
# The merge is STRICTLY ADDITIVE over today's parent read: every
|
|
3346
|
+
# child's rows are unioned in, and every row the representative-cycle
|
|
3347
|
+
# read already produced is kept. Taking the children ALONE was the
|
|
3348
|
+
# obvious construction (it mirrors P0-A's sum-of-the-cards, and it
|
|
3349
|
+
# makes the merged list incapable of disagreeing with the chip the
|
|
3350
|
+
# operator focuses next) but it LOSES rows: a child whose account has
|
|
3351
|
+
# no live cycle passes `cycle=None` to `_quota_wire`, which returns
|
|
3352
|
+
# `()` — so an account whose key survives only in
|
|
3353
|
+
# `quota_window_blocks` (the third stamping axis; see
|
|
3354
|
+
# `test_a_block_only_account_key_still_gets_a_scope`) vanished from
|
|
3355
|
+
# the merged view that used to list it. Electing a sibling's cycle to
|
|
3356
|
+
# bound it instead would be the very defect this fixes, so the parent
|
|
3357
|
+
# keeps its own rows and gains the siblings'.
|
|
3358
|
+
#
|
|
3359
|
+
# Dedup is on `(key, account_key)`: the opaque block key is built
|
|
3360
|
+
# from root/limit/slot/window/reset and deliberately excludes the
|
|
3361
|
+
# account, so two accounts sharing one physical root are two rows
|
|
3362
|
+
# with one key — never-combine extends to accounts.
|
|
3363
|
+
#
|
|
3364
|
+
# Each row keeps its own `current_percent` and its own `account_key`,
|
|
3365
|
+
# so this is a LISTING of independent windows, never a blend (D6).
|
|
3366
|
+
# Ordering mirrors `_quota_wire`'s own `resets_at DESC` with the
|
|
3367
|
+
# opaque key as the deterministic tie-break.
|
|
3368
|
+
by_identity: dict[tuple[str, str], dict[str, object]] = {}
|
|
3369
|
+
for block in (
|
|
3370
|
+
*(
|
|
3371
|
+
row
|
|
3372
|
+
for scope in account_scopes.values()
|
|
3373
|
+
for row in scope["quota"]["blocks"]
|
|
3374
|
+
),
|
|
3375
|
+
*quota["blocks"],
|
|
3376
|
+
):
|
|
3377
|
+
by_identity.setdefault(
|
|
3378
|
+
(str(block["key"]), str(block.get("account_key", ""))), block)
|
|
3379
|
+
merged_blocks = tuple(sorted(
|
|
3380
|
+
by_identity.values(),
|
|
3381
|
+
key=lambda row: (
|
|
3382
|
+
str(row["resets_at"]),
|
|
3383
|
+
str(row["key"]),
|
|
3384
|
+
str(row.get("account_key", "")),
|
|
3385
|
+
),
|
|
3386
|
+
reverse=True,
|
|
3387
|
+
))
|
|
3388
|
+
quota = {**quota, "blocks": merged_blocks}
|
|
2814
3389
|
except (sqlite3.Error, QualifiedMetadataUnavailable):
|
|
2815
3390
|
# A per-account wire failure must never fail the whole source build;
|
|
2816
3391
|
# degrade to the byte-stable undecorated shape.
|
|
2817
3392
|
accounts_wire = []
|
|
2818
3393
|
hero_cycles_wire = []
|
|
2819
|
-
|
|
3394
|
+
account_scopes = {}
|
|
3395
|
+
# #416 QA P0-A — the "All accounts" headline is the MERGED spend and tokens
|
|
3396
|
+
# (spec §6, decision D6). Everything above resolves the hero from ONE
|
|
3397
|
+
# representative cycle (`cycles_all[0]` plus that cycle's own
|
|
3398
|
+
# `source_root_keys`), which in the production shape — one Codex root per
|
|
3399
|
+
# account — cannot see a sibling's spend at all: the headline then reads as
|
|
3400
|
+
# a live total while being byte-identical to a single card sitting directly
|
|
3401
|
+
# beneath it.
|
|
3402
|
+
#
|
|
3403
|
+
# Spend and tokens are the ONLY axes D6 lets "All accounts" merge; the
|
|
3404
|
+
# percentage, reset, forecast and $/1% stay per-account and the client
|
|
3405
|
+
# blanks them with a pointer to the cards. The merge is a SUM OF THE CARDS
|
|
3406
|
+
# rather than a fresh query, so the headline can never disagree with the
|
|
3407
|
+
# strip it sits above (an account without a live cycle contributes exactly
|
|
3408
|
+
# what its own card shows, over the accounting range — the card's documented
|
|
3409
|
+
# fallback). Gated on `_codex_decorated`, so a <=1-real-account install
|
|
3410
|
+
# keeps the single-cycle hero byte-for-byte (R8); gated on `hero_failure`,
|
|
3411
|
+
# so an unavailable hero stays unavailable rather than gaining totals the
|
|
3412
|
+
# rest of the envelope says are absent.
|
|
3413
|
+
if _codex_decorated and accounts_wire and not hero_failure:
|
|
3414
|
+
cycle_cost_usd = stable_sum(
|
|
3415
|
+
float(card["spendUsd"]) for card in accounts_wire)
|
|
3416
|
+
hero_input = sum(int(card["inputTokens"]) for card in accounts_wire)
|
|
3417
|
+
hero_cached = sum(int(card["cachedInputTokens"]) for card in accounts_wire)
|
|
3418
|
+
hero_output = sum(int(card["outputTokens"]) for card in accounts_wire)
|
|
3419
|
+
hero_reasoning = sum(
|
|
3420
|
+
int(card["reasoningOutputTokens"]) for card in accounts_wire)
|
|
3421
|
+
hero_total = sum(int(card["totalTokens"]) for card in accounts_wire)
|
|
2820
3422
|
return SourceDashboardState(
|
|
2821
3423
|
source="codex",
|
|
2822
3424
|
availability=availability,
|
|
@@ -2883,16 +3485,17 @@ def build_codex_source_state(
|
|
|
2883
3485
|
**({"cycles": hero_cycles_wire} if _codex_decorated else {}),
|
|
2884
3486
|
},
|
|
2885
3487
|
**({"accounts": accounts_wire} if _codex_decorated else {}),
|
|
3488
|
+
# #416 §5.3 — the per-account children. Present ONLY under
|
|
3489
|
+
# decoration; the merged parent below is untouched by their
|
|
3490
|
+
# existence, which is what makes acceptance criterion 7 true by
|
|
3491
|
+
# construction rather than by careful re-derivation.
|
|
3492
|
+
**({"account_scopes": account_scopes} if _codex_decorated else {}),
|
|
2886
3493
|
"periods": {
|
|
2887
3494
|
"daily": _period_wire(daily),
|
|
2888
3495
|
"monthly": _period_wire(monthly),
|
|
2889
3496
|
"weekly": _period_wire(weekly),
|
|
2890
3497
|
},
|
|
2891
|
-
"sessions":
|
|
2892
|
-
sessions,
|
|
2893
|
-
metadata=conversation_metadata,
|
|
2894
|
-
private_labels=private_session_labels,
|
|
2895
|
-
),
|
|
3498
|
+
"sessions": sessions_wire,
|
|
2896
3499
|
"quota": quota,
|
|
2897
3500
|
"budget": {
|
|
2898
3501
|
"status": configured_budget,
|