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.
Files changed (64) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/bin/_cctally_account.py +32 -8
  3. package/bin/_cctally_cache.py +4394 -279
  4. package/bin/_cctally_completion.py +1287 -0
  5. package/bin/_cctally_config.py +37 -0
  6. package/bin/_cctally_core.py +178 -2
  7. package/bin/_cctally_dashboard.py +2 -2
  8. package/bin/_cctally_dashboard_conversation.py +292 -23
  9. package/bin/_cctally_dashboard_envelope.py +16 -3
  10. package/bin/_cctally_dashboard_sources.py +18 -9
  11. package/bin/_cctally_db.py +316 -5
  12. package/bin/_cctally_doctor.py +436 -27
  13. package/bin/_cctally_five_hour.py +82 -31
  14. package/bin/_cctally_forecast.py +81 -9
  15. package/bin/_cctally_journal.py +3884 -112
  16. package/bin/_cctally_journal_repair.py +101 -12
  17. package/bin/_cctally_parser.py +42 -0
  18. package/bin/_cctally_project.py +8 -2
  19. package/bin/_cctally_quarantine.py +652 -0
  20. package/bin/_cctally_quota.py +26 -4
  21. package/bin/_cctally_quota_model.py +360 -83
  22. package/bin/_cctally_record.py +677 -149
  23. package/bin/_cctally_rederive.py +297 -32
  24. package/bin/_cctally_refresh.py +84 -13
  25. package/bin/_cctally_source_analytics.py +23 -2
  26. package/bin/_cctally_statusline.py +6137 -517
  27. package/bin/_cctally_store.py +287 -48
  28. package/bin/_cctally_transcript.py +8 -7
  29. package/bin/_cctally_tui.py +57 -53
  30. package/bin/_lib_account_binding.py +152 -0
  31. package/bin/_lib_account_protocol.py +386 -0
  32. package/bin/_lib_aggregators.py +4 -0
  33. package/bin/_lib_cache_coverage.py +13 -1
  34. package/bin/_lib_cache_writer_lock.py +19 -3
  35. package/bin/_lib_clone_copy.py +142 -0
  36. package/bin/_lib_codex_conversation_query.py +133 -19
  37. package/bin/_lib_codex_conversation_watch.py +59 -14
  38. package/bin/_lib_codex_drain_admission.py +214 -0
  39. package/bin/_lib_codex_metadata.py +18 -1
  40. package/bin/_lib_codex_publication_barrier.py +277 -0
  41. package/bin/_lib_codex_rollout_container.py +502 -0
  42. package/bin/_lib_conversation_dispatch.py +3 -0
  43. package/bin/_lib_conversation_query.py +11 -3
  44. package/bin/_lib_detector_access_registry.py +3817 -0
  45. package/bin/_lib_doctor.py +452 -1
  46. package/bin/_lib_five_hour.py +126 -0
  47. package/bin/_lib_ingest_frontier.py +414 -20
  48. package/bin/_lib_journal.py +141 -10
  49. package/bin/_lib_jsonl.py +5 -4
  50. package/bin/_lib_legacy_spool.py +126 -0
  51. package/bin/_lib_legacy_writer_evidence.py +136 -0
  52. package/bin/_lib_merge_gate.py +29 -302
  53. package/bin/_lib_meter_rate_change.py +10 -0
  54. package/bin/_lib_outcome_quarantine.py +690 -0
  55. package/bin/_lib_pricing.py +7 -2
  56. package/bin/_lib_quarantine.py +154 -0
  57. package/bin/_lib_quota_calibration.py +53 -8
  58. package/bin/_lib_quota_copy.py +2 -1
  59. package/bin/_lib_quota_model.py +288 -46
  60. package/bin/_lib_rederive.py +94 -5
  61. package/bin/_lib_segment_summary.py +35 -2
  62. package/bin/_lib_statusline_candidates.py +292 -23
  63. package/bin/cctally +23 -1
  64. package/package.json +14 -1
