cctally 1.118.0 → 1.118.2
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 +15 -0
- package/bin/_cctally_cache.py +555 -25
- package/bin/_cctally_dashboard.py +73 -7
- package/bin/_cctally_dashboard_sources.py +7 -0
- package/bin/_cctally_db.py +304 -0
- package/bin/_cctally_source_analytics.py +14 -3
- package/bin/_cctally_tui.py +189 -1
- package/bin/_lib_quota.py +31 -0
- package/bin/_lib_snapshot_cache.py +480 -1
- package/package.json +1 -1
package/bin/_cctally_cache.py
CHANGED
|
@@ -289,21 +289,34 @@ _AI_TITLE_UPSERT_SQL = (
|
|
|
289
289
|
"ai_title=excluded.ai_title, source_path=excluded.source_path, byte_offset=excluded.byte_offset"
|
|
290
290
|
)
|
|
291
291
|
|
|
292
|
-
def _ai_title_upsert_sql(table: str = "conversation_ai_titles"
|
|
292
|
+
def _ai_title_upsert_sql(table: str = "conversation_ai_titles", *,
|
|
293
|
+
owner_only: bool = False) -> str:
|
|
293
294
|
"""The AI-title upsert, targeted at the live table or its staging twin.
|
|
294
295
|
|
|
295
296
|
A rebuild builds its replacement generation into staging and leaves the
|
|
296
|
-
live table alone until the publish (#752)
|
|
297
|
-
a
|
|
298
|
-
|
|
297
|
+
live table alone until the publish (#752) — except for a file replayed
|
|
298
|
+
under a NEW incarnation, whose live title rows are replaced,
|
|
299
|
+
owner-restricted, at its batch commit (#1062 §4.2). So the ONE statement
|
|
300
|
+
that writes a title has to be able to name either table. Parameterised
|
|
301
|
+
through this helper rather than by several literals, because literals
|
|
302
|
+
drift.
|
|
303
|
+
|
|
304
|
+
``owner_only`` is that #1062 §4.2 live-table write. The file's own live
|
|
305
|
+
rows were just deleted, so a conflicting live row belongs to ANOTHER file
|
|
306
|
+
of the same session and is kept until the publish installs the replayed
|
|
307
|
+
generation's choice; a later title line of the same file still replaces an
|
|
308
|
+
earlier one, as in the walk.
|
|
299
309
|
"""
|
|
300
|
-
|
|
310
|
+
sql = (
|
|
301
311
|
f"INSERT INTO {table}(session_id,ai_title,source_path,byte_offset) "
|
|
302
312
|
"VALUES(?,?,?,?) "
|
|
303
313
|
"ON CONFLICT(session_id) DO UPDATE SET "
|
|
304
314
|
"ai_title=excluded.ai_title, source_path=excluded.source_path, "
|
|
305
315
|
"byte_offset=excluded.byte_offset"
|
|
306
316
|
)
|
|
317
|
+
if owner_only:
|
|
318
|
+
sql += f" WHERE {table}.source_path = excluded.source_path"
|
|
319
|
+
return sql
|
|
307
320
|
|
|
308
321
|
|
|
309
322
|
# ---------------------------------------------------------------------------
|
|
@@ -1736,6 +1749,97 @@ class CodexWalkDeadline(Exception):
|
|
|
1736
1749
|
identity instead of deferring the file."""
|
|
1737
1750
|
|
|
1738
1751
|
|
|
1752
|
+
def _codex_reset_anchor_summary_sql(key_count: int) -> str:
|
|
1753
|
+
"""The summary seed's ONE statement (#1033 spec §5.2).
|
|
1754
|
+
|
|
1755
|
+
One read snapshot returns a header row (``kind = 0``: the readiness value
|
|
1756
|
+
and how many of the table and its three triggers exist) and the group's
|
|
1757
|
+
summary rows (``kind = 1``), each a primary-key seek per candidate key.
|
|
1758
|
+
UNION ALL and scalar subqueries build no temp structure.
|
|
1759
|
+
"""
|
|
1760
|
+
db = _cctally_db_sib
|
|
1761
|
+
names = [name for _kind, name in db.CODEX_RESET_ANCHOR_EVIDENCE_OBJECTS]
|
|
1762
|
+
triggers = [name for kind, name in db.CODEX_RESET_ANCHOR_EVIDENCE_OBJECTS
|
|
1763
|
+
if kind == "trigger"]
|
|
1764
|
+
placeholders = ",".join("?" for _ in range(key_count))
|
|
1765
|
+
return (
|
|
1766
|
+
"SELECT 0, (SELECT value FROM cache_meta WHERE key = "
|
|
1767
|
+
f"'{db.CODEX_RESET_ANCHOR_EVIDENCE_STATE_KEY}'), "
|
|
1768
|
+
"(SELECT COUNT(*) FROM sqlite_master WHERE "
|
|
1769
|
+
f"(type = 'table' AND name = '{names[0]}') OR (type = 'trigger' "
|
|
1770
|
+
"AND tbl_name = 'quota_window_snapshots' AND name IN ("
|
|
1771
|
+
+ ", ".join(f"'{name}'" for name in triggers)
|
|
1772
|
+
+ "))), NULL, NULL, NULL, NULL "
|
|
1773
|
+
"UNION ALL "
|
|
1774
|
+
"SELECT 1, resets_at_utc, row_count, min_source_path, "
|
|
1775
|
+
"min_line_offset, min_id, stale "
|
|
1776
|
+
"FROM codex_reset_anchor_evidence "
|
|
1777
|
+
"WHERE source_root_key = ? AND observed_slot = ? "
|
|
1778
|
+
f"AND logical_limit_key IN ({placeholders})")
|
|
1779
|
+
|
|
1780
|
+
|
|
1781
|
+
def _codex_reset_anchor_summary_seed(
|
|
1782
|
+
conn, root_key: str, slot: str, keys: "Sequence[str]", *,
|
|
1783
|
+
deadline: "float | None", clock,
|
|
1784
|
+
) -> "_lib_quota.ResetAnchorComponents | None":
|
|
1785
|
+
"""Seed one anchor group from the evidence summary, or ``None``.
|
|
1786
|
+
|
|
1787
|
+
``None`` means fall back to the full history read: the summary is not
|
|
1788
|
+
``ready``, its table or a trigger is missing, a returned bucket is stale,
|
|
1789
|
+
the statement raised a database error (for example the table does not
|
|
1790
|
+
exist yet, as during the pre-053 migration backfills), or a returned row
|
|
1791
|
+
is corrupt (a ``ValueError`` or ``TypeError`` while folding it, such as a
|
|
1792
|
+
count below one or a non-integer offset). The full read's
|
|
1793
|
+
``_seed_failed`` meaning is untouched. Inside a write transaction the
|
|
1794
|
+
summary carries that transaction's own uncommitted rows, because the
|
|
1795
|
+
triggers ran in it, exactly as the full read would see them.
|
|
1796
|
+
|
|
1797
|
+
A budgeted walk arms ``_codex_cancellable_read`` around the statement and
|
|
1798
|
+
checks the deadline every ``_CODEX_FINALIZATION_CHECK_EVERY`` entries;
|
|
1799
|
+
``CodexWalkDeadline`` propagates and nothing is returned, so a partial
|
|
1800
|
+
group is never cached.
|
|
1801
|
+
"""
|
|
1802
|
+
sql = _codex_reset_anchor_summary_sql(len(keys))
|
|
1803
|
+
params = (str(root_key), str(slot), *keys)
|
|
1804
|
+
try:
|
|
1805
|
+
if deadline is None:
|
|
1806
|
+
rows = conn.execute(sql, params).fetchall()
|
|
1807
|
+
else:
|
|
1808
|
+
with _codex_cancellable_read(
|
|
1809
|
+
conn, deadline, clock, cancelled=CodexWalkDeadline):
|
|
1810
|
+
rows = conn.execute(sql, params).fetchall()
|
|
1811
|
+
except sqlite3.DatabaseError:
|
|
1812
|
+
return None
|
|
1813
|
+
header = [row for row in rows if row[0] == 0]
|
|
1814
|
+
if (len(header) != 1
|
|
1815
|
+
or header[0][1] != _cctally_db_sib.CODEX_RESET_ANCHOR_EVIDENCE_READY
|
|
1816
|
+
or header[0][2] != 4):
|
|
1817
|
+
return None
|
|
1818
|
+
entries = []
|
|
1819
|
+
index = 0
|
|
1820
|
+
try:
|
|
1821
|
+
for row in rows:
|
|
1822
|
+
if row[0] != 1:
|
|
1823
|
+
continue
|
|
1824
|
+
if row[6] != 0:
|
|
1825
|
+
return None
|
|
1826
|
+
if (deadline is not None and index
|
|
1827
|
+
and index % _CODEX_FINALIZATION_CHECK_EVERY == 0
|
|
1828
|
+
and clock() >= deadline):
|
|
1829
|
+
raise CodexWalkDeadline("deadline")
|
|
1830
|
+
index += 1
|
|
1831
|
+
parsed = _parse_anchor_iso(row[1])
|
|
1832
|
+
if parsed is not None:
|
|
1833
|
+
entries.append((parsed, (str(row[3]), int(row[4]),
|
|
1834
|
+
int(row[5])), int(row[2])))
|
|
1835
|
+
return _lib_quota.ResetAnchorComponents.from_evidence(entries)
|
|
1836
|
+
except (ValueError, TypeError):
|
|
1837
|
+
# A corrupt row (the table's CHECKs bypassed) seeds nothing; the full
|
|
1838
|
+
# read decides, as for a database error. `CodexWalkDeadline` is
|
|
1839
|
+
# neither, so it propagates.
|
|
1840
|
+
return None
|
|
1841
|
+
|
|
1842
|
+
|
|
1739
1843
|
class CodexResetAnchorResolver:
|
|
1740
1844
|
"""Resolve one Codex quota observation's canonical reset anchor.
|
|
1741
1845
|
|
|
@@ -1805,10 +1909,20 @@ class CodexResetAnchorResolver:
|
|
|
1805
1909
|
if components is not None:
|
|
1806
1910
|
self._groups[group] = components
|
|
1807
1911
|
return components
|
|
1912
|
+
candidates = _lib_jsonl.codex_snap_equivalent_limit_keys(
|
|
1913
|
+
str(logical_limit_key))
|
|
1914
|
+
# #1033 spec §5.2: the exact evidence summary seeds the same
|
|
1915
|
+
# components as the full read below from O(distinct raw resets) rows;
|
|
1916
|
+
# `None` (not ready, an object missing, a stale bucket, a database
|
|
1917
|
+
# error) falls back to that read unchanged.
|
|
1918
|
+
components = _codex_reset_anchor_summary_seed(
|
|
1919
|
+
self._conn, str(group[0]), group[1], candidates,
|
|
1920
|
+
deadline=self._deadline, clock=self._clock)
|
|
1921
|
+
if components is not None:
|
|
1922
|
+
self._groups[group] = components
|
|
1923
|
+
return components
|
|
1808
1924
|
components = _lib_quota.ResetAnchorComponents()
|
|
1809
1925
|
if not self._seed_failed:
|
|
1810
|
-
candidates = _lib_jsonl.codex_snap_equivalent_limit_keys(
|
|
1811
|
-
str(logical_limit_key))
|
|
1812
1926
|
placeholders = ",".join("?" for _ in candidates)
|
|
1813
1927
|
# #901 Q11: the unary `+` keeps the plan this read always had, a
|
|
1814
1928
|
# walk of the `(source, source_path, line_offset, ...)` unique
|
|
@@ -5118,7 +5232,20 @@ def _clear_codex_derived_rows(conn: sqlite3.Connection) -> bool:
|
|
|
5118
5232
|
"SELECT CAST(value AS INTEGER), 'full' FROM cache_meta "
|
|
5119
5233
|
"WHERE key='codex_accounting_mutation_seq'"
|
|
5120
5234
|
)
|
|
5235
|
+
# #1033 spec §5.4: the per-row evidence triggers are no-ops while the
|
|
5236
|
+
# state is not ``ready``; the cleared summary is then exact and empty.
|
|
5237
|
+
_evidence = conn.execute(
|
|
5238
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name=?",
|
|
5239
|
+
(_cctally_db_sib.CODEX_RESET_ANCHOR_EVIDENCE_TABLE,)).fetchone()
|
|
5240
|
+
if _evidence is not None:
|
|
5241
|
+
_cctally_db_sib._set_codex_reset_anchor_evidence_state(
|
|
5242
|
+
conn, _cctally_db_sib.CODEX_RESET_ANCHOR_EVIDENCE_BOOTSTRAP)
|
|
5121
5243
|
conn.execute("DELETE FROM quota_window_snapshots WHERE source = 'codex'")
|
|
5244
|
+
if _evidence is not None:
|
|
5245
|
+
conn.execute("DELETE FROM codex_reset_anchor_evidence")
|
|
5246
|
+
_cctally_db_sib._advance_codex_reset_anchor_evidence_generation(conn)
|
|
5247
|
+
_cctally_db_sib._set_codex_reset_anchor_evidence_state(
|
|
5248
|
+
conn, _cctally_db_sib.CODEX_RESET_ANCHOR_EVIDENCE_READY)
|
|
5122
5249
|
conn.execute("DELETE FROM codex_conversation_threads")
|
|
5123
5250
|
conn.execute("DELETE FROM codex_conversation_events")
|
|
5124
5251
|
conn.execute("DELETE FROM codex_session_files")
|
|
@@ -7056,18 +7183,27 @@ def _codex_executor_next_unit(
|
|
|
7056
7183
|
"""The next non-prune unit the executor owes, re-read from the store.
|
|
7057
7184
|
|
|
7058
7185
|
Finalization debt first (a refused projection waits on its model flag),
|
|
7059
|
-
then the
|
|
7060
|
-
|
|
7186
|
+
then the reset-anchor evidence bootstrap (#1033, so a deferred catch-up
|
|
7187
|
+
file seeds from a ready summary), then the ingest catch-up a budgeted walk
|
|
7188
|
+
deferred at the anchor seed deadline (#980 F4), then the evidence repair
|
|
7189
|
+
(it runs only once prune debt is empty, so one run's repairs are batched
|
|
7190
|
+
after its pruning), then the accounting change-log prune, then one
|
|
7061
7191
|
TRUNCATE attempt. A kind in ``blocked`` failed or stalled earlier in this
|
|
7062
7192
|
run and waits for the next.
|
|
7063
7193
|
"""
|
|
7064
7194
|
if ("finalization" not in blocked
|
|
7065
7195
|
and not load_codex_finalization_debt(conn).empty):
|
|
7066
7196
|
return "finalization"
|
|
7197
|
+
evidence = _codex_reset_anchor_evidence_work(conn)
|
|
7198
|
+
if (evidence == "bootstrap"
|
|
7199
|
+
and CODEX_EVIDENCE_BOOTSTRAP_UNIT not in blocked):
|
|
7200
|
+
return CODEX_EVIDENCE_BOOTSTRAP_UNIT
|
|
7067
7201
|
if "ingest_catch_up" not in blocked:
|
|
7068
7202
|
whole_tree, paths = load_codex_owed_catch_up(conn)
|
|
7069
7203
|
if whole_tree or paths:
|
|
7070
7204
|
return "ingest_catch_up"
|
|
7205
|
+
if evidence == "repair" and CODEX_EVIDENCE_REPAIR_UNIT not in blocked:
|
|
7206
|
+
return CODEX_EVIDENCE_REPAIR_UNIT
|
|
7071
7207
|
owed = load_codex_owed_maintenance(conn)
|
|
7072
7208
|
if owed.get("change_log_prune") and "change_log_prune" not in blocked:
|
|
7073
7209
|
return "change_log_prune"
|
|
@@ -7206,6 +7342,298 @@ def _run_codex_ingest_catch_up_unit(conn: sqlite3.Connection, adm) -> str:
|
|
|
7206
7342
|
return "done"
|
|
7207
7343
|
|
|
7208
7344
|
|
|
7345
|
+
# ── #1033 spec §5.3: bootstrap and repair of the reset-anchor evidence ───────
|
|
7346
|
+
|
|
7347
|
+
CODEX_EVIDENCE_BOOTSTRAP_UNIT = "reset_anchor_evidence_bootstrap"
|
|
7348
|
+
CODEX_EVIDENCE_REPAIR_UNIT = "reset_anchor_evidence_repair"
|
|
7349
|
+
|
|
7350
|
+
#: Prepare/publish attempts one unit kind may spend per run (one drain run,
|
|
7351
|
+
#: or one inline ``cache-sync`` run), shared across yields: a token mismatch,
|
|
7352
|
+
#: a ``None`` token and every admission refusal (the setup step's included)
|
|
7353
|
+
#: each spend one. Once spent, the unit is blocked for the rest of the run,
|
|
7354
|
+
#: its work still owed.
|
|
7355
|
+
_CODEX_EVIDENCE_ATTEMPTS = 3
|
|
7356
|
+
|
|
7357
|
+
#: Private test seam: called after each prepare, before admission, as
|
|
7358
|
+
#: ``hook(kind, attempt)``.
|
|
7359
|
+
_CODEX_EVIDENCE_PREPUBLISH_HOOK: "Callable[[str, int], None] | None" = None
|
|
7360
|
+
|
|
7361
|
+
|
|
7362
|
+
def _codex_reset_anchor_evidence_columns_ok(conn: sqlite3.Connection) -> bool:
|
|
7363
|
+
"""The columns the triggers read exist (a legacy-shape cache whose schema
|
|
7364
|
+
apply returned early never receives the summary, and owes nothing)."""
|
|
7365
|
+
columns = {
|
|
7366
|
+
str(row[0]) for row in conn.execute(
|
|
7367
|
+
"SELECT name FROM pragma_table_info('quota_window_snapshots')")
|
|
7368
|
+
}
|
|
7369
|
+
return set(
|
|
7370
|
+
_cctally_db_sib._CODEX_RESET_ANCHOR_EVIDENCE_UPDATE_OF) <= columns
|
|
7371
|
+
|
|
7372
|
+
|
|
7373
|
+
def _codex_reset_anchor_evidence_objects(conn: sqlite3.Connection) -> int:
|
|
7374
|
+
names = [name for _kind, name
|
|
7375
|
+
in _cctally_db_sib.CODEX_RESET_ANCHOR_EVIDENCE_OBJECTS]
|
|
7376
|
+
return int(conn.execute(
|
|
7377
|
+
"SELECT COUNT(*) FROM sqlite_master WHERE name IN "
|
|
7378
|
+
f"({','.join('?' for _ in names)})", names).fetchone()[0])
|
|
7379
|
+
|
|
7380
|
+
|
|
7381
|
+
def _codex_reset_anchor_evidence_work(conn: sqlite3.Connection) -> "str | None":
|
|
7382
|
+
"""The evidence summary's owed work: ``"bootstrap"`` when any of its five
|
|
7383
|
+
objects is missing or the state is not ``ready``, ``"repair"`` when a
|
|
7384
|
+
stale bucket exists (served by the partial index), else ``None``."""
|
|
7385
|
+
db = _cctally_db_sib
|
|
7386
|
+
if not _codex_reset_anchor_evidence_columns_ok(conn):
|
|
7387
|
+
return None
|
|
7388
|
+
if _codex_reset_anchor_evidence_objects(conn) != len(
|
|
7389
|
+
db.CODEX_RESET_ANCHOR_EVIDENCE_OBJECTS):
|
|
7390
|
+
return "bootstrap"
|
|
7391
|
+
state = conn.execute(
|
|
7392
|
+
"SELECT value FROM cache_meta WHERE key=?",
|
|
7393
|
+
(db.CODEX_RESET_ANCHOR_EVIDENCE_STATE_KEY,)).fetchone()
|
|
7394
|
+
if state is None or state[0] != db.CODEX_RESET_ANCHOR_EVIDENCE_READY:
|
|
7395
|
+
return "bootstrap"
|
|
7396
|
+
if conn.execute(
|
|
7397
|
+
"SELECT 1 FROM codex_reset_anchor_evidence WHERE stale = 1 "
|
|
7398
|
+
"LIMIT 1").fetchone() is not None:
|
|
7399
|
+
return "repair"
|
|
7400
|
+
return None
|
|
7401
|
+
|
|
7402
|
+
|
|
7403
|
+
def _codex_reset_anchor_evidence_aggregate(rows) -> dict:
|
|
7404
|
+
"""``(root, slot, key, raw) -> [count, (path, offset, id)]`` with the
|
|
7405
|
+
LEXICOGRAPHIC minimum tuple (never per-column minima), in one pass."""
|
|
7406
|
+
out: dict = {}
|
|
7407
|
+
for root, slot, key, raw, path, offset, row_id in rows:
|
|
7408
|
+
bucket = (root, slot, key, raw)
|
|
7409
|
+
order = (path, offset, row_id)
|
|
7410
|
+
entry = out.get(bucket)
|
|
7411
|
+
if entry is None:
|
|
7412
|
+
out[bucket] = [1, order]
|
|
7413
|
+
else:
|
|
7414
|
+
entry[0] += 1
|
|
7415
|
+
if order < entry[1]:
|
|
7416
|
+
entry[1] = order
|
|
7417
|
+
return out
|
|
7418
|
+
|
|
7419
|
+
|
|
7420
|
+
def _codex_reset_anchor_evidence_token(conn: sqlite3.Connection):
|
|
7421
|
+
"""The publication token, or ``None`` when publication cannot be trusted:
|
|
7422
|
+
the walk-share token (store identity, schema version, the quota ledger's
|
|
7423
|
+
sequence, the physical mutation sequence) plus the evidence generation,
|
|
7424
|
+
the state value and how many of the five objects exist. Any insert or
|
|
7425
|
+
delete moves the ledger sequence; a key or order UPDATE (``id`` included,
|
|
7426
|
+
which the ledger cannot see) moves the generation."""
|
|
7427
|
+
walk = _CodexWalkShare.token(conn)
|
|
7428
|
+
if walk is None:
|
|
7429
|
+
return None
|
|
7430
|
+
db = _cctally_db_sib
|
|
7431
|
+
meta = dict(conn.execute(
|
|
7432
|
+
"SELECT key, value FROM cache_meta WHERE key IN (?, ?)",
|
|
7433
|
+
(db.CODEX_RESET_ANCHOR_EVIDENCE_STATE_KEY,
|
|
7434
|
+
db.CODEX_RESET_ANCHOR_EVIDENCE_GENERATION_KEY)).fetchall())
|
|
7435
|
+
return (walk, meta.get(db.CODEX_RESET_ANCHOR_EVIDENCE_GENERATION_KEY),
|
|
7436
|
+
meta.get(db.CODEX_RESET_ANCHOR_EVIDENCE_STATE_KEY),
|
|
7437
|
+
_codex_reset_anchor_evidence_objects(conn))
|
|
7438
|
+
|
|
7439
|
+
|
|
7440
|
+
_EVIDENCE_SCAN_COLUMNS = (
|
|
7441
|
+
"source_root_key, observed_slot, logical_limit_key, resets_at_utc, "
|
|
7442
|
+
"source_path, line_offset, id")
|
|
7443
|
+
|
|
7444
|
+
|
|
7445
|
+
def _codex_reset_anchor_evidence_prepare(conn: sqlite3.Connection, kind: str):
|
|
7446
|
+
"""One read snapshot: the token and the aggregate (no locks, no
|
|
7447
|
+
admission). Returns ``(token, payload)``; ``payload`` is the whole
|
|
7448
|
+
aggregate for a bootstrap, ``{group: aggregate}`` for a repair."""
|
|
7449
|
+
if conn.in_transaction:
|
|
7450
|
+
conn.rollback()
|
|
7451
|
+
conn.execute("BEGIN")
|
|
7452
|
+
try:
|
|
7453
|
+
token = _codex_reset_anchor_evidence_token(conn)
|
|
7454
|
+
if kind == CODEX_EVIDENCE_BOOTSTRAP_UNIT:
|
|
7455
|
+
payload = _codex_reset_anchor_evidence_aggregate(conn.execute(
|
|
7456
|
+
f"SELECT {_EVIDENCE_SCAN_COLUMNS} FROM quota_window_snapshots "
|
|
7457
|
+
"WHERE source = 'codex' AND observed_slot IS NOT NULL"))
|
|
7458
|
+
else:
|
|
7459
|
+
groups = {tuple(row) for row in conn.execute(
|
|
7460
|
+
"SELECT source_root_key, observed_slot, logical_limit_key "
|
|
7461
|
+
"FROM codex_reset_anchor_evidence WHERE stale = 1")}
|
|
7462
|
+
payload = {
|
|
7463
|
+
group: _codex_reset_anchor_evidence_aggregate(conn.execute(
|
|
7464
|
+
f"SELECT {_EVIDENCE_SCAN_COLUMNS} "
|
|
7465
|
+
"FROM quota_window_snapshots "
|
|
7466
|
+
"WHERE source = 'codex' AND source_root_key = ? "
|
|
7467
|
+
"AND logical_limit_key = ? AND observed_slot = ?",
|
|
7468
|
+
(group[0], group[2], group[1])))
|
|
7469
|
+
for group in sorted(groups)}
|
|
7470
|
+
finally:
|
|
7471
|
+
conn.rollback()
|
|
7472
|
+
return token, payload
|
|
7473
|
+
|
|
7474
|
+
|
|
7475
|
+
def _codex_reset_anchor_evidence_insert(conn, aggregate: dict) -> None:
|
|
7476
|
+
conn.executemany(
|
|
7477
|
+
"INSERT INTO codex_reset_anchor_evidence (source_root_key, "
|
|
7478
|
+
"observed_slot, logical_limit_key, resets_at_utc, row_count, "
|
|
7479
|
+
"min_source_path, min_line_offset, min_id, stale) "
|
|
7480
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)",
|
|
7481
|
+
[(*bucket, count, *minimum)
|
|
7482
|
+
for bucket, (count, minimum) in sorted(aggregate.items())])
|
|
7483
|
+
|
|
7484
|
+
|
|
7485
|
+
def _run_codex_reset_anchor_evidence_unit(
|
|
7486
|
+
conn: sqlite3.Connection, adm, kind: str, *,
|
|
7487
|
+
budget: "dict[str, int] | None" = None,
|
|
7488
|
+
) -> str:
|
|
7489
|
+
"""Bootstrap or repair the reset-anchor evidence summary (#1033 §5.3).
|
|
7490
|
+
|
|
7491
|
+
Missing objects first get an admitted setup step: the shared schema
|
|
7492
|
+
helper re-creates them, invalidates readiness and advances the
|
|
7493
|
+
generation, and commits on its own. Then prepare/publish attempts:
|
|
7494
|
+
prepare reads the token and the aggregate in one snapshot with no
|
|
7495
|
+
admission and no store locks; publication admits through the global gate
|
|
7496
|
+
with both writer locks, takes ``BEGIN IMMEDIATE``, re-reads the token and
|
|
7497
|
+
publishes only when it is unchanged (a mismatch rolls back and
|
|
7498
|
+
re-prepares). A bootstrap replaces the whole summary and records
|
|
7499
|
+
``ready``; a repair replaces each stale group's rows with exact,
|
|
7500
|
+
non-stale ones. Neither touches stored canonical anchors or the physical
|
|
7501
|
+
mutation sequence.
|
|
7502
|
+
|
|
7503
|
+
``budget`` is the caller's per-run map of remaining attempts per kind
|
|
7504
|
+
(``_CODEX_EVIDENCE_ATTEMPTS`` when absent; ``None`` makes this call its
|
|
7505
|
+
own run). It is shared across yields, so a run that waits out requests
|
|
7506
|
+
and calls again does not start over: a token mismatch, a ``None`` token
|
|
7507
|
+
and an admission refusal (the setup step's included) each spend one.
|
|
7508
|
+
|
|
7509
|
+
Returns ``"published"``, ``"yielded"`` (admission refused with attempts
|
|
7510
|
+
left), ``"blocked"`` (the run's attempts are spent, or an error; the work
|
|
7511
|
+
stays owed for a later run) or ``"none"`` (nothing was owed). ``conn``
|
|
7512
|
+
holds no transaction on entry or exit.
|
|
7513
|
+
"""
|
|
7514
|
+
if conn.in_transaction:
|
|
7515
|
+
conn.rollback()
|
|
7516
|
+
work = _codex_reset_anchor_evidence_work(conn)
|
|
7517
|
+
if work is None:
|
|
7518
|
+
return "none"
|
|
7519
|
+
expected = (CODEX_EVIDENCE_BOOTSTRAP_UNIT if work == "bootstrap"
|
|
7520
|
+
else CODEX_EVIDENCE_REPAIR_UNIT)
|
|
7521
|
+
if expected != kind:
|
|
7522
|
+
return "none"
|
|
7523
|
+
if budget is None:
|
|
7524
|
+
budget = {}
|
|
7525
|
+
budget.setdefault(kind, _CODEX_EVIDENCE_ATTEMPTS)
|
|
7526
|
+
|
|
7527
|
+
def spend() -> bool:
|
|
7528
|
+
"""Spend one attempt; True while attempts remain."""
|
|
7529
|
+
budget[kind] -= 1
|
|
7530
|
+
return budget[kind] > 0
|
|
7531
|
+
|
|
7532
|
+
if budget[kind] <= 0:
|
|
7533
|
+
return "blocked"
|
|
7534
|
+
db = _cctally_db_sib
|
|
7535
|
+
if (kind == CODEX_EVIDENCE_BOOTSTRAP_UNIT
|
|
7536
|
+
and _codex_reset_anchor_evidence_objects(conn)
|
|
7537
|
+
!= len(db.CODEX_RESET_ANCHOR_EVIDENCE_OBJECTS)):
|
|
7538
|
+
admission = adm.admit(_cctally_core.APP_DIR, _cache_store_locks,
|
|
7539
|
+
_release_cache_store_locks)
|
|
7540
|
+
if not admission.admitted:
|
|
7541
|
+
return "yielded" if spend() else "blocked"
|
|
7542
|
+
try:
|
|
7543
|
+
db._apply_codex_reset_anchor_evidence(conn)
|
|
7544
|
+
except sqlite3.Error as exc:
|
|
7545
|
+
eprint(f"[codex] reset-anchor evidence setup failed: {exc}")
|
|
7546
|
+
if conn.in_transaction:
|
|
7547
|
+
conn.rollback()
|
|
7548
|
+
return "blocked"
|
|
7549
|
+
finally:
|
|
7550
|
+
adm.finish(admission, _release_cache_store_locks)
|
|
7551
|
+
if _codex_reset_anchor_evidence_work(conn) is None:
|
|
7552
|
+
return "published" # an empty store came back ready
|
|
7553
|
+
while budget[kind] > 0:
|
|
7554
|
+
attempt = _CODEX_EVIDENCE_ATTEMPTS - budget[kind] + 1
|
|
7555
|
+
try:
|
|
7556
|
+
token, payload = _codex_reset_anchor_evidence_prepare(conn, kind)
|
|
7557
|
+
except sqlite3.Error as exc:
|
|
7558
|
+
eprint(f"[codex] reset-anchor evidence {kind} failed: {exc}")
|
|
7559
|
+
return "blocked"
|
|
7560
|
+
if _CODEX_EVIDENCE_PREPUBLISH_HOOK is not None:
|
|
7561
|
+
_CODEX_EVIDENCE_PREPUBLISH_HOOK(kind, attempt)
|
|
7562
|
+
if token is None:
|
|
7563
|
+
spend()
|
|
7564
|
+
continue
|
|
7565
|
+
admission = adm.admit(_cctally_core.APP_DIR, _cache_store_locks,
|
|
7566
|
+
_release_cache_store_locks)
|
|
7567
|
+
if not admission.admitted:
|
|
7568
|
+
return "yielded" if spend() else "blocked"
|
|
7569
|
+
try:
|
|
7570
|
+
conn.execute("BEGIN IMMEDIATE")
|
|
7571
|
+
if _codex_reset_anchor_evidence_token(conn) != token:
|
|
7572
|
+
conn.rollback()
|
|
7573
|
+
spend()
|
|
7574
|
+
continue
|
|
7575
|
+
if kind == CODEX_EVIDENCE_BOOTSTRAP_UNIT:
|
|
7576
|
+
conn.execute("DELETE FROM codex_reset_anchor_evidence")
|
|
7577
|
+
_codex_reset_anchor_evidence_insert(conn, payload)
|
|
7578
|
+
db._set_codex_reset_anchor_evidence_state(
|
|
7579
|
+
conn, db.CODEX_RESET_ANCHOR_EVIDENCE_READY)
|
|
7580
|
+
else:
|
|
7581
|
+
for (root, slot, key), aggregate in payload.items():
|
|
7582
|
+
conn.execute(
|
|
7583
|
+
"DELETE FROM codex_reset_anchor_evidence "
|
|
7584
|
+
"WHERE source_root_key = ? AND observed_slot = ? "
|
|
7585
|
+
"AND logical_limit_key = ?", (root, slot, key))
|
|
7586
|
+
_codex_reset_anchor_evidence_insert(conn, aggregate)
|
|
7587
|
+
conn.commit()
|
|
7588
|
+
return "published"
|
|
7589
|
+
except sqlite3.Error as exc:
|
|
7590
|
+
eprint(f"[codex] reset-anchor evidence {kind} failed: {exc}")
|
|
7591
|
+
if conn.in_transaction:
|
|
7592
|
+
conn.rollback()
|
|
7593
|
+
return "blocked"
|
|
7594
|
+
except BaseException:
|
|
7595
|
+
if conn.in_transaction:
|
|
7596
|
+
conn.rollback()
|
|
7597
|
+
raise
|
|
7598
|
+
finally:
|
|
7599
|
+
adm.finish(admission, _release_cache_store_locks)
|
|
7600
|
+
return "blocked"
|
|
7601
|
+
|
|
7602
|
+
|
|
7603
|
+
def run_codex_reset_anchor_evidence(*, wait_for_requests: bool = True) -> int:
|
|
7604
|
+
"""Run the evidence units inline (``cache-sync``), with the drain's bounds.
|
|
7605
|
+
|
|
7606
|
+
The same function the drain runs, with one attempt budget per kind for
|
|
7607
|
+
this run, shared across yields; a yield waits out the requests when
|
|
7608
|
+
``wait_for_requests`` and stops otherwise, and a kind whose budget is
|
|
7609
|
+
spent is blocked. Returns the number of units published."""
|
|
7610
|
+
import _lib_codex_drain_admission as adm
|
|
7611
|
+
published = 0
|
|
7612
|
+
blocked: "set[str]" = set()
|
|
7613
|
+
budget: "dict[str, int]" = {}
|
|
7614
|
+
while True:
|
|
7615
|
+
conn = open_cache_db()
|
|
7616
|
+
try:
|
|
7617
|
+
work = _codex_reset_anchor_evidence_work(conn)
|
|
7618
|
+
conn.rollback()
|
|
7619
|
+
kind = {"bootstrap": CODEX_EVIDENCE_BOOTSTRAP_UNIT,
|
|
7620
|
+
"repair": CODEX_EVIDENCE_REPAIR_UNIT}.get(work)
|
|
7621
|
+
if kind is None or kind in blocked:
|
|
7622
|
+
return published
|
|
7623
|
+
outcome = _run_codex_reset_anchor_evidence_unit(
|
|
7624
|
+
conn, adm, kind, budget=budget)
|
|
7625
|
+
finally:
|
|
7626
|
+
conn.close()
|
|
7627
|
+
if outcome == "published":
|
|
7628
|
+
published += 1
|
|
7629
|
+
elif outcome == "yielded":
|
|
7630
|
+
if not wait_for_requests:
|
|
7631
|
+
return published
|
|
7632
|
+
_drain_backoff(adm)
|
|
7633
|
+
else:
|
|
7634
|
+
blocked.add(kind)
|
|
7635
|
+
|
|
7636
|
+
|
|
7209
7637
|
def _next_codex_debt_row(conn: sqlite3.Connection, table: str,
|
|
7210
7638
|
after: "tuple[str, str] | None"):
|
|
7211
7639
|
"""The next debt row in key order, strictly past ``after`` when given.
|
|
@@ -7257,6 +7685,8 @@ def run_codex_prune_drain(*, wait_for_requests: bool = True,
|
|
|
7257
7685
|
deleted = dropped = yielded = 0
|
|
7258
7686
|
finalization_units = owed_units = wal_attempts = 0
|
|
7259
7687
|
blocked: "set[str]" = set()
|
|
7688
|
+
# #1033 §5.3: the evidence units' attempts per kind for this run.
|
|
7689
|
+
evidence_budget: "dict[str, int]" = {}
|
|
7260
7690
|
discovery = _CodexPruneDiscoveryCache() # one discovery per pass (N10)
|
|
7261
7691
|
# #970 spec 5.6: an identity whose absence is unknown keeps its debt and
|
|
7262
7692
|
# is passed over for the rest of this run (keyset past it).
|
|
@@ -7277,6 +7707,21 @@ def run_codex_prune_drain(*, wait_for_requests: bool = True,
|
|
|
7277
7707
|
# admission, re-reads its state under the gate and commits.
|
|
7278
7708
|
unit = _codex_executor_next_unit(conn, blocked)
|
|
7279
7709
|
conn.rollback()
|
|
7710
|
+
if unit == CODEX_EVIDENCE_BOOTSTRAP_UNIT:
|
|
7711
|
+
# #1033 §5.3: like the catch-up, special-cased ahead of
|
|
7712
|
+
# prune rows; its scan runs before its own admission.
|
|
7713
|
+
outcome = _run_codex_reset_anchor_evidence_unit(
|
|
7714
|
+
conn, adm, unit, budget=evidence_budget)
|
|
7715
|
+
if outcome == "yielded":
|
|
7716
|
+
yielded += 1
|
|
7717
|
+
if not wait_for_requests:
|
|
7718
|
+
break
|
|
7719
|
+
_drain_backoff(adm)
|
|
7720
|
+
elif outcome == "published":
|
|
7721
|
+
owed_units += 1
|
|
7722
|
+
elif outcome == "blocked":
|
|
7723
|
+
blocked.add(unit)
|
|
7724
|
+
continue
|
|
7280
7725
|
if unit == "ingest_catch_up":
|
|
7281
7726
|
# #980 F4: its sync owns the locks and its own admission.
|
|
7282
7727
|
caught = _run_codex_ingest_catch_up_unit(conn, adm)
|
|
@@ -7296,6 +7741,21 @@ def run_codex_prune_drain(*, wait_for_requests: bool = True,
|
|
|
7296
7741
|
conn.rollback()
|
|
7297
7742
|
if row is None and unit is None:
|
|
7298
7743
|
break
|
|
7744
|
+
if row is None and unit == CODEX_EVIDENCE_REPAIR_UNIT:
|
|
7745
|
+
# #1033 §5.3: after pruning; prepare runs unadmitted, so
|
|
7746
|
+
# it never goes through the admitted-only executor body.
|
|
7747
|
+
outcome = _run_codex_reset_anchor_evidence_unit(
|
|
7748
|
+
conn, adm, unit, budget=evidence_budget)
|
|
7749
|
+
if outcome == "yielded":
|
|
7750
|
+
yielded += 1
|
|
7751
|
+
if not wait_for_requests:
|
|
7752
|
+
break
|
|
7753
|
+
_drain_backoff(adm)
|
|
7754
|
+
elif outcome == "published":
|
|
7755
|
+
owed_units += 1
|
|
7756
|
+
elif outcome == "blocked":
|
|
7757
|
+
blocked.add(unit)
|
|
7758
|
+
continue
|
|
7299
7759
|
if row is None:
|
|
7300
7760
|
admission = adm.admit(_cctally_core.APP_DIR,
|
|
7301
7761
|
_cache_store_locks,
|
|
@@ -7310,11 +7770,15 @@ def run_codex_prune_drain(*, wait_for_requests: bool = True,
|
|
|
7310
7770
|
try:
|
|
7311
7771
|
unit = _codex_executor_next_unit(conn, blocked)
|
|
7312
7772
|
conn.rollback()
|
|
7313
|
-
if unit is None or unit
|
|
7773
|
+
if unit is None or unit in (
|
|
7774
|
+
"ingest_catch_up",
|
|
7775
|
+
CODEX_EVIDENCE_BOOTSTRAP_UNIT,
|
|
7776
|
+
CODEX_EVIDENCE_REPAIR_UNIT):
|
|
7314
7777
|
# #981 R2: a catch-up a hook recorded since the
|
|
7315
7778
|
# unlocked read runs through its own path (its sync
|
|
7316
7779
|
# owns the locks): release this admission and let
|
|
7317
|
-
# the next pass dispatch it.
|
|
7780
|
+
# the next pass dispatch it. The #1033 evidence
|
|
7781
|
+
# units likewise run through their own path.
|
|
7318
7782
|
continue
|
|
7319
7783
|
ok, model_cleared = _run_codex_executor_unit(conn, unit)
|
|
7320
7784
|
except BaseException:
|
|
@@ -17343,7 +17807,11 @@ def load_codex_owed_catch_up(
|
|
|
17343
17807
|
|
|
17344
17808
|
def _codex_owed_anything(conn: sqlite3.Connection) -> bool:
|
|
17345
17809
|
flags, whole_tree, paths = _load_codex_owed_record(conn)
|
|
17346
|
-
|
|
17810
|
+
if any(flags.values()) or whole_tree or bool(paths):
|
|
17811
|
+
return True
|
|
17812
|
+
# #1033 spec §5.3: the reset-anchor evidence work is derived from its
|
|
17813
|
+
# state and an indexed stale probe, never from a second owed flag.
|
|
17814
|
+
return _codex_reset_anchor_evidence_work(conn) is not None
|
|
17347
17815
|
|
|
17348
17816
|
|
|
17349
17817
|
def _store_codex_owed_maintenance(
|
|
@@ -21527,12 +21995,36 @@ def scope_conversations_db_to_account(
|
|
|
21527
21995
|
f"""
|
|
21528
21996
|
CREATE TEMP VIEW conversation_messages AS
|
|
21529
21997
|
SELECT * FROM main.conversation_messages WHERE {stored_predicate};
|
|
21998
|
+
-- #1062: a title is in scope iff (a) its own file's message walk has
|
|
21999
|
+
-- committed past the title's line, (b) that file still holds a
|
|
22000
|
+
-- retained in-scope message of the title's session, (c) no retained
|
|
22001
|
+
-- message of the session, in any file, is outside the scope, and
|
|
22002
|
+
-- (d) no Claude conversation rebuild is in progress (its message
|
|
22003
|
+
-- table may be partial). (b) reads the scoped TEMP view; (a), (c)
|
|
22004
|
+
-- and (d) read main. The fragment's unqualified account_key binds to
|
|
22005
|
+
-- the innermost FROM (main.conversation_messages m). The base rowid
|
|
22006
|
+
-- is projected as `rowid` so the title FTS joins resolve on a scoped
|
|
22007
|
+
-- connection and return the base row (C1).
|
|
21530
22008
|
CREATE TEMP VIEW conversation_ai_titles AS
|
|
21531
|
-
SELECT t.* FROM main.conversation_ai_titles t
|
|
22009
|
+
SELECT t.rowid AS rowid, t.* FROM main.conversation_ai_titles t
|
|
21532
22010
|
WHERE EXISTS (
|
|
22011
|
+
SELECT 1 FROM main.conversation_source_files f
|
|
22012
|
+
WHERE f.path = t.source_path
|
|
22013
|
+
AND f.last_byte_offset > t.byte_offset
|
|
22014
|
+
)
|
|
22015
|
+
AND EXISTS (
|
|
21533
22016
|
SELECT 1 FROM conversation_messages m
|
|
21534
|
-
WHERE m.
|
|
21535
|
-
AND m.
|
|
22017
|
+
WHERE m.session_id = t.session_id
|
|
22018
|
+
AND m.source_path = t.source_path
|
|
22019
|
+
)
|
|
22020
|
+
AND NOT EXISTS (
|
|
22021
|
+
SELECT 1 FROM main.conversation_messages m
|
|
22022
|
+
WHERE m.session_id = t.session_id
|
|
22023
|
+
AND NOT ({stored_predicate})
|
|
22024
|
+
)
|
|
22025
|
+
AND NOT EXISTS (
|
|
22026
|
+
SELECT 1 FROM main.cache_meta
|
|
22027
|
+
WHERE key = 'conversation_rebuild_claude_pending'
|
|
21536
22028
|
);
|
|
21537
22029
|
CREATE TEMP VIEW conversation_file_touches AS
|
|
21538
22030
|
SELECT t.* FROM main.conversation_file_touches t
|
|
@@ -23642,6 +24134,19 @@ def _write_claude_prepared_file(
|
|
|
23642
24134
|
and the scoped ``_fill_file_touches`` never commits."""
|
|
23643
24135
|
path_str = prepared.path
|
|
23644
24136
|
sessions: set[str] = set()
|
|
24137
|
+
# #1062 §4.2: a rebuild writes titles to staging and leaves the live table
|
|
24138
|
+
# to the publish (#752) — except for a file replayed under a NEW
|
|
24139
|
+
# incarnation. `prepared.truncated` means exactly that (#1062 F8): a
|
|
24140
|
+
# replacement, a truncation below the cursor, or missing identity
|
|
24141
|
+
# evidence; a continuous file that merely shrank (a trimmed unfinished
|
|
24142
|
+
# tail) is not truncated and keeps its committed rows. A new incarnation's
|
|
24143
|
+
# live rows get what an incremental sync of the file would leave: the
|
|
24144
|
+
# path's rows deleted and its replayed titles re-upserted, owner-restricted,
|
|
24145
|
+
# here in the file's savepoint, so they commit with its cursor. Without it
|
|
24146
|
+
# the fold carries a title the file has lost into the new generation, and
|
|
24147
|
+
# after an interrupted rebuild no later run detects the replacement again.
|
|
24148
|
+
refresh_live = (prepared.truncated
|
|
24149
|
+
and title_table != "conversation_ai_titles")
|
|
23645
24150
|
if prepared.truncated:
|
|
23646
24151
|
sessions.update(
|
|
23647
24152
|
row[0]
|
|
@@ -23664,6 +24169,11 @@ def _write_claude_prepared_file(
|
|
|
23664
24169
|
f"DELETE FROM {title_table} WHERE source_path=?",
|
|
23665
24170
|
(path_str,),
|
|
23666
24171
|
)
|
|
24172
|
+
if refresh_live:
|
|
24173
|
+
conn.execute(
|
|
24174
|
+
"DELETE FROM conversation_ai_titles WHERE source_path=?",
|
|
24175
|
+
(path_str,),
|
|
24176
|
+
)
|
|
23667
24177
|
if prepared.conv_rows:
|
|
23668
24178
|
conn.executemany(_CONV_ACCOUNT_INSERT_SQL, prepared.conv_rows)
|
|
23669
24179
|
_fill_file_touches(
|
|
@@ -23683,6 +24193,9 @@ def _write_claude_prepared_file(
|
|
|
23683
24193
|
)
|
|
23684
24194
|
if prepared.ai_rows:
|
|
23685
24195
|
conn.executemany(_ai_title_upsert_sql(title_table), prepared.ai_rows)
|
|
24196
|
+
if refresh_live:
|
|
24197
|
+
conn.executemany(
|
|
24198
|
+
_ai_title_upsert_sql(owner_only=True), prepared.ai_rows)
|
|
23686
24199
|
size, mtime_ns, final_offset, device_id, inode, incarnation_id, prefix = (
|
|
23687
24200
|
prepared.cursor)
|
|
23688
24201
|
conn.execute(
|
|
@@ -24028,9 +24541,11 @@ def sync_claude_conversations(
|
|
|
24028
24541
|
# nothing, so every one of its records is first observed now and takes
|
|
24029
24542
|
# the active identity rather than being read as historical-and-unknown.
|
|
24030
24543
|
high_water: "dict[tuple[str, str], int]" = {}
|
|
24031
|
-
# #752: a rebuild writes its titles and rollup into staging and
|
|
24032
|
-
#
|
|
24033
|
-
# the previous complete generation for the
|
|
24544
|
+
# #752: a rebuild writes its titles and rollup into staging and
|
|
24545
|
+
# otherwise leaves the live tables to the publish, so readers keep
|
|
24546
|
+
# seeing the previous complete generation for the replay. The one
|
|
24547
|
+
# exception is #1062 §4.2: a file replayed under a NEW incarnation has
|
|
24548
|
+
# its live title rows replaced, owner-restricted, at its batch commit.
|
|
24034
24549
|
title_table = "conversation_ai_titles"
|
|
24035
24550
|
rollup_table = "conversation_sessions"
|
|
24036
24551
|
if rebuild:
|
|
@@ -24132,6 +24647,11 @@ def sync_claude_conversations(
|
|
|
24132
24647
|
stats.files_skipped_unchanged += 1
|
|
24133
24648
|
_report_conversation_progress(progress, "ingest", stats)
|
|
24134
24649
|
continue
|
|
24650
|
+
# The raw shrink decides only the targeted deferral below,
|
|
24651
|
+
# which runs before the incarnation resolver and so cannot yet
|
|
24652
|
+
# tell a truncation below the cursor from a trimmed unfinished
|
|
24653
|
+
# tail; deferring both to a full sync is conservative. Past the
|
|
24654
|
+
# resolver, `truncated` is redefined as "a new incarnation".
|
|
24135
24655
|
truncated = prev is not None and size < prev[0]
|
|
24136
24656
|
if targeted and truncated:
|
|
24137
24657
|
# #939 D1: every exit resolves the open batch.
|
|
@@ -24165,13 +24685,20 @@ def sync_claude_conversations(
|
|
|
24165
24685
|
start_offset = incarnation.start_offset
|
|
24166
24686
|
file_high_water = high_water.get(
|
|
24167
24687
|
(path_str, incarnation.incarnation_id), 0)
|
|
24168
|
-
|
|
24169
|
-
|
|
24170
|
-
|
|
24171
|
-
|
|
24172
|
-
|
|
24173
|
-
|
|
24174
|
-
|
|
24688
|
+
# A new incarnation forces byte-zero treatment: the
|
|
24689
|
+
# bytes before the cursor are not the bytes the cursor
|
|
24690
|
+
# described, so resuming from it would skip real records
|
|
24691
|
+
# and stamp the rest against an identity that never held
|
|
24692
|
+
# them. A CONTINUOUS incarnation is never a truncation,
|
|
24693
|
+
# even when the file shrank (#1062 F8): a pass that ended
|
|
24694
|
+
# on an unfinished last line records a size past its
|
|
24695
|
+
# cursor, and trimming that line shrinks the file without
|
|
24696
|
+
# moving a committed byte. Its committed rows stay; the
|
|
24697
|
+
# walk resumes from the cursor and the cursor row records
|
|
24698
|
+
# the smaller size. Treating that shrink as a truncation
|
|
24699
|
+
# deleted every row of the path and re-inserted only the
|
|
24700
|
+
# tail.
|
|
24701
|
+
truncated = incarnation.is_new and prev is not None
|
|
24175
24702
|
# #939 D1: admission is decided BEFORE the parse. The
|
|
24176
24703
|
# read limit is the size the incarnation decision saw
|
|
24177
24704
|
# (the same fstat recorded as the change detector
|
|
@@ -27066,6 +27593,9 @@ def cmd_cache_sync(args: argparse.Namespace) -> int:
|
|
|
27066
27593
|
# identity inventory inline, through the same admitted executor the
|
|
27067
27594
|
# detached worker runs (a no-op once it is ready).
|
|
27068
27595
|
run_codex_inventory_bootstrap(wait_for_requests=True)
|
|
27596
|
+
# #1033 spec §5.3: the reset-anchor evidence bootstrap/repair runs
|
|
27597
|
+
# inline too, through the drain's own function and bounds.
|
|
27598
|
+
run_codex_reset_anchor_evidence(wait_for_requests=True)
|
|
27069
27599
|
prune_result = run_codex_prune_drain(wait_for_requests=True)
|
|
27070
27600
|
verify_result = run_codex_container_verification_drain(wait_for_requests=True)
|
|
27071
27601
|
run_codex_reset_drain(wait_for_requests=True)
|