cctally 1.114.0 → 1.116.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.
Files changed (86) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/bin/_cctally_account.py +32 -8
  3. package/bin/_cctally_cache.py +4491 -295
  4. package/bin/_cctally_completion.py +1355 -0
  5. package/bin/_cctally_config.py +37 -0
  6. package/bin/_cctally_core.py +282 -9
  7. package/bin/_cctally_dashboard.py +8 -11
  8. package/bin/_cctally_dashboard_conversation.py +292 -23
  9. package/bin/_cctally_dashboard_envelope.py +42 -35
  10. package/bin/_cctally_dashboard_sources.py +18 -9
  11. package/bin/_cctally_db.py +401 -9
  12. package/bin/_cctally_doctor.py +469 -27
  13. package/bin/_cctally_five_hour.py +166 -1049
  14. package/bin/_cctally_forecast.py +81 -9
  15. package/bin/_cctally_journal.py +5076 -177
  16. package/bin/_cctally_journal_repair.py +110 -19
  17. package/bin/_cctally_oauth_samples.py +280 -0
  18. package/bin/_cctally_parser.py +47 -0
  19. package/bin/_cctally_project.py +8 -2
  20. package/bin/_cctally_quarantine.py +662 -0
  21. package/bin/_cctally_quota.py +96 -12
  22. package/bin/_cctally_quota_model.py +391 -83
  23. package/bin/_cctally_record.py +1088 -269
  24. package/bin/_cctally_rederive.py +854 -192
  25. package/bin/_cctally_refresh.py +113 -13
  26. package/bin/_cctally_source_analytics.py +23 -2
  27. package/bin/_cctally_statusline.py +7062 -505
  28. package/bin/_cctally_store.py +1244 -91
  29. package/bin/_cctally_transcript.py +8 -7
  30. package/bin/_cctally_tui.py +119 -60
  31. package/bin/_cctally_update.py +25 -0
  32. package/bin/_lib_account_binding.py +152 -0
  33. package/bin/_lib_account_protocol.py +569 -0
  34. package/bin/_lib_aggregators.py +4 -0
  35. package/bin/_lib_cache_coverage.py +13 -1
  36. package/bin/_lib_cache_writer_lock.py +19 -3
  37. package/bin/_lib_clone_copy.py +177 -0
  38. package/bin/_lib_codex_conversation_query.py +133 -19
  39. package/bin/_lib_codex_conversation_watch.py +59 -14
  40. package/bin/_lib_codex_drain_admission.py +214 -0
  41. package/bin/_lib_codex_metadata.py +18 -1
  42. package/bin/_lib_codex_publication_barrier.py +277 -0
  43. package/bin/_lib_codex_rollout_container.py +502 -0
  44. package/bin/_lib_conversation_dispatch.py +3 -0
  45. package/bin/_lib_conversation_query.py +11 -3
  46. package/bin/_lib_dashboard_sources.py +57 -15
  47. package/bin/_lib_detector_access_registry.py +4038 -0
  48. package/bin/_lib_doctor.py +753 -1
  49. package/bin/_lib_five_hour.py +126 -0
  50. package/bin/_lib_ingest_frontier.py +414 -20
  51. package/bin/_lib_journal.py +261 -10
  52. package/bin/_lib_journal_router.py +166 -48
  53. package/bin/_lib_jsonl.py +5 -4
  54. package/bin/_lib_legacy_spool.py +126 -0
  55. package/bin/_lib_legacy_writer_evidence.py +136 -0
  56. package/bin/_lib_meter_rate_change.py +10 -0
  57. package/bin/_lib_oauth_samples.py +238 -0
  58. package/bin/_lib_outcome_quarantine.py +690 -0
  59. package/bin/_lib_pricing.py +7 -2
  60. package/bin/_lib_pricing_check.py +0 -12
  61. package/bin/_lib_provider_activity.py +770 -0
  62. package/bin/_lib_quarantine.py +155 -0
  63. package/bin/_lib_quota_calibration.py +53 -8
  64. package/bin/_lib_quota_copy.py +2 -1
  65. package/bin/_lib_quota_model.py +384 -52
  66. package/bin/_lib_rebuild_progress.py +146 -0
  67. package/bin/_lib_record.py +22 -0
  68. package/bin/_lib_rederive.py +393 -8
  69. package/bin/_lib_segment_summary.py +35 -2
  70. package/bin/_lib_snapshot_cache.py +43 -1
  71. package/bin/_lib_stale_proof.py +708 -0
  72. package/bin/_lib_statusline_candidates.py +356 -27
  73. package/bin/_lib_worker_launches.py +315 -0
  74. package/bin/cctally +29 -12
  75. package/dashboard/static/assets/{ConversationsView-Bc8LCOAZ.js → ConversationsView-Dflb_myA.js} +1 -1
  76. package/dashboard/static/assets/DoctorModal-DFX5CfIs.js +1 -0
  77. package/dashboard/static/assets/{ModalRoot-DLPgyAM_.js → ModalRoot-HlxoagIa.js} +1 -1
  78. package/dashboard/static/assets/{ProjectsDrillPanel-z53a4v4M.js → ProjectsDrillPanel-C3PJy1xx.js} +1 -1
  79. package/dashboard/static/assets/{SourceDetailModal-Bw0T8W-W.js → SourceDetailModal-BczsWKhD.js} +1 -1
  80. package/dashboard/static/assets/{UpdateModal-D5AKx4Yw.js → UpdateModal-Os-Lo5cs.js} +1 -1
  81. package/dashboard/static/assets/{index-BXDOzSng.js → index-CYZ3NLqF.js} +3 -3
  82. package/dashboard/static/assets/{outlineNavigation-JObY7cUY.js → outlineNavigation-k-zIif6W.js} +1 -1
  83. package/dashboard/static/dashboard.html +1 -1
  84. package/package.json +20 -2
  85. package/bin/_lib_merge_gate.py +0 -725
  86. package/dashboard/static/assets/DoctorModal-D--qVBwe.js +0 -1