@@ -92,7 +92,12 @@ def _chip_for_model(name: str) -> str:
92
92
  # fingerprint a store recorded and refuses a write from an older process. That
93
93
  # comparison is day-granular by construction, so two revisions sharing a date
94
94
  # compare equal and the older process is authorized to write.
95
- PRICING_SNAPSHOT_DATE = "2026-10-07"
95
+ #
96
+ # A sync of this date or of either table also changes the dynamic-SQL shape
97
+ # pinned in `bin/_lib_detector_access_registry.py`, so its registry test fails
98
+ # until the affected pins are re-pinned through the reviewed procedure in that
99
+ # module's docstring. Bulk regeneration of the pins alone is not acceptance.
100
+ PRICING_SNAPSHOT_DATE = "2026-10-14"
96
101
  PRICING_STALENESS_DAYS = 60 # release pre-flight WARNs past this age
97
102
 
98
103
 
@@ -700,7 +705,7 @@ CLAUDE_MODEL_PRICING: dict[str, dict[str, Any]] = {
700
705
  "input_cost_per_token": 2e-06,
701
706
  "output_cost_per_token": 1e-05,
702
707
  "cache_creation_input_token_cost": 2.5e-06,
703
- "cache_read_input_token_cost": 2e-07,
708
+ "cache_read_input_token_cost": 1e-07,
704
709
  },
705
710
  }
