cctally 1.114.0 → 1.115.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 +25 -0
- package/bin/_cctally_account.py +32 -8
- package/bin/_cctally_cache.py +4394 -279
- package/bin/_cctally_completion.py +1287 -0
- package/bin/_cctally_config.py +37 -0
- package/bin/_cctally_core.py +178 -2
- package/bin/_cctally_dashboard.py +2 -2
- package/bin/_cctally_dashboard_conversation.py +292 -23
- package/bin/_cctally_dashboard_envelope.py +16 -3
- package/bin/_cctally_dashboard_sources.py +18 -9
- package/bin/_cctally_db.py +316 -5
- package/bin/_cctally_doctor.py +436 -27
- package/bin/_cctally_five_hour.py +82 -31
- package/bin/_cctally_forecast.py +81 -9
- package/bin/_cctally_journal.py +3884 -112
- package/bin/_cctally_journal_repair.py +101 -12
- package/bin/_cctally_parser.py +42 -0
- package/bin/_cctally_project.py +8 -2
- package/bin/_cctally_quarantine.py +652 -0
- package/bin/_cctally_quota.py +26 -4
- package/bin/_cctally_quota_model.py +360 -83
- package/bin/_cctally_record.py +677 -149
- package/bin/_cctally_rederive.py +297 -32
- package/bin/_cctally_refresh.py +84 -13
- package/bin/_cctally_source_analytics.py +23 -2
- package/bin/_cctally_statusline.py +6137 -517
- package/bin/_cctally_store.py +287 -48
- package/bin/_cctally_transcript.py +8 -7
- package/bin/_cctally_tui.py +57 -53
- package/bin/_lib_account_binding.py +152 -0
- package/bin/_lib_account_protocol.py +386 -0
- package/bin/_lib_aggregators.py +4 -0
- package/bin/_lib_cache_coverage.py +13 -1
- package/bin/_lib_cache_writer_lock.py +19 -3
- package/bin/_lib_clone_copy.py +142 -0
- package/bin/_lib_codex_conversation_query.py +133 -19
- package/bin/_lib_codex_conversation_watch.py +59 -14
- package/bin/_lib_codex_drain_admission.py +214 -0
- package/bin/_lib_codex_metadata.py +18 -1
- package/bin/_lib_codex_publication_barrier.py +277 -0
- package/bin/_lib_codex_rollout_container.py +502 -0
- package/bin/_lib_conversation_dispatch.py +3 -0
- package/bin/_lib_conversation_query.py +11 -3
- package/bin/_lib_detector_access_registry.py +3817 -0
- package/bin/_lib_doctor.py +452 -1
- package/bin/_lib_five_hour.py +126 -0
- package/bin/_lib_ingest_frontier.py +414 -20
- package/bin/_lib_journal.py +141 -10
- package/bin/_lib_jsonl.py +5 -4
- package/bin/_lib_legacy_spool.py +126 -0
- package/bin/_lib_legacy_writer_evidence.py +136 -0
- package/bin/_lib_merge_gate.py +29 -302
- package/bin/_lib_meter_rate_change.py +10 -0
- package/bin/_lib_outcome_quarantine.py +690 -0
- package/bin/_lib_pricing.py +7 -2
- package/bin/_lib_quarantine.py +154 -0
- package/bin/_lib_quota_calibration.py +53 -8
- package/bin/_lib_quota_copy.py +2 -1
- package/bin/_lib_quota_model.py +288 -46
- package/bin/_lib_rederive.py +94 -5
- package/bin/_lib_segment_summary.py +35 -2
- package/bin/_lib_statusline_candidates.py +292 -23
- package/bin/cctally +23 -1
- package/package.json +14 -1
package/bin/_cctally_db.py
CHANGED
|
@@ -801,7 +801,7 @@ def _would_block_prod_migration(conn: sqlite3.Connection) -> bool:
|
|
|
801
801
|
test-suite's CCTALLY_DISABLE_DEV_AUTODETECT. Escape: CCTALLY_ALLOW_PROD_MIGRATION."""
|
|
802
802
|
if _cctally_core._truthy_env("CCTALLY_ALLOW_PROD_MIGRATION"):
|
|
803
803
|
return False
|
|
804
|
-
if not
|
|
804
|
+
if not _cctally_core._has_git_checkout_marker():
|
|
805
805
|
return False
|
|
806
806
|
db_dir = _conn_db_dir(conn)
|
|
807
807
|
if db_dir is None:
|
|
@@ -857,7 +857,7 @@ def _would_block_prod_stats(path: pathlib.Path) -> bool:
|
|
|
857
857
|
``CCTALLY_ALLOW_PROD_MIGRATION``."""
|
|
858
858
|
if _cctally_core._truthy_env("CCTALLY_ALLOW_PROD_MIGRATION"):
|
|
859
859
|
return False
|
|
860
|
-
if not
|
|
860
|
+
if not _cctally_core._has_git_checkout_marker():
|
|
861
861
|
return False
|
|
862
862
|
try:
|
|
863
863
|
return (
|
|
@@ -1700,6 +1700,10 @@ def cmd_db_rebuild(args: argparse.Namespace) -> int:
|
|
|
1700
1700
|
)
|
|
1701
1701
|
forensics = None
|
|
1702
1702
|
incident = None
|
|
1703
|
+
import _cctally_completion
|
|
1704
|
+
# Fix round K11 (K9K10-R7): a rebuild that published but could not record
|
|
1705
|
+
# its residual obligation; reported once every lock is released.
|
|
1706
|
+
obligation_failure = None
|
|
1703
1707
|
try:
|
|
1704
1708
|
fcntl.flock(maint_fd, fcntl.LOCK_EX)
|
|
1705
1709
|
# #386: record the hold so a nested live `open_db()` does not request
|
|
@@ -1751,9 +1755,12 @@ def cmd_db_rebuild(args: argparse.Namespace) -> int:
|
|
|
1751
1755
|
forensics_path=(
|
|
1752
1756
|
str(forensics) if forensics is not None else None
|
|
1753
1757
|
),
|
|
1754
|
-
)
|
|
1758
|
+
),
|
|
1759
|
+
complete_derivation=True,
|
|
1755
1760
|
)
|
|
1756
1761
|
incident = result.quarantine_dir
|
|
1762
|
+
except _cctally_completion.ResidualObligationNotDurable as exc:
|
|
1763
|
+
obligation_failure = exc
|
|
1757
1764
|
except Exception as exc:
|
|
1758
1765
|
eprint(f"cctally: stats.db rebuild failed: {exc}")
|
|
1759
1766
|
return 3
|
|
@@ -1767,6 +1774,20 @@ def cmd_db_rebuild(args: argparse.Namespace) -> int:
|
|
|
1767
1774
|
finally:
|
|
1768
1775
|
os.close(maint_fd)
|
|
1769
1776
|
|
|
1777
|
+
if obligation_failure is not None:
|
|
1778
|
+
# The rebuilt index IS published (spec 4.7), so its completion wake
|
|
1779
|
+
# is posted as for any publication; only then is the obligation
|
|
1780
|
+
# failure reported. The inputs past the rebuilt prefix stay in the
|
|
1781
|
+
# journal for the next ingest.
|
|
1782
|
+
_cctally_completion.post_publication_wake()
|
|
1783
|
+
eprint(
|
|
1784
|
+
"cctally: stats.db was rebuilt, but the record that the inputs "
|
|
1785
|
+
"written during the rebuild still need deriving could not be "
|
|
1786
|
+
f"saved ({obligation_failure}). Those inputs stay in the journal "
|
|
1787
|
+
"and the next ingest derives them; check that the data directory "
|
|
1788
|
+
"is writable.")
|
|
1789
|
+
return 3
|
|
1790
|
+
|
|
1770
1791
|
total_rows = sum(result.rows_by_table.values())
|
|
1771
1792
|
if as_json:
|
|
1772
1793
|
payload = {
|
|
@@ -1870,6 +1891,11 @@ def cmd_db_rebuild(args: argparse.Namespace) -> int:
|
|
|
1870
1891
|
print(f" previous stats.db quarantined -> {incident}")
|
|
1871
1892
|
if forensics is not None:
|
|
1872
1893
|
print(f" forensics -> {forensics}")
|
|
1894
|
+
# #825/#832 spec 4.3 revision 2: a completed rebuild is a live index
|
|
1895
|
+
# publication; its completion wake (posted with every lock released)
|
|
1896
|
+
# dispatches the detached continuation, so this command never waits on
|
|
1897
|
+
# it. A file-only no-op when nothing is pending.
|
|
1898
|
+
_cctally_completion.post_publication_wake()
|
|
1873
1899
|
return 0
|
|
1874
1900
|
|
|
1875
1901
|
|
|
@@ -2356,6 +2382,24 @@ def _run_pending_migrations(
|
|
|
2356
2382
|
conn.commit()
|
|
2357
2383
|
|
|
2358
2384
|
|
|
2385
|
+
def _accounts_cutover_value_or_defer(what: str):
|
|
2386
|
+
"""`find_accounts_cutover_op` for an account backfill migration.
|
|
2387
|
+
|
|
2388
|
+
#825/#832 fix round K11 (K9K10-R9): a journal the lookup cannot list,
|
|
2389
|
+
size or read — including the account-bound boundary lookup it applies,
|
|
2390
|
+
`AccountBoundCutoverUnavailable` — proves neither the op nor its
|
|
2391
|
+
absence. That is transient, so the migration defers
|
|
2392
|
+
(`MigrationGateNotMet`: no error log, no banner, retried on the next
|
|
2393
|
+
open) instead of failing hard and stopping the registry."""
|
|
2394
|
+
import _cctally_journal
|
|
2395
|
+
try:
|
|
2396
|
+
return _cctally_journal.find_accounts_cutover_op()
|
|
2397
|
+
except OSError as exc:
|
|
2398
|
+
raise MigrationGateNotMet(
|
|
2399
|
+
f"the journal could not be read to find the accounts cutover op "
|
|
2400
|
+
f"({exc}); deferring the {what}") from exc
|
|
2401
|
+
|
|
2402
|
+
|
|
2359
2403
|
# === Region 3: 001 handler (was bin/cctally:11232-11344) ===
|
|
2360
2404
|
|
|
2361
2405
|
@stats_migration("001_five_hour_block_models_backfill_v1")
|
|
@@ -4743,6 +4787,125 @@ def _apply_codex_window_attribution_read_index(conn: sqlite3.Connection) -> None
|
|
|
4743
4787
|
# === Region 7b2: Eager cache-migration trigger (V4 — same-invocation 008 apply) ===
|
|
4744
4788
|
|
|
4745
4789
|
|
|
4790
|
+
# #968 spec 5.3: container evidence beside each Codex cursor row. Every
|
|
4791
|
+
# persisted key stays the LOGICAL ``.jsonl`` path; these columns record which
|
|
4792
|
+
# physical container's content the cursor accepted. Defaults describe what an
|
|
4793
|
+
# existing row already is: a plain file whose content was accepted. No evidence
|
|
4794
|
+
# column is backfilled by statting a file now, for the reason migration 046
|
|
4795
|
+
# gives for device_id/inode.
|
|
4796
|
+
_CODEX_CONTAINER_CURSOR_COLUMNS: "tuple[tuple[str, str], ...]" = (
|
|
4797
|
+
("container_kind", "TEXT NOT NULL DEFAULT 'plain'"),
|
|
4798
|
+
("container_path", "TEXT"),
|
|
4799
|
+
("container_size_bytes", "INTEGER"),
|
|
4800
|
+
("decoded_size_bytes", "INTEGER"),
|
|
4801
|
+
("content_anchor_version", "INTEGER"),
|
|
4802
|
+
("content_anchor", "BLOB"),
|
|
4803
|
+
("content_anchor_offset", "INTEGER"),
|
|
4804
|
+
("container_state", "TEXT NOT NULL DEFAULT 'accepted'"),
|
|
4805
|
+
("container_state_reason", "TEXT"),
|
|
4806
|
+
("pending_container_fingerprint", "TEXT"),
|
|
4807
|
+
)
|
|
4808
|
+
_CODEX_CONTAINER_CACHE_ONLY_COLUMNS: "tuple[tuple[str, str], ...]" = (
|
|
4809
|
+
("container_resume_strikes", "INTEGER NOT NULL DEFAULT 0"),
|
|
4810
|
+
)
|
|
4811
|
+
_CODEX_968_CACHE_TABLES = (
|
|
4812
|
+
"codex_prune_debt", "codex_barrier_membership", "codex_barrier_targets",
|
|
4813
|
+
"codex_barrier_population", "codex_barrier_certificates",
|
|
4814
|
+
"codex_barrier_repairs", "codex_barrier_entry_threads",
|
|
4815
|
+
)
|
|
4816
|
+
_CODEX_968_CONVERSATIONS_TABLES = (
|
|
4817
|
+
"codex_conversation_prune_debt", "codex_conversation_publications",
|
|
4818
|
+
"codex_conversation_published_threads",
|
|
4819
|
+
)
|
|
4820
|
+
#: The column list of `codex_conversation_threads`, shared by the two
|
|
4821
|
+
#: byte-preserving thread snapshots the publication barrier keeps: a thread
|
|
4822
|
+
#: row is copied with INSERT ... SELECT, never through JSON, so an undecodable
|
|
4823
|
+
#: cwd/git_json (#845) survives exactly as stored.
|
|
4824
|
+
_CODEX_THREAD_SNAPSHOT_COLUMNS_DDL = (
|
|
4825
|
+
" conversation_key TEXT NOT NULL PRIMARY KEY,"
|
|
4826
|
+
" source_root_key TEXT NOT NULL,"
|
|
4827
|
+
" native_thread_id TEXT NOT NULL,"
|
|
4828
|
+
" root_thread_id TEXT NOT NULL,"
|
|
4829
|
+
" parent_thread_id TEXT,"
|
|
4830
|
+
" source_path TEXT NOT NULL,"
|
|
4831
|
+
" cwd TEXT,"
|
|
4832
|
+
" git_json TEXT,"
|
|
4833
|
+
" source_kind TEXT,"
|
|
4834
|
+
" thread_source_json TEXT,"
|
|
4835
|
+
" model_provider TEXT,"
|
|
4836
|
+
" context_window INTEGER,"
|
|
4837
|
+
" first_seen_utc TEXT,"
|
|
4838
|
+
" last_seen_utc TEXT)"
|
|
4839
|
+
)
|
|
4840
|
+
_CODEX_968_CACHE_DDL = (
|
|
4841
|
+
"CREATE TABLE IF NOT EXISTS codex_prune_debt ("
|
|
4842
|
+
" source_path TEXT NOT NULL,"
|
|
4843
|
+
" source_root_key TEXT NOT NULL DEFAULT '',"
|
|
4844
|
+
" recorded_at_utc TEXT NOT NULL,"
|
|
4845
|
+
" PRIMARY KEY (source_path, source_root_key)) WITHOUT ROWID",
|
|
4846
|
+
"CREATE TABLE IF NOT EXISTS codex_barrier_membership ("
|
|
4847
|
+
" file_identity TEXT NOT NULL,"
|
|
4848
|
+
" generation_token TEXT NOT NULL,"
|
|
4849
|
+
" conversation_key TEXT NOT NULL,"
|
|
4850
|
+
" source_path TEXT NOT NULL,"
|
|
4851
|
+
" metadata_json TEXT,"
|
|
4852
|
+
" metadata_offset INTEGER,"
|
|
4853
|
+
" PRIMARY KEY (file_identity, generation_token, conversation_key))"
|
|
4854
|
+
" WITHOUT ROWID",
|
|
4855
|
+
"CREATE INDEX IF NOT EXISTS idx_codex_barrier_membership_conversation"
|
|
4856
|
+
" ON codex_barrier_membership(conversation_key)",
|
|
4857
|
+
"CREATE TABLE IF NOT EXISTS codex_barrier_targets ("
|
|
4858
|
+
" file_identity TEXT PRIMARY KEY,"
|
|
4859
|
+
" source_path TEXT NOT NULL,"
|
|
4860
|
+
" source_root_key TEXT,"
|
|
4861
|
+
" generation_token TEXT NOT NULL,"
|
|
4862
|
+
" scan_target INTEGER NOT NULL,"
|
|
4863
|
+
" complete_line_end INTEGER,"
|
|
4864
|
+
" state TEXT NOT NULL CHECK (state IN ('required','ready','suspended')),"
|
|
4865
|
+
" reason TEXT,"
|
|
4866
|
+
" accepted_generation_token TEXT,"
|
|
4867
|
+
" accepted_complete_line_end INTEGER)",
|
|
4868
|
+
"CREATE TABLE IF NOT EXISTS codex_barrier_population ("
|
|
4869
|
+
" conversation_key TEXT PRIMARY KEY,"
|
|
4870
|
+
" required_revision INTEGER NOT NULL DEFAULT 1,"
|
|
4871
|
+
" entered_at_utc TEXT NOT NULL)",
|
|
4872
|
+
"CREATE TABLE IF NOT EXISTS codex_barrier_entry_threads ("
|
|
4873
|
+
+ _CODEX_THREAD_SNAPSHOT_COLUMNS_DDL,
|
|
4874
|
+
"CREATE INDEX IF NOT EXISTS idx_codex_barrier_entry_threads_parent"
|
|
4875
|
+
" ON codex_barrier_entry_threads"
|
|
4876
|
+
"(source_root_key, native_thread_id, conversation_key)",
|
|
4877
|
+
"CREATE TABLE IF NOT EXISTS codex_barrier_certificates ("
|
|
4878
|
+
" conversation_key TEXT NOT NULL,"
|
|
4879
|
+
" revision INTEGER NOT NULL,"
|
|
4880
|
+
" certificate_json TEXT NOT NULL,"
|
|
4881
|
+
" sealed_at_utc TEXT NOT NULL,"
|
|
4882
|
+
" PRIMARY KEY (conversation_key, revision)) WITHOUT ROWID",
|
|
4883
|
+
"CREATE TABLE IF NOT EXISTS codex_barrier_repairs ("
|
|
4884
|
+
" conversation_key TEXT PRIMARY KEY,"
|
|
4885
|
+
" revision INTEGER NOT NULL,"
|
|
4886
|
+
" reason TEXT NOT NULL)",
|
|
4887
|
+
)
|
|
4888
|
+
_CODEX_968_CONVERSATIONS_DDL = (
|
|
4889
|
+
"CREATE TABLE IF NOT EXISTS codex_conversation_prune_debt ("
|
|
4890
|
+
" source_path TEXT NOT NULL,"
|
|
4891
|
+
" source_root_key TEXT NOT NULL DEFAULT '',"
|
|
4892
|
+
" recorded_at_utc TEXT NOT NULL,"
|
|
4893
|
+
" PRIMARY KEY (source_path, source_root_key)) WITHOUT ROWID",
|
|
4894
|
+
"CREATE TABLE IF NOT EXISTS codex_conversation_publications ("
|
|
4895
|
+
" conversation_key TEXT PRIMARY KEY,"
|
|
4896
|
+
" required_revision INTEGER NOT NULL DEFAULT 0,"
|
|
4897
|
+
" ready_certificate_revision INTEGER,"
|
|
4898
|
+
" published_revision INTEGER NOT NULL DEFAULT 0,"
|
|
4899
|
+
" legacy_uncertified INTEGER NOT NULL DEFAULT 0,"
|
|
4900
|
+
" updated_at_utc TEXT NOT NULL)",
|
|
4901
|
+
"CREATE TABLE IF NOT EXISTS codex_conversation_published_threads ("
|
|
4902
|
+
+ _CODEX_THREAD_SNAPSHOT_COLUMNS_DDL,
|
|
4903
|
+
"CREATE INDEX IF NOT EXISTS idx_codex_published_threads_parent"
|
|
4904
|
+
" ON codex_conversation_published_threads"
|
|
4905
|
+
"(source_root_key, native_thread_id, conversation_key)",
|
|
4906
|
+
)
|
|
4907
|
+
|
|
4908
|
+
|
|
4746
4909
|
class SchemaDeliveryObject(NamedTuple):
|
|
4747
4910
|
"""One re-derivable schema object declared by a version-gated apply.
|
|
4748
4911
|
|
|
@@ -4928,6 +5091,21 @@ CACHE_REDERIVABLE_OBJECTS: "tuple[SchemaDeliveryObject, ...]" = (
|
|
|
4928
5091
|
"column", "codex_session_files.inode",
|
|
4929
5092
|
"_apply_codex_session_file_identity",
|
|
4930
5093
|
"046_codex_source_file_identity"),
|
|
5094
|
+
*(SchemaDeliveryObject(
|
|
5095
|
+
"column", f"codex_session_files.{column}",
|
|
5096
|
+
"_apply_codex_container_cache_schema", "049_codex_rollout_containers")
|
|
5097
|
+
for column, _decl in (_CODEX_CONTAINER_CURSOR_COLUMNS
|
|
5098
|
+
+ _CODEX_CONTAINER_CACHE_ONLY_COLUMNS)),
|
|
5099
|
+
*(SchemaDeliveryObject(
|
|
5100
|
+
"table", table, "_apply_codex_container_cache_schema",
|
|
5101
|
+
"049_codex_rollout_containers")
|
|
5102
|
+
for table in _CODEX_968_CACHE_TABLES),
|
|
5103
|
+
SchemaDeliveryObject(
|
|
5104
|
+
"index", "idx_codex_barrier_membership_conversation",
|
|
5105
|
+
"_apply_codex_container_cache_schema", "049_codex_rollout_containers"),
|
|
5106
|
+
SchemaDeliveryObject(
|
|
5107
|
+
"index", "idx_codex_barrier_entry_threads_parent",
|
|
5108
|
+
"_apply_codex_container_cache_schema", "049_codex_rollout_containers"),
|
|
4931
5109
|
SchemaDeliveryObject(
|
|
4932
5110
|
"index", "idx_codex_accounting_change_mutation",
|
|
4933
5111
|
"_apply_codex_accounting_change_ledger",
|
|
@@ -5034,6 +5212,79 @@ CACHE_REDERIVABLE_OBJECTS: "tuple[SchemaDeliveryObject, ...]" = (
|
|
|
5034
5212
|
)
|
|
5035
5213
|
|
|
5036
5214
|
|
|
5215
|
+
def _apply_codex_container_cache_schema(conn: sqlite3.Connection) -> None:
|
|
5216
|
+
"""#968 spec 5.3: container columns and the cache-side maintenance state.
|
|
5217
|
+
|
|
5218
|
+
Shared by ``_apply_cache_schema`` and migration 049 so both shapes
|
|
5219
|
+
converge. Columns go through ``add_column_if_missing``; the cursor table is
|
|
5220
|
+
checked first because that helper raises on a missing table, and a table
|
|
5221
|
+
created later is created by the schema apply that calls this helper anyway.
|
|
5222
|
+
"""
|
|
5223
|
+
# Literal column names keep the schema-delivery AST guard complete.
|
|
5224
|
+
if _table_exists(conn, "codex_session_files"):
|
|
5225
|
+
add_column_if_missing(
|
|
5226
|
+
conn, "codex_session_files", "container_kind",
|
|
5227
|
+
"TEXT NOT NULL DEFAULT 'plain'")
|
|
5228
|
+
add_column_if_missing(
|
|
5229
|
+
conn, "codex_session_files", "container_path", "TEXT")
|
|
5230
|
+
add_column_if_missing(
|
|
5231
|
+
conn, "codex_session_files", "container_size_bytes", "INTEGER")
|
|
5232
|
+
add_column_if_missing(
|
|
5233
|
+
conn, "codex_session_files", "decoded_size_bytes", "INTEGER")
|
|
5234
|
+
add_column_if_missing(
|
|
5235
|
+
conn, "codex_session_files", "content_anchor_version", "INTEGER")
|
|
5236
|
+
add_column_if_missing(
|
|
5237
|
+
conn, "codex_session_files", "content_anchor", "BLOB")
|
|
5238
|
+
add_column_if_missing(
|
|
5239
|
+
conn, "codex_session_files", "content_anchor_offset", "INTEGER")
|
|
5240
|
+
add_column_if_missing(
|
|
5241
|
+
conn, "codex_session_files", "container_state",
|
|
5242
|
+
"TEXT NOT NULL DEFAULT 'accepted'")
|
|
5243
|
+
add_column_if_missing(
|
|
5244
|
+
conn, "codex_session_files", "container_state_reason", "TEXT")
|
|
5245
|
+
add_column_if_missing(
|
|
5246
|
+
conn, "codex_session_files", "pending_container_fingerprint", "TEXT")
|
|
5247
|
+
add_column_if_missing(
|
|
5248
|
+
conn, "codex_session_files", "container_resume_strikes",
|
|
5249
|
+
"INTEGER NOT NULL DEFAULT 0")
|
|
5250
|
+
for statement in _CODEX_968_CACHE_DDL:
|
|
5251
|
+
conn.execute(statement)
|
|
5252
|
+
|
|
5253
|
+
|
|
5254
|
+
def _apply_codex_conversation_container_schema(conn: sqlite3.Connection) -> None:
|
|
5255
|
+
"""#968 spec 5.3: the conversations twin of the container evidence.
|
|
5256
|
+
|
|
5257
|
+
Shared by ``_apply_conversations_schema`` (fast path and full path) and
|
|
5258
|
+
conversations migration 012.
|
|
5259
|
+
"""
|
|
5260
|
+
# Keep these in the same order as _CODEX_CONTAINER_CURSOR_COLUMNS.
|
|
5261
|
+
if _table_exists(conn, "codex_conversation_source_files"):
|
|
5262
|
+
add_column_if_missing(
|
|
5263
|
+
conn, "codex_conversation_source_files", "container_kind",
|
|
5264
|
+
"TEXT NOT NULL DEFAULT 'plain'")
|
|
5265
|
+
add_column_if_missing(
|
|
5266
|
+
conn, "codex_conversation_source_files", "container_path", "TEXT")
|
|
5267
|
+
add_column_if_missing(
|
|
5268
|
+
conn, "codex_conversation_source_files", "container_size_bytes", "INTEGER")
|
|
5269
|
+
add_column_if_missing(
|
|
5270
|
+
conn, "codex_conversation_source_files", "decoded_size_bytes", "INTEGER")
|
|
5271
|
+
add_column_if_missing(
|
|
5272
|
+
conn, "codex_conversation_source_files", "content_anchor_version", "INTEGER")
|
|
5273
|
+
add_column_if_missing(
|
|
5274
|
+
conn, "codex_conversation_source_files", "content_anchor", "BLOB")
|
|
5275
|
+
add_column_if_missing(
|
|
5276
|
+
conn, "codex_conversation_source_files", "content_anchor_offset", "INTEGER")
|
|
5277
|
+
add_column_if_missing(
|
|
5278
|
+
conn, "codex_conversation_source_files", "container_state",
|
|
5279
|
+
"TEXT NOT NULL DEFAULT 'accepted'")
|
|
5280
|
+
add_column_if_missing(
|
|
5281
|
+
conn, "codex_conversation_source_files", "container_state_reason", "TEXT")
|
|
5282
|
+
add_column_if_missing(
|
|
5283
|
+
conn, "codex_conversation_source_files", "pending_container_fingerprint", "TEXT")
|
|
5284
|
+
for statement in _CODEX_968_CONVERSATIONS_DDL:
|
|
5285
|
+
conn.execute(statement)
|
|
5286
|
+
|
|
5287
|
+
|
|
5037
5288
|
def _apply_codex_session_file_identity(conn: sqlite3.Connection) -> None:
|
|
5038
5289
|
"""Deliver the Codex accounting cursor's file identity (#769 S6, #716 A).
|
|
5039
5290
|
|
|
@@ -5605,6 +5856,7 @@ def _apply_cache_schema(conn: sqlite3.Connection) -> None:
|
|
|
5605
5856
|
conn, "codex_session_files", "ingest_complete",
|
|
5606
5857
|
"INTEGER NOT NULL DEFAULT 1")
|
|
5607
5858
|
_apply_codex_session_file_identity(conn)
|
|
5859
|
+
_apply_codex_container_cache_schema(conn)
|
|
5608
5860
|
add_column_if_missing(conn, "quota_window_snapshots", "account_key", "TEXT")
|
|
5609
5861
|
# #195: the cache-write TTL split. NULLable with NO DEFAULT — NULL is the
|
|
5610
5862
|
# "split unknown" sentinel a pre-#195 row produces for free, and a real
|
|
@@ -5898,6 +6150,7 @@ def _apply_conversations_schema(conn: sqlite3.Connection) -> None:
|
|
|
5898
6150
|
_apply_conversation_generation_schema(conn)
|
|
5899
6151
|
_apply_codex_conversation_source_identity(conn)
|
|
5900
6152
|
_apply_conversation_latest_meta_indexes(conn)
|
|
6153
|
+
_apply_codex_conversation_container_schema(conn)
|
|
5901
6154
|
return
|
|
5902
6155
|
|
|
5903
6156
|
_apply_cache_schema(conn)
|
|
@@ -5914,6 +6167,14 @@ def _apply_conversations_schema(conn: sqlite3.Connection) -> None:
|
|
|
5914
6167
|
-- it), so this only clears the now-orphan ledger and keeps
|
|
5915
6168
|
-- conversations.db transcripts-only.
|
|
5916
6169
|
DROP TABLE IF EXISTS quota_window_change_log;
|
|
6170
|
+
-- #968: only the conversation twins belong in this store.
|
|
6171
|
+
DROP TABLE IF EXISTS codex_prune_debt;
|
|
6172
|
+
DROP TABLE IF EXISTS codex_barrier_membership;
|
|
6173
|
+
DROP TABLE IF EXISTS codex_barrier_targets;
|
|
6174
|
+
DROP TABLE IF EXISTS codex_barrier_population;
|
|
6175
|
+
DROP TABLE IF EXISTS codex_barrier_certificates;
|
|
6176
|
+
DROP TABLE IF EXISTS codex_barrier_repairs;
|
|
6177
|
+
DROP TABLE IF EXISTS codex_barrier_entry_threads;
|
|
5917
6178
|
DROP TABLE IF EXISTS codex_accounting_change_log;
|
|
5918
6179
|
DROP TABLE IF EXISTS codex_conversation_threads;
|
|
5919
6180
|
DROP TABLE IF EXISTS codex_source_roots;
|
|
@@ -5987,6 +6248,7 @@ def _apply_conversations_schema(conn: sqlite3.Connection) -> None:
|
|
|
5987
6248
|
_apply_conversation_generation_schema(conn)
|
|
5988
6249
|
_apply_codex_conversation_source_identity(conn)
|
|
5989
6250
|
_apply_conversation_latest_meta_indexes(conn)
|
|
6251
|
+
_apply_codex_conversation_container_schema(conn)
|
|
5990
6252
|
|
|
5991
6253
|
|
|
5992
6254
|
def _ensure_codex_session_meta_provenance_index(
|
|
@@ -6364,6 +6626,19 @@ CONVERSATIONS_REDERIVABLE_OBJECTS: "tuple[SchemaDeliveryObject, ...]" = (
|
|
|
6364
6626
|
"index", "idx_conv_session_latest_git_branch",
|
|
6365
6627
|
"_apply_conversation_latest_meta_indexes",
|
|
6366
6628
|
"011_conversation_latest_meta_indexes"),
|
|
6629
|
+
*(SchemaDeliveryObject(
|
|
6630
|
+
"column", f"codex_conversation_source_files.{column}",
|
|
6631
|
+
"_apply_codex_conversation_container_schema",
|
|
6632
|
+
"012_codex_conversation_containers")
|
|
6633
|
+
for column, _decl in _CODEX_CONTAINER_CURSOR_COLUMNS),
|
|
6634
|
+
*(SchemaDeliveryObject(
|
|
6635
|
+
"table", table, "_apply_codex_conversation_container_schema",
|
|
6636
|
+
"012_codex_conversation_containers")
|
|
6637
|
+
for table in _CODEX_968_CONVERSATIONS_TABLES),
|
|
6638
|
+
SchemaDeliveryObject(
|
|
6639
|
+
"index", "idx_codex_published_threads_parent",
|
|
6640
|
+
"_apply_codex_conversation_container_schema",
|
|
6641
|
+
"012_codex_conversation_containers"),
|
|
6367
6642
|
SchemaDeliveryObject(
|
|
6368
6643
|
"table", "claude_conversation_account_stamp_gaps",
|
|
6369
6644
|
"_apply_conversation_generation_schema",
|
|
@@ -6573,7 +6848,8 @@ def _conv_005_conversation_account_dimension(conn: sqlite3.Connection) -> None:
|
|
|
6573
6848
|
held += _acquire_conversations_db_codex_provider_flock(
|
|
6574
6849
|
conn, migration="conversations 005 account dimension"
|
|
6575
6850
|
)
|
|
6576
|
-
claude_key =
|
|
6851
|
+
claude_key = _accounts_cutover_value_or_defer(
|
|
6852
|
+
"Claude conversation backfill")
|
|
6577
6853
|
if claude_key is None:
|
|
6578
6854
|
pending = conn.execute(
|
|
6579
6855
|
"SELECT 1 FROM conversation_messages "
|
|
@@ -7030,6 +7306,20 @@ def _conv_011_conversation_latest_meta_indexes(
|
|
|
7030
7306
|
_release_cache_db_writer_flocks(held)
|
|
7031
7307
|
|
|
7032
7308
|
|
|
7309
|
+
@conversations_migration("012_codex_conversation_containers")
|
|
7310
|
+
def _conv_012_codex_conversation_containers(conn: sqlite3.Connection) -> None:
|
|
7311
|
+
"""#968 spec 5.3: the conversations twin of cache migration 049.
|
|
7312
|
+
|
|
7313
|
+
Delivers the transcript cursor's container columns, the conversation-side
|
|
7314
|
+
prune debt and the last-good publication records to an already-current
|
|
7315
|
+
store. Same version-gate reason and the same no-backfill rule as 049; no
|
|
7316
|
+
provider flock for the same reason as 010. Re-running is a no-op. NO
|
|
7317
|
+
self-stamp — the dispatcher central-stamps on a clean return (#140).
|
|
7318
|
+
"""
|
|
7319
|
+
_apply_codex_conversation_container_schema(conn)
|
|
7320
|
+
conn.commit()
|
|
7321
|
+
|
|
7322
|
+
|
|
7033
7323
|
# #177 S6: the consolidated multi-column external-content FTS5 table that
|
|
7034
7324
|
# replaces the old conversation_fts(text) + conversation_fts_aux(search_aux)
|
|
7035
7325
|
# pair. The three column names MUST match the conversation_messages columns BY
|
|
@@ -9012,7 +9302,7 @@ def _029_backfill_claude_account(conn: sqlite3.Connection) -> None:
|
|
|
9012
9302
|
import _cctally_journal as _jr
|
|
9013
9303
|
import _lib_accounts as _acc
|
|
9014
9304
|
|
|
9015
|
-
op_value =
|
|
9305
|
+
op_value = _accounts_cutover_value_or_defer("Claude account backfill")
|
|
9016
9306
|
if op_value is None:
|
|
9017
9307
|
# Cutover hasn't appended its op yet. Defer only if there is legacy Claude
|
|
9018
9308
|
# history to attribute; a fresh install (no such rows) proceeds as a no-op.
|
|
@@ -9918,6 +10208,25 @@ def _048_codex_quota_physical_group_order(conn: sqlite3.Connection) -> None:
|
|
|
9918
10208
|
conn.commit()
|
|
9919
10209
|
|
|
9920
10210
|
|
|
10211
|
+
@cache_migration("049_codex_rollout_containers")
|
|
10212
|
+
def _049_codex_rollout_containers(conn: sqlite3.Connection) -> None:
|
|
10213
|
+
"""#968 spec 5.3: deliver container evidence and maintenance state.
|
|
10214
|
+
|
|
10215
|
+
The schema apply that declares these objects is version-gated, so only a
|
|
10216
|
+
registered migration reaches an already-current store (the reason 046
|
|
10217
|
+
gives). Existing cursor rows read as plain and accepted, which is exactly
|
|
10218
|
+
what they are; no container evidence is backfilled from a stat taken now.
|
|
10219
|
+
The seven tables start empty: prune debt, verification state and the
|
|
10220
|
+
publication barrier are written only by the runtime paths that own them.
|
|
10221
|
+
|
|
10222
|
+
Takes no provider flock: ADD COLUMN with a constant default and CREATE of
|
|
10223
|
+
an empty table rewrite no rows. Re-running is a no-op. NO self-stamp — the
|
|
10224
|
+
dispatcher central-stamps on a clean return (#140).
|
|
10225
|
+
"""
|
|
10226
|
+
_apply_codex_container_cache_schema(conn)
|
|
10227
|
+
conn.commit()
|
|
10228
|
+
|
|
10229
|
+
|
|
9921
10230
|
# === Region 7d: Stats migration 008_recompute_weekly_cost_snapshots_dedup_fix ===
|
|
9922
10231
|
|
|
9923
10232
|
@stats_migration("008_recompute_weekly_cost_snapshots_dedup_fix")
|
|
@@ -11889,6 +12198,8 @@ def _db_family_open_pids(path: pathlib.Path) -> "set[int] | None":
|
|
|
11889
12198
|
)
|
|
11890
12199
|
except OSError:
|
|
11891
12200
|
return None
|
|
12201
|
+
if result.returncode not in (0, 1):
|
|
12202
|
+
return None
|
|
11892
12203
|
return {
|
|
11893
12204
|
int(line[1:])
|
|
11894
12205
|
for line in result.stdout.splitlines()
|