@@ -529,6 +529,43 @@ def resolve_retention_days(config: dict) -> int:
529
529
  return default
530
530
 
531
531
 
532
+ #: The test-only switch (#965). Honored only at exactly "1", only when no
533
+ #: retention setting exists, and only for code running from a git checkout.
534
+ CONVERSATION_RETENTION_TEST_SWITCH = "CCTALLY_TEST_CONVERSATION_RETENTION_DEFAULT_OFF"
535
+
536
+
537
+ def _conversation_retention_setting_absent(config) -> bool:
538
+ """True when no ``conversation.retention_days`` setting exists (#965 5.2).
539
+
540
+ Absent: no ``conversation`` key, or a ``conversation`` dictionary without
541
+ ``retention_days``. A present key of any value (``null`` and malformed
542
+ values included), a non-dictionary ``conversation`` and a non-dictionary
543
+ config are not absent: they keep ``resolve_retention_days``'s meaning."""
544
+ if not isinstance(config, dict):
545
+ return False
546
+ if "conversation" not in config:
547
+ return True
548
+ block = config["conversation"]
549
+ return isinstance(block, dict) and "retention_days" not in block
550
+
551
+
552
+ def resolve_prune_retention_days(config: dict) -> int:
553
+ """Operational transcript-retention window of the three prune paths (#965).
554
+
555
+ ``resolve_retention_days`` stays the reporting resolver (``config get``,
556
+ the settings manifest, docs). This one differs in exactly one case: with
557
+ no retention setting, the test switch at exactly ``"1"`` and a ``.git``
558
+ entry at the running code's root, it returns ``0`` (keep forever). The
559
+ switch and the marker are read on every call; nothing is cached, written
560
+ or logged. docs/fixtures-gotchas.md, "Conversation retention is off in
561
+ test environments (#965)"."""
562
+ if (_conversation_retention_setting_absent(config)
563
+ and os.environ.get(CONVERSATION_RETENTION_TEST_SWITCH) == "1"
564
+ and _cctally_core._has_git_checkout_marker()):
565
+ return 0
566
+ return resolve_retention_days(config)
567
+
568
+
532
569
  def _config_codex_leaf_value(config: dict, key: str) -> object:
533
570
  """Read one nested Codex budget leaf with its public default.
534
571
 
@@ -76,6 +76,9 @@ def _init_paths_from_env() -> None:
76
76
  global STATUSLINE_CANDIDATE_DIR, STATUSLINE_SELECTED_PATH
77
77
  global STATUSLINE_TRANSPORT_MARKER_PATH
78
78
  global STATUSLINE_AUTHORITATIVE_7D_PATH, STATUSLINE_AUTHORITATIVE_5H_PATH
79
+ global STATUSLINE_ACCOUNT_BOUND_DIR, LEGACY_SPOOL_BASELINE_PATH
80
+ global ACCOUNT_BOUND_CUTOVER_CACHE_PATH, LEGACY_WRITER_EVIDENCE_PATH
81
+ global STATUSLINE_ACCOUNT_BOUND_V1_DIR, ACCOUNT_BOUND_REVISION_CACHE_PATH
79
82
  global OAUTH_BACKOFF_MARKER_PATH, OAUTH_BACKOFF_COUNT_PATH
80
83
  global UPDATE_STATE_PATH, UPDATE_SUPPRESS_PATH
81
84
  global UPDATE_LOCK_PATH, UPDATE_LOG_PATH, UPDATE_LOG_ROTATED_PATH
@@ -184,6 +187,33 @@ def _init_paths_from_env() -> None:
184
187
  STATUSLINE_TRANSPORT_MARKER_PATH = APP_DIR / "statusline-transport.last"
185
188
  STATUSLINE_AUTHORITATIVE_7D_PATH = APP_DIR / "statusline-authoritative-7d.json"
186
189
  STATUSLINE_AUTHORITATIVE_5H_PATH = APP_DIR / "statusline-authoritative-5h.json"
190
+ # #825/#832 account-bound protocol (spec section 7). Every current
191
+ # coordination file lives under its own sibling root; the legacy paths
192
+ # above are never read as authority after the cutover, only compared with
193
+ # the journal-anchored legacy spool baseline. The coordinate cache only
194
+ # accelerates locating the cutover op and never confers authority; the
195
+ # legacy-writer evidence sidecar is diagnostic only (spec section 8).
196
+ #
197
+ # #797/#823/#840 (spec 4.6 B4): revision 2 of the fence moves every
198
+ # current coordination file — candidates, intake, evaluations, control,
199
+ # pending and retry state, acknowledgements, tombstones, frozen plans,
200
+ # locks, transition and recovery metadata, and the protocol files below
201
+ # — to the v2 root. No v1 state is imported. The v1 root is retired: its
202
+ # `accounts/` tree and `transport.last` join the legacy spool inventory
203
+ # (`_cctally_journal._legacy_spool_paths`), and this binary never writes,
204
+ # prunes or deletes anything under it.
205
+ STATUSLINE_ACCOUNT_BOUND_V1_DIR = APP_DIR / "statusline-account-bound-v1"
206
+ STATUSLINE_ACCOUNT_BOUND_DIR = APP_DIR / "statusline-account-bound-v2"
207
+ LEGACY_SPOOL_BASELINE_PATH = (
208
+ STATUSLINE_ACCOUNT_BOUND_DIR / "legacy-spool-baseline.json")
209
+ ACCOUNT_BOUND_CUTOVER_CACHE_PATH = (
210
+ STATUSLINE_ACCOUNT_BOUND_DIR / "cutover-coordinate.json")
211
+ #: The revision boundary's coordinate cache; like the cutover's, it only
212
+ #: accelerates the lookup and never confers authority.
213
+ ACCOUNT_BOUND_REVISION_CACHE_PATH = (
214
+ STATUSLINE_ACCOUNT_BOUND_DIR / "revision-coordinate.json")
215
+ LEGACY_WRITER_EVIDENCE_PATH = (
216
+ STATUSLINE_ACCOUNT_BOUND_DIR / "legacy-writer-evidence.json")
187
217
  OAUTH_BACKOFF_MARKER_PATH = APP_DIR / "oauth-backoff.until"
188
218
  # Consecutive-429 counter (text int) driving the headerless exponential
189
219
  # backoff (base * 2**count). Separate from the deadline marker so the
@@ -261,6 +291,18 @@ def _repo_root() -> pathlib.Path:
261
291
  return pathlib.Path(__file__).resolve().parent.parent
262
292
 
263
293
 
294
+ def _has_git_checkout_marker() -> bool:
295
+ """True iff the running code has a ``.git`` entry at its root (#965 5.1).
296
+
297
+ A main checkout has a ``.git`` directory and a linked worktree a ``.git``
298
+ file; both count. Eligibility follows the code's resolved location
299
+ (``_repo_root()``), never the current directory, and is evaluated on every
300
+ call. No suppressor (``CCTALLY_DISABLE_DEV_AUTODETECT``,
301
+ ``CCTALLY_ALLOW_PROD_MIGRATION``) affects it. npm and Homebrew installs
302
+ ship without ``.git``, so they never read True."""
303
+ return (_repo_root() / ".git").exists()
304
+
305
+
264
306
  def _is_dev_checkout() -> bool:
265
307
  """True iff running from a git checkout (a ``.git`` entry at the repo
266
308
  root — a directory for a main checkout, a file for a worktree) AND the
@@ -272,7 +314,7 @@ def _is_dev_checkout() -> bool:
272
314
  install copies ship without ``.git`` so they never read True."""
273
315
  if _truthy_env("CCTALLY_DISABLE_DEV_AUTODETECT"):
274
316
  return False
275
- return (_repo_root() / ".git").exists()
317
+ return _has_git_checkout_marker()
276
318
 
277
319
 
278
320
  def is_preview_channel() -> bool:
@@ -487,9 +529,35 @@ _init_paths_from_env()
487
529
  # reason this is a bump and not a migration is unchanged: the registry is
488
530
  # frozen at 13, and an epoch-current open returns before any schema work, so a
489
531
  # schema helper alone would never run on an upgraded install.
490
- STATS_INDEX_EPOCH = 1017
532
+ # 1017 -> 1018 (#825/#832 spec section 6): the same journal now has a different
533
+ # authoritative interpretation — the account-bound admission predicate excludes
534
+ # unstamped post-cutover records before any derivation — so an index
535
+ # materialized under the old interpretation is not current even though its
536
+ # schema is usable. The epoch also carries the disposable
537
+ # `journal_admission_summary` table, whose presence is the protocol marker
538
+ # `_cctally_store.validated_read_snapshot` checks inside each decision
539
+ # scope's own snapshot. An epoch transition, not a numbered migration: the
540
+ # registry stays frozen at 13.
541
+ # 1018 -> 1019 (#797/#823/#840 spec 4.6 B6): revision 2 of the fence changes
542
+ # the authoritative interpretation of retained journal records again — after
543
+ # the `account_bound_revision` boundary a Claude-scoped record without
544
+ # `wpr == 2` is excluded — so an index, a derivation-completeness proof or a
545
+ # selector continuation produced under revision 1 is not current. The #825
546
+ # section 6 precedent; still no numbered migration and no accounting column.
547
+ # The same epoch carries the provider-sample index state of stage 1 (#797
548
+ # Task 2, spec 4.3 D8 and D11), so 1019 is introduced once: the
549
+ # `claude_oauth_samples` projection of raw api observations carrying
550
+ # `oauth_request_started_at`, the `claude_weekly_observation_decisions`
551
+ # projection of reviewed weekly decision ops that resolves a sample's weekly
552
+ # eligibility at read time, and four NULLABLE identity columns on
553
+ # `five_hour_credit_confirmation_state` (`baseline_observation_id`,
554
+ # `baseline_generation`, `pending_generation`, `pending_position`) that the
555
+ # five-hour credit generation (Task 5) writes. Both projections are rebuilt
556
+ # from the journal; the confirmation state stays disposable.
557
+ STATS_INDEX_EPOCH = 1019
491
558
  LEGACY_STATS_HEAD = 13
492
559
 
560
+
493
561
  #: #496 S1 F1. A NEW branch, for a state that cannot occur before the
494
562
  #: publication transaction exists: a replacement index was published and then
495
563
  #: failed validation on a fresh connection. The existing corrupt-stats text
@@ -2654,20 +2722,86 @@ def open_db(*, _target_path=None) -> sqlite3.Connection:
2654
2722
  # above: a rebuild legitimately loses it, and the cold-start
2655
2723
  # consequence is stated at `_read_five_hour_source_state` in
2656
2724
  # `bin/_cctally_record.py`.
2725
+ #
2726
+ # #797/#823/#840 epoch 1019 (spec 4.9 D11): the four identity columns
2727
+ # the five-hour credit generation writes, all NULL until it does.
2728
+ # `baseline_observation_id` names the observation that set the
2729
+ # baseline; `baseline_generation` / `pending_generation` are the
2730
+ # effective five-hour credit generation the baseline and the armed
2731
+ # descent were observed under, encoded `"<event_id>@<revision>"` with
2732
+ # the no-credit sentinel `"none"`; `pending_position` is the arming
2733
+ # observation's logical position. Columns of the CREATE, not an
2734
+ # `add_column_if_missing`: an epoch-current open returns before any
2735
+ # schema work, so only the epoch's fresh CREATE can carry them.
2657
2736
  conn.execute(
2658
2737
  """
2659
2738
  CREATE TABLE IF NOT EXISTS five_hour_credit_confirmation_state (
2660
- account_key TEXT NOT NULL,
2661
- five_hour_window_key INTEGER NOT NULL,
2662
- source TEXT NOT NULL,
2663
- baseline_pct REAL NOT NULL,
2664
- pending_low_pct REAL,
2665
- pending_at_utc TEXT,
2666
- pending_observation_id TEXT,
2739
+ account_key TEXT NOT NULL,
2740
+ five_hour_window_key INTEGER NOT NULL,
2741
+ source TEXT NOT NULL,
2742
+ baseline_pct REAL NOT NULL,
2743
+ pending_low_pct REAL,
2744
+ pending_at_utc TEXT,
2745
+ pending_observation_id TEXT,
2746
+ baseline_observation_id TEXT,
2747
+ baseline_generation TEXT,
2748
+ pending_generation TEXT,
2749
+ pending_position TEXT,
2667
2750
  PRIMARY KEY (account_key, five_hour_window_key, source)
2668
2751
  )
2669
2752
  """
2670
2753
  )
2754
+ # #797/#823/#840 epoch 1019 (spec 4.3 D8, D9): provider samples. One
2755
+ # row per ADMITTED Claude api observation carrying a valid
2756
+ # `oauth_request_started_at` (`requested_at_utc`, canonical
2757
+ # full-precision UTC, so text order is time order), projected from
2758
+ # the RAW record by `_cctally_oauth_samples.project_sample` on every
2759
+ # derivation path, independently of the snapshot fold, the five-hour
2760
+ # adjustment and credit suppression or retirement. Values are the raw
2761
+ # readings, never the MAX-clamped stored ones; `weekly_resets_at_utc`
2762
+ # is nearest-hour normalized and `five_hour_window_key` is the
2763
+ # authoritative canonical key. Journal truth (rebuilt, never pruned),
2764
+ # indexed by account, axis window and `q` for the exact-window read.
2765
+ conn.execute(
2766
+ """
2767
+ CREATE TABLE IF NOT EXISTS claude_oauth_samples (
2768
+ account_key TEXT NOT NULL,
2769
+ observation_id TEXT NOT NULL,
2770
+ requested_at_utc TEXT NOT NULL,
2771
+ captured_at_utc TEXT NOT NULL,
2772
+ weekly_percent REAL,
2773
+ weekly_resets_at_utc TEXT,
2774
+ five_hour_percent REAL,
2775
+ five_hour_resets_at_utc TEXT,
2776
+ five_hour_window_key INTEGER,
2777
+ PRIMARY KEY (account_key, observation_id)
2778
+ )
2779
+ """
2780
+ )
2781
+ conn.execute(
2782
+ "CREATE INDEX IF NOT EXISTS idx_claude_oauth_samples_weekly "
2783
+ "ON claude_oauth_samples "
2784
+ "(account_key, weekly_resets_at_utc, requested_at_utc)"
2785
+ )
2786
+ conn.execute(
2787
+ "CREATE INDEX IF NOT EXISTS idx_claude_oauth_samples_five_hour "
2788
+ "ON claude_oauth_samples "
2789
+ "(account_key, five_hour_window_key, requested_at_utc)"
2790
+ )
2791
+ # The effective reviewed weekly decision per observation (latest
2792
+ # `claude_weekly_observation_decision[_v2]` op wins, journal order),
2793
+ # projected by that op's fold applier so a sample's weekly
2794
+ # eligibility is resolved at read time: `hold` excludes the weekly
2795
+ # sample only, a later `accept` restores it.
2796
+ conn.execute(
2797
+ """
2798
+ CREATE TABLE IF NOT EXISTS claude_weekly_observation_decisions (
2799
+ observation_id TEXT PRIMARY KEY,
2800
+ disposition TEXT NOT NULL,
2801
+ decision_op_id TEXT NOT NULL
2802
+ )
2803
+ """
2804
+ )
2671
2805
  _backfill_week_reset_events(conn)
2672
2806
 
2673
2807
  # ── five_hour_reset_events (Anthropic-issued in-place 5h credits) ──
@@ -3310,6 +3444,106 @@ def open_db(*, _target_path=None) -> sqlite3.Connection:
3310
3444
  "cutover_seen INTEGER NOT NULL DEFAULT 0, "
3311
3445
  "cutover_account_key TEXT)"
3312
3446
  )
3447
+ # #825/#832 epoch 1018 (spec section 8): the bounded, disposable
3448
+ # summary of the complete journal records the admission predicate
3449
+ # EXCLUDED (post-cutover records without the current writer
3450
+ # generation). Ingest extends it inside its cycle transaction and a
3451
+ # rebuild reproduces it from the pinned prefix; `complete_through` is
3452
+ # NULL when the row was started mid-journal (it then describes only
3453
+ # what it saw). Coordinates are JSON `{"segment", "offset"}` text, and
3454
+ # `coordinate_digest` is the order-independent population digest of
3455
+ # every excluded coordinate (`_lib_account_protocol`).
3456
+ conn.execute(
3457
+ "CREATE TABLE IF NOT EXISTS journal_admission_summary ("
3458
+ "id INTEGER PRIMARY KEY CHECK (id = 1), "
3459
+ "cutover_id TEXT, "
3460
+ "scanned_high_water TEXT, "
3461
+ "excluded_total INTEGER NOT NULL DEFAULT 0, "
3462
+ "family_counts_json TEXT NOT NULL DEFAULT '{}', "
3463
+ "first_excluded_coordinate TEXT, "
3464
+ "latest_excluded_coordinate TEXT, "
3465
+ "coordinate_digest TEXT, "
3466
+ "complete_through TEXT, "
3467
+ # The boundary payload this summary classified against, so the
3468
+ # live ingest path resolves admission without scanning the journal.
3469
+ "boundary_json TEXT)"
3470
+ )
3471
+ # #825/#832 fix round A (Codex fix-round-1 Q1): the derivation
3472
+ # frontier. Every admitted derivable input at or before this journal
3473
+ # coordinate has had its decisions journaled by a current binary. Live
3474
+ # ingest advances it with the cursor only while the two are equal; a
3475
+ # rebuild carries it from a trusted predecessor index or sets it after
3476
+ # the shared completion step proved the window. Absent means unknown,
3477
+ # which the completion step reads as "replay from the cutover".
3478
+ # Fix round C (P1-1): the frontier counts CONSUMED INPUTS, and the
3479
+ # decisions a cycle derives from them land beyond it, so the row also
3480
+ # names the output end — the coordinate through which every decision
3481
+ # derived from an input at or before the frontier is journaled, with
3482
+ # nothing between the two derived from a later input. NULL means
3483
+ # nothing is owed beyond the frontier.
3484
+ conn.execute(
3485
+ "CREATE TABLE IF NOT EXISTS journal_derivation_frontier ("
3486
+ "id INTEGER PRIMARY KEY CHECK (id = 1), "
3487
+ "segment TEXT NOT NULL, "
3488
+ "offset INTEGER NOT NULL, "
3489
+ "output_segment TEXT, "
3490
+ "output_offset INTEGER)"
3491
+ )
3492
+ # An index an earlier build of this epoch created lacks the two
3493
+ # columns; additive, so it keeps its frontier row.
3494
+ add_column_if_missing(
3495
+ conn, "journal_derivation_frontier", "output_segment", "TEXT")
3496
+ add_column_if_missing(
3497
+ conn, "journal_derivation_frontier", "output_offset", "INTEGER")
3498
+ # Revision 2 (#946, Codex design-fix-round-3 Q5): the proof
3499
+ # generation. A row written under the old traversal rule (NULL here)
3500
+ # may name a valid endpoint past an input nothing derived, so only
3501
+ # `_cctally_journal.DERIVATION_PROOF_GENERATION` is trusted.
3502
+ add_column_if_missing(
3503
+ conn, "journal_derivation_frontier", "proof_generation", "INTEGER")
3504
+ # #825/#832 fix round A (Codex fix-round-1 Q2): the physical revision
3505
+ # inventory of every EXCLUDED event or correction action the index has
3506
+ # consumed, by logical id. Admission hides these from effective
3507
+ # selection, so without it a current writer would classify the id NEW
3508
+ # and append a revision-0 twin the previous release reads as a
3509
+ # same-revision conflict.
3510
+ conn.execute(
3511
+ "CREATE TABLE IF NOT EXISTS journal_excluded_event_revisions ("
3512
+ "event_id TEXT PRIMARY KEY, "
3513
+ "max_rev INTEGER NOT NULL)"
3514
+ )
3515
+ # #825/#832 epoch 1018 (spec 5.6 item 9): the outcome quarantine. A
3516
+ # deterministic function of journal order that ingest extends inside
3517
+ # its cycle transaction and a rebuild reproduces from the pinned
3518
+ # prefix: one row per uncertainty trigger (an excluded record or a
3519
+ # current `legacy_spool_activity` op, at its physical coordinate), the
3520
+ # whole-prefix envelope per scope ("excluded" applies to every
3521
+ # account), and each account's accounting and control certificates
3522
+ # with the trigger sequence they were granted against. A certificate
3523
+ # granted against an older sequence is void; a missing or
3524
+ # unverifiable row reads as quarantined.
3525
+ conn.execute(
3526
+ "CREATE TABLE IF NOT EXISTS outcome_quarantine_triggers ("
3527
+ "seq INTEGER PRIMARY KEY, "
3528
+ "segment TEXT, "
3529
+ "byte_offset INTEGER, "
3530
+ "kind TEXT NOT NULL, "
3531
+ "reason TEXT NOT NULL, "
3532
+ "at TEXT)"
3533
+ )
3534
+ conn.execute(
3535
+ "CREATE TABLE IF NOT EXISTS outcome_quarantine_envelope ("
3536
+ "scope TEXT PRIMARY KEY, "
3537
+ "envelope_json TEXT NOT NULL)"
3538
+ )
3539
+ conn.execute(
3540
+ "CREATE TABLE IF NOT EXISTS outcome_quarantine_certificates ("
3541
+ "account_key TEXT PRIMARY KEY, "
3542
+ "accounting_certificate_json TEXT, "
3543
+ "accounting_trigger_seq INTEGER, "
3544
+ "control_certificate_json TEXT, "
3545
+ "control_trigger_seq INTEGER)"
3546
+ )
3313
3547
  conn.execute(
3314
3548
  "CREATE TABLE IF NOT EXISTS journal_selector_batches ("
3315
3549
  "batch_id TEXT PRIMARY KEY, "
@@ -3369,6 +3603,45 @@ def open_db(*, _target_path=None) -> sqlite3.Connection:
3369
3603
  "started_at_utc TEXT NOT NULL, "
3370
3604
  "stamped_at_utc TEXT NOT NULL)"
3371
3605
  )
3606
+ # #825/#832 Task 4.0 (epoch 1018): a milestone whose snapshot a
3607
+ # credit's stale-replica removal deleted. The removal's band starts at
3608
+ # floor10 of the arming tick, so it also reaches the GENUINE pre-credit
3609
+ # peak tick when that tick shares the arming tick's ten-minute slot —
3610
+ # and that tick is the one that crossed the last weekly and five-hour
3611
+ # milestones. A rebuild folds the suppression (order 30) before the
3612
+ # milestones (order 60), so their journaled `usage_snapshot_ref` named
3613
+ # a row that no longer existed and the NOT NULL row was silently
3614
+ # dropped; the next tick then re-recorded the highest weekly milestone
3615
+ # and its conflict convergence raised on every cycle.
3616
+ #
3617
+ # The tombstone records every journaled snapshot the index deleted —
3618
+ # the live stale-replica removals and the suppression appliers alike,
3619
+ # because a trigger sees every DELETE — so the fold's ONE resolver
3620
+ # (`_cctally_journal._resolve_fold_ref`) can tell "a suppression
3621
+ # removed it" from a genuinely unresolvable reference. The trigger's
3622
+ # second half points every milestone that referenced the deleted row at
3623
+ # the 0 "no snapshot row" sentinel, which is what the rebuild resolves
3624
+ # such a reference to, so the live index and a rebuild agree. Both
3625
+ # milestone readers already treat 0 as "no row": the five-hour
3626
+ # milestone read LEFT JOINs (and so publishes the effective weekly
3627
+ # value as unavailable, as it already did for the dangling rowid), and
3628
+ # the observation-gap classifier never continues a run through 0.
3629
+ conn.execute(
3630
+ "CREATE TABLE IF NOT EXISTS usage_snapshot_suppressions ("
3631
+ "journal_id TEXT PRIMARY KEY)"
3632
+ )
3633
+ conn.execute(
3634
+ "CREATE TRIGGER IF NOT EXISTS trg_usage_snapshot_suppression "
3635
+ "AFTER DELETE ON weekly_usage_snapshots "
3636
+ "BEGIN "
3637
+ "INSERT OR IGNORE INTO usage_snapshot_suppressions (journal_id) "
3638
+ "SELECT OLD.journal_id WHERE OLD.journal_id IS NOT NULL; "
3639
+ "UPDATE percent_milestones SET usage_snapshot_id = 0 "
3640
+ "WHERE usage_snapshot_id = OLD.id; "
3641
+ "UPDATE five_hour_milestones SET usage_snapshot_id = 0 "
3642
+ "WHERE usage_snapshot_id = OLD.id; "
3643
+ "END"
3644
+ )
3372
3645
 
3373
3646
  # §6.2 backfill gate (Task 8): stamp the one-shot marker AFTER the three
3374
3647
  # open-time backfills ran, so the next open skips them (and their probes)
@@ -2586,9 +2586,9 @@ def _dashboard_maybe_prune_retention() -> None:
2586
2586
  crash the sync thread)."""
2587
2587
  try:
2588
2588
  c = sys.modules["cctally"]
2589
- from _cctally_config import resolve_retention_days
2589
+ from _cctally_config import resolve_prune_retention_days
2590
2590
  import _lib_conversation_retention as retention
2591
- retention_days = resolve_retention_days(c.load_config())
2591
+ retention_days = resolve_prune_retention_days(c.load_config())
2592
2592
  if retention_days <= 0:
2593
2593
  return
2594
2594
  conn = c.open_conversations_db(attach_cache=False)
@@ -11999,15 +11999,12 @@ def _dashboard_stats_deferred_snapshot(args, *, pinned_now, exc):
11999
11999
  # worker owns maintenance-exclusive, doing that before HTTP bind reintroduces
12000
12000
  # the exact multi-second startup stall this path exists to remove. Supply a
12001
12001
  # schema-valid, privacy-safe warning block; the first successful background
12002
- # snapshot replaces it with a real doctor payload.
12003
- doctor_payload = {
12004
- "severity": "warn",
12005
- "counts": {"ok": 0, "warn": 1, "fail": 0},
12006
- "generated_at": now_utc.astimezone(dt.timezone.utc).strftime(
12007
- "%Y-%m-%dT%H:%M:%SZ"
12008
- ),
12009
- "fingerprint": "sha1:" + ("0" * 40),
12010
- }
12002
+ # snapshot replaces it with a real doctor payload. #997 D10: the block still
12003
+ # carries the rebuild-worker aggregate, from the worker snapshots alone
12004
+ # (lock-free, never opens stats), so the chip names the running or dead
12005
+ # rebuild from the first frame.
12006
+ doctor_payload = tui._deferred_doctor_placeholder(now_utc)
12007
+ doctor_payload["rebuild_worker"] = tui._tui_rebuild_worker_value(now_utc)
12011
12008
  replacements = {
12012
12009
  # #583 S2 §6.1: this frame is degraded by construction — it carries a
12013
12010
  # `stats-open` error and no successful build ran, so it must not stamp