706
711
 
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env python3
2
+ """The one parser of tests/quarantine.json, the reviewed flaky-test list (spec 4.3).
3
+
4
+ Stdlib only. An entry's `id` takes one of two forms:
5
+
6
+ * a pytest node id, `tests/<file>.py::<node>` (a prefix: pytest's --deselect
7
+ semantics cover every parametrization of a listed function);
8
+ * a harness case, `harness:<harness-name>::<case>`, where `<harness-name>` is
9
+ the `NAME` of `bin/cctally-NAME-test` and `<case>` is one scenario of that
10
+ harness's own case inventory (`bin/cctally-NAME-test --list-cases`).
11
+
12
+ `bin/cctally-test-all`, `bin/_lib_merge_gate.py` and the quick gate read the
13
+ list through this module; the scheduled CI quarantine leg calls its CLI. An
14
+ absent list quarantines nothing (the public mirror may carry none); a list
15
+ that exists but cannot be read raises QuarantineError, never "nothing listed".
16
+
17
+ CLI (exit 0, or 2 on an unreadable list or bad usage):
18
+ python3 bin/_lib_quarantine.py pytest-ids
19
+ python3 bin/_lib_quarantine.py harness-cases <harness>
20
+ """
21
+
22
+ from dataclasses import dataclass
23
+ import json
24
+ from pathlib import Path
25
+ import re
26
+ import subprocess
27
+ import sys
28
+
29
+ LIST_PATH = Path('tests') / 'quarantine.json'
30
+ FIELDS = ('id', 'evidence', 'invariant', 'issue', 'replacement', 'reviewBy')
31
+ PYTEST_ID = re.compile(r'tests/[^:\s]+\.py::\S+')
32
+ HARNESS_ID = re.compile(r'harness:([a-z0-9][a-z0-9-]*)::([A-Za-z0-9][A-Za-z0-9_.-]*)')
33
+
34
+
35
+ class QuarantineError(ValueError):
36
+ """tests/quarantine.json exists but is not a list this parser can apply."""
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class Entry:
41
+ id: str
42
+ kind: str # 'pytest' or 'harness'
43
+ harness: str | None # the harness name for a harness case
44
+ case: str | None # the case name for a harness case
45
+ fields: dict
46
+
47
+
48
+ def parse_id(entry_id: str) -> tuple[str, str | None, str | None]:
49
+ """(kind, harness, case) of one id; QuarantineError for any other form."""
50
+ if not isinstance(entry_id, str):
51
+ raise QuarantineError(f'an entry id must be a string, not {entry_id!r}')
52
+ if PYTEST_ID.fullmatch(entry_id):
53
+ return 'pytest', None, None
54
+ match = HARNESS_ID.fullmatch(entry_id)
55
+ if match:
56
+ return 'harness', match.group(1), match.group(2)
57
+ raise QuarantineError(
58
+ f'{entry_id!r} is neither a pytest node id (tests/<file>.py::<node>) '
59
+ 'nor a harness case (harness:<harness>::<case>)')
60
+
61
+
62
+ def parse(doc) -> list[Entry]:
63
+ if not isinstance(doc, list):
64
+ raise QuarantineError('expected a JSON list of entries')
65
+ entries = []
66
+ for item in doc:
67
+ if not isinstance(item, dict):
68
+ raise QuarantineError(f'expected each entry to be an object, not {item!r}')
69
+ kind, harness, case = parse_id(item.get('id'))
70
+ entries.append(Entry(item['id'], kind, harness, case, dict(item)))
71
+ ids = [e.id for e in entries]
72
+ if len(set(ids)) != len(ids):
73
+ raise QuarantineError('duplicate entry ids: ' + ', '.join(
74
+ sorted({i for i in ids if ids.count(i) > 1})))
75
+ return entries
76
+
77
+
78
+ def load_path(path: Path) -> list[Entry]:
79
+ path = Path(path)
80
+ if not path.is_file():
81
+ return []
82
+ try:
83
+ doc = json.loads(path.read_text(encoding='utf-8'))
84
+ except (OSError, ValueError) as exc:
85
+ raise QuarantineError(f'{path}: {exc}') from None
86
+ return parse(doc)
87
+
88
+
89
+ def load(repo: Path) -> list[Entry]:
90
+ return load_path(Path(repo) / LIST_PATH)
91
+
92
+
93
+ def pytest_ids(entries) -> list[str]:
94
+ return [e.id for e in entries if e.kind == 'pytest']
95
+
96
+
97
+ def harness_cases(entries, harness: str) -> list[str]:
98
+ return [e.case for e in entries if e.kind == 'harness' and e.harness == harness]
99
+
100
+
101
+ def harness_inventory(repo: Path, harness: str, *, timeout: float = 60) -> list[str]:
102
+ """The case names `bin/cctally-<harness>-test --list-cases` prints.
103
+
104
+ QuarantineError when the harness does not exist or has no case inventory:
105
+ a harness case that cannot be validated against the harness is not
106
+ quarantinable."""
107
+ script = Path(repo) / 'bin' / f'cctally-{harness}-test'
108
+ if not script.is_file():
109
+ raise QuarantineError(f'harness:{harness}: bin/cctally-{harness}-test does not exist')
110
+ proc = subprocess.run([str(script), '--list-cases'], cwd=repo, capture_output=True,
111
+ text=True, timeout=timeout)
112
+ if proc.returncode != 0:
113
+ raise QuarantineError(f'harness:{harness}: --list-cases exited {proc.returncode}: '
114
+ + (proc.stderr or proc.stdout).strip()[-400:])
115
+ return [line.strip() for line in proc.stdout.splitlines() if line.strip()]
116
+
117
+
118
+ def validate_harness_cases(repo: Path, entries) -> None:
119
+ """Each harness case names an existing harness and a case in its inventory."""
120
+ inventories: dict[str, list[str]] = {}
121
+ for entry in entries:
122
+ if entry.kind != 'harness':
123
+ continue
124
+ if entry.harness not in inventories:
125
+ inventories[entry.harness] = harness_inventory(repo, entry.harness)
126
+ if entry.case not in inventories[entry.harness]:
127
+ raise QuarantineError(f'{entry.id}: no case {entry.case!r} in the '
128
+ f'{entry.harness} harness inventory')
129
+
130
+
131
+ def main(argv=None) -> int:
132
+ argv = list(sys.argv[1:] if argv is None else argv)
133
+ repo = Path(__file__).resolve().parents[1]
134
+ if len(argv) >= 2 and argv[0] == '--repo':
135
+ repo, argv = Path(argv[1]), argv[2:]
136
+ try:
137
+ if argv == ['pytest-ids']:
138
+ out = pytest_ids(load(repo))
139
+ elif len(argv) == 2 and argv[0] == 'harness-cases':
140
+ out = harness_cases(load(repo), argv[1])
141
+ else:
142
+ print('usage: _lib_quarantine.py [--repo DIR] pytest-ids | harness-cases <harness>',
143
+ file=sys.stderr)
144
+ return 2
145
+ except QuarantineError as exc:
146
+ print(f'quarantine: {exc}', file=sys.stderr)
147
+ return 2
148
+ for item in out:
149
+ print(item)
150
+ return 0
151
+
152
+
153
+ if __name__ == '__main__':
154
+ raise SystemExit(main())
@@ -62,6 +62,10 @@ class ApplyRejection(str, enum.Enum):
62
62
 
