cctally 1.117.2 → 1.118.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 +30 -0
- package/README.md +7 -3
- package/bin/_cctally_cache.py +2783 -175
- package/bin/_cctally_completion.py +106 -33
- package/bin/_cctally_core.py +69 -4
- package/bin/_cctally_dashboard.py +188 -13
- package/bin/_cctally_dashboard_conversation.py +5 -1
- package/bin/_cctally_dashboard_envelope.py +50 -19
- package/bin/_cctally_dashboard_sources.py +23 -1
- package/bin/_cctally_db.py +193 -153
- package/bin/_cctally_diagnosis_sources.py +63 -7
- package/bin/_cctally_doctor.py +865 -93
- package/bin/_cctally_five_hour.py +67 -50
- package/bin/_cctally_forecast.py +35 -13
- package/bin/_cctally_journal.py +186 -18
- package/bin/_cctally_milestone_history.py +859 -62
- package/bin/_cctally_oauth_samples.py +286 -15
- package/bin/_cctally_parser.py +2 -2
- package/bin/_cctally_quota.py +104 -12
- package/bin/_cctally_record.py +153 -29
- package/bin/_cctally_rederive.py +608 -68
- package/bin/_cctally_statusline.py +108 -81
- package/bin/_cctally_store.py +179 -38
- package/bin/_cctally_tui.py +892 -181
- package/bin/_cctally_update.py +32 -13
- package/bin/_lib_codex_conversation_query.py +52 -14
- package/bin/_lib_codex_drain_admission.py +224 -6
- package/bin/_lib_codex_metadata.py +39 -0
- package/bin/_lib_conversation_anon.py +15 -8
- package/bin/_lib_conversation_dispatch.py +7 -0
- package/bin/_lib_conversation_query.py +289 -70
- package/bin/_lib_dashboard_sources.py +37 -1
- package/bin/_lib_detector_access_registry.py +497 -26
- package/bin/_lib_doctor.py +85 -20
- package/bin/_lib_doctor_scan_index.py +321 -0
- package/bin/_lib_ingest_frontier.py +20 -1
- package/bin/_lib_oauth_samples.py +23 -7
- package/bin/_lib_pricing.py +146 -16
- package/bin/_lib_quota.py +19 -0
- package/bin/_lib_rederive.py +11 -6
- package/bin/_lib_retained_records.py +516 -0
- package/bin/_lib_snapshot_cache.py +10 -0
- package/bin/_lib_stale_proof.py +272 -43
- package/bin/_lib_stats_content_revision.py +831 -0
- package/bin/_lib_write_io.py +11 -4
- package/bin/cctally +4 -1
- package/dashboard/static/assets/ConversationsView-D4Iv4ku0.js +72 -0
- package/dashboard/static/assets/{DoctorModal-B0DnU9d9.js → DoctorModal-D4QSq6uz.js} +1 -1
- package/dashboard/static/assets/ModalRoot-C9F5dhZi.js +1 -0
- package/dashboard/static/assets/{ProjectsDrillPanel-DSxXQSa-.js → ProjectsDrillPanel-WxAdRDMt.js} +1 -1
- package/dashboard/static/assets/{SourceDetailModal-BleDpQqG.js → SourceDetailModal-NyyEfXmc.js} +1 -1
- package/dashboard/static/assets/{UpdateModal-CMqaAkSK.js → UpdateModal-CFLq9pT1.js} +1 -1
- package/dashboard/static/assets/{index-BjQV-0-D.js → index-BKTWH9JE.js} +2 -2
- package/dashboard/static/assets/{outlineNavigation-CO-VWBm7.js → outlineNavigation-BnGUwCS3.js} +3 -3
- package/dashboard/static/dashboard.html +1 -1
- package/package.json +4 -1
- package/dashboard/static/assets/ConversationsView-oqNsWzWV.js +0 -72
- package/dashboard/static/assets/ModalRoot-CYua2wP_.js +0 -1
package/bin/_lib_pricing.py
CHANGED
|
@@ -18,6 +18,8 @@ Spec: docs/superpowers/specs/2026-05-13-bin-cctally-split-design.md
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
import dataclasses
|
|
21
|
+
import hashlib
|
|
22
|
+
import json
|
|
21
23
|
import datetime as dt
|
|
22
24
|
import math
|
|
23
25
|
import re
|
|
@@ -81,9 +83,11 @@ def _chip_for_model(name: str) -> str:
|
|
|
81
83
|
return "other"
|
|
82
84
|
|
|
83
85
|
|
|
84
|
-
# Date the
|
|
85
|
-
#
|
|
86
|
-
#
|
|
86
|
+
# Date of the latest pricing sync of the embedded tables below. Bump whenever
|
|
87
|
+
# CLAUDE_MODEL_PRICING / CODEX_MODEL_PRICING is synced. A targeted sync
|
|
88
|
+
# re-verifies only the cards it names (each table's header log says which);
|
|
89
|
+
# it does not re-verify every card. Read by `pricing-check` + the release
|
|
90
|
+
# pre-flight staleness nudge.
|
|
87
91
|
#
|
|
88
92
|
# CONTRACT (#705): a pricing revision must always ADVANCE this date. A second
|
|
89
93
|
# revision within one UTC day therefore takes the NEXT day's date rather than
|
|
@@ -97,7 +101,7 @@ def _chip_for_model(name: str) -> str:
|
|
|
97
101
|
# pinned in `bin/_lib_detector_access_registry.py`, so its registry test fails
|
|
98
102
|
# until the affected pins are re-pinned through the reviewed procedure in that
|
|
99
103
|
# module's docstring. Bulk regeneration of the pins alone is not acceptance.
|
|
100
|
-
PRICING_SNAPSHOT_DATE = "2026-
|
|
104
|
+
PRICING_SNAPSHOT_DATE = "2026-11-05"
|
|
101
105
|
PRICING_STALENESS_DAYS = 60 # release pre-flight WARNs past this age
|
|
102
106
|
|
|
103
107
|
|
|
@@ -324,6 +328,16 @@ LITELLM_PRICES_URL = (
|
|
|
324
328
|
# `diff_pricing` keys on raw table membership and is deliberately NOT
|
|
325
329
|
# alias-aware, so the omission needs this entry. It is self-policing: it goes
|
|
326
330
|
# stale automatically if upstream drops `gpt-5.6` or we restore a direct card.
|
|
331
|
+
#
|
|
332
|
+
# gpt-5.5-cyber-preview (#989): also model-only. OpenAI publishes no row, model
|
|
333
|
+
# page or alias statement for this identifier, which third-party press ties
|
|
334
|
+
# to GPT-5.5-Cyber's May 2026 limited preview. LiteLLM prices it with cost
|
|
335
|
+
# fields identical to
|
|
336
|
+
# `gpt-5.5-cyber`, whose card OpenAI's pricing page does list, so
|
|
337
|
+
# CODEX_MODEL_ALIASES maps it there as an INFERRED pricing equivalence rather
|
|
338
|
+
# than a vendor-declared alias. This entry polices membership only: it goes
|
|
339
|
+
# stale if upstream drops the identifier or a direct card is added, but a
|
|
340
|
+
# preview-specific repricing upstream would NOT surface here.
|
|
327
341
|
PRICING_DRIFT_ALLOWLIST: list[dict] = [
|
|
328
342
|
{
|
|
329
343
|
"model": "claude-mythos-preview",
|
|
@@ -374,6 +388,17 @@ PRICING_DRIFT_ALLOWLIST: list[dict] = [
|
|
|
374
388
|
"(#643)."
|
|
375
389
|
),
|
|
376
390
|
},
|
|
391
|
+
{
|
|
392
|
+
"model": "gpt-5.5-cyber-preview",
|
|
393
|
+
"reason": (
|
|
394
|
+
"OpenAI publishes no price or alias for gpt-5.5-cyber-preview. "
|
|
395
|
+
"LiteLLM's cost fields for it equal gpt-5.5-cyber's (observed "
|
|
396
|
+
"2026-11-05), whose card OpenAI lists, so CODEX_MODEL_ALIASES "
|
|
397
|
+
"maps it there as an inferred equivalence and this table carries "
|
|
398
|
+
"no duplicate card. This entry polices membership only; a "
|
|
399
|
+
"preview-specific repricing upstream would not surface (#989)."
|
|
400
|
+
),
|
|
401
|
+
},
|
|
377
402
|
]
|
|
378
403
|
|
|
379
404
|
# Anthropic API pricing snapshot:
|
|
@@ -748,13 +773,14 @@ _unknown_model_warnings: set[str] = set()
|
|
|
748
773
|
# Codex (OpenAI) API pricing snapshot:
|
|
749
774
|
# - Source: https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json
|
|
750
775
|
# - Captured: 2026-07-19 — the last FULL Codex sync. PRICING_SNAPSHOT_DATE has
|
|
751
|
-
# since moved for
|
|
752
|
-
#
|
|
753
|
-
# 2026-
|
|
754
|
-
# gpt-5.6-cyber
|
|
755
|
-
#
|
|
756
|
-
#
|
|
757
|
-
#
|
|
776
|
+
# since moved for many targeted syncs, Claude-side ones included (the git
|
|
777
|
+
# history of the constant is authoritative). The Codex-side ones are logged
|
|
778
|
+
# below: 2026-07-31 (gpt-5.6-terra/-luna correction), 2026-08-25
|
|
779
|
+
# (gpt-5.6-cyber), 2026-09-05 (gpt-6-astra), 2026-09-22 (gpt-6-sol,
|
|
780
|
+
# gpt-6-luna), 2026-09-23 (gpt-5.5-cyber), 2026-09-30 (gpt-6.1-sol) and
|
|
781
|
+
# 2026-11-05 (gpt-6.1-terra / gpt-6-rosalind / gpt-6-astra-law and the
|
|
782
|
+
# gpt-5.5-cyber-preview alias). Codex values outside the cards each of
|
|
783
|
+
# those syncs names were NOT re-verified on those days.
|
|
758
784
|
# - As of the 2026-07-19 sync this carries every openai-provider
|
|
759
785
|
# gpt-5* model the LiteLLM snapshot lists, so `pricing-check`'s scope finds
|
|
760
786
|
# nothing missing. Models absent from this table still fall back to `gpt-5`
|
|
@@ -809,13 +835,32 @@ _unknown_model_warnings: set[str] = set()
|
|
|
809
835
|
# existing gpt-5.6-cyber card.
|
|
810
836
|
# 2026-09-23: adopted LiteLLM's historical gpt-5.5-cyber OpenAI-provider
|
|
811
837
|
# card at $12.50 input / $1.25 cached input / $75.00 output per MTok.
|
|
812
|
-
#
|
|
813
|
-
# not publish an above-272K rate for 5.5 Cyber, so do not infer that
|
|
838
|
+
# At that capture the OpenAI table listed only its 5.6 successor. LiteLLM
|
|
839
|
+
# does not publish an above-272K rate for 5.5 Cyber, so do not infer that
|
|
840
|
+
# tier. (2026-11-05: OpenAI's pricing page now lists gpt-5.5-cyber at the
|
|
841
|
+
# same $12.50 / $1.25 / $75.00 with dashes in the long-context columns, so
|
|
842
|
+
# the card is vendor-confirmed; values unchanged.)
|
|
814
843
|
# 2026-09-29: added gpt-6.1-sol from OpenAI's model and pricing pages:
|
|
815
844
|
# Standard $2.00 input / $0.10 cached input / $10.00 output per MTok;
|
|
816
845
|
# above 272K, $4.00 / $0.20 / $15.00. Fast mode is 2x. LiteLLM does not
|
|
817
846
|
# yet carry this identifier. The snapshot date advances to 2026-09-30
|
|
818
847
|
# because the Sonnet 5.5 sync already used 2026-09-29 (#705).
|
|
848
|
+
# 2026-11-05 (#989): `pricing-check` reported four OpenAI-provider
|
|
849
|
+
# identifiers missing; each was checked against OpenAI's pricing page
|
|
850
|
+
# (https://developers.openai.com/api/docs/pricing) and LiteLLM, which agree.
|
|
851
|
+
# Added gpt-6.1-terra (Standard $1.00 input / $0.05 cached / $5.00 output
|
|
852
|
+
# from the pricing page; its model page states the above-272K rule, 2x
|
|
853
|
+
# input/cache and 1.5x output for the full request, giving $2.00 / $0.10 /
|
|
854
|
+
# $7.50, and 2x for Fast),
|
|
855
|
+
# gpt-6-rosalind (trusted-access life-sciences model, $15.00 / $1.50 /
|
|
856
|
+
# $75.00; above 272K $30.00 / $3.00 / $112.50) and gpt-6-astra-law ($10.00
|
|
857
|
+
# / $1.00 / $50.00, no long-context column; OpenAI marks this rate
|
|
858
|
+
# promotional "at least through December 31, 2026" and publishes no
|
|
859
|
+
# successor, so the only published rate is adopted and a later LiteLLM
|
|
860
|
+
# change will surface as value drift). gpt-5.5-cyber-preview has no vendor
|
|
861
|
+
# row and is aliased to gpt-5.5-cyber (see CODEX_MODEL_ALIASES). The fast
|
|
862
|
+
# multiplier of all three resolves to CODEX_FAST_MULTIPLIER_FALLBACK; the
|
|
863
|
+
# vendor publishes no Fast tier for Rosalind or Astra for Law.
|
|
819
864
|
#
|
|
820
865
|
# Billing rules:
|
|
821
866
|
# - reasoning_output_tokens is billed at the *output* rate (matches
|
|
@@ -945,8 +990,10 @@ CODEX_MODEL_PRICING: dict[str, dict[str, Any]] = {
|
|
|
945
990
|
# No cache_creation field: the Codex cost kernel never reads one, so
|
|
946
991
|
# carrying LiteLLM's would only give `diff_pricing` a value to compare.
|
|
947
992
|
"gpt-5.5-cyber": {
|
|
948
|
-
#
|
|
949
|
-
#
|
|
993
|
+
# Adopted from LiteLLM's OpenAI-provider card; since confirmed by
|
|
994
|
+
# OpenAI's pricing page (2026-11-05, #989). Neither source publishes
|
|
995
|
+
# long-context rates for this ID, unlike LiteLLM's 5.6 successor card
|
|
996
|
+
# (OpenAI's page shows dashes for both).
|
|
950
997
|
"input_cost_per_token": 1.25e-05,
|
|
951
998
|
"cache_read_input_token_cost": 1.25e-06,
|
|
952
999
|
"output_cost_per_token": 7.5e-05,
|
|
@@ -1001,6 +1048,41 @@ CODEX_MODEL_PRICING: dict[str, dict[str, Any]] = {
|
|
|
1001
1048
|
"cache_read_input_token_cost_above_272k_tokens": 2e-08,
|
|
1002
1049
|
"output_cost_per_token_above_272k_tokens": 7.5e-07,
|
|
1003
1050
|
},
|
|
1051
|
+
"gpt-6.1-terra": {
|
|
1052
|
+
# Source: https://developers.openai.com/api/docs/models/gpt-6.1-terra
|
|
1053
|
+
# and /api/docs/pricing (#989). Standard $1.00/M input, $0.05/M cached
|
|
1054
|
+
# input (5% of input), $5.00/M output; above 272K input, 2x
|
|
1055
|
+
# input/cache and 1.5x output for the full request.
|
|
1056
|
+
"input_cost_per_token": 1e-06,
|
|
1057
|
+
"cache_read_input_token_cost": 5e-08,
|
|
1058
|
+
"output_cost_per_token": 5e-06,
|
|
1059
|
+
"input_cost_per_token_above_272k_tokens": 2e-06,
|
|
1060
|
+
"cache_read_input_token_cost_above_272k_tokens": 1e-07,
|
|
1061
|
+
"output_cost_per_token_above_272k_tokens": 7.5e-06,
|
|
1062
|
+
},
|
|
1063
|
+
"gpt-6-rosalind": {
|
|
1064
|
+
# Source: https://developers.openai.com/api/docs/pricing (#989), the
|
|
1065
|
+
# life-sciences table; access is limited to OpenAI's trusted-access
|
|
1066
|
+
# program. $15.00/M input, $1.50/M cached input, $75.00/M output;
|
|
1067
|
+
# long context $30.00 / $3.00 / $112.50.
|
|
1068
|
+
"input_cost_per_token": 1.5e-05,
|
|
1069
|
+
"cache_read_input_token_cost": 1.5e-06,
|
|
1070
|
+
"output_cost_per_token": 7.5e-05,
|
|
1071
|
+
"input_cost_per_token_above_272k_tokens": 3e-05,
|
|
1072
|
+
"cache_read_input_token_cost_above_272k_tokens": 3e-06,
|
|
1073
|
+
"output_cost_per_token_above_272k_tokens": 1.125e-04,
|
|
1074
|
+
},
|
|
1075
|
+
"gpt-6-astra-law": {
|
|
1076
|
+
# Source: https://developers.openai.com/api/docs/pricing (#989), the
|
|
1077
|
+
# legal table: $10.00/M input, $1.00/M cached input, $50.00/M output,
|
|
1078
|
+
# marked promotional "at least through December 31, 2026" with no
|
|
1079
|
+
# successor published. No long-context column is published (nor a
|
|
1080
|
+
# LiteLLM tier), so none is inferred and long requests price at these
|
|
1081
|
+
# base rates.
|
|
1082
|
+
"input_cost_per_token": 1e-05,
|
|
1083
|
+
"cache_read_input_token_cost": 1e-06,
|
|
1084
|
+
"output_cost_per_token": 5e-05,
|
|
1085
|
+
},
|
|
1004
1086
|
# ── Issue #123: full gpt-5.x LiteLLM sync (2026-05-30 snapshot) ──
|
|
1005
1087
|
# Exact model_prices_and_context_window.json values for every
|
|
1006
1088
|
# openai-provider gpt-5* model `pricing-check`'s scope flags but the
|
|
@@ -1192,9 +1274,15 @@ CODEX_LEGACY_FALLBACK_MODEL = "gpt-5"
|
|
|
1192
1274
|
# covered by a model-only PRICING_DRIFT_ALLOWLIST entry.
|
|
1193
1275
|
# ``codex-auto-review`` is the hidden Guardian model and maps to the model
|
|
1194
1276
|
# current when it appeared, covering every retained event observed for issue
|
|
1195
|
-
# #535.
|
|
1277
|
+
# #535. ``gpt-5.5-cyber-preview`` (#989) is an INFERRED pricing equivalence,
|
|
1278
|
+
# not a vendor-declared alias: OpenAI publishes nothing for the identifier
|
|
1279
|
+
# (third-party press ties it to GPT-5.5-Cyber's limited preview), LiteLLM
|
|
1280
|
+
# prices it with cost fields
|
|
1281
|
+
# identical to ``gpt-5.5-cyber`` (observed 2026-11-05), and OpenAI lists that
|
|
1282
|
+
# card. Its model-only PRICING_DRIFT_ALLOWLIST entry covers the omission.
|
|
1196
1283
|
CODEX_MODEL_ALIASES: dict[str, str] = {
|
|
1197
1284
|
"codex-auto-review": "gpt-5.5",
|
|
1285
|
+
"gpt-5.5-cyber-preview": "gpt-5.5-cyber",
|
|
1198
1286
|
"gpt-5.6": "gpt-5.6-sol",
|
|
1199
1287
|
"gpt-daybreak-blue-latest": "gpt-5.6-sol",
|
|
1200
1288
|
"gpt-daybreak-red-latest": "gpt-5.6-cyber",
|
|
@@ -1744,3 +1832,45 @@ def current_pricing_snapshot() -> PricingSnapshot:
|
|
|
1744
1832
|
"""The snapshot every cost kernel prices from; it never changes after
|
|
1745
1833
|
import."""
|
|
1746
1834
|
return _LIVE_PRICING_SNAPSHOT
|
|
1835
|
+
|
|
1836
|
+
|
|
1837
|
+
def codex_pricing_identity(snapshot: PricingSnapshot) -> str:
|
|
1838
|
+
"""A hash of every Codex pricing semantic in ``snapshot`` (#1050 spec 4.7).
|
|
1839
|
+
|
|
1840
|
+
The Codex cycle-detail stamp carries it, because the detail prices each
|
|
1841
|
+
milestone at read time: the rate table alone is not every input — an alias
|
|
1842
|
+
re-points a runtime name at another card, the fallback model prices every
|
|
1843
|
+
unknown name, the tier threshold switches a row onto the long-context
|
|
1844
|
+
rates, and the fast multipliers scale the whole row. Claude's semantics are
|
|
1845
|
+
excluded so a Claude-only pricing edit leaves every Codex stamp alone.
|
|
1846
|
+
|
|
1847
|
+
Canonical JSON (sorted keys, no NaN): equal semantics hash equally whatever
|
|
1848
|
+
the dict insertion order.
|
|
1849
|
+
"""
|
|
1850
|
+
payload = {
|
|
1851
|
+
"codex_pricing": snapshot.codex_pricing,
|
|
1852
|
+
"aliases": snapshot.aliases,
|
|
1853
|
+
"fallback_model": snapshot.fallback_model,
|
|
1854
|
+
"tier_threshold": snapshot.tier_thresholds.get("codex"),
|
|
1855
|
+
"fast_multipliers": {
|
|
1856
|
+
"codex": snapshot.fast_multipliers.get("codex"),
|
|
1857
|
+
"codex_fallback": snapshot.fast_multipliers.get("codex_fallback"),
|
|
1858
|
+
},
|
|
1859
|
+
}
|
|
1860
|
+
return hashlib.sha256(json.dumps(
|
|
1861
|
+
payload, sort_keys=True, separators=(",", ":"), allow_nan=False,
|
|
1862
|
+
).encode("utf-8")).hexdigest()[:16]
|
|
1863
|
+
|
|
1864
|
+
|
|
1865
|
+
_LIVE_CODEX_PRICING_IDENTITY: "str | None" = None
|
|
1866
|
+
|
|
1867
|
+
|
|
1868
|
+
def live_codex_pricing_identity() -> str:
|
|
1869
|
+
"""``codex_pricing_identity`` of the live snapshot, computed once per
|
|
1870
|
+
process: the snapshot never changes after import, and a newer binary is a
|
|
1871
|
+
new process (#868)."""
|
|
1872
|
+
global _LIVE_CODEX_PRICING_IDENTITY
|
|
1873
|
+
if _LIVE_CODEX_PRICING_IDENTITY is None:
|
|
1874
|
+
_LIVE_CODEX_PRICING_IDENTITY = codex_pricing_identity(
|
|
1875
|
+
current_pricing_snapshot())
|
|
1876
|
+
return _LIVE_CODEX_PRICING_IDENTITY
|
package/bin/_lib_quota.py
CHANGED
|
@@ -256,6 +256,25 @@ class ResetAnchorComponents:
|
|
|
256
256
|
def tolerance_seconds(self) -> int:
|
|
257
257
|
return self._tolerance
|
|
258
258
|
|
|
259
|
+
def copy(self) -> "ResetAnchorComponents":
|
|
260
|
+
"""An independent copy: mutating it never changes this index.
|
|
261
|
+
|
|
262
|
+
O(distinct raw resets). The provenance drain (#975, spec 4.2) starts
|
|
263
|
+
each identity from a copy of the committed-evidence base, so evidence
|
|
264
|
+
that identity resolves but does not store stays private to it.
|
|
265
|
+
"""
|
|
266
|
+
other = object.__new__(type(self))
|
|
267
|
+
other._tolerance = self._tolerance
|
|
268
|
+
other._buckets = {
|
|
269
|
+
bucket: list(members) for bucket, members in self._buckets.items()}
|
|
270
|
+
other._parent = dict(self._parent)
|
|
271
|
+
other._rank = dict(self._rank)
|
|
272
|
+
other._anchor = dict(self._anchor)
|
|
273
|
+
other._first_order = dict(self._first_order)
|
|
274
|
+
other._member_order = dict(self._member_order)
|
|
275
|
+
other._next_order = self._next_order
|
|
276
|
+
return other
|
|
277
|
+
|
|
259
278
|
def _bucket(self, value: dt.datetime) -> int:
|
|
260
279
|
return int(value.timestamp() // self._tolerance)
|
|
261
280
|
|
package/bin/_lib_rederive.py
CHANGED
|
@@ -3135,7 +3135,8 @@ def _reconcile_snapshot_identities(
|
|
|
3135
3135
|
decision only when exact retained origins prove their causal order, axes,
|
|
3136
3136
|
and dependency graph; otherwise leave the ordinary correction diff intact.
|
|
3137
3137
|
"""
|
|
3138
|
-
|
|
3138
|
+
# #1004 D4: any sequence (a coordinate-backed one is read, never listed).
|
|
3139
|
+
if not isinstance(raw_observations, Sequence):
|
|
3139
3140
|
raw_observations = tuple(raw_observations)
|
|
3140
3141
|
reviewed_pairs = tuple(snapshot_identity_decisions or ())
|
|
3141
3142
|
if not raw_observations and not reviewed_pairs:
|
|
@@ -3170,6 +3171,8 @@ def _reconcile_snapshot_identities(
|
|
|
3170
3171
|
}
|
|
3171
3172
|
if not needed_raw_ids:
|
|
3172
3173
|
return
|
|
3174
|
+
# #1004 D4: positions only; a record is decoded again where it is read,
|
|
3175
|
+
# so the collection pass keeps no observation alive.
|
|
3173
3176
|
raw_positions = {}
|
|
3174
3177
|
repeated_raw_ids = set()
|
|
3175
3178
|
for index, record in enumerate(raw_observations):
|
|
@@ -3183,7 +3186,7 @@ def _reconcile_snapshot_identities(
|
|
|
3183
3186
|
if raw_id in raw_positions:
|
|
3184
3187
|
repeated_raw_ids.add(raw_id)
|
|
3185
3188
|
else:
|
|
3186
|
-
raw_positions[raw_id] =
|
|
3189
|
+
raw_positions[raw_id] = index
|
|
3187
3190
|
# A repeated physical raw id has no unique order position. Leave its
|
|
3188
3191
|
# accepted decision to the normal diff instead of choosing an occurrence.
|
|
3189
3192
|
for raw_id in repeated_raw_ids:
|
|
@@ -3205,14 +3208,15 @@ def _reconcile_snapshot_identities(
|
|
|
3205
3208
|
}
|
|
3206
3209
|
candidates: dict[str, list[str]] = {}
|
|
3207
3210
|
for old_id, old in unmatched_current.items():
|
|
3208
|
-
old_index
|
|
3211
|
+
old_index = raw_positions[old_id[3:]]
|
|
3212
|
+
old_raw = raw_observations[old_index]
|
|
3209
3213
|
candidates[old_id] = []
|
|
3210
3214
|
for new_id, new in unmatched_desired.items():
|
|
3211
|
-
new_index
|
|
3215
|
+
new_index = raw_positions[new_id[3:]]
|
|
3212
3216
|
if (
|
|
3213
3217
|
new_index < old_index
|
|
3214
3218
|
and _same_acceptance_transition(
|
|
3215
|
-
old, new, old_raw,
|
|
3219
|
+
old, new, old_raw, raw_observations[new_index],
|
|
3216
3220
|
raw_observations[new_index + 1:old_index],
|
|
3217
3221
|
allow_intervening_same_account_observations=(
|
|
3218
3222
|
(old_id[3:], new_id[3:]) in reviewed_set
|
|
@@ -3880,7 +3884,8 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
3880
3884
|
and selected.record is not None
|
|
3881
3885
|
and _is_owned_event(selected.record)
|
|
3882
3886
|
}
|
|
3883
|
-
|
|
3887
|
+
# #1004 D4: any sequence; a coordinate-backed one is never listed.
|
|
3888
|
+
if not isinstance(raw_observations, Sequence):
|
|
3884
3889
|
raw_observations = tuple(raw_observations)
|
|
3885
3890
|
_reconcile_snapshot_identities(
|
|
3886
3891
|
current, desired, selection, raw_observations,
|