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
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""Pure decisions of the #968 conversation publication barrier (spec 5.11).
|
|
2
|
+
|
|
3
|
+
No I/O and no SQLite. `_cctally_cache` owns the durable cache.db state (the
|
|
4
|
+
membership index, readiness targets, population, certificates and repair
|
|
5
|
+
queue) and the conversation pass owns the publication records; both call these
|
|
6
|
+
functions with plain values, so the readiness rule, the thread-metadata rule
|
|
7
|
+
and the certificate format are stated exactly once.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from typing import Iterable, NamedTuple, Optional
|
|
13
|
+
|
|
14
|
+
CERTIFICATE_VERSION = 1
|
|
15
|
+
|
|
16
|
+
READY = "ready"
|
|
17
|
+
REQUIRED = "required"
|
|
18
|
+
SUSPENDED = "suspended"
|
|
19
|
+
|
|
20
|
+
#: The pending hold (5.11 U6 table, first row): the candidate stays a
|
|
21
|
+
#: dependency of the next revision; pending work is never an exclusion.
|
|
22
|
+
PENDING_HOLD = "verification-pending"
|
|
23
|
+
#: Holds that suspend the contributor's current-generation readiness
|
|
24
|
+
#: obligation (U6 table, second row). Retained accepted membership is kept;
|
|
25
|
+
#: "excluded" never means deleting rows or forgetting membership.
|
|
26
|
+
#: `container-absent` is an accounted identity whose container is gone (an
|
|
27
|
+
#: orphan awaiting the off-hook prune, or one #485 refuses to prune).
|
|
28
|
+
SUSPENSION_HOLDS = frozenset({
|
|
29
|
+
"decoder-unavailable", "decode-error", "verification-unverified",
|
|
30
|
+
"container-absent",
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
#: The twelve source columns of a `codex_conversation_threads` row, in column
|
|
34
|
+
#: order. The two timestamp columns are bookkeeping, carried separately.
|
|
35
|
+
THREAD_FIELDS = (
|
|
36
|
+
"conversation_key", "source_root_key", "native_thread_id",
|
|
37
|
+
"root_thread_id", "parent_thread_id", "source_path", "cwd", "git_json",
|
|
38
|
+
"source_kind", "thread_source_json", "model_provider", "context_window",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
#: The generation token of a membership row that records a key a suspended
|
|
42
|
+
#: contributor of unknown membership (no watermark) is KNOWN to feed, from its
|
|
43
|
+
#: retained rows (review N2). Never equal to a real generation token, so it
|
|
44
|
+
#: never feeds a READY contributor; it is such an exclusion's effective
|
|
45
|
+
#: generation, and the row carries the key's retained thread as metadata when
|
|
46
|
+
#: that row names this contributor (5.11 U6 "using retained accepted facts
|
|
47
|
+
#: where they exist"). The rows are deleted when the membership is proven.
|
|
48
|
+
RETAINED_GENERATION = "retained"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Target(NamedTuple):
|
|
52
|
+
"""One contributor's readiness target (a `codex_barrier_targets` row)."""
|
|
53
|
+
identity: str
|
|
54
|
+
source_path: str
|
|
55
|
+
generation: str
|
|
56
|
+
#: Offset after the last newline-terminated line of the captured target,
|
|
57
|
+
#: set once accounting reached that target; None while it has not.
|
|
58
|
+
complete_line_end: Optional[int]
|
|
59
|
+
hold: Optional[str]
|
|
60
|
+
#: Generation and decoded offset through which the production parser has
|
|
61
|
+
#: recorded this identity's membership from offset 0.
|
|
62
|
+
watermark_generation: Optional[str]
|
|
63
|
+
watermark_offset: Optional[int]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TargetVerdict(NamedTuple):
|
|
67
|
+
state: str
|
|
68
|
+
reason: Optional[str]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def classify_target(target: Target) -> TargetVerdict:
|
|
72
|
+
"""Readiness of one contributor (5.11 "The readiness target", U6).
|
|
73
|
+
|
|
74
|
+
A watermark of another generation proves nothing, whatever its offset
|
|
75
|
+
(968-SR-012: a G0 cursor never satisfies G1's target). A suspension is
|
|
76
|
+
SUSPENDED whether or not its membership was ever proven (U6: "degraded
|
|
77
|
+
contributors degrade; they do not freeze", review N2): one of unknown
|
|
78
|
+
membership is an exclusion that blocks no key, and its keys keep the
|
|
79
|
+
thread its retained facts supply (``decide_seal``).
|
|
80
|
+
"""
|
|
81
|
+
if target.hold in SUSPENSION_HOLDS:
|
|
82
|
+
return TargetVerdict(SUSPENDED, target.hold)
|
|
83
|
+
if target.hold == PENDING_HOLD:
|
|
84
|
+
return TargetVerdict(REQUIRED, PENDING_HOLD)
|
|
85
|
+
if target.hold is not None:
|
|
86
|
+
raise ValueError(f"unknown barrier hold {target.hold!r}")
|
|
87
|
+
if (target.watermark_generation != target.generation
|
|
88
|
+
or target.watermark_offset is None):
|
|
89
|
+
return TargetVerdict(REQUIRED, "membership-unproven")
|
|
90
|
+
if target.complete_line_end is None:
|
|
91
|
+
return TargetVerdict(REQUIRED, "accounting-incomplete")
|
|
92
|
+
if target.watermark_offset < target.complete_line_end:
|
|
93
|
+
return TargetVerdict(REQUIRED, "membership-unproven")
|
|
94
|
+
return TargetVerdict(READY, None)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class MemberFact(NamedTuple):
|
|
98
|
+
"""One `codex_barrier_membership` row."""
|
|
99
|
+
identity: str
|
|
100
|
+
generation: str
|
|
101
|
+
source_path: str
|
|
102
|
+
metadata_json: Optional[str]
|
|
103
|
+
metadata_offset: Optional[int]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def metadata_from_thread(thread) -> str:
|
|
107
|
+
"""The generation-bound metadata one `session_meta` supplies (JSON).
|
|
108
|
+
|
|
109
|
+
``ensure_ascii`` escapes a lone surrogate (#845's undecodable cwd), and
|
|
110
|
+
``json.loads`` restores the identical string.
|
|
111
|
+
"""
|
|
112
|
+
return json.dumps({name: getattr(thread, name) for name in THREAD_FIELDS},
|
|
113
|
+
sort_keys=True, separators=(",", ":"), ensure_ascii=True)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def metadata_from_row(row: dict) -> str:
|
|
117
|
+
"""``metadata_from_thread``'s JSON for a retained thread row (a dict)."""
|
|
118
|
+
return json.dumps({name: row.get(name) for name in THREAD_FIELDS},
|
|
119
|
+
sort_keys=True, separators=(",", ":"), ensure_ascii=True)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def select_thread(candidates: Iterable[MemberFact], *,
|
|
123
|
+
retained: Optional[dict] = None) -> Optional[dict]:
|
|
124
|
+
"""The conversation's thread metadata.
|
|
125
|
+
|
|
126
|
+
Review P2-2: when the retained thread row (the entry snapshot, else the
|
|
127
|
+
current accounting row) equals the metadata some candidate supplies, it
|
|
128
|
+
is kept, so certifying a legacy conversation changes nothing visible.
|
|
129
|
+
Measured on the frozen 2026-10-14 baseline: of 243 multi-file keys the
|
|
130
|
+
stored row equals the maximum-path choice for 236 and another
|
|
131
|
+
contributor's metadata for 7. Otherwise (a new publication, or a retained
|
|
132
|
+
row no contributor supplies) the rule is what a complete walk writes
|
|
133
|
+
LAST: a full walk visits a root's rollouts in logical-path order and a
|
|
134
|
+
file's records in offset order, so the last writer is the maximum of
|
|
135
|
+
``(source_path, metadata_offset)``, independent of the order in which
|
|
136
|
+
healing reads the files.
|
|
137
|
+
"""
|
|
138
|
+
facts = [f for f in candidates
|
|
139
|
+
if f.metadata_json is not None and f.metadata_offset is not None]
|
|
140
|
+
if retained is not None:
|
|
141
|
+
want = {name: retained.get(name) for name in THREAD_FIELDS}
|
|
142
|
+
for fact in facts:
|
|
143
|
+
meta = json.loads(fact.metadata_json)
|
|
144
|
+
if {name: meta.get(name) for name in THREAD_FIELDS} == want:
|
|
145
|
+
return meta
|
|
146
|
+
best = None
|
|
147
|
+
for fact in facts:
|
|
148
|
+
rank = (fact.source_path, int(fact.metadata_offset))
|
|
149
|
+
if best is None or rank > best[0]:
|
|
150
|
+
best = (rank, fact.metadata_json)
|
|
151
|
+
return None if best is None else json.loads(best[1])
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class SealDecision(NamedTuple):
|
|
155
|
+
sealable: bool
|
|
156
|
+
blocking: tuple
|
|
157
|
+
certificate: Optional[dict]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def decide_seal(conversation_key: str, revision: int,
|
|
161
|
+
targets: Iterable[Target], members: Iterable[MemberFact], *,
|
|
162
|
+
timestamps: "tuple[str | None, str | None] | None" = None,
|
|
163
|
+
retained_thread: Optional[dict] = None,
|
|
164
|
+
) -> SealDecision:
|
|
165
|
+
"""Seal a certificate for one conversation, or name what blocks it.
|
|
166
|
+
|
|
167
|
+
``targets`` holds every identity with any membership row for the key
|
|
168
|
+
(``RETAINED_GENERATION`` rows included); ``members`` those rows. A
|
|
169
|
+
REQUIRED contributor blocks (the transient pending hold included); a
|
|
170
|
+
SUSPENDED one is an explicit exclusion whose retained accepted facts
|
|
171
|
+
still supply metadata (U6): its watermark generation's rows when its
|
|
172
|
+
membership was proven, else its ``RETAINED_GENERATION`` rows (review N2).
|
|
173
|
+
``retained_thread`` is the key's retained thread row: preferred when a
|
|
174
|
+
candidate supplies it (P2-2), and kept when an exclusion leaves no
|
|
175
|
+
metadata, so a seal never retracts a published thread to None because of
|
|
176
|
+
an excluded contributor (P1-1, N2).
|
|
177
|
+
"""
|
|
178
|
+
by_identity = {t.identity: t for t in targets}
|
|
179
|
+
verdicts = {i: classify_target(t) for i, t in by_identity.items()}
|
|
180
|
+
blocking = tuple(sorted((i, v.reason) for i, v in verdicts.items()
|
|
181
|
+
if v.state == REQUIRED))
|
|
182
|
+
if blocking:
|
|
183
|
+
return SealDecision(False, blocking, None)
|
|
184
|
+
facts = list(members)
|
|
185
|
+
contributors, exclusions, candidates = [], [], []
|
|
186
|
+
for identity in sorted(by_identity):
|
|
187
|
+
target, verdict = by_identity[identity], verdicts[identity]
|
|
188
|
+
if verdict.state == READY:
|
|
189
|
+
contributors.append({
|
|
190
|
+
"identity": identity, "path": target.source_path,
|
|
191
|
+
"generation": target.generation,
|
|
192
|
+
"completeLineEnd": int(target.complete_line_end)})
|
|
193
|
+
effective = target.generation
|
|
194
|
+
else:
|
|
195
|
+
exclusions.append({
|
|
196
|
+
"identity": identity, "path": target.source_path,
|
|
197
|
+
"reason": verdict.reason,
|
|
198
|
+
"acceptedGeneration": target.watermark_generation})
|
|
199
|
+
effective = (target.watermark_generation
|
|
200
|
+
if target.watermark_generation is not None
|
|
201
|
+
else RETAINED_GENERATION)
|
|
202
|
+
candidates.extend(f for f in facts if f.identity == identity
|
|
203
|
+
and f.generation == effective)
|
|
204
|
+
thread = select_thread(candidates, retained=retained_thread)
|
|
205
|
+
if thread is None and retained_thread is not None and exclusions:
|
|
206
|
+
# U6 / reviews P1-1, N2: an unreadable contributor never changes an
|
|
207
|
+
# existing publication on its own, and it never freezes the key.
|
|
208
|
+
# When no readable contributor and no retained fact supplies a
|
|
209
|
+
# thread, the retained one is kept. Thread None is sealed only when
|
|
210
|
+
# every contributor is readable and none supplies metadata, or when
|
|
211
|
+
# there is no retained thread to retract.
|
|
212
|
+
thread = {name: retained_thread.get(name) for name in THREAD_FIELDS}
|
|
213
|
+
if thread is not None and timestamps is not None:
|
|
214
|
+
thread = dict(thread, firstSeenUtc=timestamps[0],
|
|
215
|
+
lastSeenUtc=timestamps[1])
|
|
216
|
+
return SealDecision(True, (), {
|
|
217
|
+
"version": CERTIFICATE_VERSION,
|
|
218
|
+
"conversationKey": conversation_key,
|
|
219
|
+
"revision": int(revision),
|
|
220
|
+
"contributors": contributors,
|
|
221
|
+
"exclusions": exclusions,
|
|
222
|
+
"thread": thread,
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def certificate_json(cert: dict) -> str:
|
|
227
|
+
return json.dumps(cert, sort_keys=True, separators=(",", ":"),
|
|
228
|
+
ensure_ascii=True)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def parse_certificate(text) -> Optional[dict]:
|
|
232
|
+
try:
|
|
233
|
+
cert = json.loads(text)
|
|
234
|
+
except (TypeError, ValueError):
|
|
235
|
+
return None
|
|
236
|
+
if not isinstance(cert, dict) or cert.get("version") != CERTIFICATE_VERSION:
|
|
237
|
+
return None
|
|
238
|
+
return cert
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def certificate_covers(cert: dict, source_path: str,
|
|
242
|
+
generation: str) -> Optional[int]:
|
|
243
|
+
"""The certified complete-line end for this file at this generation."""
|
|
244
|
+
for contributor in cert.get("contributors", ()):
|
|
245
|
+
if (contributor.get("path") == source_path
|
|
246
|
+
and contributor.get("generation") == generation):
|
|
247
|
+
return int(contributor["completeLineEnd"])
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def certificate_excludes(cert: dict, source_path: str) -> bool:
|
|
252
|
+
return any(e.get("path") == source_path for e in cert.get("exclusions", ()))
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def publication_advances(published_revision, candidate_revision) -> bool:
|
|
256
|
+
"""Monotonic publication: only a strictly newer revision may publish."""
|
|
257
|
+
return int(candidate_revision) > int(published_revision or 0)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
#: cache.db ``cache_meta``: a fresh token written by every seal batch that
|
|
261
|
+
#: sealed anything (a token, not a counter: a deleted cache.db must never
|
|
262
|
+
#: repeat a value a conversations store has already acknowledged).
|
|
263
|
+
SEAL_SEQ_KEY = "codex_barrier_seal_seq"
|
|
264
|
+
#: conversations.db ``cache_meta``: the seal token whose certificates a
|
|
265
|
+
#: complete publication phase has considered.
|
|
266
|
+
PUBLISHED_SEQ_KEY = "codex_publication_seal_seq"
|
|
267
|
+
#: The TEMP view every conversations connection carries (5.11 "Reader
|
|
268
|
+
#: admission: last-good"); ``codex_conversation_threads`` shadows to it.
|
|
269
|
+
LAST_GOOD_THREADS_VIEW = "codex_last_good_threads"
|
|
270
|
+
#: Certificates newer than their publication, or never published. Run on a
|
|
271
|
+
#: conversations connection with cache.db attached as ``cache_db``.
|
|
272
|
+
BACKLOG_CERTIFICATES_SQL = (
|
|
273
|
+
"SELECT c.conversation_key, c.revision, c.certificate_json "
|
|
274
|
+
"FROM cache_db.codex_barrier_certificates c "
|
|
275
|
+
"LEFT JOIN main.codex_conversation_publications p "
|
|
276
|
+
"ON p.conversation_key = c.conversation_key "
|
|
277
|
+
"WHERE p.conversation_key IS NULL OR c.revision > p.published_revision")
|