cctally 1.97.0 → 1.99.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 +47 -0
- package/README.md +4 -4
- package/bin/_cctally_account.py +925 -0
- package/bin/_cctally_cache.py +829 -31
- package/bin/_cctally_core.py +52 -0
- package/bin/_cctally_dashboard.py +960 -55
- package/bin/_cctally_dashboard_envelope.py +43 -23
- package/bin/_cctally_dashboard_share.py +50 -1
- package/bin/_cctally_dashboard_sources.py +1580 -131
- package/bin/_cctally_db.py +810 -34
- package/bin/_cctally_doctor.py +184 -1
- package/bin/_cctally_journal.py +732 -76
- package/bin/_cctally_parser.py +65 -0
- package/bin/_cctally_quota.py +896 -17
- package/bin/_cctally_rederive.py +157 -5
- package/bin/_cctally_source_analytics.py +60 -6
- package/bin/_cctally_tui.py +811 -67
- package/bin/_lib_aggregators.py +117 -11
- package/bin/_lib_alert_axes.py +8 -3
- package/bin/_lib_budget.py +60 -0
- package/bin/_lib_codex_window_attribution.py +259 -0
- package/bin/_lib_dashboard_sources.py +488 -19
- package/bin/_lib_doctor.py +71 -0
- package/bin/_lib_journal.py +212 -0
- package/bin/_lib_jsonl.py +6 -0
- package/bin/_lib_rederive.py +8 -0
- package/bin/_lib_snapshot_cache.py +248 -7
- package/bin/_lib_source_analytics.py +20 -2
- package/bin/cctally +25 -0
- package/dashboard/static/assets/index-Bcbm-DNP.js +97 -0
- package/dashboard/static/assets/index-hJP4wlIO.css +1 -0
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +2 -1
- package/dashboard/static/assets/index-BSESoPIK.css +0 -1
- package/dashboard/static/assets/index-DgsMz5hA.js +0 -97
package/bin/_cctally_quota.py
CHANGED
|
@@ -14,11 +14,13 @@ import secrets
|
|
|
14
14
|
import sqlite3
|
|
15
15
|
import sys
|
|
16
16
|
import time
|
|
17
|
-
from dataclasses import dataclass
|
|
17
|
+
from dataclasses import dataclass, replace
|
|
18
18
|
from typing import Callable, Iterable, Mapping, NoReturn, Sequence
|
|
19
19
|
|
|
20
20
|
import _cctally_core
|
|
21
21
|
import _lib_accounts
|
|
22
|
+
import _lib_codex_window_attribution as _wa
|
|
23
|
+
import _lib_quota
|
|
22
24
|
from _cctally_core import _command_as_of, eprint
|
|
23
25
|
from _lib_quota import (
|
|
24
26
|
CODEX_RESET_ANCHOR_TOLERANCE_SECONDS,
|
|
@@ -538,10 +540,15 @@ def codex_physical_mutation_seq(conn: sqlite3.Connection) -> int:
|
|
|
538
540
|
return 0
|
|
539
541
|
|
|
540
542
|
|
|
541
|
-
def
|
|
543
|
+
def _codex_quota_projection_certificate_payload(
|
|
542
544
|
conn: sqlite3.Connection,
|
|
543
|
-
) ->
|
|
544
|
-
"""
|
|
545
|
+
) -> "dict | None":
|
|
546
|
+
"""The stored certificate as written, with no revision gate applied.
|
|
547
|
+
|
|
548
|
+
Separate from the reader below because the reconcile needs to know WHY a
|
|
549
|
+
certificate is not usable: an attribution-stale one has to widen the pass to
|
|
550
|
+
the attribution's own groups, while an absent one is an ordinary full pass.
|
|
551
|
+
"""
|
|
545
552
|
try:
|
|
546
553
|
row = conn.execute(
|
|
547
554
|
"SELECT value FROM cache_meta WHERE key=?",
|
|
@@ -550,6 +557,46 @@ def load_codex_quota_projection_certificate(
|
|
|
550
557
|
if row is None:
|
|
551
558
|
return None
|
|
552
559
|
payload = json.loads(str(row[0]))
|
|
560
|
+
except (sqlite3.Error, TypeError, ValueError, json.JSONDecodeError):
|
|
561
|
+
return None
|
|
562
|
+
return payload if isinstance(payload, dict) else None
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def _certificate_attribution_revision(payload: "Mapping[str, object] | None") -> int:
|
|
566
|
+
"""The attribution revision a stored certificate was computed against.
|
|
567
|
+
|
|
568
|
+
A certificate written before #500 carries no such member and reads as ``0``,
|
|
569
|
+
which is also the revision of a store where nothing has ever been asserted —
|
|
570
|
+
so an install with no attributions keeps its existing certificate and its
|
|
571
|
+
existing short-circuit unchanged.
|
|
572
|
+
"""
|
|
573
|
+
if not payload:
|
|
574
|
+
return 0
|
|
575
|
+
try:
|
|
576
|
+
return int(payload.get("attributionRevision", 0))
|
|
577
|
+
except (TypeError, ValueError):
|
|
578
|
+
return 0
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def load_codex_quota_projection_certificate(
|
|
582
|
+
conn: sqlite3.Connection,
|
|
583
|
+
) -> tuple[int, dict[str, str]] | None:
|
|
584
|
+
"""Read the post-reconciliation physical-signature certificate in O(1).
|
|
585
|
+
|
|
586
|
+
Also gated on the ATTRIBUTION REVISION (#500 spec §8.3). This design writes
|
|
587
|
+
nothing to ``quota_window_snapshots``, so an operator attribution fires no
|
|
588
|
+
trigger and appears in no change-ledger entry; left alone, every consumer of
|
|
589
|
+
this certificate would read a confident no-op and the attribution would never
|
|
590
|
+
reach ``quota_window_blocks`` until some unrelated future ingest happened to
|
|
591
|
+
dirty the same group. Returning ``None`` when the stored revision is not the
|
|
592
|
+
live one puts every consumer — the reconcile short-circuit, the dashboard
|
|
593
|
+
source coherence check, the deferred cache-sync gate — on the fail-safe side
|
|
594
|
+
of that question at once.
|
|
595
|
+
"""
|
|
596
|
+
payload = _codex_quota_projection_certificate_payload(conn)
|
|
597
|
+
if payload is None:
|
|
598
|
+
return None
|
|
599
|
+
try:
|
|
553
600
|
if (
|
|
554
601
|
int(payload["interpretationVersion"])
|
|
555
602
|
!= _CODEX_QUOTA_INTERPRETATION_VERSION
|
|
@@ -560,10 +607,14 @@ def load_codex_quota_projection_certificate(
|
|
|
560
607
|
str(root_key): str(signature)
|
|
561
608
|
for root_key, signature in dict(payload["signatures"]).items()
|
|
562
609
|
}
|
|
563
|
-
except (sqlite3.Error, TypeError, ValueError, KeyError
|
|
610
|
+
except (sqlite3.Error, TypeError, ValueError, KeyError):
|
|
564
611
|
return None
|
|
565
612
|
if sequence < 0 or any(len(signature) != 64 for signature in signatures.values()):
|
|
566
613
|
return None
|
|
614
|
+
if _certificate_attribution_revision(payload) != (
|
|
615
|
+
_cache_module().codex_window_attribution_revision(conn)
|
|
616
|
+
):
|
|
617
|
+
return None
|
|
567
618
|
return sequence, signatures
|
|
568
619
|
|
|
569
620
|
|
|
@@ -572,6 +623,7 @@ def _store_codex_quota_projection_certificate(
|
|
|
572
623
|
sequence: int,
|
|
573
624
|
signatures: Mapping[str, str],
|
|
574
625
|
prune_ledger_through: "int | None" = None,
|
|
626
|
+
attribution_revision: "int | None" = None,
|
|
575
627
|
) -> None:
|
|
576
628
|
"""Stamp exact validated signatures only if cache physical state is unchanged.
|
|
577
629
|
|
|
@@ -592,6 +644,17 @@ def _store_codex_quota_projection_certificate(
|
|
|
592
644
|
The prune runs even when the certificate itself is declined: a sequence that
|
|
593
645
|
advanced mid-pass means new evidence landed, not that the old entries are
|
|
594
646
|
unconsumed.
|
|
647
|
+
|
|
648
|
+
``attribution_revision`` (#500 §8.3) is the SEMANTIC twin of ``sequence``
|
|
649
|
+
and is gated the same way: the caller passes the revision its pass actually
|
|
650
|
+
computed against, and a live revision that no longer equals it declines the
|
|
651
|
+
stamp. Reading the live value here instead would certify a projection built
|
|
652
|
+
from observations that never saw the assertion — and it needs no concurrency
|
|
653
|
+
to happen, because the pass's OWN ingest cycle materializes a pending
|
|
654
|
+
``codex_window_attribution`` op after the observations are loaded. That is
|
|
655
|
+
§8.5's ``recordedPending`` state, and the certificate would poison every
|
|
656
|
+
later reconcile with a confident no-op. ``None`` means no gate, for callers
|
|
657
|
+
that are not a projection pass.
|
|
595
658
|
"""
|
|
596
659
|
path = _cctally_core.CACHE_DB_PATH
|
|
597
660
|
if not path.exists():
|
|
@@ -611,10 +674,21 @@ def _store_codex_quota_projection_certificate(
|
|
|
611
674
|
if codex_physical_mutation_seq(conn) != sequence:
|
|
612
675
|
conn.commit()
|
|
613
676
|
return
|
|
677
|
+
live_revision = _cache_module().codex_window_attribution_revision(
|
|
678
|
+
conn)
|
|
679
|
+
if (
|
|
680
|
+
attribution_revision is not None
|
|
681
|
+
and live_revision != int(attribution_revision)
|
|
682
|
+
):
|
|
683
|
+
conn.commit()
|
|
684
|
+
return
|
|
614
685
|
payload = json.dumps({
|
|
615
686
|
"interpretationVersion": _CODEX_QUOTA_INTERPRETATION_VERSION,
|
|
616
687
|
"sequence": sequence,
|
|
617
688
|
"signatures": dict(sorted(signatures.items())),
|
|
689
|
+
# #500 §8.3: the semantic revision this projection was computed
|
|
690
|
+
# against — the caller's, verified equal to the live one above.
|
|
691
|
+
"attributionRevision": live_revision,
|
|
618
692
|
}, sort_keys=True, separators=(",", ":"))
|
|
619
693
|
conn.execute(
|
|
620
694
|
"INSERT INTO cache_meta(key, value) VALUES (?, ?) "
|
|
@@ -1110,6 +1184,640 @@ def _iter_shard_rows(conn, shards):
|
|
|
1110
1184
|
yield from conn.execute(shard_sql, shard_params)
|
|
1111
1185
|
|
|
1112
1186
|
|
|
1187
|
+
#: Every ``quota_window_snapshots`` column that must be present and non-blank
|
|
1188
|
+
#: before ``load_codex_quota_observations`` will interpret the row.
|
|
1189
|
+
#:
|
|
1190
|
+
#: Named once because TWO populations have to agree on it: the loader's own, and
|
|
1191
|
+
#: the #500 attribution evidence pass's. A row one of them keeps and the other
|
|
1192
|
+
#: drops is a silent divergence in BOTH directions — an evidence row the loader
|
|
1193
|
+
#: discarded can name an account no loaded observation carries, turning a group
|
|
1194
|
+
#: the fold sees as cleanly unattributed into a suppressed one.
|
|
1195
|
+
#:
|
|
1196
|
+
#: One divergence between those two populations is KNOWN and is not on this
|
|
1197
|
+
#: list, because it is not about a required column. Both sides derive the group
|
|
1198
|
+
#: anchor as ``COALESCE(canonical_resets_at_utc, resets_at_utc)``, but SQL
|
|
1199
|
+
#: ``COALESCE`` only replaces NULL, while the loader's per-row interpretation
|
|
1200
|
+
#: treats ``''`` as absent too (``in (None, "")``) and falls back to the raw
|
|
1201
|
+
#: reset. So a row storing an EMPTY canonical anchor is kept by the loader and
|
|
1202
|
+
#: dropped by the evidence pass, which discards one witness for that group. The
|
|
1203
|
+
#: append path never writes ``''`` — this shape comes from a hand-repaired row —
|
|
1204
|
+
#: and the effect is one-directional (a narrower witness set can only make an
|
|
1205
|
+
#: assertion dormant, never make it claim a group it does not own), so it is
|
|
1206
|
+
#: recorded here rather than repaired. It predates #500.
|
|
1207
|
+
_CODEX_QUOTA_REQUIRED_TEXT = (
|
|
1208
|
+
"source", "source_root_key", "source_path", "captured_at_utc",
|
|
1209
|
+
"observed_slot", "logical_limit_key", "resets_at_utc",
|
|
1210
|
+
)
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
def _codex_quota_required_text_present(values: Iterable[object]) -> bool:
|
|
1214
|
+
"""True iff every required text value is present and not blank."""
|
|
1215
|
+
return not any(
|
|
1216
|
+
value is None or not str(value).strip() for value in values)
|
|
1217
|
+
|
|
1218
|
+
|
|
1219
|
+
# --------------------------------------------------------------------------
|
|
1220
|
+
# #500 §6.4 — the fold-time operator attribution overlay
|
|
1221
|
+
# --------------------------------------------------------------------------
|
|
1222
|
+
#
|
|
1223
|
+
# The operator's assertions live in the append-only journal and are indexed into
|
|
1224
|
+
# cache.db's `codex_window_attributions` (#500 Task 1). This is where they are
|
|
1225
|
+
# APPLIED: immediately before the continuity fold, so an attributed group is
|
|
1226
|
+
# genuinely identified by the time `adopt_unidentified_observations`,
|
|
1227
|
+
# `build_blocks` and the spend-adoption pass see it. Nothing is written back to
|
|
1228
|
+
# `quota_window_snapshots`, which stays raw pre-fold provider evidence.
|
|
1229
|
+
#
|
|
1230
|
+
# Resolution and application are separate phases and are NOT interchangeable
|
|
1231
|
+
# (spec §6.4.1). Witness matching is population-dependent, and several callers
|
|
1232
|
+
# deliberately hand the loader a reduced population — the dashboard bounds by
|
|
1233
|
+
# recent days and a row cap, doctor asks for the latest row per identity. If
|
|
1234
|
+
# matching ran against the reduced set, a component a later observation bridged
|
|
1235
|
+
# could present a subset carrying none of the assertion's original witnesses and
|
|
1236
|
+
# the attribution would vanish from the dashboard alone. So resolution always
|
|
1237
|
+
# reads its own COMPLETE evidence for the roots in play, and only application
|
|
1238
|
+
# touches the caller's rows.
|
|
1239
|
+
|
|
1240
|
+
|
|
1241
|
+
def _cache_module():
|
|
1242
|
+
"""`_cctally_cache`, imported lazily.
|
|
1243
|
+
|
|
1244
|
+
That module imports THIS one inside its own functions for the same reason:
|
|
1245
|
+
the cache leg reads quota observations and the quota leg reads the cache's
|
|
1246
|
+
derived attribution index, so a module-level import either way is a cycle.
|
|
1247
|
+
"""
|
|
1248
|
+
import _cctally_cache
|
|
1249
|
+
|
|
1250
|
+
return _cctally_cache
|
|
1251
|
+
|
|
1252
|
+
|
|
1253
|
+
def _codex_quota_instant_is_valid(value: object) -> bool:
|
|
1254
|
+
"""``_parse_utc`` succeeds on ``value``, without paying for the conversion.
|
|
1255
|
+
|
|
1256
|
+
The evidence pass asks this question about every row's capture and reset
|
|
1257
|
+
only to agree with the loader on which rows exist, and never uses the
|
|
1258
|
+
resulting datetime — while ``astimezone`` is the expensive half of
|
|
1259
|
+
``_parse_utc``.
|
|
1260
|
+
|
|
1261
|
+
Same acceptance set, and the last branch is what makes that true rather than
|
|
1262
|
+
approximately true. ``_parse_utc`` ends in ``astimezone(UTC)``, which raises
|
|
1263
|
+
``OverflowError`` when shifting the instant leaves the representable range —
|
|
1264
|
+
a year-9999 instant with a negative offset, or a year-1 instant with a
|
|
1265
|
+
positive one. Skipping the conversion outright would accept a row the loader
|
|
1266
|
+
rejects, so it is performed on exactly the two years where it can fail, and
|
|
1267
|
+
nowhere else.
|
|
1268
|
+
"""
|
|
1269
|
+
try:
|
|
1270
|
+
parsed = dt.datetime.fromisoformat(str(value).replace("Z", "+00:00"))
|
|
1271
|
+
except (TypeError, ValueError):
|
|
1272
|
+
return False
|
|
1273
|
+
if parsed.tzinfo is None or parsed.utcoffset() is None:
|
|
1274
|
+
return False
|
|
1275
|
+
if parsed.year in (dt.MINYEAR, dt.MAXYEAR):
|
|
1276
|
+
try:
|
|
1277
|
+
parsed.astimezone(UTC)
|
|
1278
|
+
except (OverflowError, OSError, ValueError):
|
|
1279
|
+
return False
|
|
1280
|
+
return True
|
|
1281
|
+
|
|
1282
|
+
|
|
1283
|
+
def _codex_attribution_witness(value: object) -> str:
|
|
1284
|
+
"""One spelling for one reset instant, applied to BOTH sides of the binding.
|
|
1285
|
+
|
|
1286
|
+
The cache retains whichever spelling the provider sent and the journal
|
|
1287
|
+
payload carries whichever spelling the command read, so a `Z` witness and a
|
|
1288
|
+
`+00:00` group member are the same instant and must intersect. This is the
|
|
1289
|
+
same fail-open normalizer the loading-unit key uses.
|
|
1290
|
+
"""
|
|
1291
|
+
return _ledger.normalize_reset(value)
|
|
1292
|
+
|
|
1293
|
+
|
|
1294
|
+
def _codex_attribution_table_present(conn: sqlite3.Connection) -> bool:
|
|
1295
|
+
"""Whether this cache carries the derived index at all.
|
|
1296
|
+
|
|
1297
|
+
A cache too old to have it degrades to "no assertions", exactly as every
|
|
1298
|
+
other column probe in this loader degrades. The fail-LOUD requirement of
|
|
1299
|
+
§6.2 is about the rebuild's publication gate, which is where a partial index
|
|
1300
|
+
would be certified as complete; a read against a pre-#500 cache has no
|
|
1301
|
+
assertion to under-apply.
|
|
1302
|
+
"""
|
|
1303
|
+
try:
|
|
1304
|
+
return bool(conn.execute(
|
|
1305
|
+
"SELECT 1 FROM sqlite_master "
|
|
1306
|
+
" WHERE type='table' AND name='codex_window_attributions' LIMIT 1"
|
|
1307
|
+
).fetchone())
|
|
1308
|
+
except sqlite3.DatabaseError:
|
|
1309
|
+
return False
|
|
1310
|
+
|
|
1311
|
+
|
|
1312
|
+
def _codex_attribution_axis_shards(
|
|
1313
|
+
assertions: "Sequence[Mapping[str, object]]",
|
|
1314
|
+
) -> "dict[tuple, set[str]]":
|
|
1315
|
+
"""Assertions grouped by their four normalized axes, witnesses unioned.
|
|
1316
|
+
|
|
1317
|
+
The shard, not the assertion, is the unit both evidence passes are issued
|
|
1318
|
+
per — because the kernel binds on ``group.axes == assertion.axes`` and every
|
|
1319
|
+
assertion sharing one axis combination therefore reaches the same candidate
|
|
1320
|
+
groups. One query per combination is what keeps the pass proportional to the
|
|
1321
|
+
axes in play instead of to the number of assertions.
|
|
1322
|
+
"""
|
|
1323
|
+
shards: "dict[tuple, set[str]]" = {}
|
|
1324
|
+
for assertion in assertions:
|
|
1325
|
+
try:
|
|
1326
|
+
axes = (
|
|
1327
|
+
str(assertion["source_root_key"]),
|
|
1328
|
+
str(assertion["logical_limit_key"]),
|
|
1329
|
+
str(assertion["observed_slot"]),
|
|
1330
|
+
int(assertion["window_minutes"]),
|
|
1331
|
+
)
|
|
1332
|
+
witnesses = {
|
|
1333
|
+
str(witness) for witness in assertion["raw_resets_at_utc"]}
|
|
1334
|
+
except (KeyError, TypeError, ValueError):
|
|
1335
|
+
continue
|
|
1336
|
+
if not all(str(member).strip() for member in axes[:3]) or not witnesses:
|
|
1337
|
+
continue
|
|
1338
|
+
shards.setdefault(axes, set()).update(witnesses)
|
|
1339
|
+
return shards
|
|
1340
|
+
|
|
1341
|
+
|
|
1342
|
+
def _codex_attribution_stored_limit_keys(logical_limit_key: str) -> "list[str]":
|
|
1343
|
+
"""Every spelling ``quota_window_snapshots`` can hold for one axis key.
|
|
1344
|
+
|
|
1345
|
+
Two closures, because the stored key and the INTERPRETED key are not the
|
|
1346
|
+
same value. The snap closure covers the weekly jitter that lives in the key
|
|
1347
|
+
as well as in the column, and the model-pool strip covers the read-path
|
|
1348
|
+
rewrite: a row the interpretation moves INTO a model-scoped bucket is stored
|
|
1349
|
+
under the ordinary key, so an axis key carrying a ``modelPool`` member has to
|
|
1350
|
+
ask for the stripped spellings too or its own members become unreachable.
|
|
1351
|
+
For an ordinary key ``strip_model_pool`` is the identity and this is exactly
|
|
1352
|
+
the three snap spellings.
|
|
1353
|
+
"""
|
|
1354
|
+
return sorted(
|
|
1355
|
+
set(codex_snap_equivalent_limit_keys(logical_limit_key))
|
|
1356
|
+
| set(codex_snap_equivalent_limit_keys(
|
|
1357
|
+
_ledger.strip_model_pool(logical_limit_key)))
|
|
1358
|
+
)
|
|
1359
|
+
|
|
1360
|
+
|
|
1361
|
+
def _load_codex_window_group_evidence(
|
|
1362
|
+
conn: sqlite3.Connection, assertions: "Sequence[Mapping[str, object]]",
|
|
1363
|
+
) -> "tuple[_wa.WindowGroup, ...]":
|
|
1364
|
+
"""Complete current evidence for every group a stored witness can reach.
|
|
1365
|
+
|
|
1366
|
+
Two indexed passes rather than one interpretation of all history, and the
|
|
1367
|
+
two are exactly equivalent to a full read for RESOLUTION's purposes:
|
|
1368
|
+
|
|
1369
|
+
* Pass 1 finds the canonical anchors of every group holding a row whose RAW
|
|
1370
|
+
reset is one of the stored witnesses. The kernel binds on an intersection
|
|
1371
|
+
against those witnesses, so a group holding none of them cannot match and
|
|
1372
|
+
its absence here changes no verdict — the assertion is dormant either way.
|
|
1373
|
+
* Pass 2 loads the COMPLETE membership of exactly those groups. Every member
|
|
1374
|
+
of a group shares its axes and its canonical anchor by definition of the
|
|
1375
|
+
physical window key, so the shard is a superset of the group and the
|
|
1376
|
+
identified accounts, the model-pool verdict and the full witness set are
|
|
1377
|
+
all read over the whole population.
|
|
1378
|
+
|
|
1379
|
+
BOTH passes are sharded on the assertion's four axes, and that is the whole
|
|
1380
|
+
performance story. ``idx_qws_physical_group`` is ``(source_root_key,
|
|
1381
|
+
logical_limit_key, observed_slot, window_minutes, unixepoch(COALESCE(
|
|
1382
|
+
canonical_resets_at_utc, resets_at_utc))) WHERE source='codex'``, and SQLite
|
|
1383
|
+
cannot skip an interior member: a pass constraining only the root falls back
|
|
1384
|
+
to ``idx_quota_window_source_root`` and reads the root's whole history per
|
|
1385
|
+
query. Measured read-only on the maintainer's 267,163-row store, 63
|
|
1386
|
+
assertions over 2 axis combinations resolving 57 groups:
|
|
1387
|
+
|
|
1388
|
+
============================================== ========= =========
|
|
1389
|
+
pass root-only sharded
|
|
1390
|
+
============================================== ========= =========
|
|
1391
|
+
1 (one query, or one per axis combination) 63.9 ms 43.8 ms
|
|
1392
|
+
2 (one query per group) 3609.7 ms 119.2 ms
|
|
1393
|
+
whole call, SQL and interpretation together 4443.5 ms 453.8 ms
|
|
1394
|
+
============================================== ========= =========
|
|
1395
|
+
|
|
1396
|
+
Pass 2 is where the plan actually changes, and it is the dominant cost. It
|
|
1397
|
+
moves from ``SEARCH ... USING INDEX idx_quota_window_source_root
|
|
1398
|
+
(source_root_key=?)`` at 63.3 ms per group to ``SEARCH ... USING INDEX
|
|
1399
|
+
idx_qws_physical_group (source_root_key=? AND logical_limit_key=? AND
|
|
1400
|
+
observed_slot=? AND window_minutes=? AND <expr>=?)`` at 2.1 ms per group.
|
|
1401
|
+
Pass 1 cannot constrain the fifth member at all — it matches RAW resets
|
|
1402
|
+
while the index holds the coalesced anchor — so it seeks the four-member
|
|
1403
|
+
prefix and scans that range; its gain is modest and, on a store where every
|
|
1404
|
+
axis combination is asserted at once, it can cost slightly more than one
|
|
1405
|
+
root-wide query. That is accepted: pass 1 is under 2% of the call, and
|
|
1406
|
+
pairing the axes with each anchor is what lets pass 2 seek all five members.
|
|
1407
|
+
|
|
1408
|
+
This runs three times per ``sync_codex_cache`` and once per dashboard tick
|
|
1409
|
+
and per ``codex quota``, inside both cache flocks.
|
|
1410
|
+
|
|
1411
|
+
Interpretation matches ``load_codex_quota_observations`` member for member —
|
|
1412
|
+
the ``window_minutes`` snap, the limit-key snap, the model-pool rewrite, and
|
|
1413
|
+
the row-validity predicate — because the group key produced here has to equal
|
|
1414
|
+
``_lib_quota._physical_window_key`` of the observations it will own, and the
|
|
1415
|
+
accounts read here have to be the accounts those observations carry.
|
|
1416
|
+
|
|
1417
|
+
A row whose ``canonical_resets_at_utc`` is NULL is NOT lost: the anchor
|
|
1418
|
+
expression coalesces onto the raw reset, and ``QuotaObservation`` fills
|
|
1419
|
+
``canonical_resets_at`` from ``resets_at`` under exactly the same rule
|
|
1420
|
+
(``bin/_lib_quota.py`` ``__post_init__``), so both sides key such a row on
|
|
1421
|
+
its own raw reset and they agree. Cache migration 032 backfills the column
|
|
1422
|
+
for ``source='codex'`` anyway.
|
|
1423
|
+
"""
|
|
1424
|
+
shards = _codex_attribution_axis_shards(assertions)
|
|
1425
|
+
if not shards:
|
|
1426
|
+
return ()
|
|
1427
|
+
columns = {
|
|
1428
|
+
str(row[1]) for row in conn.execute(
|
|
1429
|
+
"PRAGMA table_info(quota_window_snapshots)")
|
|
1430
|
+
}
|
|
1431
|
+
if not {"source", "source_root_key", "resets_at_utc"} <= columns:
|
|
1432
|
+
return ()
|
|
1433
|
+
has_anchor = "canonical_resets_at_utc" in columns
|
|
1434
|
+
has_account = "account_key" in columns
|
|
1435
|
+
has_model = "observed_model" in columns
|
|
1436
|
+
anchor_expr = (
|
|
1437
|
+
"COALESCE(canonical_resets_at_utc, resets_at_utc)"
|
|
1438
|
+
if has_anchor else "resets_at_utc"
|
|
1439
|
+
)
|
|
1440
|
+
account_expr = "account_key" if has_account else "NULL"
|
|
1441
|
+
model_expr = "observed_model" if has_model else "NULL"
|
|
1442
|
+
# The seven required-text columns first, in `_CODEX_QUOTA_REQUIRED_TEXT`
|
|
1443
|
+
# order, so the shared predicate can be applied to `row[:7]` verbatim.
|
|
1444
|
+
pass_two_select = (
|
|
1445
|
+
"SELECT source, source_root_key, source_path, captured_at_utc,"
|
|
1446
|
+
" observed_slot, logical_limit_key, resets_at_utc,"
|
|
1447
|
+
" window_minutes, limit_id, limit_name, used_percent,"
|
|
1448
|
+
" line_offset,"
|
|
1449
|
+
f" {model_expr} AS observed_model,"
|
|
1450
|
+
f" {account_expr} AS account_key,"
|
|
1451
|
+
f" {anchor_expr} AS anchor"
|
|
1452
|
+
" FROM quota_window_snapshots"
|
|
1453
|
+
)
|
|
1454
|
+
|
|
1455
|
+
buckets: "dict[tuple, dict]" = {}
|
|
1456
|
+
# Per-CALL memos. Every one of these is a pure function of a value drawn
|
|
1457
|
+
# from a tiny domain — at most a handful of stored limit-key spellings, one
|
|
1458
|
+
# anchor spelling per group, a few distinct raw resets, one or two models —
|
|
1459
|
+
# evaluated once per ROW over a population that is ~1,700 rows per group on
|
|
1460
|
+
# the maintainer's store. Interpreting each row independently made the
|
|
1461
|
+
# Python half of this pass cost more than the (now indexed) SQL half.
|
|
1462
|
+
snapped_keys: "dict[str, str]" = {}
|
|
1463
|
+
pools: "dict[object, object]" = {}
|
|
1464
|
+
instants: "dict[str, object]" = {}
|
|
1465
|
+
valid_instants: "dict[str, bool]" = {}
|
|
1466
|
+
witness_texts: "dict[str, str]" = {}
|
|
1467
|
+
model_scoped: "dict[tuple, bool]" = {}
|
|
1468
|
+
for axes, shard_witnesses in sorted(shards.items()):
|
|
1469
|
+
root_key, limit_key, slot, window_minutes = axes
|
|
1470
|
+
# Rows are STORED under whichever length and key spelling the provider
|
|
1471
|
+
# sent, so the filter enumerates every equivalent rather than snapping
|
|
1472
|
+
# in SQL — which it could not do and still seek the index.
|
|
1473
|
+
limit_keys = _codex_attribution_stored_limit_keys(limit_key)
|
|
1474
|
+
minutes = sorted({
|
|
1475
|
+
int(value)
|
|
1476
|
+
for value in codex_snap_equivalent_window_minutes(window_minutes)
|
|
1477
|
+
})
|
|
1478
|
+
witnesses = sorted(shard_witnesses)
|
|
1479
|
+
axis_clause = (
|
|
1480
|
+
" WHERE source='codex' AND source_root_key = ?"
|
|
1481
|
+
" AND logical_limit_key IN ("
|
|
1482
|
+
+ ",".join("?" * len(limit_keys)) + ")"
|
|
1483
|
+
" AND observed_slot = ?"
|
|
1484
|
+
" AND window_minutes IN (" + ",".join("?" * len(minutes)) + ")"
|
|
1485
|
+
)
|
|
1486
|
+
axis_params = (root_key, *limit_keys, slot, *minutes)
|
|
1487
|
+
anchors: "set[str]" = set()
|
|
1488
|
+
pass_one = (
|
|
1489
|
+
f"SELECT DISTINCT {anchor_expr} AS anchor"
|
|
1490
|
+
" FROM quota_window_snapshots"
|
|
1491
|
+
+ axis_clause
|
|
1492
|
+
+ " AND unixepoch(resets_at_utc) IN ("
|
|
1493
|
+
+ ",".join("unixepoch(?)" for _ in witnesses) + ")"
|
|
1494
|
+
)
|
|
1495
|
+
for row in conn.execute(pass_one, (*axis_params, *witnesses)):
|
|
1496
|
+
if row[0] is None or not str(row[0]).strip():
|
|
1497
|
+
continue
|
|
1498
|
+
anchors.add(str(row[0]))
|
|
1499
|
+
if not anchors:
|
|
1500
|
+
continue
|
|
1501
|
+
# ONE SHARD PER GROUP, for the reason the loader states at its own group
|
|
1502
|
+
# filter: an OR over the equality gives up and scans, while each group
|
|
1503
|
+
# as its own query seeks all five members of `idx_qws_physical_group`.
|
|
1504
|
+
pass_two = (
|
|
1505
|
+
pass_two_select + axis_clause
|
|
1506
|
+
+ f" AND unixepoch({anchor_expr}) = unixepoch(?)"
|
|
1507
|
+
)
|
|
1508
|
+
for anchor_text in sorted(anchors):
|
|
1509
|
+
for row in conn.execute(pass_two, (*axis_params, anchor_text)):
|
|
1510
|
+
if not _codex_quota_required_text_present(row[:7]):
|
|
1511
|
+
continue
|
|
1512
|
+
if row[14] is None or not str(row[14]).strip():
|
|
1513
|
+
continue
|
|
1514
|
+
stored_key = str(row[5])
|
|
1515
|
+
anchor_value = str(row[14])
|
|
1516
|
+
capture_text = str(row[3])
|
|
1517
|
+
reset_text = str(row[6])
|
|
1518
|
+
try:
|
|
1519
|
+
snapped_minutes = snap_codex_window_minutes(int(row[7]))
|
|
1520
|
+
logical_limit_key = snapped_keys.get(stored_key)
|
|
1521
|
+
if logical_limit_key is None:
|
|
1522
|
+
logical_limit_key = snapped_keys[stored_key] = (
|
|
1523
|
+
snap_window_minutes(stored_key))
|
|
1524
|
+
model = row[12]
|
|
1525
|
+
if model not in pools:
|
|
1526
|
+
pools[model] = codex_model_scoped_quota_pool(model)
|
|
1527
|
+
if pools[model] is not None:
|
|
1528
|
+
logical_limit_key = _codex_logical_limit_key(
|
|
1529
|
+
str(row[1]), row[8], str(row[4]), snapped_minutes,
|
|
1530
|
+
str(model),
|
|
1531
|
+
)
|
|
1532
|
+
anchor = instants.get(anchor_value)
|
|
1533
|
+
if anchor is None:
|
|
1534
|
+
anchor = instants[anchor_value] = _parse_utc(
|
|
1535
|
+
anchor_value, "canonical_resets_at_utc")
|
|
1536
|
+
used_percent = float(row[10])
|
|
1537
|
+
line_offset = int(row[11])
|
|
1538
|
+
except (TypeError, ValueError, OverflowError):
|
|
1539
|
+
# The loader skips a malformed physical row window-by-window
|
|
1540
|
+
# rather than suppressing unrelated valid windows; so does
|
|
1541
|
+
# this.
|
|
1542
|
+
continue
|
|
1543
|
+
# The rest of the loader's per-row contract, which
|
|
1544
|
+
# `QuotaObservation.__post_init__` enforces there and which has
|
|
1545
|
+
# to be enforced here or the two populations disagree.
|
|
1546
|
+
for text in (capture_text, reset_text):
|
|
1547
|
+
if text not in valid_instants:
|
|
1548
|
+
valid_instants[text] = _codex_quota_instant_is_valid(
|
|
1549
|
+
text)
|
|
1550
|
+
if (
|
|
1551
|
+
not valid_instants[capture_text]
|
|
1552
|
+
or not valid_instants[reset_text]
|
|
1553
|
+
or not 0 <= used_percent <= 100
|
|
1554
|
+
or line_offset < 0
|
|
1555
|
+
or not str(row[2]).startswith("/")
|
|
1556
|
+
):
|
|
1557
|
+
continue
|
|
1558
|
+
key = ("codex", str(row[1]), logical_limit_key, str(row[4]),
|
|
1559
|
+
snapped_minutes, anchor)
|
|
1560
|
+
bucket = buckets.get(key)
|
|
1561
|
+
if bucket is None:
|
|
1562
|
+
bucket = buckets[key] = {
|
|
1563
|
+
"root": str(row[1]),
|
|
1564
|
+
"limit_key": logical_limit_key,
|
|
1565
|
+
"slot": str(row[4]),
|
|
1566
|
+
"minutes": snapped_minutes,
|
|
1567
|
+
"witnesses": set(),
|
|
1568
|
+
"accounts": set(),
|
|
1569
|
+
"model_scoped": False,
|
|
1570
|
+
}
|
|
1571
|
+
witness = witness_texts.get(reset_text)
|
|
1572
|
+
if witness is None:
|
|
1573
|
+
witness = witness_texts[reset_text] = (
|
|
1574
|
+
_codex_attribution_witness(reset_text))
|
|
1575
|
+
bucket["witnesses"].add(witness)
|
|
1576
|
+
account = row[13]
|
|
1577
|
+
if account not in (None, "", _lib_accounts.UNATTRIBUTED):
|
|
1578
|
+
bucket["accounts"].add(str(account))
|
|
1579
|
+
# `limit_name` is compare=False on the identity, so the label can
|
|
1580
|
+
# differ across one group's observations; ANY Spark evidence
|
|
1581
|
+
# demotes the whole group out of account weekly quota (#373),
|
|
1582
|
+
# matching the spend fold. That direction only ever withholds an
|
|
1583
|
+
# attribution.
|
|
1584
|
+
scope_key = (logical_limit_key, row[9])
|
|
1585
|
+
if scope_key not in model_scoped:
|
|
1586
|
+
model_scoped[scope_key] = is_model_scoped_codex_quota(
|
|
1587
|
+
logical_limit_key, row[9])
|
|
1588
|
+
if model_scoped[scope_key]:
|
|
1589
|
+
bucket["model_scoped"] = True
|
|
1590
|
+
return tuple(
|
|
1591
|
+
_wa.WindowGroup(
|
|
1592
|
+
group_key=key,
|
|
1593
|
+
source_root_key=bucket["root"],
|
|
1594
|
+
logical_limit_key=bucket["limit_key"],
|
|
1595
|
+
observed_slot=bucket["slot"],
|
|
1596
|
+
window_minutes=bucket["minutes"],
|
|
1597
|
+
raw_resets_at_utc=frozenset(bucket["witnesses"]),
|
|
1598
|
+
identified_accounts=frozenset(bucket["accounts"]),
|
|
1599
|
+
model_scoped=bucket["model_scoped"],
|
|
1600
|
+
)
|
|
1601
|
+
for key, bucket in sorted(buckets.items(), key=lambda item: str(item[0]))
|
|
1602
|
+
)
|
|
1603
|
+
|
|
1604
|
+
|
|
1605
|
+
def resolve_codex_window_attributions(
|
|
1606
|
+
conn: sqlite3.Connection,
|
|
1607
|
+
*,
|
|
1608
|
+
source_root_keys: "Iterable[str] | None" = None,
|
|
1609
|
+
include_retracted: bool = False,
|
|
1610
|
+
) -> "tuple[tuple[_wa.AssertionResolution, ...], Mapping[tuple, str]]":
|
|
1611
|
+
"""``resolve_codex_window_attributions_with_evidence`` without the groups."""
|
|
1612
|
+
resolutions, ownership, _groups = (
|
|
1613
|
+
resolve_codex_window_attributions_with_evidence(
|
|
1614
|
+
conn, source_root_keys=source_root_keys,
|
|
1615
|
+
include_retracted=include_retracted))
|
|
1616
|
+
return resolutions, ownership
|
|
1617
|
+
|
|
1618
|
+
|
|
1619
|
+
def resolve_codex_window_attributions_with_evidence(
|
|
1620
|
+
conn: sqlite3.Connection,
|
|
1621
|
+
*,
|
|
1622
|
+
source_root_keys: "Iterable[str] | None" = None,
|
|
1623
|
+
include_retracted: bool = False,
|
|
1624
|
+
) -> "tuple[tuple[_wa.AssertionResolution, ...], Mapping[tuple, str], tuple[_wa.WindowGroup, ...]]":
|
|
1625
|
+
"""Resolve every recorded assertion against COMPLETE current evidence.
|
|
1626
|
+
|
|
1627
|
+
Returns the loaded GROUPS alongside the resolutions and the ownership map,
|
|
1628
|
+
because one caller needs a question the resolutions cannot answer: the §7.1
|
|
1629
|
+
spend reconciliation has to know every account the current world can
|
|
1630
|
+
JUSTIFY for a group, not only the account a resolved assertion supplies. A
|
|
1631
|
+
group's own ``identified_accounts`` is that justification — the fold adopts
|
|
1632
|
+
every unattributed member into it — and a resolution carries it only for
|
|
1633
|
+
``SUPPRESSED_NATIVE``, so reading it from the groups covers the split and
|
|
1634
|
+
dormant shapes as well. The evidence is loaded once either way; this only
|
|
1635
|
+
stops it being discarded.
|
|
1636
|
+
|
|
1637
|
+
The seam the overlay, ``account attribute``'s preview and the
|
|
1638
|
+
``accounts.codex_window_attribution`` doctor leg all read: it returns a
|
|
1639
|
+
resolution for EVERY assertion, including the dormant, split and suppressed
|
|
1640
|
+
ones that apply nothing, plus the ownership map only the resolved ones
|
|
1641
|
+
populate.
|
|
1642
|
+
|
|
1643
|
+
``source_root_keys`` bounds which ASSERTIONS are loaded. It never bounds the
|
|
1644
|
+
evidence they are resolved against — that read derives its own roots from
|
|
1645
|
+
the assertions and is deliberately independent of any presentation bound.
|
|
1646
|
+
|
|
1647
|
+
``include_retracted`` additionally returns the tombstoned records so the
|
|
1648
|
+
§7.1 spend reconciliation can see the groups an assertion used to own. Their
|
|
1649
|
+
resolutions are reported, but they never enter the ownership map.
|
|
1650
|
+
"""
|
|
1651
|
+
if not _codex_attribution_table_present(conn):
|
|
1652
|
+
return (), {}, ()
|
|
1653
|
+
assertions = _cache_module().load_active_window_attributions(
|
|
1654
|
+
conn, source_root_keys=source_root_keys)
|
|
1655
|
+
retracted: "tuple[Mapping[str, object], ...]" = ()
|
|
1656
|
+
if include_retracted:
|
|
1657
|
+
retracted = _cache_module().load_active_window_attributions(
|
|
1658
|
+
conn, source_root_keys=source_root_keys, retracted_only=True)
|
|
1659
|
+
if not assertions and not retracted:
|
|
1660
|
+
return (), {}, ()
|
|
1661
|
+
groups = _load_codex_window_group_evidence(conn, (*assertions, *retracted))
|
|
1662
|
+
resolutions, ownership = _wa.resolve_window_attributions(
|
|
1663
|
+
[
|
|
1664
|
+
_wa.WindowAssertion(
|
|
1665
|
+
op_id=str(record["op_id"]),
|
|
1666
|
+
account_key=str(record["account_key"]),
|
|
1667
|
+
source_root_key=str(record["source_root_key"]),
|
|
1668
|
+
logical_limit_key=str(record["logical_limit_key"]),
|
|
1669
|
+
observed_slot=str(record["observed_slot"]),
|
|
1670
|
+
window_minutes=int(record["window_minutes"]),
|
|
1671
|
+
raw_resets_at_utc=frozenset(
|
|
1672
|
+
_codex_attribution_witness(value)
|
|
1673
|
+
for value in record["raw_resets_at_utc"]
|
|
1674
|
+
),
|
|
1675
|
+
)
|
|
1676
|
+
for record in assertions
|
|
1677
|
+
],
|
|
1678
|
+
groups,
|
|
1679
|
+
)
|
|
1680
|
+
if not include_retracted:
|
|
1681
|
+
return resolutions, ownership, groups
|
|
1682
|
+
# Retracted records are resolved SEPARATELY, against the same evidence, so a
|
|
1683
|
+
# tombstone can never claim a group or join a conflict. Their only purpose
|
|
1684
|
+
# here is to name the range whose spend rows may still be stamped.
|
|
1685
|
+
tombstoned, _ignored = _wa.resolve_window_attributions(
|
|
1686
|
+
[
|
|
1687
|
+
_wa.WindowAssertion(
|
|
1688
|
+
op_id=str(record["op_id"]),
|
|
1689
|
+
account_key=str(record["account_key"]),
|
|
1690
|
+
source_root_key=str(record["source_root_key"]),
|
|
1691
|
+
logical_limit_key=str(record["logical_limit_key"]),
|
|
1692
|
+
observed_slot=str(record["observed_slot"]),
|
|
1693
|
+
window_minutes=int(record["window_minutes"]),
|
|
1694
|
+
raw_resets_at_utc=frozenset(
|
|
1695
|
+
_codex_attribution_witness(value)
|
|
1696
|
+
for value in record["raw_resets_at_utc"]
|
|
1697
|
+
),
|
|
1698
|
+
)
|
|
1699
|
+
for record in retracted
|
|
1700
|
+
],
|
|
1701
|
+
groups,
|
|
1702
|
+
)
|
|
1703
|
+
return (*resolutions, *tombstoned), ownership, groups
|
|
1704
|
+
|
|
1705
|
+
|
|
1706
|
+
def codex_attribution_projection_scope(
|
|
1707
|
+
conn: sqlite3.Connection,
|
|
1708
|
+
) -> "dict[str, frozenset] | None":
|
|
1709
|
+
"""The dirty units an attribution change makes the projection owe.
|
|
1710
|
+
|
|
1711
|
+
Returns the same ``{"raw_groups", "units"}`` shape ``_resolve_pass_scope``
|
|
1712
|
+
returns for a ledger-derived scope, so the two are unioned rather than
|
|
1713
|
+
special-cased, or ``None`` when this store has no attribution records at all.
|
|
1714
|
+
|
|
1715
|
+
Three of §8.3's four points live here.
|
|
1716
|
+
|
|
1717
|
+
**Snap expansion.** ``_apply_quota_projection_rows`` requires the loaded
|
|
1718
|
+
observations to carry the COMPLETE current membership of every dirty unit,
|
|
1719
|
+
and the loader's targeted filter matches RAW stored coordinates. One minute
|
|
1720
|
+
of weekly jitter lives in both the limit key and the column, so two raw
|
|
1721
|
+
groups can interpret into one window and asking only for the un-snapped
|
|
1722
|
+
spelling would hand the fold a partial population. This inherits the ledger's
|
|
1723
|
+
own helper rather than re-deriving the closure.
|
|
1724
|
+
|
|
1725
|
+
**Prior units, not only current ones.** A retraction, a newly dormant
|
|
1726
|
+
assertion and a component split all change which unit a group's rows belong
|
|
1727
|
+
to, so the sweep set carries every unit a record named AT ASSERTION TIME
|
|
1728
|
+
alongside every unit its current resolution names. Without the first half the
|
|
1729
|
+
obsolete blocks survive as exactly the duplicated window this work removes.
|
|
1730
|
+
|
|
1731
|
+
**Suppressed and dormant records still count.** A record that resolves to
|
|
1732
|
+
nothing is precisely the one whose previously-materialized rows have to be
|
|
1733
|
+
swept, so the scope is built from every record — active, retracted,
|
|
1734
|
+
suppressed, split — and not from the ownership map.
|
|
1735
|
+
"""
|
|
1736
|
+
if not _codex_attribution_table_present(conn):
|
|
1737
|
+
return None
|
|
1738
|
+
cache = _cache_module()
|
|
1739
|
+
records = (
|
|
1740
|
+
*cache.load_active_window_attributions(conn),
|
|
1741
|
+
*cache.load_active_window_attributions(conn, retracted_only=True),
|
|
1742
|
+
)
|
|
1743
|
+
if not records:
|
|
1744
|
+
return None
|
|
1745
|
+
raw: "set[tuple]" = set()
|
|
1746
|
+
units: "set[str]" = set()
|
|
1747
|
+
for record in records:
|
|
1748
|
+
anchor = _ledger.normalize_reset(record["canonical_resets_at_utc"])
|
|
1749
|
+
group = (
|
|
1750
|
+
str(record["source_root_key"]),
|
|
1751
|
+
str(record["logical_limit_key"]),
|
|
1752
|
+
str(record["observed_slot"]),
|
|
1753
|
+
int(record["window_minutes"]),
|
|
1754
|
+
anchor,
|
|
1755
|
+
)
|
|
1756
|
+
raw.add(group)
|
|
1757
|
+
units.add(_ledger.physical_group_key_text(
|
|
1758
|
+
_ledger.loading_unit_from_raw(group)))
|
|
1759
|
+
resolutions, _ownership = resolve_codex_window_attributions(
|
|
1760
|
+
conn, include_retracted=True)
|
|
1761
|
+
for resolution in resolutions:
|
|
1762
|
+
group_key = resolution.group_key
|
|
1763
|
+
if group_key is None:
|
|
1764
|
+
continue
|
|
1765
|
+
# A resolution's group key is the INTERPRETED one, so its unit is taken
|
|
1766
|
+
# through `loading_unit_from_identity` — the half that strips a
|
|
1767
|
+
# `modelPool` member. `loading_unit_from_raw` applies the length snap
|
|
1768
|
+
# only, so a SUPPRESSED_MODEL_SCOPED resolution fed through it would
|
|
1769
|
+
# produce a unit string no block ever wrote, and the sweep would look for
|
|
1770
|
+
# a key nothing stamped. The raw-coordinate filter below is a different
|
|
1771
|
+
# question and keeps the raw helper: the loader matches STORED columns,
|
|
1772
|
+
# and the stored row is what carries the un-stripped key.
|
|
1773
|
+
raw.add((
|
|
1774
|
+
str(group_key[1]), str(group_key[2]), str(group_key[3]),
|
|
1775
|
+
int(group_key[4]), _ledger.normalize_reset(_utc_iso(group_key[5])),
|
|
1776
|
+
))
|
|
1777
|
+
units.add(_ledger.physical_group_key_text(
|
|
1778
|
+
_ledger.loading_unit_from_identity(
|
|
1779
|
+
source_root_key=str(group_key[1]),
|
|
1780
|
+
logical_limit_key=str(group_key[2]),
|
|
1781
|
+
observed_slot=str(group_key[3]),
|
|
1782
|
+
window_minutes=int(group_key[4]),
|
|
1783
|
+
canonical_reset_iso=_utc_iso(group_key[5]),
|
|
1784
|
+
)))
|
|
1785
|
+
return {
|
|
1786
|
+
"raw_groups": _ledger.snap_equivalent_raw_groups(raw),
|
|
1787
|
+
"units": frozenset(units),
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
|
|
1791
|
+
def _apply_codex_window_attribution_overlay(
|
|
1792
|
+
conn: sqlite3.Connection,
|
|
1793
|
+
observations: "list[QuotaObservation]",
|
|
1794
|
+
*,
|
|
1795
|
+
source_root_keys: "set[str] | None",
|
|
1796
|
+
) -> "list[QuotaObservation]":
|
|
1797
|
+
"""Stamp resolved ownership onto this load's currently-unattributed rows.
|
|
1798
|
+
|
|
1799
|
+
The table-presence probe lives in the resolver, so this is one extra
|
|
1800
|
+
`sqlite_master` read and one indexed read of an empty table on a store that
|
|
1801
|
+
has never asserted anything, and nothing else.
|
|
1802
|
+
"""
|
|
1803
|
+
if not observations:
|
|
1804
|
+
return observations
|
|
1805
|
+
_resolutions, ownership = resolve_codex_window_attributions(
|
|
1806
|
+
conn, source_root_keys=source_root_keys)
|
|
1807
|
+
if not ownership:
|
|
1808
|
+
return observations
|
|
1809
|
+
return list(_wa.apply_resolution(
|
|
1810
|
+
ownership,
|
|
1811
|
+
observations,
|
|
1812
|
+
_lib_quota._physical_window_key,
|
|
1813
|
+
lambda observation: observation.identity.account_key,
|
|
1814
|
+
lambda observation, account: replace(
|
|
1815
|
+
observation,
|
|
1816
|
+
identity=replace(observation.identity, account_key=account),
|
|
1817
|
+
),
|
|
1818
|
+
))
|
|
1819
|
+
|
|
1820
|
+
|
|
1113
1821
|
def load_codex_quota_observations(
|
|
1114
1822
|
*,
|
|
1115
1823
|
source_root_keys: Iterable[str] | None = None,
|
|
@@ -1120,6 +1828,7 @@ def load_codex_quota_observations(
|
|
|
1120
1828
|
physical_signatures: dict[str, str] | None = None,
|
|
1121
1829
|
canonical_resets_between: "tuple[dt.datetime, dt.datetime] | None" = None,
|
|
1122
1830
|
physical_groups: "Iterable[tuple[str, str, str, int, str]] | None" = None,
|
|
1831
|
+
latest_per_identity: bool = False,
|
|
1123
1832
|
) -> tuple[QuotaObservation, ...]:
|
|
1124
1833
|
"""Load only valid root-qualified S1 physical quota rows.
|
|
1125
1834
|
|
|
@@ -1173,6 +1882,39 @@ def load_codex_quota_observations(
|
|
|
1173
1882
|
the account over the population) happens below, in Python, exactly as it
|
|
1174
1883
|
does on the unbounded path — so the caller is responsible for widening a
|
|
1175
1884
|
dirty group to every raw spelling that snaps onto it before asking for it.
|
|
1885
|
+
|
|
1886
|
+
``latest_per_identity`` (#566 §5.1 item 5) returns exactly the latest
|
|
1887
|
+
physical observation of each interpreted identity over the SAME all-history
|
|
1888
|
+
population, without materializing one Python object per retained row. It is
|
|
1889
|
+
a work bound, not a scope bound: no time range, no root filter and no row
|
|
1890
|
+
cap is introduced, so a caller that only needs each window's most recent
|
|
1891
|
+
capture — ``doctor`` — reads the same identities, the same latest captures
|
|
1892
|
+
and therefore the same verdict as the full load, on a store where the full
|
|
1893
|
+
load meant interpreting 266,337 rows to answer a question about 608 of them.
|
|
1894
|
+
|
|
1895
|
+
SQL narrows to the rows that can possibly win, and Python still decides. The
|
|
1896
|
+
partition is the RAW spelling of every column the interpretation reads
|
|
1897
|
+
(root, limit key, slot, window minutes, limit id and name, observed model,
|
|
1898
|
+
account key, and the coalesced reset anchor), which is strictly FINER than
|
|
1899
|
+
the interpreted identity — snapping and the model-pool rewrite only ever
|
|
1900
|
+
merge raw partitions, never split one — and the maximum over a set equals
|
|
1901
|
+
the maximum of its parts' maxima, so narrowing to per-partition winners
|
|
1902
|
+
cannot change the answer. Two consequences are load-bearing rather than
|
|
1903
|
+
incidental: the account key is a partition member, so every (physical
|
|
1904
|
+
window, identified account) pair still contributes a row and the
|
|
1905
|
+
window-account continuity fold below sees the same identified-account set it
|
|
1906
|
+
sees on the full population; and the cut keeps EVERY row tied at the
|
|
1907
|
+
partition's maximum whole-second capture rather than one row per partition,
|
|
1908
|
+
because SQL compares seconds while ``physical_order_key`` compares full
|
|
1909
|
+
datetimes and then breaks ties on the reset anchor and physical position.
|
|
1910
|
+
|
|
1911
|
+
The residual difference is a malformed row. Rows whose required text is
|
|
1912
|
+
blank, or whose capture or reset instant SQLite cannot read as a time, are
|
|
1913
|
+
excluded in SQL exactly as the loop below excludes them; a row that survives
|
|
1914
|
+
those predicates but still fails a Python parse can, on this path, hide an
|
|
1915
|
+
older valid capture of the same window that the full load would have
|
|
1916
|
+
reported. That trade is confined to a store already carrying corrupt quota
|
|
1917
|
+
rows, and it is the only behavioural difference between the two paths.
|
|
1176
1918
|
"""
|
|
1177
1919
|
for name, value in (
|
|
1178
1920
|
("captured_at_or_after", captured_at_or_after), ("active_at", active_at),
|
|
@@ -1200,6 +1942,20 @@ def load_codex_quota_observations(
|
|
|
1200
1942
|
if max_rows is not None:
|
|
1201
1943
|
if not isinstance(max_rows, int) or isinstance(max_rows, bool) or max_rows <= 0:
|
|
1202
1944
|
raise ValueError("max_rows must be a positive integer or None")
|
|
1945
|
+
if latest_per_identity and (
|
|
1946
|
+
max_rows is not None
|
|
1947
|
+
or physical_signatures is not None
|
|
1948
|
+
or physical_groups is not None
|
|
1949
|
+
or captured_at_or_after is not None
|
|
1950
|
+
or active_at is not None
|
|
1951
|
+
or canonical_resets_between is not None
|
|
1952
|
+
):
|
|
1953
|
+
# Every one of these either bounds the population or accumulates over
|
|
1954
|
+
# it, and the reduction below already discards the rows a bound would
|
|
1955
|
+
# act on. Refuse the combination rather than return a set whose meaning
|
|
1956
|
+
# depends on which narrowing ran first.
|
|
1957
|
+
raise ValueError(
|
|
1958
|
+
"latest_per_identity cannot be combined with a population bound")
|
|
1203
1959
|
group_filter: tuple[tuple[object, ...], ...] | None = None
|
|
1204
1960
|
if physical_groups is not None:
|
|
1205
1961
|
# Neither combination has a coherent meaning, and both would fail
|
|
@@ -1273,16 +2029,19 @@ def load_codex_quota_observations(
|
|
|
1273
2029
|
"quota_window_snapshots.observed_model AS observed_model"
|
|
1274
2030
|
if has_observed_model else "NULL AS observed_model"
|
|
1275
2031
|
)
|
|
1276
|
-
|
|
1277
|
-
SELECT source, source_root_key, source_path, line_offset,
|
|
2032
|
+
select_list = """source, source_root_key, source_path, line_offset,
|
|
1278
2033
|
captured_at_utc, observed_slot, logical_limit_key, limit_id,
|
|
1279
2034
|
limit_name, window_minutes, used_percent, resets_at_utc,
|
|
1280
2035
|
plan_type, individual_limit_json, reached_type,
|
|
1281
|
-
{model_expr}, {account_expr}, {anchor_expr}
|
|
2036
|
+
{model_expr}, {account_expr}, {anchor_expr}""".format(
|
|
2037
|
+
model_expr=model_expr, account_expr=account_expr,
|
|
2038
|
+
anchor_expr=anchor_expr,
|
|
2039
|
+
)
|
|
2040
|
+
sql = f"""
|
|
2041
|
+
SELECT {select_list}
|
|
1282
2042
|
FROM quota_window_snapshots
|
|
1283
2043
|
WHERE source='codex' AND source_root_key IS NOT NULL
|
|
1284
|
-
"""
|
|
1285
|
-
anchor_expr=anchor_expr)
|
|
2044
|
+
"""
|
|
1286
2045
|
params: list[object] = []
|
|
1287
2046
|
if requested is not None:
|
|
1288
2047
|
if not requested:
|
|
@@ -1354,6 +2113,52 @@ def load_codex_quota_observations(
|
|
|
1354
2113
|
"COALESCE(canonical_resets_at_utc, resets_at_utc)"
|
|
1355
2114
|
if has_anchor else "resets_at_utc"
|
|
1356
2115
|
)
|
|
2116
|
+
if latest_per_identity:
|
|
2117
|
+
# Every column the per-row interpretation reads, in its RAW
|
|
2118
|
+
# spelling. Partitioning on the raw columns is strictly finer than
|
|
2119
|
+
# the interpreted identity, which is what makes the per-partition
|
|
2120
|
+
# maximum a safe candidate set (see the docstring).
|
|
2121
|
+
partition_by = ", ".join((
|
|
2122
|
+
"source_root_key",
|
|
2123
|
+
"logical_limit_key",
|
|
2124
|
+
"observed_slot",
|
|
2125
|
+
"window_minutes",
|
|
2126
|
+
"limit_id",
|
|
2127
|
+
"limit_name",
|
|
2128
|
+
"observed_model" if has_observed_model else "NULL",
|
|
2129
|
+
"account_key" if has_account else "NULL",
|
|
2130
|
+
reset_group_expr,
|
|
2131
|
+
))
|
|
2132
|
+
# The same validity predicates the interpretation loop applies,
|
|
2133
|
+
# restricted to the ones SQL can state. A row SQLite cannot read as
|
|
2134
|
+
# a time can never win its partition, which matters because the
|
|
2135
|
+
# window maximum ignores NULLs.
|
|
2136
|
+
required_not_blank = " AND ".join(
|
|
2137
|
+
f"trim(coalesce({column}, '')) <> ''"
|
|
2138
|
+
for column in _CODEX_QUOTA_REQUIRED_TEXT
|
|
2139
|
+
)
|
|
2140
|
+
# The root filter is the only optional clause that can still be in
|
|
2141
|
+
# play here, because every other bound is refused above, so the
|
|
2142
|
+
# inner query is rebuilt rather than surgically edited.
|
|
2143
|
+
root_clause = (
|
|
2144
|
+
" AND source_root_key IN ("
|
|
2145
|
+
+ ",".join("?" for _ in requested) + ")"
|
|
2146
|
+
if requested is not None else ""
|
|
2147
|
+
)
|
|
2148
|
+
sql = (
|
|
2149
|
+
f"SELECT * FROM (SELECT {select_list},"
|
|
2150
|
+
" MAX(unixepoch(captured_at_utc)) OVER"
|
|
2151
|
+
f" (PARTITION BY {partition_by}) AS _group_latest_capture"
|
|
2152
|
+
" FROM quota_window_snapshots WHERE"
|
|
2153
|
+
" source='codex' AND source_root_key IS NOT NULL"
|
|
2154
|
+
f" AND {required_not_blank}"
|
|
2155
|
+
" AND unixepoch(captured_at_utc) IS NOT NULL"
|
|
2156
|
+
" AND unixepoch(resets_at_utc) IS NOT NULL"
|
|
2157
|
+
f"{root_clause}"
|
|
2158
|
+
") WHERE unixepoch(captured_at_utc) = _group_latest_capture"
|
|
2159
|
+
" ORDER BY source_root_key, captured_at_utc, resets_at_utc,"
|
|
2160
|
+
" source_path, line_offset"
|
|
2161
|
+
)
|
|
1357
2162
|
shards: list[tuple[str, tuple[object, ...]]] = []
|
|
1358
2163
|
if group_filter is None:
|
|
1359
2164
|
shards.append((sql, tuple(params)))
|
|
@@ -1371,11 +2176,9 @@ def load_codex_quota_observations(
|
|
|
1371
2176
|
result: list[QuotaObservation] = []
|
|
1372
2177
|
signature_tuples: dict[str, dict[str, list[tuple[object, ...]]]] = {}
|
|
1373
2178
|
for row in _iter_shard_rows(conn, shards):
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
)
|
|
1378
|
-
if any(row[name] is None or not str(row[name]).strip() for name in required_text):
|
|
2179
|
+
if not _codex_quota_required_text_present(
|
|
2180
|
+
row[name] for name in _CODEX_QUOTA_REQUIRED_TEXT
|
|
2181
|
+
):
|
|
1379
2182
|
continue
|
|
1380
2183
|
try:
|
|
1381
2184
|
# #416 spec §4.3: snap a jittered `window_minutes` (the stray
|
|
@@ -1467,6 +2270,19 @@ def load_codex_quota_observations(
|
|
|
1467
2270
|
observation.source_path,
|
|
1468
2271
|
observation.line_offset,
|
|
1469
2272
|
))
|
|
2273
|
+
# Operator attribution overlay (#500 spec §6.4), immediately before the
|
|
2274
|
+
# fold. Ordered here and nowhere else: the fold then finds nothing left
|
|
2275
|
+
# to adopt in an attributed group, `build_blocks` carries the account
|
|
2276
|
+
# into `quota_window_blocks`, and the spend-adoption pass sees a
|
|
2277
|
+
# claiming window that identifies exactly one account — all three axes
|
|
2278
|
+
# from ONE insertion point, with no new adoption logic anywhere.
|
|
2279
|
+
#
|
|
2280
|
+
# Ordered AFTER the physical signatures too, which are computed from the
|
|
2281
|
+
# physical tuple alone and are therefore account-independent, so the
|
|
2282
|
+
# certificate a bounded read stamps does not depend on whether an
|
|
2283
|
+
# assertion happened to apply.
|
|
2284
|
+
result = _apply_codex_window_attribution_overlay(
|
|
2285
|
+
conn, result, source_root_keys=requested)
|
|
1470
2286
|
# Window-account continuity fold (#341 spec §2): adopt unidentified
|
|
1471
2287
|
# observations into a same-physical-window identified account (exactly
|
|
1472
2288
|
# one). Physical signatures above are account-independent (computed from
|
|
@@ -1474,6 +2290,23 @@ def load_codex_quota_observations(
|
|
|
1474
2290
|
# recursion path below re-fetches + re-folds. Idempotent for an
|
|
1475
2291
|
# already-identified set — a single-account cache is a no-op.
|
|
1476
2292
|
result = list(adopt_unidentified_observations(result))
|
|
2293
|
+
if latest_per_identity:
|
|
2294
|
+
# Python decides the winner, using the same total order every other
|
|
2295
|
+
# caller uses. SQL only removed rows that could not have won.
|
|
2296
|
+
latest: dict[QuotaWindowIdentity, QuotaObservation] = {}
|
|
2297
|
+
for observation in result:
|
|
2298
|
+
held = latest.get(observation.identity)
|
|
2299
|
+
if held is None or physical_order_key(
|
|
2300
|
+
observation,
|
|
2301
|
+
) > physical_order_key(held):
|
|
2302
|
+
latest[observation.identity] = observation
|
|
2303
|
+
result = sorted(latest.values(), key=lambda observation: (
|
|
2304
|
+
observation.identity.source_root_key,
|
|
2305
|
+
observation.captured_at,
|
|
2306
|
+
observation.resets_at,
|
|
2307
|
+
observation.source_path,
|
|
2308
|
+
observation.line_offset,
|
|
2309
|
+
))
|
|
1477
2310
|
if physical_signatures is not None:
|
|
1478
2311
|
physical_signatures.clear()
|
|
1479
2312
|
roots = requested if requested is not None else set(signature_tuples)
|
|
@@ -2806,6 +3639,21 @@ def reconcile_codex_quota_projection(
|
|
|
2806
3639
|
physical_sequence = codex_physical_mutation_seq(cache)
|
|
2807
3640
|
certificate = load_codex_quota_projection_certificate(cache)
|
|
2808
3641
|
ledger_high = _ledger_max_seq(cache)
|
|
3642
|
+
# #500 §8.3. An attribution change fires no `quota_window_snapshots`
|
|
3643
|
+
# trigger and writes no ledger entry, so the change ledger cannot
|
|
3644
|
+
# see it and the certificate reader above has already declined a
|
|
3645
|
+
# certificate computed against a different revision. What is left is
|
|
3646
|
+
# naming the groups the pass now owes, which is only worth reading
|
|
3647
|
+
# when the revision actually moved.
|
|
3648
|
+
attribution_revision = (
|
|
3649
|
+
_cache_module().codex_window_attribution_revision(cache))
|
|
3650
|
+
attribution_stale = _certificate_attribution_revision(
|
|
3651
|
+
_codex_quota_projection_certificate_payload(cache)
|
|
3652
|
+
) != attribution_revision
|
|
3653
|
+
attribution_scope = (
|
|
3654
|
+
codex_attribution_projection_scope(cache)
|
|
3655
|
+
if attribution_stale else None
|
|
3656
|
+
)
|
|
2809
3657
|
finally:
|
|
2810
3658
|
cache.commit()
|
|
2811
3659
|
# ONE stats read decides the whole shape of the pass: whether the
|
|
@@ -2924,6 +3772,22 @@ def reconcile_codex_quota_projection(
|
|
|
2924
3772
|
stale_reverse_map=stale_reverse_map,
|
|
2925
3773
|
verification_due=verification_due,
|
|
2926
3774
|
)
|
|
3775
|
+
if dirty_units is not None and attribution_scope:
|
|
3776
|
+
# Union, never replace: the ledger's own dirty set is still owed.
|
|
3777
|
+
# A bounded pass that then exceeds the incremental ceiling falls
|
|
3778
|
+
# back to whole history exactly as `_resolve_pass_scope` would,
|
|
3779
|
+
# because N indexed seeks stop beating one scan at the same point
|
|
3780
|
+
# however the units were arrived at.
|
|
3781
|
+
merged_units = (
|
|
3782
|
+
frozenset(dirty_units["units"]) | attribution_scope["units"])
|
|
3783
|
+
if len(merged_units) > _MAX_INCREMENTAL_UNITS:
|
|
3784
|
+
dirty_units = None
|
|
3785
|
+
else:
|
|
3786
|
+
dirty_units = {
|
|
3787
|
+
"raw_groups": frozenset(dirty_units["raw_groups"])
|
|
3788
|
+
| attribution_scope["raw_groups"],
|
|
3789
|
+
"units": merged_units,
|
|
3790
|
+
}
|
|
2927
3791
|
# ``None`` means the genuine whole-history path. A set means complete
|
|
2928
3792
|
# histories for only those roots — axis 2 and epoch-1007 axis 4 can both
|
|
2929
3793
|
# be satisfied without scanning unrelated roots.
|
|
@@ -3202,9 +4066,15 @@ def reconcile_codex_quota_projection(
|
|
|
3202
4066
|
# cache write this function makes, and folding it in avoids adding a second
|
|
3203
4067
|
# unflocked mutator.
|
|
3204
4068
|
if holder["signatures"] is not None:
|
|
4069
|
+
# `attribution_revision` is the value read at the top of the pass, and
|
|
4070
|
+
# passing it is what makes the certificate bind THIS pass's inputs: the
|
|
4071
|
+
# ingest cycle above can materialize a pending attribution op, and a
|
|
4072
|
+
# certificate stamped at the post-ingest revision would certify a
|
|
4073
|
+
# projection built from pre-ingest observations (#500 §8.3).
|
|
3205
4074
|
_store_codex_quota_projection_certificate(
|
|
3206
4075
|
sequence=physical_sequence, signatures=holder["signatures"],
|
|
3207
4076
|
prune_ledger_through=watermark_target,
|
|
4077
|
+
attribution_revision=attribution_revision,
|
|
3208
4078
|
)
|
|
3209
4079
|
return holder["result"]
|
|
3210
4080
|
|
|
@@ -3266,7 +4136,7 @@ def _codex_cache_account_predicate(
|
|
|
3266
4136
|
dollars are these" — widening it IS attribution, which D1 forbids, and it
|
|
3267
4137
|
puts one row in two scopes.
|
|
3268
4138
|
|
|
3269
|
-
|
|
4139
|
+
FIVE stamping mechanisms exist and must never be conflated: the
|
|
3270
4140
|
quota-observation fold (``adopt_unidentified_observations``, per physical-
|
|
3271
4141
|
window group, landing post-fold in ``quota_window_blocks`` /
|
|
3272
4142
|
``quota_percent_milestones`` and NEVER written back to
|
|
@@ -3276,7 +4146,16 @@ def _codex_cache_account_predicate(
|
|
|
3276
4146
|
spec — ``_lib_codex_account_adoption`` +
|
|
3277
4147
|
``_cctally_cache.apply_codex_window_spend_adoption``, which stamps that same
|
|
3278
4148
|
``codex_session_entries.account_key`` column at ingest from the window's
|
|
3279
|
-
single identified account);
|
|
4149
|
+
single identified account); OPERATOR ATTRIBUTION (#500 —
|
|
4150
|
+
``_lib_codex_window_attribution`` + ``_apply_codex_window_attribution_overlay``,
|
|
4151
|
+
which makes a group identified at FOLD time from a journal-recorded
|
|
4152
|
+
assertion and therefore reaches both the percentage and the spend axis
|
|
4153
|
+
through the two mechanisms above rather than beside them); and the stats
|
|
4154
|
+
``accounts`` registry.
|
|
4155
|
+
|
|
4156
|
+
The rule below is unchanged by that fifth entry, and deliberately so:
|
|
4157
|
+
operator attribution feeds the fold, so every read already scoped by the
|
|
4158
|
+
fold's answer stays scoped the same way and no predicate flavour moves.
|
|
3280
4159
|
|
|
3281
4160
|
So ``codex_session_entries.account_key`` now carries window-derived
|
|
3282
4161
|
attribution IN ADDITION to per-file decisions, and that is precisely what
|