63
63
  UNSUPPORTED_COMPOSITION = "unsupported-composition"
64
64
  EMPTY_POPULATION = "no-local-history"
65
+ #: The requested interval, or an entry of the population, lies outside
66
+ #: the regime's own coefficient era (#978 spec 4.6). Units from another
67
+ #: era are never divided by this regime's units-per-point.
68
+ UNVALIDATED_COEFFICIENT_ERA = "unvalidated-coefficient-era"
65
69
 
66
70
 
67
71
  @dataclasses.dataclass(frozen=True)
@@ -88,6 +92,9 @@ class ValidatedRegime:
88
92
  class_shares: dict
89
93
  family_radius: "float | None"
90
94
  class_radius: "float | None"
95
+ #: The id of the coefficient era the regime was fitted in, always a
96
+ #: member of `qm.COEFFICIENT_ERAS` (#978).
97
+ coefficient_era: str
91
98
  qualifications: tuple = ()
92
99
 
93
100
 
@@ -196,6 +203,13 @@ def validate_regime(raw, *, account_key, expected_fingerprint,
196
203
  if isinstance(revision, bool) or not isinstance(revision, int) \
197
204
  or revision != expected_revision:
198
205
  return RegimeRejection.REVISION
206
+ # #978 spec 4.5: under the current constants a regime names the
207
+ # coefficient era it was fitted in. Missing or unknown is malformed, never
208
+ # a guess at an era; a regime from an older build has already been
209
+ # reported above as a fingerprint mismatch.
210
+ era = qm.era_by_id(raw.get("coefficientEra"))
211
+ if era is None:
212
+ return RegimeRejection.MALFORMED
199
213
 
200
214
  point = raw.get("unitsPerPoint")
201
215
  if not _finite(point) or point <= 0.0:
@@ -256,31 +270,42 @@ def validate_regime(raw, *, account_key, expected_fingerprint,
256
270
  class_shares=class_shares,
257
271
  family_radius=family_radius,
258
272
  class_radius=class_radius,
273
+ coefficient_era=era.id,
259
274
  qualifications=marks,
260
275
  )
261
276
 
262
277
 
263
- def population_units(entries) -> float:
278
+ def population_units(entries, *, era) -> float:
264
279
  """Summed weighted quota units over the general-meter entries.
265
280
 
266
281
  The same filter `_cctally_quota_model.analyse_account` applies to the
267
282
  current week: an entry whose family does not drain the general weekly
268
283
  meter contributes nothing, and one whose cache-write split is unknown is
269
- skipped rather than priced at zero.
284
+ skipped rather than priced at zero. Every entry is weighted with `era`,
285
+ the regime's own coefficient era (#978).
286
+
287
+ An entry `era` does not cover raises `CoefficientEraMismatch`, as the
288
+ kernel's population functions do (#978 spec 4.2, invariant 5): skipping
289
+ it would divide part of the population by the regime's budget and report
290
+ the result as the whole.
270
291
  """
271
292
  total = 0.0
272
293
  for entry in entries:
273
294
  if qm.family_participation(qm.normalize_family(entry.model)) \
274
295
  != "general":
275
296
  continue
276
- units = qm.weighted_units(entry)
277
- if units is None:
297
+ weighed = qm.weigh_entry(entry, era)
298
+ if weighed.cause == qm.ERA_UNAVAILABLE:
299
+ raise qm.CoefficientEraMismatch(
300
+ f"entry.at {entry.at.isoformat()} lies outside coefficient "
301
+ f"era {era.id!r}")
302
+ if weighed.units is None:
278
303
  continue
279
- total += units
304
+ total += weighed.units
280
305
  return total
281
306
 
282
307
 
283
- def apply_regime(regime: ValidatedRegime, entries
308
+ def apply_regime(regime: ValidatedRegime, entries, *, interval
284
309
  ) -> "AppliedQuota | ApplyRejection":
285
310
  """Modelled consumption for `entries` under `regime`.
286
311
 
@@ -288,8 +313,28 @@ def apply_regime(regime: ValidatedRegime, entries
288
313
  status, because that status describes S1's fit population and this one is
289
314
  the consumer's. The arithmetic afterwards reproduces the kernel's, so a
290
315
  consumer never re-derives the inversion by hand.
316
+
317
+ `interval` is the half-open `[start, end)` the caller models, which a
318
+ consumer extrapolating over a whole week must pass as that week (#978
319
+ spec 4.6). Two independent checks run before any aggregation: the
320
+ interval must lie inside the regime's coefficient era, and so must every
321
+ entry. The interval check is not implied by the entry check — a week
322
+ starting before the era whose retained entries all fall after its start
323
+ is in-era entry by entry, yet modelling the whole week is forbidden.
291
324
  """
292
- aggregated = qm.aggregate_composition(entries)
325
+ era = qm.era_by_id(regime.coefficient_era)
326
+ if era is None:
327
+ return ApplyRejection.UNVALIDATED_COEFFICIENT_ERA
328
+ start, end = interval
329
+ qm.require_aware(start, "interval.start")
330
+ qm.require_aware(end, "interval.end")
331
+ era_start, era_end = qm.era_bounds(era)
332
+ if start < era_start or (era_end is not None and end > era_end):
333
+ return ApplyRejection.UNVALIDATED_COEFFICIENT_ERA
334
+ entries = list(entries)
335
+ if any(not qm.era_contains(era, entry.at) for entry in entries):
336
+ return ApplyRejection.UNVALIDATED_COEFFICIENT_ERA
337
+ aggregated = qm.aggregate_composition(entries, era=era)
293
338
  if aggregated is None:
294
339
  return ApplyRejection.EMPTY_POPULATION
295
340
  family_shares, class_shares = aggregated
@@ -298,7 +343,7 @@ def apply_regime(regime: ValidatedRegime, entries
298
343
  regime.class_shares, regime.class_radius):
299
344
  return ApplyRejection.UNSUPPORTED_COMPOSITION
300
345
 
301
- units = population_units(entries)
346
+ units = population_units(entries, era=era)
302
347
  if not _finite(units) or units < 0.0:
303
348
  return ApplyRejection.EMPTY_POPULATION
304
349
 
@@ -70,7 +70,8 @@ _LONG_FORM: dict = {
70
70
  "the models in use sit outside the mix the calibration was fitted "
71
71
  "over",
72
72
  "unvalidated-coefficient-era":
73
- "this window predates the token weights the model is validated for",
73
+ "this window is not inside a single coefficient era the token "
74
+ "weights are validated for",
74
75
  "stale":
75
76
  "the stored calibration was fitted under different constants, so it "
76
77
  "no longer describes this build",