cctally 1.113.0 → 1.114.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 +22 -0
- package/README.md +4 -6
- package/bin/_cctally_cache.py +1683 -485
- package/bin/_cctally_core.py +223 -1
- package/bin/_cctally_dashboard.py +96 -18
- package/bin/_cctally_dashboard_perf.py +128 -9
- package/bin/_cctally_dashboard_sources.py +267 -204
- package/bin/_cctally_db.py +861 -18
- package/bin/_cctally_diagnosis_sources.py +22 -4
- package/bin/_cctally_doctor.py +136 -18
- package/bin/_cctally_five_hour.py +2 -2
- package/bin/_cctally_forecast.py +27 -6
- package/bin/_cctally_journal.py +67 -5
- package/bin/_cctally_milestone_history.py +82 -9
- package/bin/_cctally_percent_breakdown.py +3 -2
- package/bin/_cctally_quota.py +201 -33
- package/bin/_cctally_record.py +14 -0
- package/bin/_cctally_rederive.py +23 -9
- package/bin/_cctally_source_analytics.py +60 -15
- package/bin/_cctally_statusline.py +17 -0
- package/bin/_cctally_store.py +80 -5
- package/bin/_cctally_tui.py +4 -3
- package/bin/_cctally_weekrefs.py +128 -21
- package/bin/_lib_cache_report.py +35 -6
- package/bin/_lib_codex_conversation.py +32 -0
- package/bin/_lib_codex_conversation_query.py +164 -30
- package/bin/_lib_conversation_query.py +95 -56
- package/bin/_lib_conversation_retention.py +1605 -640
- package/bin/_lib_cost_provenance.py +53 -2
- package/bin/_lib_doctor.py +263 -69
- package/bin/_lib_fmt.py +11 -0
- package/bin/_lib_merge_gate.py +469 -51
- package/bin/_lib_perf.py +27 -1
- package/bin/_lib_pricing.py +193 -8
- package/bin/_lib_pricing_check.py +20 -4
- package/bin/_lib_reclaim_planner.py +792 -0
- package/bin/_lib_rederive.py +225 -77
- package/bin/_lib_snapshot_cache.py +21 -0
- package/bin/_lib_sqlite_close.py +81 -0
- package/bin/_lib_subscription_weeks.py +100 -17
- package/bin/_lib_tick_stats.py +217 -17
- package/bin/_lib_wal_checkpoint.py +1077 -0
- package/bin/_lib_write_budget.py +483 -0
- package/bin/_lib_write_io.py +703 -0
- package/bin/cctally +8 -0
- package/dashboard/static/assets/{ConversationsView-CEZ-BUyi.js → ConversationsView-Bc8LCOAZ.js} +1 -1
- package/dashboard/static/assets/{DoctorModal-CE1oH_Zy.js → DoctorModal-D--qVBwe.js} +1 -1
- package/dashboard/static/assets/ModalRoot-DLPgyAM_.js +1 -0
- package/dashboard/static/assets/{ProjectsDrillPanel-B3DZoCeD.js → ProjectsDrillPanel-z53a4v4M.js} +1 -1
- package/dashboard/static/assets/{SourceDetailModal-CHp9EgLP.js → SourceDetailModal-Bw0T8W-W.js} +1 -1
- package/dashboard/static/assets/{UpdateModal-5iKST6jo.js → UpdateModal-D5AKx4Yw.js} +1 -1
- package/dashboard/static/assets/index-BKITk1CT.css +1 -0
- package/dashboard/static/assets/{index-Cm0VTxJC.js → index-BXDOzSng.js} +3 -3
- package/dashboard/static/assets/{outlineNavigation-DkXGL8J6.js → outlineNavigation-JObY7cUY.js} +1 -1
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +6 -1
- package/dashboard/static/assets/ModalRoot-D5Y8IFUG.js +0 -1
- package/dashboard/static/assets/index-Dt4tqf43.css +0 -1
|
@@ -2865,23 +2865,57 @@ def _codex_cache_report_wire(
|
|
|
2865
2865
|
)
|
|
2866
2866
|
|
|
2867
2867
|
|
|
2868
|
-
#: Per-file terminal thread aliases,
|
|
2869
|
-
#:
|
|
2870
|
-
#:
|
|
2871
|
-
#:
|
|
2872
|
-
#: join
|
|
2873
|
-
#:
|
|
2868
|
+
#: Per-file terminal thread aliases, each with its file's first accounting
|
|
2869
|
+
#: instant.
|
|
2870
|
+
#:
|
|
2871
|
+
#: #901 W2 (spec §5.2, T2). The pre-#901 form LEFT JOINed every accounting row
|
|
2872
|
+
#: and GROUPed the join back to one row per file, which needed two temp b-trees
|
|
2873
|
+
#: over the joined history (168 MB of temp-file writes per Codex-active build on
|
|
2874
|
+
#: the measured store). ``codex_session_files.path`` is the primary key, so the
|
|
2875
|
+
#: grouping only ever collapsed one file's joined rows; a correlated MIN per
|
|
2876
|
+
#: file answers the same question with one covering seek of
|
|
2877
|
+
#: ``idx_codex_entries_root_path_time``, and ``idx_codex_files_alias_recent``
|
|
2878
|
+
#: (partial on the same filter, in the final order) removes the sorter. A file
|
|
2879
|
+
#: with no accounting row still yields NULL, and the root qualification is
|
|
2880
|
+
#: unchanged. Module-level so the plan tests assert THIS text rather than a
|
|
2881
|
+
#: copy that can drift from it.
|
|
2874
2882
|
_CODEX_FILE_ALIAS_SQL = (
|
|
2875
2883
|
"SELECT f.source_root_key, f.path, f.last_native_thread_id, "
|
|
2876
|
-
"f.last_session_id,
|
|
2884
|
+
"f.last_session_id, "
|
|
2885
|
+
"(SELECT MIN(e.timestamp_utc) FROM codex_session_entries AS e "
|
|
2886
|
+
"WHERE e.source_root_key=f.source_root_key AND e.source_path=f.path) "
|
|
2877
2887
|
"FROM codex_session_files AS f "
|
|
2878
|
-
"LEFT JOIN codex_session_entries AS e "
|
|
2879
|
-
"ON e.source_root_key=f.source_root_key AND e.source_path=f.path "
|
|
2880
2888
|
"WHERE f.last_native_thread_id IS NOT NULL AND f.last_native_thread_id != '' "
|
|
2881
|
-
"GROUP BY f.source_root_key, f.path, f.last_native_thread_id, f.last_session_id "
|
|
2882
2889
|
"ORDER BY f.last_ingested_at DESC, f.path DESC"
|
|
2883
2890
|
)
|
|
2884
2891
|
|
|
2892
|
+
#: Conversation threads with their accounting session and first accounting
|
|
2893
|
+
#: instant (#901 W2, spec §5.2, T2).
|
|
2894
|
+
#:
|
|
2895
|
+
#: Two INDEPENDENT correlated lookups replace the pre-#901 ``WITH accounting``
|
|
2896
|
+
#: CTE, which grouped every accounting row (materialized, then probed through
|
|
2897
|
+
#: an automatic index, then sorted). The accounting session is the row with the
|
|
2898
|
+
#: minimum ``id`` (``idx_codex_entries_root_path``, whose trailing rowid makes it
|
|
2899
|
+
#: a seek) and ``started_at`` is the minimum ``timestamp_utc``
|
|
2900
|
+
#: (``idx_codex_entries_root_path_time``); they need not be the same row, which
|
|
2901
|
+
#: is exactly what the CTE's two independent minima computed.
|
|
2902
|
+
#: ``idx_codex_threads_recent`` serves the final order. A thread with no
|
|
2903
|
+
#: accounting rows still yields NULL for both.
|
|
2904
|
+
_CODEX_CONVERSATION_METADATA_CORE_SQL = (
|
|
2905
|
+
"SELECT t.source_root_key, t.source_path, t.native_thread_id, "
|
|
2906
|
+
"(SELECT e.session_id FROM codex_session_entries AS e WHERE e.id = ("
|
|
2907
|
+
"SELECT MIN(f.id) FROM codex_session_entries AS f "
|
|
2908
|
+
"WHERE f.source_root_key=t.source_root_key AND f.source_path=t.source_path"
|
|
2909
|
+
")) AS accounting_session_id, "
|
|
2910
|
+
"CAST(t.cwd AS BLOB) AS cwd_blob, "
|
|
2911
|
+
"CAST(t.git_json AS BLOB) AS git_json_blob, "
|
|
2912
|
+
"(SELECT MIN(s.timestamp_utc) FROM codex_session_entries AS s "
|
|
2913
|
+
"WHERE s.source_root_key=t.source_root_key AND s.source_path=t.source_path"
|
|
2914
|
+
") AS started_at, t.last_seen_utc "
|
|
2915
|
+
"FROM codex_conversation_threads AS t "
|
|
2916
|
+
"ORDER BY t.last_seen_utc DESC, t.conversation_key DESC"
|
|
2917
|
+
)
|
|
2918
|
+
|
|
2885
2919
|
|
|
2886
2920
|
@dataclass(frozen=True)
|
|
2887
2921
|
class _CodexConversationMetadataRead:
|
|
@@ -3015,23 +3049,7 @@ def _codex_conversation_metadata(
|
|
|
3015
3049
|
metadata: dict[tuple[str, str], dict[str, object]] = {}
|
|
3016
3050
|
try:
|
|
3017
3051
|
core_rows = tuple(cache_conn.execute(
|
|
3018
|
-
|
|
3019
|
-
" SELECT source_root_key, source_path, MIN(id) AS first_id,"
|
|
3020
|
-
" MIN(timestamp_utc) AS started_at"
|
|
3021
|
-
" FROM codex_session_entries"
|
|
3022
|
-
" GROUP BY source_root_key, source_path"
|
|
3023
|
-
") "
|
|
3024
|
-
"SELECT t.source_root_key, t.source_path, t.native_thread_id, "
|
|
3025
|
-
"e.session_id AS accounting_session_id, "
|
|
3026
|
-
"CAST(t.cwd AS BLOB) AS cwd_blob, "
|
|
3027
|
-
"CAST(t.git_json AS BLOB) AS git_json_blob, "
|
|
3028
|
-
"a.started_at, t.last_seen_utc "
|
|
3029
|
-
"FROM codex_conversation_threads AS t "
|
|
3030
|
-
"LEFT JOIN accounting AS a "
|
|
3031
|
-
"ON a.source_root_key=t.source_root_key AND a.source_path=t.source_path "
|
|
3032
|
-
"LEFT JOIN codex_session_entries AS e ON e.id=a.first_id "
|
|
3033
|
-
"ORDER BY t.last_seen_utc DESC, t.conversation_key DESC"
|
|
3034
|
-
))
|
|
3052
|
+
_CODEX_CONVERSATION_METADATA_CORE_SQL))
|
|
3035
3053
|
from _cctally_cache import _codex_conversation_project_attribution
|
|
3036
3054
|
|
|
3037
3055
|
def _attribution(root_key, cwd_blob, git_json_blob):
|
|
@@ -4535,6 +4553,28 @@ _CODEX_PROJECT_LABEL_CACHE: dict[object, dict[str, object]] = _ObservedDict()
|
|
|
4535
4553
|
_CODEX_PROJECT_LABEL_ALGORITHM_VERSION = "collision-safe-labels-v1"
|
|
4536
4554
|
|
|
4537
4555
|
|
|
4556
|
+
def _codex_visible_population_name(
|
|
4557
|
+
signature: tuple | None,
|
|
4558
|
+
token: int | None,
|
|
4559
|
+
visible_start: dt.datetime,
|
|
4560
|
+
) -> tuple | None:
|
|
4561
|
+
"""#872: the exact in-process name of one VISIBLE Codex population.
|
|
4562
|
+
|
|
4563
|
+
The carrier signature alone repeats across a cleared state (its
|
|
4564
|
+
generation leg restarts at one, #857), and it names the CARRIER window,
|
|
4565
|
+
which a configured budget widens below the visible lower bound. The
|
|
4566
|
+
never-reused provenance token closes the first gap and the visible lower
|
|
4567
|
+
bound the second. `None` — no signature (no ledger) or no token (metadata
|
|
4568
|
+
incomplete) — names nothing, so it can be neither a hit nor a base.
|
|
4569
|
+
"""
|
|
4570
|
+
if signature is None or token is None:
|
|
4571
|
+
return None
|
|
4572
|
+
return (
|
|
4573
|
+
*signature, ("provenance", int(token)),
|
|
4574
|
+
("visible-start", visible_start),
|
|
4575
|
+
)
|
|
4576
|
+
|
|
4577
|
+
|
|
4538
4578
|
def _codex_project_label_signature(base: tuple | None) -> tuple | None:
|
|
4539
4579
|
"""Extend one accounting population name with the labelling algorithm's.
|
|
4540
4580
|
|
|
@@ -4554,6 +4594,9 @@ def _codex_project_label_signature(base: tuple | None) -> tuple | None:
|
|
|
4554
4594
|
def _codex_project_population_signature() -> tuple | None:
|
|
4555
4595
|
"""The identity the project and label caches compare instead of scanning.
|
|
4556
4596
|
|
|
4597
|
+
Test-facing since #872: a build names its population from its OWN capture
|
|
4598
|
+
(`_codex_visible_population_name`), never from this process-wide reading.
|
|
4599
|
+
|
|
4557
4600
|
`None` means the accounting carrier could not establish an identity, and
|
|
4558
4601
|
every consumer must then prove its hit the old way rather than assume one.
|
|
4559
4602
|
"""
|
|
@@ -4722,124 +4765,41 @@ def _cached_projects_wire(
|
|
|
4722
4765
|
semantic_signature: object,
|
|
4723
4766
|
population_signature: tuple | None = None,
|
|
4724
4767
|
) -> dict[str, object]:
|
|
4725
|
-
"""
|
|
4726
|
-
|
|
4727
|
-
The
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4768
|
+
"""Reuse the project wire only under a name that identifies the population.
|
|
4769
|
+
|
|
4770
|
+
The name is `(semantic_signature, population_signature)`, where the
|
|
4771
|
+
population half is the visible population's #872 name (provenance token
|
|
4772
|
+
and visible start included). An equal name therefore means an unchanged
|
|
4773
|
+
population, so a hit returns the retained value as it stands. A miss
|
|
4774
|
+
rebuilds through `_projects_wire`, whose label cache advances by the delta
|
|
4775
|
+
when G1 has kept the layer coherent. `None` names nothing: no reuse.
|
|
4776
|
+
|
|
4777
|
+
The former group splice applied a delta onto retained groups under a
|
|
4778
|
+
matching key and read labels from the label cache's state. With a sound
|
|
4779
|
+
name it was reachable only through a reused name or the signature-less
|
|
4780
|
+
pair-set key, and there it raised or double-counted (#872 D3).
|
|
4732
4781
|
"""
|
|
4733
4782
|
values = tuple(entries)
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
population_key: object = population_signature
|
|
4738
|
-
else:
|
|
4739
|
-
population_key = frozenset(
|
|
4740
|
-
(str(entry.project_key), str(entry.project_label))
|
|
4741
|
-
for entry in values
|
|
4742
|
-
)
|
|
4743
|
-
# `entries` is already the complete half-open population for the advancing
|
|
4744
|
-
# upper bound. A moving wall clock is therefore not an aggregation
|
|
4745
|
-
# semantic: `build_cached_codex_accounting` emits newly-visible rows in the
|
|
4746
|
-
# delta when its upper bound advances. Keeping `accounting_end` here made
|
|
4747
|
-
# every live dirty tick discard every project group before that delta could
|
|
4748
|
-
# be spliced.
|
|
4749
|
-
signature = (semantic_signature, population_key, context.range_start)
|
|
4750
|
-
state = _CODEX_PROJECT_WIRE_CACHE.get(cache_key)
|
|
4751
|
-
if state is None or state[0] != signature:
|
|
4752
|
-
value = _projects_wire(
|
|
4783
|
+
if population_signature is None:
|
|
4784
|
+
_CODEX_PROJECT_WIRE_CACHE.pop(cache_key, None)
|
|
4785
|
+
return _projects_wire(
|
|
4753
4786
|
context, quota_observations, values,
|
|
4754
4787
|
accounting_end=accounting_end, cache_key=cache_key,
|
|
4755
|
-
population_signature=
|
|
4788
|
+
population_signature=None,
|
|
4756
4789
|
changed_old=changed_old, changed_new=changed_new,
|
|
4757
4790
|
)
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
).append(entry)
|
|
4763
|
-
_CODEX_PROJECT_WIRE_CACHE[cache_key] = (
|
|
4764
|
-
signature, value,
|
|
4765
|
-
{key: tuple(group) for key, group in groups.items()},
|
|
4766
|
-
)
|
|
4767
|
-
return value
|
|
4768
|
-
|
|
4769
|
-
_CODEX_PROJECT_WIRE_CACHE.touch(cache_key)
|
|
4770
|
-
affected = {
|
|
4771
|
-
(str(entry.source_root_key), str(entry.project_key))
|
|
4772
|
-
for entry in (*changed_old, *changed_new)
|
|
4773
|
-
}
|
|
4774
|
-
if not affected:
|
|
4791
|
+
signature = (semantic_signature, population_signature)
|
|
4792
|
+
state = _CODEX_PROJECT_WIRE_CACHE.get(cache_key)
|
|
4793
|
+
if state is not None and state[0] == signature:
|
|
4794
|
+
_CODEX_PROJECT_WIRE_CACHE.touch(cache_key)
|
|
4775
4795
|
return state[1]
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
}
|
|
4782
|
-
groups = dict(state[2])
|
|
4783
|
-
for key in affected:
|
|
4784
|
-
groups[key] = tuple(
|
|
4785
|
-
entry for entry in groups.get(key, ())
|
|
4786
|
-
if int(getattr(entry, "cache_entry_id", 0) or 0) not in old_ids
|
|
4787
|
-
)
|
|
4788
|
-
for entry in changed_new:
|
|
4789
|
-
key = (str(entry.source_root_key), str(entry.project_key))
|
|
4790
|
-
groups[key] = (*groups.get(key, ()), entry)
|
|
4791
|
-
for key in affected:
|
|
4792
|
-
if groups.get(key):
|
|
4793
|
-
groups[key] = tuple(sorted(
|
|
4794
|
-
groups[key], key=_codex_incremental_entry_order,
|
|
4795
|
-
))
|
|
4796
|
-
else:
|
|
4797
|
-
groups.pop(key, None)
|
|
4798
|
-
partial_entries = tuple(
|
|
4799
|
-
replace(entry, display_label=labels[str(entry.project_key)])
|
|
4800
|
-
for key in sorted(affected) for entry in groups.get(key, ())
|
|
4801
|
-
)
|
|
4802
|
-
result = build_codex_project_result(
|
|
4803
|
-
partial_entries,
|
|
4804
|
-
range_start=context.range_start,
|
|
4805
|
-
range_end=accounting_end,
|
|
4806
|
-
as_of=context.now_utc,
|
|
4796
|
+
value = _projects_wire(
|
|
4797
|
+
context, quota_observations, values,
|
|
4798
|
+
accounting_end=accounting_end, cache_key=cache_key,
|
|
4799
|
+
population_signature=population_signature,
|
|
4800
|
+
changed_old=changed_old, changed_new=changed_new,
|
|
4807
4801
|
)
|
|
4808
|
-
|
|
4809
|
-
"key": dashboard_resource_key("project", "codex", row.project_key),
|
|
4810
|
-
"source": "codex",
|
|
4811
|
-
"label": row.display_label,
|
|
4812
|
-
"session_count": row.session_count,
|
|
4813
|
-
"first_seen": row.first_seen.astimezone(UTC).isoformat(),
|
|
4814
|
-
"last_seen": row.last_seen.astimezone(UTC).isoformat(),
|
|
4815
|
-
"cost_usd": row.totals.cost_usd,
|
|
4816
|
-
"input_tokens": row.totals.input_tokens,
|
|
4817
|
-
"cached_input_tokens": row.totals.cached_input_tokens,
|
|
4818
|
-
"output_tokens": row.totals.output_tokens,
|
|
4819
|
-
"reasoning_output_tokens": row.totals.reasoning_output_tokens,
|
|
4820
|
-
"total_tokens": row.totals.total_tokens,
|
|
4821
|
-
} for row in result.data.projects)
|
|
4822
|
-
affected_keys = {
|
|
4823
|
-
dashboard_resource_key("project", "codex", project_key)
|
|
4824
|
-
for _root_key, project_key in affected
|
|
4825
|
-
}
|
|
4826
|
-
rows = tuple(sorted(
|
|
4827
|
-
(
|
|
4828
|
-
*(row for row in state[1]["rows"] if row["key"] not in affected_keys),
|
|
4829
|
-
*partial_rows,
|
|
4830
|
-
),
|
|
4831
|
-
key=lambda row: (
|
|
4832
|
-
float(row["cost_usd"]), str(row["label"]), str(row["key"]),
|
|
4833
|
-
),
|
|
4834
|
-
reverse=True,
|
|
4835
|
-
))
|
|
4836
|
-
value = {
|
|
4837
|
-
"rows": rows,
|
|
4838
|
-
"total_cost_usd": stable_sum(
|
|
4839
|
-
float(row["cost_usd"]) for row in rows),
|
|
4840
|
-
"total_tokens": sum(int(row["total_tokens"]) for row in rows),
|
|
4841
|
-
}
|
|
4842
|
-
_CODEX_PROJECT_WIRE_CACHE[cache_key] = (signature, value, groups)
|
|
4802
|
+
_CODEX_PROJECT_WIRE_CACHE[cache_key] = (signature, value)
|
|
4843
4803
|
return value
|
|
4844
4804
|
|
|
4845
4805
|
|
|
@@ -5648,6 +5608,12 @@ def _codex_fold_visible_rows(
|
|
|
5648
5608
|
|
|
5649
5609
|
|
|
5650
5610
|
_CODEX_VISIBLE_POPULATION_MAX_BYTES = 128 * 1024 * 1024
|
|
5611
|
+
#: #872 (review F7): every per-account visible version comes from this
|
|
5612
|
+
#: allocator, which nothing rewinds. `_CODEX_ACCOUNT_CARD_TOTALS_CACHE` keys
|
|
5613
|
+
#: card totals on these versions, and the two caches are evicted
|
|
5614
|
+
#: independently; a reconstruction that restarted at one would re-issue a
|
|
5615
|
+
#: version a surviving card-total entry still holds and serve its old spend.
|
|
5616
|
+
_CODEX_VISIBLE_ACCOUNT_VERSIONS = itertools.count(1)
|
|
5651
5617
|
_CODEX_VISIBLE_POPULATION_CACHE: dict[str, object] = _ObservedDict()
|
|
5652
5618
|
_CODEX_ACCOUNT_CARD_TOTALS_MAX_BYTES = 256 * 1024
|
|
5653
5619
|
_CODEX_ACCOUNT_CARD_TOTAL_ESTIMATED_BYTES = 1024
|
|
@@ -5780,7 +5746,9 @@ def _cached_codex_visible_rows(
|
|
|
5780
5746
|
"entries_by_id": entry_by_id,
|
|
5781
5747
|
"rows_by_account": dict(rows_by_account),
|
|
5782
5748
|
"entries_by_account": dict(entries_by_account),
|
|
5783
|
-
"account_versions": {
|
|
5749
|
+
"account_versions": {
|
|
5750
|
+
key: next(_CODEX_VISIBLE_ACCOUNT_VERSIONS)
|
|
5751
|
+
for key in rows_by_account},
|
|
5784
5752
|
"estimated_bytes": estimated_bytes,
|
|
5785
5753
|
"entry_count": len(rows),
|
|
5786
5754
|
"account_count": len(rows_by_account),
|
|
@@ -5848,7 +5816,7 @@ def _cached_codex_visible_rows(
|
|
|
5848
5816
|
else:
|
|
5849
5817
|
rows_by_account.pop(key, None)
|
|
5850
5818
|
entries_by_account.pop(key, None)
|
|
5851
|
-
account_versions[key] =
|
|
5819
|
+
account_versions[key] = next(_CODEX_VISIBLE_ACCOUNT_VERSIONS)
|
|
5852
5820
|
|
|
5853
5821
|
try:
|
|
5854
5822
|
entries = tuple(
|
|
@@ -5895,13 +5863,59 @@ _CODEX_ACCOUNT_SCOPE_CACHE: dict[
|
|
|
5895
5863
|
] = _ObservedDict()
|
|
5896
5864
|
|
|
5897
5865
|
|
|
5898
|
-
|
|
5899
|
-
|
|
5866
|
+
@dataclass(frozen=True)
|
|
5867
|
+
class _CodexDerivedCoherence:
|
|
5868
|
+
"""#872: the population every delta-advanced derived Codex cache reflects.
|
|
5869
|
+
|
|
5870
|
+
`name` is the visible-population name the whole derived layer was last
|
|
5871
|
+
advanced to, or `None` when that cannot be stated; `account_keys` is
|
|
5872
|
+
every account whose per-account derived state was built since the last
|
|
5873
|
+
discard. ONE immutable value under ONE key, so the two are written,
|
|
5874
|
+
journaled, charged and evicted together and can never disagree.
|
|
5875
|
+
"""
|
|
5876
|
+
|
|
5877
|
+
name: tuple | None
|
|
5878
|
+
account_keys: frozenset
|
|
5879
|
+
|
|
5880
|
+
|
|
5881
|
+
_CODEX_DERIVED_COHERENCE_KEY = "state"
|
|
5882
|
+
_CODEX_DERIVED_COHERENCE: dict[str, _CodexDerivedCoherence] = _ObservedDict()
|
|
5883
|
+
|
|
5884
|
+
|
|
5885
|
+
def _codex_derived_coherence() -> _CodexDerivedCoherence:
|
|
5886
|
+
"""The current record; an absent or evicted slot names nothing."""
|
|
5887
|
+
record = _CODEX_DERIVED_COHERENCE.get(_CODEX_DERIVED_COHERENCE_KEY)
|
|
5888
|
+
if isinstance(record, _CodexDerivedCoherence):
|
|
5889
|
+
return record
|
|
5890
|
+
return _CodexDerivedCoherence(None, frozenset())
|
|
5891
|
+
|
|
5892
|
+
|
|
5893
|
+
def reset_codex_account_scope_cache(*, keep_counters: bool = False) -> None:
|
|
5894
|
+
"""Discard every delta-advanced derived Codex cache and the #872 record.
|
|
5895
|
+
|
|
5896
|
+
The complete derived reset: a test/process reset, the metadata-incomplete
|
|
5897
|
+
fold's reset, and — with `keep_counters=True` — #872's G1 discard, which
|
|
5898
|
+
runs in production on every base mismatch. G1 must not zero the
|
|
5899
|
+
cumulative fallback counters `bin/cctally-snapshot-measure` reads (the
|
|
5900
|
+
card-totals fallbacks and the visible population's `fallback_count`), so
|
|
5901
|
+
it keeps them; the caches themselves are discarded either way.
|
|
5902
|
+
"""
|
|
5900
5903
|
global _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS
|
|
5904
|
+
visible_fallbacks = int(
|
|
5905
|
+
_CODEX_VISIBLE_POPULATION_CACHE.get("fallback_count", 0) or 0)
|
|
5901
5906
|
_CODEX_ACCOUNT_SCOPE_CACHE.clear()
|
|
5902
5907
|
_CODEX_VISIBLE_POPULATION_CACHE.clear()
|
|
5908
|
+
if keep_counters and visible_fallbacks:
|
|
5909
|
+
# The over-size shape `_cached_codex_visible_rows` already writes:
|
|
5910
|
+
# counters only, nothing reusable.
|
|
5911
|
+
_CODEX_VISIBLE_POPULATION_CACHE.update({
|
|
5912
|
+
"fallback_count": visible_fallbacks,
|
|
5913
|
+
"estimated_bytes": 0,
|
|
5914
|
+
"entry_count": 0,
|
|
5915
|
+
})
|
|
5903
5916
|
_CODEX_ACCOUNT_CARD_TOTALS_CACHE.clear()
|
|
5904
|
-
|
|
5917
|
+
if not keep_counters:
|
|
5918
|
+
_CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS = 0
|
|
5905
5919
|
_CODEX_ENTRY_ADAPTER_CACHE.clear()
|
|
5906
5920
|
_CODEX_PROJECT_LABEL_CACHE.clear()
|
|
5907
5921
|
_CODEX_PERIOD_VIEW_CACHE.clear()
|
|
@@ -5909,6 +5923,7 @@ def reset_codex_account_scope_cache() -> None:
|
|
|
5909
5923
|
_CODEX_CACHE_REPORT_ROWS.clear()
|
|
5910
5924
|
_CODEX_SESSION_VIEW_CACHE.clear()
|
|
5911
5925
|
_CODEX_PROJECT_WIRE_CACHE.clear()
|
|
5926
|
+
_CODEX_DERIVED_COHERENCE.clear()
|
|
5912
5927
|
|
|
5913
5928
|
|
|
5914
5929
|
def _codex_account_scopes_wire(
|
|
@@ -6494,6 +6509,7 @@ def _codex_source_caches() -> tuple[dict, ...]:
|
|
|
6494
6509
|
_CODEX_ACCOUNT_CARD_TOTALS_CACHE,
|
|
6495
6510
|
_CODEX_WEEKLY_VIEW_CACHE,
|
|
6496
6511
|
_CODEX_ACCOUNT_SCOPE_CACHE,
|
|
6512
|
+
_CODEX_DERIVED_COHERENCE,
|
|
6497
6513
|
)
|
|
6498
6514
|
|
|
6499
6515
|
|
|
@@ -6525,7 +6541,7 @@ def _codex_source_caches() -> tuple[dict, ...]:
|
|
|
6525
6541
|
# incremental byte estimate of its own, and a second charge computed here would
|
|
6526
6542
|
# be the same number derived twice. Those readers are named beside the owner.
|
|
6527
6543
|
#
|
|
6528
|
-
# WHAT THE TOTAL IS, AND WHAT IT IS NOT. It is the sum of
|
|
6544
|
+
# WHAT THE TOTAL IS, AND WHAT IT IS NOT. It is the sum of twelve DECLARED
|
|
6529
6545
|
# CHARGES. It is not a measurement of process memory, and
|
|
6530
6546
|
# `_CODEX_SOURCE_ACCELERATOR_MAX_BYTES` is not a cap on process memory: it is a
|
|
6531
6547
|
# budget over these charges, and the charges depart from the retained graph in
|
|
@@ -6553,8 +6569,8 @@ def _codex_source_caches() -> tuple[dict, ...]:
|
|
|
6553
6569
|
# `tests/test_source_cache_ownership.py` holds the WALKED owners to a two-sided
|
|
6554
6570
|
# band against one walk of their own cache — the upper half is what caught the
|
|
6555
6571
|
# adapter walking its row's strings once per row — while asserting only the
|
|
6556
|
-
# upper bound for the
|
|
6557
|
-
# closed-form set is exactly the
|
|
6572
|
+
# upper bound for the three closed-form owners and asserting that the
|
|
6573
|
+
# closed-form set is exactly the three named here.
|
|
6558
6574
|
|
|
6559
6575
|
|
|
6560
6576
|
def _is_codex_accounting_row(obj) -> bool:
|
|
@@ -6626,23 +6642,13 @@ def _charge_project_label_entry(_key, value) -> int:
|
|
|
6626
6642
|
|
|
6627
6643
|
|
|
6628
6644
|
def _charge_project_wire_entry(_key, value) -> int:
|
|
6629
|
-
"""`(signature, wire
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
"""
|
|
6637
|
-
signature, wire, groups = value[0], value[1], value[2]
|
|
6638
|
-
total = sys.getsizeof(value)
|
|
6639
|
-
total += retained_size_bytes(
|
|
6640
|
-
signature, borrowed=_is_borrowed_codex_payload)
|
|
6641
|
-
total += retained_size_bytes(wire, borrowed=_is_borrowed_codex_payload)
|
|
6642
|
-
total += sys.getsizeof(groups)
|
|
6643
|
-
for key, group in groups.items():
|
|
6644
|
-
total += sys.getsizeof(key) + sys.getsizeof(group)
|
|
6645
|
-
return total
|
|
6645
|
+
"""`(signature, wire)` — both owned; borrowed payloads are excluded."""
|
|
6646
|
+
signature, wire = value[0], value[1]
|
|
6647
|
+
return (
|
|
6648
|
+
sys.getsizeof(value)
|
|
6649
|
+
+ retained_size_bytes(signature, borrowed=_is_borrowed_codex_payload)
|
|
6650
|
+
+ retained_size_bytes(wire, borrowed=_is_borrowed_codex_payload)
|
|
6651
|
+
)
|
|
6646
6652
|
|
|
6647
6653
|
|
|
6648
6654
|
#: How faithfully each owner's declared charge tracks what one walk of that
|
|
@@ -6652,9 +6658,9 @@ def _charge_project_wire_entry(_key, value) -> int:
|
|
|
6652
6658
|
#: formula that DELIBERATELY UNDERSTATES, with no stated bound on the gap; the
|
|
6653
6659
|
#: value beside the name is why that trade was taken.
|
|
6654
6660
|
#:
|
|
6655
|
-
#: The budget is compared against a sum that includes the
|
|
6661
|
+
#: The budget is compared against a sum that includes the three closed-form
|
|
6656
6662
|
#: owners, so the budget is a heuristic over declared charges rather than a cap
|
|
6657
|
-
#: on memory. Adding a
|
|
6663
|
+
#: on memory. Adding a fourth closed-form owner without adding it here is a
|
|
6658
6664
|
#: test failure, which is the only reason this constant exists.
|
|
6659
6665
|
CODEX_SOURCE_CHARGE_FIDELITY = MappingProxyType({
|
|
6660
6666
|
"quota_observations": "walked",
|
|
@@ -6666,10 +6672,7 @@ CODEX_SOURCE_CHARGE_FIDELITY = MappingProxyType({
|
|
|
6666
6672
|
"string each, because a labelled copy is `replace(row, "
|
|
6667
6673
|
"display_label=...)` and every other field is the borrowed row's own "
|
|
6668
6674
|
"object"),
|
|
6669
|
-
"project_wire":
|
|
6670
|
-
"closed-form: the group containers, never the borrowed rows inside "
|
|
6671
|
-
"them, because descending into them makes an ordinary tick "
|
|
6672
|
-
"proportional to the whole population"),
|
|
6675
|
+
"project_wire": "walked",
|
|
6673
6676
|
"entry_adapters": (
|
|
6674
6677
|
"closed-form: two object shells and one float, because the adapted "
|
|
6675
6678
|
"entry shares every string with the row beside it and walking it "
|
|
@@ -6681,6 +6684,7 @@ CODEX_SOURCE_CHARGE_FIDELITY = MappingProxyType({
|
|
|
6681
6684
|
"account_card_totals": "walked",
|
|
6682
6685
|
"weekly_views": "walked",
|
|
6683
6686
|
"account_scopes": "walked",
|
|
6687
|
+
"derived_coherence": "walked",
|
|
6684
6688
|
})
|
|
6685
6689
|
|
|
6686
6690
|
|
|
@@ -6834,8 +6838,9 @@ def _declare_codex_source_cache_owners() -> tuple[_CodexCacheOwner, ...]:
|
|
|
6834
6838
|
_CodexCacheOwner(
|
|
6835
6839
|
"project_wire", _CODEX_PROJECT_WIRE_CACHE,
|
|
6836
6840
|
_charge_project_wire_entry, None,
|
|
6837
|
-
owns="the rendered project wire and the
|
|
6838
|
-
|
|
6841
|
+
owns="the rendered project wire and the population name it was "
|
|
6842
|
+
"built for (#872 removed the retained group splice)",
|
|
6843
|
+
borrows="nothing; borrowed payloads are excluded from its charge",
|
|
6839
6844
|
),
|
|
6840
6845
|
_CodexCacheOwner(
|
|
6841
6846
|
"entry_adapters", _CODEX_ENTRY_ADAPTER_CACHE,
|
|
@@ -6872,6 +6877,13 @@ def _declare_codex_source_cache_owners() -> tuple[_CodexCacheOwner, ...]:
|
|
|
6872
6877
|
owns="the finalized per-account scope mappings",
|
|
6873
6878
|
borrows="accounting rows and adapted entries",
|
|
6874
6879
|
),
|
|
6880
|
+
_CodexCacheOwner(
|
|
6881
|
+
"derived_coherence", _CODEX_DERIVED_COHERENCE,
|
|
6882
|
+
_charge_borrowing_payloads, None,
|
|
6883
|
+
owns="the one immutable record naming the population every "
|
|
6884
|
+
"delta-advanced derived cache reflects",
|
|
6885
|
+
borrows="nothing",
|
|
6886
|
+
),
|
|
6875
6887
|
)
|
|
6876
6888
|
caches = _codex_source_caches()
|
|
6877
6889
|
if tuple(owner.cache for owner in owners) != caches:
|
|
@@ -6888,10 +6900,10 @@ _CODEX_SOURCE_CACHE_OWNERS = _declare_codex_source_cache_owners()
|
|
|
6888
6900
|
|
|
6889
6901
|
|
|
6890
6902
|
def codex_source_retained_bytes() -> int:
|
|
6891
|
-
"""The
|
|
6903
|
+
"""The twelve Codex source caches' DECLARED CHARGES, summed.
|
|
6892
6904
|
|
|
6893
6905
|
Read it as "what these caches say they cost", not as "how much memory
|
|
6894
|
-
these caches hold".
|
|
6906
|
+
these caches hold". THREE of the twelve are charged in closed form and
|
|
6895
6907
|
understate without a stated bound — see `CODEX_SOURCE_CHARGE_FIDELITY` —
|
|
6896
6908
|
so this figure is the input to a heuristic budget rather than a
|
|
6897
6909
|
measurement of the retained graph.
|
|
@@ -6907,8 +6919,8 @@ def codex_source_retained_bytes() -> int:
|
|
|
6907
6919
|
|
|
6908
6920
|
|
|
6909
6921
|
#: The budget the declared charges are held to. NOT a bound on process memory:
|
|
6910
|
-
#: it is compared against `codex_source_retained_bytes()`,
|
|
6911
|
-
#:
|
|
6922
|
+
#: it is compared against `codex_source_retained_bytes()`, three of whose
|
|
6923
|
+
#: twelve terms are closed-form under-charges with no stated bound on the gap.
|
|
6912
6924
|
#: See `CODEX_SOURCE_CHARGE_FIDELITY`.
|
|
6913
6925
|
_CODEX_SOURCE_ACCELERATOR_MAX_BYTES = 768 * 1024 * 1024
|
|
6914
6926
|
_CODEX_SOURCE_ACCELERATOR_MAX_ENTRIES = 500_000
|
|
@@ -6995,15 +7007,16 @@ def _evict_codex_source_buckets(caches) -> tuple[int, bool]:
|
|
|
6995
7007
|
|
|
6996
7008
|
Returns `(evicted_entries, saw_an_indivisible_oversized_bucket)`.
|
|
6997
7009
|
|
|
6998
|
-
|
|
7010
|
+
Eleven of the twelve caches are keyed by stable semantic units —
|
|
6999
7011
|
`("account", key)`, `("parent",)`, a project identity, a session identity,
|
|
7000
|
-
a physical row id
|
|
7001
|
-
|
|
7012
|
+
a physical row id, the #872 coherence record's single key — so those keys
|
|
7013
|
+
ARE the buckets and no new partition is needed. The visible population is
|
|
7014
|
+
declared indivisible and is evicted whole; see
|
|
7002
7015
|
`CODEX_VISIBLE_POPULATION_INDIVISIBLE_REASON`.
|
|
7003
7016
|
|
|
7004
7017
|
Cold buckets go first and everything else stays resident, which is what
|
|
7005
7018
|
makes an above-cap steady state rebuild only what it lost. Clearing all
|
|
7006
|
-
|
|
7019
|
+
the caches, which is what this replaced, made a population permanently
|
|
7007
7020
|
above a cap rebuild and discard on every generation: measured on the sealed
|
|
7008
7021
|
#786 v3 `current` corpus, one run cleared 222,932 entries at a stroke.
|
|
7009
7022
|
|
|
@@ -7022,7 +7035,7 @@ def _evict_codex_source_buckets(caches) -> tuple[int, bool]:
|
|
|
7022
7035
|
# budget leaves the next build one entry over it again, and every tick then
|
|
7023
7036
|
# pays the candidate build below over every resident key — 222,932 of them
|
|
7024
7037
|
# on the measurement corpus. Freeing headroom instead lets the early return
|
|
7025
|
-
# above answer the next several ticks for
|
|
7038
|
+
# above answer the next several ticks for twelve integer reads.
|
|
7026
7039
|
if count >= int(_CODEX_SOURCE_ACCELERATOR_LOW_WATER_MIN_ENTRIES):
|
|
7027
7040
|
low_water = min(
|
|
7028
7041
|
budget, int(budget * _CODEX_SOURCE_ACCELERATOR_LOW_WATER))
|
|
@@ -7058,7 +7071,7 @@ def _evict_codex_source_buckets(caches) -> tuple[int, bool]:
|
|
|
7058
7071
|
# proportional to the whole population. Together they mean the
|
|
7059
7072
|
# owner with the largest under-charge is also the last one this
|
|
7060
7073
|
# pass will ever reach, so freeing memory by lowering the budget
|
|
7061
|
-
# acts on the other
|
|
7074
|
+
# acts on the other eleven owners first.
|
|
7062
7075
|
candidates.append((
|
|
7063
7076
|
max(recency for recency, _key, _bytes in entries),
|
|
7064
7077
|
index, position, _CACHE_MISSING, owner.retained_bytes(),
|
|
@@ -7102,7 +7115,7 @@ def _evict_codex_source_buckets(caches) -> tuple[int, bool]:
|
|
|
7102
7115
|
continue
|
|
7103
7116
|
evicted += removed
|
|
7104
7117
|
count -= removed
|
|
7105
|
-
# Re-read ONE owner rather than summing all
|
|
7118
|
+
# Re-read ONE owner rather than summing all twelve after every single
|
|
7106
7119
|
# eviction. A charged owner's total moves by exactly this bucket, and a
|
|
7107
7120
|
# self-reported one answers for itself; either way the arithmetic below
|
|
7108
7121
|
# is checked against a full re-read once, after the loop.
|
|
@@ -7127,17 +7140,17 @@ def _evict_codex_source_buckets(caches) -> tuple[int, bool]:
|
|
|
7127
7140
|
def enforce_codex_source_accelerator_bounds() -> None:
|
|
7128
7141
|
"""Apply byte and entry admission from the incrementally maintained total.
|
|
7129
7142
|
|
|
7130
|
-
The byte total is a sum of
|
|
7143
|
+
The byte total is a sum of twelve integers each owner keeps current, so
|
|
7131
7144
|
admission is a read. It used to be a background traversal of two full
|
|
7132
7145
|
container copies of the whole cache set, once per generation, in a worker
|
|
7133
7146
|
limited to a quarter of one core — which on the measurement corpus never
|
|
7134
7147
|
finished a generation at all, so the caches were admitted against a byte
|
|
7135
7148
|
figure that stayed at zero until the walk finally returned the over-budget
|
|
7136
|
-
sentinel and cleared
|
|
7149
|
+
sentinel and cleared every cache at once.
|
|
7137
7150
|
|
|
7138
7151
|
Publication is unconditional now rather than gated on the dirty flag,
|
|
7139
7152
|
because there is nothing left to schedule: an unchanged generation costs
|
|
7140
|
-
the same
|
|
7153
|
+
the same twelve reads and publishes the same number.
|
|
7141
7154
|
"""
|
|
7142
7155
|
caches = _codex_source_caches()
|
|
7143
7156
|
global _CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES
|
|
@@ -7271,7 +7284,7 @@ _CODEX_SOURCE_PUBLISH_LOCK = threading.Lock()
|
|
|
7271
7284
|
|
|
7272
7285
|
|
|
7273
7286
|
class _CodexSourceBuildOverlay:
|
|
7274
|
-
"""One build's tentative generation over the
|
|
7287
|
+
"""One build's tentative generation over the twelve source caches.
|
|
7275
7288
|
|
|
7276
7289
|
THE JOURNAL IS THE OVERLAY. Rather than hold changed keys in a separate
|
|
7277
7290
|
mapping every read would have to consult, each mutation records the key's
|
|
@@ -7463,6 +7476,11 @@ class _CodexAccountingCapture:
|
|
|
7463
7476
|
#: qualified accounting read: such a generation names no population, and
|
|
7464
7477
|
#: borrowing the process's current token would name one it never read.
|
|
7465
7478
|
provenance_token: int | None = None
|
|
7479
|
+
#: #872: the name of the population this capture's delta is relative to,
|
|
7480
|
+
#: from the accounting result (`None` on a cold result and whenever the
|
|
7481
|
+
#: capture bypassed the qualified read, exactly like the fields above).
|
|
7482
|
+
base_population_signature: tuple | None = None
|
|
7483
|
+
base_provenance_token: int | None = None
|
|
7466
7484
|
|
|
7467
7485
|
|
|
7468
7486
|
#: The detail routes' horizon: one year ending at the generation instant. The
|
|
@@ -7556,6 +7574,9 @@ def _capture_codex_accounting(
|
|
|
7556
7574
|
population_signature: tuple | None = None
|
|
7557
7575
|
# #857: set only by a qualified read that returned; every bypass keeps it.
|
|
7558
7576
|
provenance_token: int | None = None
|
|
7577
|
+
# #872: the delta's base, under the same rule.
|
|
7578
|
+
base_population_signature: tuple | None = None
|
|
7579
|
+
base_provenance_token: int | None = None
|
|
7559
7580
|
|
|
7560
7581
|
def fallback_entries() -> tuple[object, ...]:
|
|
7561
7582
|
nonlocal metadata_read_failed, failed_legs
|
|
@@ -7625,6 +7646,9 @@ def _capture_codex_accounting(
|
|
|
7625
7646
|
changed_new = cached_accounting.changed_new
|
|
7626
7647
|
population_signature = cached_accounting.population_signature
|
|
7627
7648
|
provenance_token = cached_accounting.provenance_token
|
|
7649
|
+
base_population_signature = (
|
|
7650
|
+
cached_accounting.base_population_signature)
|
|
7651
|
+
base_provenance_token = cached_accounting.base_provenance_token
|
|
7628
7652
|
entries: tuple[object, ...] = qualified_entries
|
|
7629
7653
|
except QualifiedMetadataUnavailable as exc:
|
|
7630
7654
|
_lib_log.get_logger("dashboard").warning(
|
|
@@ -7671,6 +7695,10 @@ def _capture_codex_accounting(
|
|
|
7671
7695
|
None if metadata_incomplete else population_signature),
|
|
7672
7696
|
provenance_token=(
|
|
7673
7697
|
None if metadata_incomplete else provenance_token),
|
|
7698
|
+
base_population_signature=(
|
|
7699
|
+
None if metadata_incomplete else base_population_signature),
|
|
7700
|
+
base_provenance_token=(
|
|
7701
|
+
None if metadata_incomplete else base_provenance_token),
|
|
7674
7702
|
)
|
|
7675
7703
|
|
|
7676
7704
|
|
|
@@ -7797,7 +7825,7 @@ def _drop_codex_source_generation(
|
|
|
7797
7825
|
builds and replaying it would overwrite whatever the other build
|
|
7798
7826
|
legitimately published.
|
|
7799
7827
|
|
|
7800
|
-
ALL
|
|
7828
|
+
ALL TWELVE CACHES GO, not only the ones this build wrote to, and that is a
|
|
7801
7829
|
correctness requirement rather than caution. Ten of them are fed by the
|
|
7802
7830
|
accounting carrier's DELTA lists, not by its population: a build hands
|
|
7803
7831
|
`_cached_codex_period_view` and its siblings `changed_old` and
|
|
@@ -8320,6 +8348,23 @@ def _build_codex_source_state(
|
|
|
8320
8348
|
accounting_changed_new = accounting_capture.changed_new
|
|
8321
8349
|
accounting_entries = accounting_capture.entries
|
|
8322
8350
|
metadata_incomplete = accounting_capture.metadata_incomplete
|
|
8351
|
+
# #872 G1: a delta reaches the derived layer only while the layer's
|
|
8352
|
+
# coherence record names the delta's BASE. A cold carrier result has no
|
|
8353
|
+
# base, and a record that names anything else — an overflow-cleared or
|
|
8354
|
+
# reset carrier, a moved visible start, a skipped generation, a vanished
|
|
8355
|
+
# account — means the retained state is not what the delta is relative
|
|
8356
|
+
# to. Every delta-advanced derived cache is then discarded, and each
|
|
8357
|
+
# consumer derives from the full population through its own cold path.
|
|
8358
|
+
derived_base_name = _codex_visible_population_name(
|
|
8359
|
+
accounting_capture.base_population_signature,
|
|
8360
|
+
accounting_capture.base_provenance_token, context.range_start)
|
|
8361
|
+
derived_current_name = _codex_visible_population_name(
|
|
8362
|
+
accounting_capture.population_signature,
|
|
8363
|
+
accounting_capture.provenance_token, context.range_start)
|
|
8364
|
+
prior_coherence = _codex_derived_coherence()
|
|
8365
|
+
if derived_base_name is None or prior_coherence.name != derived_base_name:
|
|
8366
|
+
reset_codex_account_scope_cache(keep_counters=True)
|
|
8367
|
+
prior_coherence = _CodexDerivedCoherence(None, frozenset())
|
|
8323
8368
|
|
|
8324
8369
|
conversation_metadata_read = (
|
|
8325
8370
|
capture.conversation_metadata
|
|
@@ -8557,24 +8602,13 @@ def _build_codex_source_state(
|
|
|
8557
8602
|
period_signature = _codex_accounting_period_signature(
|
|
8558
8603
|
context.cache_conn, context.range_start, metadata_incomplete,
|
|
8559
8604
|
)
|
|
8560
|
-
# The
|
|
8561
|
-
#
|
|
8562
|
-
#
|
|
8563
|
-
#
|
|
8564
|
-
#
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
if capture is not None:
|
|
8568
|
-
project_population_signature = (
|
|
8569
|
-
None if metadata_incomplete
|
|
8570
|
-
else _codex_project_label_signature(
|
|
8571
|
-
capture.accounting.population_signature)
|
|
8572
|
-
)
|
|
8573
|
-
else:
|
|
8574
|
-
project_population_signature = (
|
|
8575
|
-
None if metadata_incomplete
|
|
8576
|
-
else _codex_project_population_signature()
|
|
8577
|
-
)
|
|
8605
|
+
# The project caches' name for this build's population: the visible
|
|
8606
|
+
# population name G1 computed from THIS build's own capture (sound across
|
|
8607
|
+
# a cleared carrier and a moved visible start, #872), plus the label
|
|
8608
|
+
# algorithm version. `None` when the population cannot be named, and then
|
|
8609
|
+
# nothing is reused. See `_CodexAccountingCapture.population_signature`.
|
|
8610
|
+
project_population_signature = _codex_project_label_signature(
|
|
8611
|
+
derived_current_name)
|
|
8578
8612
|
daily = _cached_codex_period_view(
|
|
8579
8613
|
entries, changed_old=changed_old_entries,
|
|
8580
8614
|
changed_new=changed_new_entries, kind="daily", cache_key=("parent",),
|
|
@@ -8796,6 +8830,8 @@ def _build_codex_source_state(
|
|
|
8796
8830
|
hero_cycles_wire: list[dict[str, object]] = []
|
|
8797
8831
|
combined_accounting: dict[str, object] | None = None
|
|
8798
8832
|
account_scopes: dict[str, dict[str, object]] = {}
|
|
8833
|
+
# #872 G2 input: whether the per-account scopes were built this build.
|
|
8834
|
+
account_scope_status = "not-built"
|
|
8799
8835
|
# #556 S5 §3.8: bound OUTSIDE the try, because the degrade path below has to
|
|
8800
8836
|
# be able to clear it, and the retained `clock_data` reads it either way.
|
|
8801
8837
|
budget_events_by_account: dict[str, tuple[tuple[dt.datetime, float], ...]] = {}
|
|
@@ -8941,6 +8977,7 @@ def _build_codex_source_state(
|
|
|
8941
8977
|
scope_signature=period_signature,
|
|
8942
8978
|
project_population_signature=project_population_signature,
|
|
8943
8979
|
)
|
|
8980
|
+
account_scope_status = "built"
|
|
8944
8981
|
# #416 QA P1-A — the "All accounts" Blocks panel is the UNION of
|
|
8945
8982
|
# every account's 5-hour blocks. `_quota_wire` filters
|
|
8946
8983
|
# `str(root_key) not in cycle.source_root_keys` against a single
|
|
@@ -8997,6 +9034,7 @@ def _build_codex_source_state(
|
|
|
8997
9034
|
))
|
|
8998
9035
|
quota = {**quota, "blocks": merged_blocks}
|
|
8999
9036
|
except (sqlite3.Error, QualifiedMetadataUnavailable):
|
|
9037
|
+
account_scope_status = "degraded"
|
|
9000
9038
|
# A per-account wire failure must never fail the whole source build;
|
|
9001
9039
|
# degrade to the byte-stable undecorated shape.
|
|
9002
9040
|
accounts_wire = []
|
|
@@ -9129,6 +9167,31 @@ def _build_codex_source_state(
|
|
|
9129
9167
|
{name: operands[name] for name in _HERO_CARD_OPERANDS
|
|
9130
9168
|
if name in operands}
|
|
9131
9169
|
)
|
|
9170
|
+
# #872 G2: the record advances to this build's population only when the
|
|
9171
|
+
# build advanced EVERY delta-advanced consumer over the capture's whole
|
|
9172
|
+
# delta. Completeness is stated positively: metadata complete and not
|
|
9173
|
+
# transient (a transient generation skips both project legs), scopes not
|
|
9174
|
+
# degraded, and every account that holds per-account state still live
|
|
9175
|
+
# (an account that left the live set kept a stale state the delta never
|
|
9176
|
+
# reached). Anything else records `None`, so the next build's G1 discards.
|
|
9177
|
+
scope_keys = frozenset(str(key) for key in account_scopes)
|
|
9178
|
+
coherent = (
|
|
9179
|
+
derived_current_name is not None
|
|
9180
|
+
and not metadata_incomplete
|
|
9181
|
+
and not metadata_transient
|
|
9182
|
+
and account_scope_status != "degraded"
|
|
9183
|
+
and prior_coherence.account_keys <= scope_keys
|
|
9184
|
+
)
|
|
9185
|
+
record = _CodexDerivedCoherence(
|
|
9186
|
+
name=derived_current_name if coherent else None,
|
|
9187
|
+
account_keys=prior_coherence.account_keys | scope_keys,
|
|
9188
|
+
)
|
|
9189
|
+
if _CODEX_DERIVED_COHERENCE.get(_CODEX_DERIVED_COHERENCE_KEY) == record:
|
|
9190
|
+
# Unchanged (a warm-clean tick on the same ledger head): say it was
|
|
9191
|
+
# used without rewriting it, so an idle tick dirties nothing.
|
|
9192
|
+
_CODEX_DERIVED_COHERENCE.touch(_CODEX_DERIVED_COHERENCE_KEY)
|
|
9193
|
+
else:
|
|
9194
|
+
_CODEX_DERIVED_COHERENCE[_CODEX_DERIVED_COHERENCE_KEY] = record
|
|
9132
9195
|
return SourceDashboardState(
|
|
9133
9196
|
source="codex",
|
|
9134
9197
|
availability=availability,
|