graphite-code 0.3.0__py3-none-any.whl

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 (112) hide show
  1. graphite/__init__.py +41 -0
  2. graphite/__main__.py +7 -0
  3. graphite/_cleanup_worker.py +525 -0
  4. graphite/activation.py +164 -0
  5. graphite/agent_hooks.py +577 -0
  6. graphite/agent_settings.py +226 -0
  7. graphite/analyze.py +146 -0
  8. graphite/answer_contract.py +420 -0
  9. graphite/bootstrap.py +210 -0
  10. graphite/buildlock.py +99 -0
  11. graphite/cache.py +131 -0
  12. graphite/channel.py +1325 -0
  13. graphite/cli.py +3053 -0
  14. graphite/cluster.py +111 -0
  15. graphite/config.py +209 -0
  16. graphite/context.py +355 -0
  17. graphite/daemon.py +745 -0
  18. graphite/daemon_health.py +733 -0
  19. graphite/debt.py +118 -0
  20. graphite/dependency_install.py +1597 -0
  21. graphite/detach.py +33 -0
  22. graphite/doctor.py +678 -0
  23. graphite/doctor_probes.py +2100 -0
  24. graphite/engine_identity.py +238 -0
  25. graphite/export/__init__.py +6 -0
  26. graphite/export/html.py +244 -0
  27. graphite/export/json.py +39 -0
  28. graphite/export/md.py +68 -0
  29. graphite/extract/__init__.py +4 -0
  30. graphite/extract/ast.py +1964 -0
  31. graphite/freshness.py +127 -0
  32. graphite/git.py +406 -0
  33. graphite/graph.py +117 -0
  34. graphite/graph_io.py +188 -0
  35. graphite/health.py +147 -0
  36. graphite/hook_entry.py +68 -0
  37. graphite/hookinstall.py +224 -0
  38. graphite/hookshim.py +86 -0
  39. graphite/incident_ledger.py +247 -0
  40. graphite/ingest.py +279 -0
  41. graphite/init.py +791 -0
  42. graphite/io.py +32 -0
  43. graphite/listing.py +51 -0
  44. graphite/llm.py +518 -0
  45. graphite/llm_probe.py +157 -0
  46. graphite/mcp.py +7 -0
  47. graphite/mcp_server.py +450 -0
  48. graphite/natural_query.py +252 -0
  49. graphite/overlays.py +713 -0
  50. graphite/probe_process.py +879 -0
  51. graphite/probe_workspace.py +728 -0
  52. graphite/process_contracts.py +22 -0
  53. graphite/provider_observer.py +397 -0
  54. graphite/query.py +646 -0
  55. graphite/query_plan.py +97 -0
  56. graphite/replacement_audit.py +291 -0
  57. graphite/resolve.py +660 -0
  58. graphite/review.py +782 -0
  59. graphite/routing/__init__.py +5 -0
  60. graphite/routing/approval.py +362 -0
  61. graphite/routing/classifier.py +169 -0
  62. graphite/routing/claude_executor.py +419 -0
  63. graphite/routing/claude_probe.py +102 -0
  64. graphite/routing/cli_identity.py +84 -0
  65. graphite/routing/codex_executor.py +383 -0
  66. graphite/routing/codex_probe.py +93 -0
  67. graphite/routing/context_builder.py +327 -0
  68. graphite/routing/contracts.py +802 -0
  69. graphite/routing/diff_policy.py +468 -0
  70. graphite/routing/edit_apply.py +166 -0
  71. graphite/routing/effort.py +43 -0
  72. graphite/routing/lifecycle.py +771 -0
  73. graphite/routing/lifecycle_operator.py +227 -0
  74. graphite/routing/lifecycle_service.py +555 -0
  75. graphite/routing/lifecycle_storage.py +977 -0
  76. graphite/routing/ollama_executor.py +341 -0
  77. graphite/routing/ollama_probe.py +72 -0
  78. graphite/routing/openrouter_executor.py +338 -0
  79. graphite/routing/openrouter_probe.py +188 -0
  80. graphite/routing/policy.py +815 -0
  81. graphite/routing/probe_runner.py +543 -0
  82. graphite/routing/process_runner.py +523 -0
  83. graphite/routing/profiles.py +554 -0
  84. graphite/routing/prompt.py +58 -0
  85. graphite/routing/registry.py +444 -0
  86. graphite/routing/route_pool.py +629 -0
  87. graphite/routing/route_pool_execution.py +275 -0
  88. graphite/routing/schema_validation.py +169 -0
  89. graphite/routing/service.py +1263 -0
  90. graphite/routing/settings.py +99 -0
  91. graphite/routing/shadow.py +201 -0
  92. graphite/routing/storage.py +4001 -0
  93. graphite/routing/telemetry.py +346 -0
  94. graphite/routing/worktree.py +259 -0
  95. graphite/routing/zai_edit.py +113 -0
  96. graphite/routing/zai_executor.py +191 -0
  97. graphite/routing/zai_probe.py +126 -0
  98. graphite/savings.py +84 -0
  99. graphite/ts_bridge.py +142 -0
  100. graphite/ts_resolver.mjs +314 -0
  101. graphite/typescript_activation.py +1586 -0
  102. graphite/usage_ledger.py +156 -0
  103. graphite/validation.py +148 -0
  104. graphite/watch.py +167 -0
  105. graphite/windows_job.py +368 -0
  106. graphite/windows_startup.py +144 -0
  107. graphite/windows_task.py +212 -0
  108. graphite_code-0.3.0.dist-info/METADATA +743 -0
  109. graphite_code-0.3.0.dist-info/RECORD +112 -0
  110. graphite_code-0.3.0.dist-info/WHEEL +4 -0
  111. graphite_code-0.3.0.dist-info/entry_points.txt +3 -0
  112. graphite_code-0.3.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,4001 @@
1
+ """SQLite evidence storage with repository and aggregate trust boundaries."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ import hashlib
6
+ import hmac
7
+ import json
8
+ import re
9
+ import secrets
10
+ import sqlite3
11
+ import stat
12
+ from contextlib import closing, contextmanager, suppress
13
+ from collections.abc import Iterator
14
+ from dataclasses import dataclass
15
+ from pathlib import Path
16
+ from typing import Any, Final
17
+
18
+ from .contracts import (
19
+ CapabilitySnapshot,
20
+ EvidenceProvenance,
21
+ Effort,
22
+ ExecutionOutcome,
23
+ ExecutionReceipt,
24
+ PermissionMode,
25
+ ProviderId,
26
+ RiskTier,
27
+ TaskCategory,
28
+ )
29
+
30
+ SCHEMA_VERSION: Final = "8"
31
+ BUSY_TIMEOUT_MS: Final = 2_000
32
+ DEFAULT_RECOVERY_PAGE_SIZE: Final = 50
33
+ MAX_RECOVERY_PAGE_SIZE: Final = 100
34
+ _REPARSE_POINT = getattr(stat, "FILE_ATTRIBUTE_REPARSE_POINT", 0x400)
35
+ _IDENTIFIER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$")
36
+ _MODEL_IDENTIFIER = re.compile(
37
+ r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}(/[A-Za-z0-9][A-Za-z0-9._:+-]{0,127})?$"
38
+ )
39
+ _MODEL_ID = re.compile(r"^[a-z0-9][a-z0-9._-]{0,127}(?::cloud)?$")
40
+ _SNAPSHOT_MODEL_ID = re.compile(
41
+ r"^[a-z0-9][a-z0-9._-]{0,127}(?::[a-z0-9][a-z0-9._-]{0,63})?$"
42
+ )
43
+ _CAPABILITY = re.compile(r"^[a-z][a-z0-9_-]{0,31}$")
44
+ _VERSION = re.compile(r"^[a-z0-9][a-z0-9._-]{0,31}$")
45
+ _HEX_64 = re.compile(r"^[0-9a-f]{64}$")
46
+ _GIT_OBJECT = re.compile(r"^(?:[0-9a-f]{40}|[0-9a-f]{64})$")
47
+ _SEMANTIC_VERSION = re.compile(
48
+ r"^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)"
49
+ r"(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?(?:\+[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?$"
50
+ )
51
+ _CLI_TELEMETRY_KEYS: Final = frozenset(
52
+ {
53
+ "provider", "requested_model", "effective_model", "effort",
54
+ "capability_snapshot_digest", "category", "risk", "latency_ms",
55
+ "input_tokens", "output_tokens", "cost_status", "changed_file_count",
56
+ "changed_byte_count", "validation_outcome", "review_defect_classes",
57
+ "rework_count", "human_verdict", "provenance", "observed_at",
58
+ "diff_sha256",
59
+ }
60
+ )
61
+ _CLI_POLICY_KEYS: Final = frozenset(
62
+ {
63
+ "schema_version", "autonomy", "allowed_providers", "permission_ceiling",
64
+ "risk_ceilings", "scoring_weights", "minimum_samples",
65
+ "recency_half_life_days", "source_policy_version",
66
+ }
67
+ )
68
+ _TABLES = frozenset(
69
+ {
70
+ "tasks",
71
+ "decisions",
72
+ "approvals",
73
+ "executions",
74
+ "outcomes",
75
+ "shadow_comparisons",
76
+ "policy_versions",
77
+ "budget_ledger",
78
+ "confidence_stats",
79
+ "registry_snapshots",
80
+ "execution_evidence",
81
+ "execution_attempts",
82
+ "execution_receipts",
83
+ "staged_execution_receipts",
84
+ "incident_reviews",
85
+ "blind_comparisons",
86
+ "capability_snapshots",
87
+ "task_worktrees",
88
+ "cli_execution_attempts",
89
+ "validation_results",
90
+ "review_links",
91
+ "cli_telemetry_events",
92
+ "cli_policy_versions",
93
+ "cli_policy_events",
94
+ "lifecycle_snapshot_bindings",
95
+ "lifecycle_approval_bindings",
96
+ "lifecycle_attempt_bindings",
97
+ }
98
+ )
99
+
100
+
101
+ class StorageError(RuntimeError):
102
+ """A stable, path-free persistence failure."""
103
+
104
+ def __init__(self, code: str) -> None:
105
+ self.code = code
106
+ super().__init__(code)
107
+
108
+
109
+ def _is_reparse_point(metadata: os.stat_result) -> bool:
110
+ return bool(getattr(metadata, "st_file_attributes", 0) & _REPARSE_POINT)
111
+
112
+
113
+ def _selected_root(root: Path) -> Path:
114
+ try:
115
+ metadata = root.lstat()
116
+ except OSError as exc:
117
+ raise StorageError("repository_root_invalid") from exc
118
+ if (
119
+ not stat.S_ISDIR(metadata.st_mode)
120
+ or stat.S_ISLNK(metadata.st_mode)
121
+ or _is_reparse_point(metadata)
122
+ ):
123
+ raise StorageError("repository_root_invalid")
124
+ try:
125
+ return root.resolve(strict=True)
126
+ except OSError as exc:
127
+ raise StorageError("repository_root_invalid") from exc
128
+
129
+
130
+ def _identifier(value: object, code: str) -> str:
131
+ if not isinstance(value, str) or not _IDENTIFIER.fullmatch(value):
132
+ raise ValueError(code)
133
+ return value
134
+
135
+
136
+ def _model_identifier(value: object, code: str) -> str:
137
+ """Model names additionally permit one OpenRouter-style vendor/model slash."""
138
+ if not isinstance(value, str) or not _MODEL_IDENTIFIER.fullmatch(value):
139
+ raise ValueError(code)
140
+ return value
141
+
142
+
143
+ def _nonnegative_integer(value: object, code: str, maximum: int = 10**12) -> int:
144
+ if isinstance(value, bool) or not isinstance(value, int) or value < 0 or value > maximum:
145
+ raise ValueError(code)
146
+ return value
147
+
148
+
149
+ def _positive_integer(value: object, code: str, maximum: int = 10**12) -> int:
150
+ normalized = _nonnegative_integer(value, code, maximum)
151
+ if normalized == 0:
152
+ raise ValueError(code)
153
+ return normalized
154
+
155
+
156
+ def _semantic_version(value: object, code: str) -> str:
157
+ if not isinstance(value, str) or _SEMANTIC_VERSION.fullmatch(value) is None:
158
+ raise ValueError(code)
159
+ return value
160
+
161
+
162
+ def _translate_database_error(exc: sqlite3.Error) -> StorageError:
163
+ message = str(exc).casefold()
164
+ for code in (
165
+ "lifecycle_snapshot_binding_missing",
166
+ "lifecycle_approval_binding_missing",
167
+ "lifecycle_snapshot_binding_immutable",
168
+ "lifecycle_approval_binding_immutable",
169
+ "lifecycle_attempt_binding_immutable",
170
+ "lifecycle_binding_carry_immutable",
171
+ ):
172
+ if code in message:
173
+ return StorageError(code)
174
+ if "locked" in message or "busy" in message:
175
+ return StorageError("storage_locked")
176
+ if "database disk image is malformed" in message or "not a database" in message:
177
+ return StorageError("storage_corrupt")
178
+ return StorageError("storage_unavailable")
179
+
180
+
181
+ def _secure_directory(path: Path) -> None:
182
+ try:
183
+ path.mkdir(parents=True, exist_ok=True, mode=0o700)
184
+ metadata = path.lstat()
185
+ if (
186
+ not stat.S_ISDIR(metadata.st_mode)
187
+ or stat.S_ISLNK(metadata.st_mode)
188
+ or _is_reparse_point(metadata)
189
+ ):
190
+ raise StorageError("storage_path_invalid")
191
+ if os.name != "nt":
192
+ path.chmod(0o700)
193
+ except StorageError:
194
+ raise
195
+ except OSError as exc:
196
+ raise StorageError("storage_unavailable") from exc
197
+
198
+
199
+ def _secure_repository_directory(root: Path, path: Path) -> None:
200
+ try:
201
+ relative = path.relative_to(root)
202
+ except ValueError as exc:
203
+ raise StorageError("storage_path_invalid") from exc
204
+ current = root
205
+ for part in relative.parts:
206
+ current /= part
207
+ try:
208
+ metadata = current.lstat()
209
+ except FileNotFoundError:
210
+ metadata = None
211
+ except OSError as exc:
212
+ raise StorageError("storage_unavailable") from exc
213
+ if metadata is not None:
214
+ if (
215
+ not stat.S_ISDIR(metadata.st_mode)
216
+ or stat.S_ISLNK(metadata.st_mode)
217
+ or _is_reparse_point(metadata)
218
+ ):
219
+ raise StorageError("storage_path_invalid")
220
+ else:
221
+ try:
222
+ current.mkdir(mode=0o700)
223
+ except OSError as exc:
224
+ raise StorageError("storage_unavailable") from exc
225
+ if os.name != "nt":
226
+ try:
227
+ current.chmod(0o700)
228
+ except OSError as exc:
229
+ raise StorageError("storage_unavailable") from exc
230
+
231
+
232
+ def _validate_database_file(path: Path) -> None:
233
+ try:
234
+ metadata = path.lstat()
235
+ except FileNotFoundError:
236
+ return
237
+ except OSError as exc:
238
+ raise StorageError("storage_unavailable") from exc
239
+ if (
240
+ not stat.S_ISREG(metadata.st_mode)
241
+ or stat.S_ISLNK(metadata.st_mode)
242
+ or _is_reparse_point(metadata)
243
+ ):
244
+ raise StorageError("storage_path_invalid")
245
+
246
+
247
+ def _secure_file(path: Path) -> None:
248
+ if os.name == "nt":
249
+ return
250
+ try:
251
+ path.chmod(0o600)
252
+ except OSError as exc:
253
+ raise StorageError("storage_unavailable") from exc
254
+
255
+
256
+ _CAPABILITY_SNAPSHOTS_DDL: Final = """CREATE TABLE IF NOT EXISTS capability_snapshots (
257
+ capability_snapshot_digest TEXT PRIMARY KEY CHECK(
258
+ length(capability_snapshot_digest) = 64
259
+ AND capability_snapshot_digest NOT GLOB '*[^0-9a-f]*'
260
+ ),
261
+ provider TEXT NOT NULL CHECK(provider IN ('claude-code', 'codex', 'openrouter', 'zai')),
262
+ requested_model TEXT NOT NULL,
263
+ effective_model TEXT NOT NULL,
264
+ effort TEXT NOT NULL CHECK(effort IN ('default', 'low', 'medium', 'high', 'xhigh', 'max')),
265
+ executable_sha256 TEXT NOT NULL CHECK(
266
+ length(executable_sha256) = 64
267
+ AND executable_sha256 NOT GLOB '*[^0-9a-f]*'
268
+ ),
269
+ cli_version TEXT NOT NULL,
270
+ adapter_protocol_version TEXT NOT NULL,
271
+ permission_mode TEXT NOT NULL CHECK(permission_mode IN ('read-only', 'workspace-write')),
272
+ payload_json TEXT NOT NULL,
273
+ verified_at INTEGER NOT NULL,
274
+ expires_at INTEGER NOT NULL CHECK(expires_at > verified_at)
275
+ )"""
276
+ _CAPABILITY_SNAPSHOTS_COLUMNS: Final = (
277
+ "capability_snapshot_digest, provider, requested_model, effective_model, "
278
+ "effort, executable_sha256, cli_version, adapter_protocol_version, "
279
+ "permission_mode, payload_json, verified_at, expires_at"
280
+ )
281
+ _CLI_EXECUTION_ATTEMPTS_DDL: Final = """CREATE TABLE IF NOT EXISTS cli_execution_attempts (
282
+ attempt_id TEXT PRIMARY KEY,
283
+ approval_id TEXT NOT NULL UNIQUE REFERENCES approvals(approval_id) ON DELETE RESTRICT,
284
+ task_id TEXT NOT NULL REFERENCES tasks(task_id) ON DELETE RESTRICT,
285
+ decision_id TEXT NOT NULL REFERENCES decisions(decision_id) ON DELETE RESTRICT,
286
+ worktree_id TEXT NOT NULL REFERENCES task_worktrees(worktree_id) ON DELETE RESTRICT,
287
+ provider TEXT NOT NULL CHECK(provider IN ('claude-code', 'codex', 'openrouter', 'zai')),
288
+ requested_model TEXT NOT NULL,
289
+ effective_model TEXT NOT NULL,
290
+ effort TEXT NOT NULL CHECK(effort IN ('default', 'low', 'medium', 'high', 'xhigh', 'max')),
291
+ cli_version TEXT NOT NULL,
292
+ executable_sha256 TEXT NOT NULL CHECK(
293
+ length(executable_sha256) = 64
294
+ AND executable_sha256 NOT GLOB '*[^0-9a-f]*'
295
+ ),
296
+ capability_snapshot_digest TEXT NOT NULL REFERENCES capability_snapshots(capability_snapshot_digest) ON DELETE RESTRICT,
297
+ manifest_hash TEXT NOT NULL CHECK(
298
+ length(manifest_hash) = 64 AND manifest_hash NOT GLOB '*[^0-9a-f]*'
299
+ ),
300
+ expected_prompt_hash TEXT NOT NULL CHECK(
301
+ length(expected_prompt_hash) = 64 AND expected_prompt_hash NOT GLOB '*[^0-9a-f]*'
302
+ ),
303
+ reserved_tokens INTEGER NOT NULL CHECK(reserved_tokens > 0),
304
+ status TEXT NOT NULL CHECK(status IN ('pending', 'completed', 'failed', 'persistence_failed', 'quarantined')),
305
+ failure_reason TEXT,
306
+ execution_id TEXT UNIQUE REFERENCES executions(execution_id) ON DELETE RESTRICT,
307
+ created_at INTEGER NOT NULL,
308
+ updated_at INTEGER NOT NULL
309
+ )"""
310
+ _CLI_EXECUTION_ATTEMPTS_COLUMNS: Final = (
311
+ "attempt_id, approval_id, task_id, decision_id, worktree_id, provider, "
312
+ "requested_model, effective_model, effort, cli_version, executable_sha256, "
313
+ "capability_snapshot_digest, manifest_hash, expected_prompt_hash, "
314
+ "reserved_tokens, status, failure_reason, execution_id, created_at, updated_at"
315
+ )
316
+ _PRE_V6_WIDENED_TABLES: Final = {
317
+ "capability_snapshots": (_CAPABILITY_SNAPSHOTS_DDL, _CAPABILITY_SNAPSHOTS_COLUMNS),
318
+ "cli_execution_attempts": (_CLI_EXECUTION_ATTEMPTS_DDL, _CLI_EXECUTION_ATTEMPTS_COLUMNS),
319
+ }
320
+
321
+ _SCHEMA = (
322
+ "CREATE TABLE IF NOT EXISTS schema_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)",
323
+ """CREATE TABLE IF NOT EXISTS tasks (
324
+ task_id TEXT PRIMARY KEY,
325
+ category TEXT NOT NULL,
326
+ risk TEXT NOT NULL,
327
+ objective_hash TEXT NOT NULL,
328
+ created_at INTEGER NOT NULL
329
+ )""",
330
+ """CREATE TABLE IF NOT EXISTS decisions (
331
+ decision_id TEXT PRIMARY KEY,
332
+ task_id TEXT REFERENCES tasks(task_id) ON DELETE CASCADE,
333
+ model_id TEXT,
334
+ effort TEXT,
335
+ policy_version TEXT NOT NULL,
336
+ evidence_version TEXT NOT NULL,
337
+ created_at INTEGER NOT NULL
338
+ )""",
339
+ """CREATE TABLE IF NOT EXISTS approvals (
340
+ approval_id TEXT PRIMARY KEY,
341
+ task_id TEXT REFERENCES tasks(task_id) ON DELETE CASCADE,
342
+ decision_id TEXT REFERENCES decisions(decision_id) ON DELETE CASCADE,
343
+ nonce_hash TEXT NOT NULL UNIQUE,
344
+ manifest_hash TEXT NOT NULL,
345
+ status TEXT NOT NULL,
346
+ expires_at INTEGER NOT NULL,
347
+ reserved_tokens INTEGER NOT NULL
348
+ )""",
349
+ """CREATE TABLE IF NOT EXISTS executions (
350
+ execution_id TEXT PRIMARY KEY,
351
+ idempotency_key TEXT NOT NULL UNIQUE,
352
+ task_id TEXT REFERENCES tasks(task_id) ON DELETE SET NULL,
353
+ decision_id TEXT REFERENCES decisions(decision_id) ON DELETE SET NULL,
354
+ approval_id TEXT REFERENCES approvals(approval_id) ON DELETE SET NULL,
355
+ model_id TEXT NOT NULL,
356
+ effort TEXT NOT NULL,
357
+ status TEXT NOT NULL,
358
+ reserved_tokens INTEGER NOT NULL,
359
+ actual_input_tokens INTEGER,
360
+ actual_output_tokens INTEGER,
361
+ created_at INTEGER NOT NULL,
362
+ completed_at INTEGER
363
+ )""",
364
+ """CREATE TABLE IF NOT EXISTS outcomes (
365
+ outcome_id TEXT PRIMARY KEY,
366
+ execution_id TEXT NOT NULL REFERENCES executions(execution_id) ON DELETE CASCADE,
367
+ provenance TEXT NOT NULL,
368
+ success INTEGER NOT NULL CHECK (success IN (0, 1)),
369
+ severe_failure INTEGER NOT NULL CHECK (severe_failure IN (0, 1)),
370
+ recorded_at INTEGER NOT NULL
371
+ )""",
372
+ """CREATE TABLE IF NOT EXISTS shadow_comparisons (
373
+ comparison_id TEXT PRIMARY KEY,
374
+ primary_execution_id TEXT NOT NULL REFERENCES executions(execution_id) ON DELETE CASCADE,
375
+ shadow_execution_id TEXT NOT NULL REFERENCES executions(execution_id) ON DELETE CASCADE,
376
+ verdict TEXT,
377
+ created_at INTEGER NOT NULL
378
+ )""",
379
+ """CREATE TABLE IF NOT EXISTS policy_versions (
380
+ policy_version TEXT PRIMARY KEY,
381
+ policy_hash TEXT NOT NULL,
382
+ status TEXT NOT NULL,
383
+ created_at INTEGER NOT NULL
384
+ )""",
385
+ """CREATE TABLE IF NOT EXISTS budget_ledger (
386
+ entry_id TEXT PRIMARY KEY,
387
+ approval_id TEXT REFERENCES approvals(approval_id) ON DELETE SET NULL,
388
+ execution_id TEXT REFERENCES executions(execution_id) ON DELETE SET NULL,
389
+ entry_type TEXT NOT NULL,
390
+ token_amount INTEGER NOT NULL,
391
+ created_at INTEGER NOT NULL
392
+ )""",
393
+ """CREATE TABLE IF NOT EXISTS confidence_stats (
394
+ model_id TEXT NOT NULL,
395
+ effort TEXT NOT NULL,
396
+ category TEXT NOT NULL,
397
+ risk TEXT NOT NULL,
398
+ sample_count INTEGER NOT NULL,
399
+ success_count INTEGER NOT NULL,
400
+ severe_failure_count INTEGER NOT NULL,
401
+ PRIMARY KEY (model_id, effort, category, risk)
402
+ )""",
403
+ "CREATE INDEX IF NOT EXISTS outcomes_recorded_at_idx ON outcomes(recorded_at)",
404
+ "CREATE INDEX IF NOT EXISTS executions_task_idx ON executions(task_id)",
405
+ """CREATE TABLE IF NOT EXISTS execution_evidence (
406
+ execution_id TEXT PRIMARY KEY REFERENCES executions(execution_id) ON DELETE CASCADE,
407
+ task_id TEXT NOT NULL,
408
+ decision_id TEXT NOT NULL,
409
+ graph_fingerprint TEXT NOT NULL
410
+ )""",
411
+ """CREATE TABLE IF NOT EXISTS execution_attempts (
412
+ attempt_id TEXT PRIMARY KEY,
413
+ approval_id TEXT NOT NULL UNIQUE REFERENCES approvals(approval_id) ON DELETE RESTRICT,
414
+ task_id TEXT NOT NULL REFERENCES tasks(task_id) ON DELETE RESTRICT,
415
+ decision_id TEXT NOT NULL REFERENCES decisions(decision_id) ON DELETE RESTRICT,
416
+ manifest_hash TEXT NOT NULL,
417
+ graph_fingerprint TEXT NOT NULL,
418
+ model_id TEXT NOT NULL,
419
+ effort TEXT NOT NULL,
420
+ reserved_tokens INTEGER NOT NULL,
421
+ max_input_tokens INTEGER NOT NULL,
422
+ max_output_tokens INTEGER NOT NULL,
423
+ expected_prompt_hash TEXT NOT NULL,
424
+ inventory_digest TEXT NOT NULL CHECK(
425
+ length(inventory_digest) = 64
426
+ AND inventory_digest NOT GLOB '*[^0-9a-f]*'
427
+ ),
428
+ status TEXT NOT NULL CHECK(status IN (
429
+ 'pending', 'completed', 'failed', 'persistence_failed', 'legacy_unrecoverable'
430
+ )),
431
+ failure_reason TEXT,
432
+ execution_id TEXT UNIQUE REFERENCES executions(execution_id) ON DELETE RESTRICT,
433
+ created_at INTEGER NOT NULL,
434
+ updated_at INTEGER NOT NULL
435
+ )""",
436
+ """CREATE TABLE IF NOT EXISTS staged_execution_receipts (
437
+ attempt_id TEXT PRIMARY KEY REFERENCES execution_attempts(attempt_id) ON DELETE CASCADE,
438
+ execution_id TEXT NOT NULL UNIQUE,
439
+ approval_id TEXT NOT NULL,
440
+ model_id TEXT NOT NULL,
441
+ effort TEXT NOT NULL,
442
+ outcome TEXT NOT NULL,
443
+ input_tokens INTEGER NOT NULL,
444
+ output_tokens INTEGER NOT NULL,
445
+ latency_ms INTEGER NOT NULL,
446
+ prompt_hash TEXT NOT NULL,
447
+ response_hash TEXT NOT NULL,
448
+ inventory_digest TEXT NOT NULL CHECK(
449
+ length(inventory_digest) = 64
450
+ AND inventory_digest NOT GLOB '*[^0-9a-f]*'
451
+ ),
452
+ failure_reason TEXT,
453
+ staged_at INTEGER NOT NULL
454
+ )""",
455
+ """CREATE TABLE IF NOT EXISTS execution_receipts (
456
+ execution_id TEXT PRIMARY KEY REFERENCES executions(execution_id) ON DELETE CASCADE,
457
+ approval_id TEXT NOT NULL,
458
+ model_id TEXT NOT NULL,
459
+ effort TEXT NOT NULL,
460
+ outcome TEXT NOT NULL,
461
+ input_tokens INTEGER NOT NULL,
462
+ output_tokens INTEGER NOT NULL,
463
+ latency_ms INTEGER NOT NULL,
464
+ prompt_hash TEXT NOT NULL,
465
+ response_hash TEXT NOT NULL,
466
+ failure_reason TEXT,
467
+ completed_at INTEGER NOT NULL
468
+ )""",
469
+ """CREATE TABLE IF NOT EXISTS incident_reviews (
470
+ review_id INTEGER PRIMARY KEY AUTOINCREMENT,
471
+ execution_id TEXT NOT NULL REFERENCES executions(execution_id) ON DELETE CASCADE,
472
+ reviewed_at INTEGER NOT NULL,
473
+ UNIQUE(execution_id, reviewed_at)
474
+ )""",
475
+ """CREATE TABLE IF NOT EXISTS blind_comparisons (
476
+ comparison_id TEXT PRIMARY KEY,
477
+ primary_execution_id TEXT NOT NULL,
478
+ shadow_execution_id TEXT NOT NULL,
479
+ label_a_hash TEXT NOT NULL,
480
+ label_b_hash TEXT NOT NULL,
481
+ label_a_is_shadow INTEGER NOT NULL CHECK (label_a_is_shadow IN (0, 1)),
482
+ verdict TEXT,
483
+ recorded_at INTEGER,
484
+ created_at INTEGER NOT NULL
485
+ )""",
486
+ """CREATE TABLE IF NOT EXISTS registry_snapshots (
487
+ snapshot_id INTEGER PRIMARY KEY CHECK (snapshot_id = 1),
488
+ payload_json TEXT NOT NULL,
489
+ payload_hash TEXT NOT NULL,
490
+ refreshed_at INTEGER NOT NULL,
491
+ expires_at INTEGER NOT NULL
492
+ )""",
493
+ _CAPABILITY_SNAPSHOTS_DDL,
494
+ """CREATE TABLE IF NOT EXISTS task_worktrees (
495
+ worktree_id TEXT PRIMARY KEY,
496
+ task_id TEXT NOT NULL REFERENCES tasks(task_id) ON DELETE RESTRICT,
497
+ baseline_commit TEXT NOT NULL CHECK(
498
+ (length(baseline_commit) = 40 OR length(baseline_commit) = 64)
499
+ AND baseline_commit NOT GLOB '*[^0-9a-f]*'
500
+ ),
501
+ canonical_root_hash TEXT NOT NULL CHECK(
502
+ length(canonical_root_hash) = 64
503
+ AND canonical_root_hash NOT GLOB '*[^0-9a-f]*'
504
+ ),
505
+ status TEXT NOT NULL CHECK(status IN ('prepared', 'executed', 'accepted', 'rejected', 'quarantined', 'cleaned')),
506
+ created_at INTEGER NOT NULL,
507
+ updated_at INTEGER NOT NULL
508
+ )""",
509
+ _CLI_EXECUTION_ATTEMPTS_DDL,
510
+ """CREATE TABLE IF NOT EXISTS validation_results (
511
+ attempt_id TEXT PRIMARY KEY REFERENCES cli_execution_attempts(attempt_id) ON DELETE RESTRICT,
512
+ diff_hash TEXT NOT NULL CHECK(
513
+ length(diff_hash) = 64 AND diff_hash NOT GLOB '*[^0-9a-f]*'
514
+ ),
515
+ changed_file_count INTEGER NOT NULL CHECK(changed_file_count >= 0),
516
+ changed_byte_count INTEGER NOT NULL CHECK(changed_byte_count >= 0),
517
+ outcome TEXT NOT NULL CHECK(outcome IN ('passed', 'failed', 'blocked', 'not_run')),
518
+ recorded_at INTEGER NOT NULL
519
+ )""",
520
+ """CREATE TABLE IF NOT EXISTS review_links (
521
+ review_attempt_id TEXT PRIMARY KEY REFERENCES cli_execution_attempts(attempt_id) ON DELETE RESTRICT,
522
+ primary_attempt_id TEXT NOT NULL REFERENCES cli_execution_attempts(attempt_id) ON DELETE RESTRICT,
523
+ primary_diff_hash TEXT NOT NULL CHECK(
524
+ length(primary_diff_hash) = 64 AND primary_diff_hash NOT GLOB '*[^0-9a-f]*'
525
+ ),
526
+ created_at INTEGER NOT NULL,
527
+ CHECK(review_attempt_id != primary_attempt_id)
528
+ )""",
529
+ """CREATE TABLE IF NOT EXISTS cli_telemetry_events (
530
+ event_hash TEXT PRIMARY KEY CHECK(
531
+ length(event_hash) = 64 AND event_hash NOT GLOB '*[^0-9a-f]*'
532
+ ),
533
+ payload_json TEXT NOT NULL CHECK(length(payload_json) <= 8192),
534
+ observed_at INTEGER NOT NULL
535
+ )""",
536
+ """CREATE TABLE IF NOT EXISTS cli_policy_versions (
537
+ policy_version TEXT PRIMARY KEY,
538
+ parent_version TEXT REFERENCES cli_policy_versions(policy_version) ON DELETE RESTRICT,
539
+ payload_json TEXT NOT NULL CHECK(length(payload_json) <= 16384),
540
+ payload_hash TEXT NOT NULL CHECK(
541
+ length(payload_hash) = 64 AND payload_hash NOT GLOB '*[^0-9a-f]*'
542
+ ),
543
+ signature TEXT NOT NULL CHECK(
544
+ length(signature) = 64 AND signature NOT GLOB '*[^0-9a-f]*'
545
+ ),
546
+ evidence_hash TEXT NOT NULL CHECK(
547
+ length(evidence_hash) = 64 AND evidence_hash NOT GLOB '*[^0-9a-f]*'
548
+ ),
549
+ created_at INTEGER NOT NULL
550
+ )""",
551
+ """CREATE TABLE IF NOT EXISTS cli_policy_events (
552
+ event_id INTEGER PRIMARY KEY AUTOINCREMENT,
553
+ policy_version TEXT NOT NULL REFERENCES cli_policy_versions(policy_version) ON DELETE RESTRICT,
554
+ action TEXT NOT NULL CHECK(action IN ('candidate', 'promote', 'rollback')),
555
+ prior_version TEXT REFERENCES cli_policy_versions(policy_version) ON DELETE RESTRICT,
556
+ actor TEXT NOT NULL CHECK(actor = 'human'),
557
+ created_at INTEGER NOT NULL
558
+ )""",
559
+ """CREATE TABLE IF NOT EXISTS lifecycle_snapshot_bindings (
560
+ capability_snapshot_digest TEXT PRIMARY KEY
561
+ REFERENCES capability_snapshots(capability_snapshot_digest) ON DELETE RESTRICT,
562
+ lifecycle_identity_digest TEXT NOT NULL CHECK(
563
+ length(lifecycle_identity_digest) = 64
564
+ AND lifecycle_identity_digest NOT GLOB '*[^0-9a-f]*'
565
+ ),
566
+ bound_at INTEGER NOT NULL CHECK(bound_at >= 0)
567
+ )""",
568
+ """CREATE TABLE IF NOT EXISTS lifecycle_approval_bindings (
569
+ approval_id TEXT PRIMARY KEY REFERENCES approvals(approval_id) ON DELETE RESTRICT,
570
+ capability_snapshot_digest TEXT NOT NULL
571
+ REFERENCES capability_snapshots(capability_snapshot_digest) ON DELETE RESTRICT,
572
+ lifecycle_identity_digest TEXT NOT NULL CHECK(
573
+ length(lifecycle_identity_digest) = 64
574
+ AND lifecycle_identity_digest NOT GLOB '*[^0-9a-f]*'
575
+ ),
576
+ bound_at INTEGER NOT NULL CHECK(bound_at >= 0)
577
+ )""",
578
+ """CREATE TABLE IF NOT EXISTS lifecycle_attempt_bindings (
579
+ attempt_id TEXT PRIMARY KEY
580
+ REFERENCES cli_execution_attempts(attempt_id) ON DELETE RESTRICT,
581
+ lifecycle_identity_digest TEXT NOT NULL CHECK(
582
+ length(lifecycle_identity_digest) = 64
583
+ AND lifecycle_identity_digest NOT GLOB '*[^0-9a-f]*'
584
+ ),
585
+ bound_at INTEGER NOT NULL CHECK(bound_at >= 0)
586
+ )""",
587
+ """CREATE TRIGGER IF NOT EXISTS lifecycle_snapshot_binding_update_guard
588
+ BEFORE UPDATE ON lifecycle_snapshot_bindings BEGIN
589
+ SELECT RAISE(ABORT, 'lifecycle_snapshot_binding_immutable');
590
+ END""",
591
+ """CREATE TRIGGER IF NOT EXISTS lifecycle_snapshot_binding_delete_guard
592
+ BEFORE DELETE ON lifecycle_snapshot_bindings BEGIN
593
+ SELECT RAISE(ABORT, 'lifecycle_snapshot_binding_immutable');
594
+ END""",
595
+ """CREATE TRIGGER IF NOT EXISTS lifecycle_approval_binding_update_guard
596
+ BEFORE UPDATE ON lifecycle_approval_bindings BEGIN
597
+ SELECT RAISE(ABORT, 'lifecycle_approval_binding_immutable');
598
+ END""",
599
+ """CREATE TRIGGER IF NOT EXISTS lifecycle_approval_binding_delete_guard
600
+ BEFORE DELETE ON lifecycle_approval_bindings BEGIN
601
+ SELECT RAISE(ABORT, 'lifecycle_approval_binding_immutable');
602
+ END""",
603
+ """CREATE TRIGGER IF NOT EXISTS lifecycle_attempt_binding_update_guard
604
+ BEFORE UPDATE ON lifecycle_attempt_bindings BEGIN
605
+ SELECT RAISE(ABORT, 'lifecycle_attempt_binding_immutable');
606
+ END""",
607
+ """CREATE TRIGGER IF NOT EXISTS lifecycle_attempt_binding_insert_guard
608
+ BEFORE INSERT ON lifecycle_attempt_bindings
609
+ WHEN NOT EXISTS (
610
+ SELECT 1
611
+ FROM cli_execution_attempts AS attempt
612
+ JOIN lifecycle_approval_bindings AS binding
613
+ ON binding.approval_id = attempt.approval_id
614
+ WHERE attempt.attempt_id = NEW.attempt_id
615
+ AND binding.lifecycle_identity_digest = NEW.lifecycle_identity_digest
616
+ )
617
+ BEGIN
618
+ SELECT RAISE(ABORT, 'lifecycle_approval_binding_missing');
619
+ END""",
620
+ """CREATE TRIGGER IF NOT EXISTS lifecycle_attempt_binding_delete_guard
621
+ BEFORE DELETE ON lifecycle_attempt_bindings BEGIN
622
+ SELECT RAISE(ABORT, 'lifecycle_attempt_binding_immutable');
623
+ END""",
624
+ """CREATE TABLE IF NOT EXISTS lifecycle_binding_carries (
625
+ carry_id INTEGER PRIMARY KEY AUTOINCREMENT,
626
+ capability_snapshot_digest TEXT NOT NULL
627
+ REFERENCES capability_snapshots(capability_snapshot_digest) ON DELETE RESTRICT,
628
+ previous_identity_digest TEXT NOT NULL CHECK(
629
+ length(previous_identity_digest) = 64
630
+ AND previous_identity_digest NOT GLOB '*[^0-9a-f]*'
631
+ ),
632
+ new_identity_digest TEXT NOT NULL CHECK(
633
+ length(new_identity_digest) = 64
634
+ AND new_identity_digest NOT GLOB '*[^0-9a-f]*'
635
+ ),
636
+ lifecycle_event_id TEXT NOT NULL CHECK(
637
+ length(lifecycle_event_id) = 64
638
+ AND lifecycle_event_id NOT GLOB '*[^0-9a-f]*'
639
+ ),
640
+ carried_at INTEGER NOT NULL CHECK(carried_at >= 0)
641
+ )""",
642
+ """CREATE TRIGGER IF NOT EXISTS lifecycle_binding_carry_update_guard
643
+ BEFORE UPDATE ON lifecycle_binding_carries BEGIN
644
+ SELECT RAISE(ABORT, 'lifecycle_binding_carry_immutable');
645
+ END""",
646
+ """CREATE TRIGGER IF NOT EXISTS lifecycle_binding_carry_delete_guard
647
+ BEFORE DELETE ON lifecycle_binding_carries BEGIN
648
+ SELECT RAISE(ABORT, 'lifecycle_binding_carry_immutable');
649
+ END""",
650
+ """CREATE TRIGGER IF NOT EXISTS lifecycle_approval_binding_insert_guard_v8
651
+ BEFORE INSERT ON lifecycle_approval_bindings
652
+ WHEN NOT EXISTS (
653
+ SELECT 1 FROM lifecycle_snapshot_bindings AS binding
654
+ WHERE binding.capability_snapshot_digest = NEW.capability_snapshot_digest
655
+ AND binding.lifecycle_identity_digest = NEW.lifecycle_identity_digest
656
+ ) AND NOT EXISTS (
657
+ SELECT 1 FROM lifecycle_binding_carries AS carry
658
+ WHERE carry.capability_snapshot_digest = NEW.capability_snapshot_digest
659
+ AND carry.new_identity_digest = NEW.lifecycle_identity_digest
660
+ )
661
+ BEGIN
662
+ SELECT RAISE(ABORT, 'lifecycle_snapshot_binding_missing');
663
+ END""",
664
+ """CREATE TRIGGER IF NOT EXISTS cli_attempt_identity_update_guard
665
+ BEFORE UPDATE OF approval_id, task_id, decision_id, worktree_id, provider,
666
+ requested_model, effective_model, effort, cli_version, executable_sha256,
667
+ capability_snapshot_digest, manifest_hash, expected_prompt_hash, reserved_tokens
668
+ ON cli_execution_attempts
669
+ BEGIN
670
+ SELECT RAISE(ABORT, 'cli_attempt_identity_immutable');
671
+ END""",
672
+ """CREATE TRIGGER IF NOT EXISTS capability_snapshot_update_guard
673
+ BEFORE UPDATE ON capability_snapshots
674
+ BEGIN
675
+ SELECT RAISE(ABORT, 'capability_snapshot_immutable');
676
+ END""",
677
+ """CREATE TRIGGER IF NOT EXISTS capability_snapshot_delete_guard
678
+ BEFORE DELETE ON capability_snapshots
679
+ BEGIN
680
+ SELECT RAISE(ABORT, 'capability_snapshot_immutable');
681
+ END""",
682
+ """CREATE TRIGGER IF NOT EXISTS cli_attempt_status_transition_guard
683
+ BEFORE UPDATE OF status ON cli_execution_attempts
684
+ WHEN NOT (
685
+ (OLD.status = 'pending' AND NEW.status IN (
686
+ 'completed', 'failed', 'persistence_failed', 'quarantined'
687
+ ))
688
+ OR (OLD.status = 'persistence_failed' AND NEW.status IN (
689
+ 'completed', 'failed', 'quarantined'
690
+ ))
691
+ OR OLD.status = NEW.status
692
+ )
693
+ BEGIN
694
+ SELECT RAISE(ABORT, 'cli_attempt_transition_invalid');
695
+ END""",
696
+ """CREATE TRIGGER IF NOT EXISTS cli_attempt_delete_guard
697
+ BEFORE DELETE ON cli_execution_attempts
698
+ BEGIN
699
+ SELECT RAISE(ABORT, 'cli_attempt_immutable');
700
+ END""",
701
+ """CREATE TRIGGER IF NOT EXISTS worktree_identity_update_guard
702
+ BEFORE UPDATE OF worktree_id, task_id, baseline_commit, canonical_root_hash, created_at
703
+ ON task_worktrees
704
+ BEGIN
705
+ SELECT RAISE(ABORT, 'worktree_identity_immutable');
706
+ END""",
707
+ """CREATE TRIGGER IF NOT EXISTS worktree_status_transition_guard
708
+ BEFORE UPDATE OF status ON task_worktrees
709
+ WHEN NOT (
710
+ (OLD.status = 'prepared' AND NEW.status IN ('executed', 'quarantined'))
711
+ OR (OLD.status = 'executed' AND NEW.status IN ('accepted', 'rejected', 'quarantined'))
712
+ OR (OLD.status IN ('accepted', 'rejected', 'quarantined') AND NEW.status = 'cleaned')
713
+ OR OLD.status = NEW.status
714
+ )
715
+ BEGIN
716
+ SELECT RAISE(ABORT, 'worktree_transition_invalid');
717
+ END""",
718
+ """CREATE TRIGGER IF NOT EXISTS worktree_delete_guard
719
+ BEFORE DELETE ON task_worktrees
720
+ BEGIN
721
+ SELECT RAISE(ABORT, 'worktree_evidence_immutable');
722
+ END""",
723
+ """CREATE TRIGGER IF NOT EXISTS validation_result_update_guard
724
+ BEFORE UPDATE ON validation_results
725
+ BEGIN
726
+ SELECT RAISE(ABORT, 'validation_result_immutable');
727
+ END""",
728
+ """CREATE TRIGGER IF NOT EXISTS validation_result_delete_guard
729
+ BEFORE DELETE ON validation_results
730
+ BEGIN
731
+ SELECT RAISE(ABORT, 'validation_result_immutable');
732
+ END""",
733
+ """CREATE TRIGGER IF NOT EXISTS review_link_update_guard
734
+ BEFORE UPDATE ON review_links
735
+ BEGIN
736
+ SELECT RAISE(ABORT, 'review_link_immutable');
737
+ END""",
738
+ """CREATE TRIGGER IF NOT EXISTS review_link_delete_guard
739
+ BEFORE DELETE ON review_links
740
+ BEGIN
741
+ SELECT RAISE(ABORT, 'review_link_immutable');
742
+ END""",
743
+ """CREATE TRIGGER IF NOT EXISTS cli_telemetry_update_guard
744
+ BEFORE UPDATE ON cli_telemetry_events
745
+ BEGIN
746
+ SELECT RAISE(ABORT, 'cli_telemetry_immutable');
747
+ END""",
748
+ """CREATE TRIGGER IF NOT EXISTS cli_telemetry_delete_guard
749
+ BEFORE DELETE ON cli_telemetry_events
750
+ BEGIN
751
+ SELECT RAISE(ABORT, 'cli_telemetry_immutable');
752
+ END""",
753
+ """CREATE TRIGGER IF NOT EXISTS cli_policy_version_update_guard
754
+ BEFORE UPDATE ON cli_policy_versions
755
+ BEGIN
756
+ SELECT RAISE(ABORT, 'cli_policy_version_immutable');
757
+ END""",
758
+ """CREATE TRIGGER IF NOT EXISTS cli_policy_version_delete_guard
759
+ BEFORE DELETE ON cli_policy_versions
760
+ BEGIN
761
+ SELECT RAISE(ABORT, 'cli_policy_version_immutable');
762
+ END""",
763
+ """CREATE TRIGGER IF NOT EXISTS cli_policy_event_update_guard
764
+ BEFORE UPDATE ON cli_policy_events
765
+ BEGIN
766
+ SELECT RAISE(ABORT, 'cli_policy_event_immutable');
767
+ END""",
768
+ """CREATE TRIGGER IF NOT EXISTS cli_policy_event_delete_guard
769
+ BEFORE DELETE ON cli_policy_events
770
+ BEGIN
771
+ SELECT RAISE(ABORT, 'cli_policy_event_immutable');
772
+ END""",
773
+ """CREATE TRIGGER IF NOT EXISTS execution_attempt_digest_insert_guard
774
+ BEFORE INSERT ON execution_attempts
775
+ WHEN NEW.inventory_digest IS NULL
776
+ OR length(NEW.inventory_digest) != 64
777
+ OR NEW.inventory_digest GLOB '*[^0-9a-f]*'
778
+ BEGIN
779
+ SELECT RAISE(ABORT, 'inventory_digest_invalid');
780
+ END""",
781
+ """CREATE TRIGGER IF NOT EXISTS execution_attempt_digest_update_guard
782
+ BEFORE UPDATE ON execution_attempts
783
+ WHEN OLD.inventory_digest IS NULL
784
+ OR NEW.inventory_digest IS NULL
785
+ OR length(NEW.inventory_digest) != 64
786
+ OR NEW.inventory_digest GLOB '*[^0-9a-f]*'
787
+ BEGIN
788
+ SELECT RAISE(ABORT, 'inventory_digest_invalid');
789
+ END""",
790
+ """CREATE TRIGGER IF NOT EXISTS execution_attempt_digest_delete_guard
791
+ BEFORE DELETE ON execution_attempts
792
+ WHEN OLD.inventory_digest IS NULL
793
+ BEGIN
794
+ SELECT RAISE(ABORT, 'inventory_digest_invalid');
795
+ END""",
796
+ """CREATE TRIGGER IF NOT EXISTS staged_receipt_digest_insert_guard
797
+ BEFORE INSERT ON staged_execution_receipts
798
+ WHEN NEW.inventory_digest IS NULL
799
+ OR length(NEW.inventory_digest) != 64
800
+ OR NEW.inventory_digest GLOB '*[^0-9a-f]*'
801
+ BEGIN
802
+ SELECT RAISE(ABORT, 'inventory_digest_invalid');
803
+ END""",
804
+ """CREATE TRIGGER IF NOT EXISTS staged_receipt_digest_update_guard
805
+ BEFORE UPDATE ON staged_execution_receipts
806
+ WHEN OLD.inventory_digest IS NULL
807
+ OR NEW.inventory_digest IS NULL
808
+ OR length(NEW.inventory_digest) != 64
809
+ OR NEW.inventory_digest GLOB '*[^0-9a-f]*'
810
+ BEGIN
811
+ SELECT RAISE(ABORT, 'inventory_digest_invalid');
812
+ END""",
813
+ """CREATE TRIGGER IF NOT EXISTS staged_receipt_digest_delete_guard
814
+ BEFORE DELETE ON staged_execution_receipts
815
+ WHEN OLD.inventory_digest IS NULL
816
+ BEGIN
817
+ SELECT RAISE(ABORT, 'inventory_digest_invalid');
818
+ END""",
819
+ )
820
+
821
+
822
+ @dataclass(frozen=True, slots=True)
823
+ class RecoverableAttemptPage:
824
+ """Bounded public recovery enumeration with an opaque validated cursor."""
825
+
826
+ attempt_ids: tuple[str, ...]
827
+ next_cursor: str | None
828
+ has_more: bool
829
+
830
+ def to_dict(self) -> dict[str, Any]:
831
+ return {
832
+ "attempts": [
833
+ {"attempt_id": attempt_id, "status": "recoverable"}
834
+ for attempt_id in self.attempt_ids
835
+ ],
836
+ "has_more": self.has_more,
837
+ "next_cursor": self.next_cursor,
838
+ }
839
+
840
+
841
+ class RepositoryStore:
842
+ """Detailed evidence that never leaves the selected repository by default."""
843
+
844
+ def __init__(self, repository_root: Path, *, busy_timeout_ms: int = BUSY_TIMEOUT_MS) -> None:
845
+ self.root = _selected_root(repository_root)
846
+ if isinstance(busy_timeout_ms, bool) or not 100 <= busy_timeout_ms <= 30_000:
847
+ raise ValueError("busy_timeout_invalid")
848
+ self.busy_timeout_ms = busy_timeout_ms
849
+ self.path = self.root / ".graphite" / "routing" / "events.sqlite3"
850
+
851
+ @property
852
+ def schema_v3_backup_path(self) -> Path:
853
+ return self.path.parent / "backups" / "events-schema-v3.sqlite3"
854
+
855
+ @property
856
+ def schema_v3_backup_marker_path(self) -> Path:
857
+ return self.path.parent / "backups" / "events-schema-v3.sha256.json"
858
+
859
+ @property
860
+ def schema_v4_backup_path(self) -> Path:
861
+ return self.path.parent / "backups" / "events-schema-v4.sqlite3"
862
+
863
+ @property
864
+ def schema_v4_backup_marker_path(self) -> Path:
865
+ return self.path.parent / "backups" / "events-schema-v4.sha256.json"
866
+
867
+ def _pre_v6_backup_path(self, source_version: str) -> Path:
868
+ return self.path.parent / "backups" / f"events-schema-v{source_version}-pre-v6.sqlite3"
869
+
870
+ def _pre_v6_backup_marker_path(self, source_version: str) -> Path:
871
+ return self.path.parent / "backups" / f"events-schema-v{source_version}-pre-v6.sha256.json"
872
+
873
+ def _pre_v7_backup_path(self, source_version: str) -> Path:
874
+ return self.path.parent / "backups" / f"events-schema-v{source_version}-pre-v7.sqlite3"
875
+
876
+ def _pre_v7_backup_marker_path(self, source_version: str) -> Path:
877
+ return self.path.parent / "backups" / f"events-schema-v{source_version}-pre-v7.sha256.json"
878
+
879
+ def _pre_v8_backup_path(self, source_version: str) -> Path:
880
+ return self.path.parent / "backups" / f"events-schema-v{source_version}-pre-v8.sqlite3"
881
+
882
+ def _pre_v8_backup_marker_path(self, source_version: str) -> Path:
883
+ return self.path.parent / "backups" / f"events-schema-v{source_version}-pre-v8.sha256.json"
884
+
885
+ def _connect(self) -> sqlite3.Connection:
886
+ # The handle is opened first and configured second, so a failure in the
887
+ # configuration leaves a live handle that the caller never receives --
888
+ # no `try/finally` upstream can reach it, and only the collector ever
889
+ # frees it. `PRAGMA journal_mode = WAL` is the realistic trigger: it
890
+ # takes a lock, so it is what answers "database is locked" when another
891
+ # connection holds one.
892
+ #
893
+ # This is the `with connection:` defect one level lower. There the
894
+ # transaction ended and the handle stayed open; here the error path drops
895
+ # the reference before anyone can own it. Both leave Windows unable to
896
+ # unlink the database, and both do it on the RECOVERY path.
897
+ connection: sqlite3.Connection | None = None
898
+ try:
899
+ connection = sqlite3.connect(
900
+ self.path,
901
+ timeout=self.busy_timeout_ms / 1_000,
902
+ isolation_level=None,
903
+ )
904
+ connection.row_factory = sqlite3.Row
905
+ connection.execute(f"PRAGMA busy_timeout = {self.busy_timeout_ms}")
906
+ connection.execute("PRAGMA foreign_keys = ON")
907
+ connection.execute("PRAGMA journal_mode = WAL")
908
+ return connection
909
+ except sqlite3.Error as exc:
910
+ if connection is not None:
911
+ # Suppressed, not swallowed: a close that also fails must not
912
+ # replace the diagnosis the caller is about to receive.
913
+ with suppress(sqlite3.Error):
914
+ connection.close()
915
+ raise _translate_database_error(exc) from exc
916
+
917
+ @contextmanager
918
+ def _connection(self) -> Iterator[sqlite3.Connection]:
919
+ """End the transaction like `with connection:` does, then actually CLOSE it.
920
+
921
+ `sqlite3.Connection.__exit__` commits or rolls back and leaves the handle
922
+ OPEN -- a long-standing trap in that API. `with self._connection() as c:`
923
+ therefore released nothing, and 51 sites across this package used it.
924
+ Measured: 934 `ResourceWarning: unclosed database` in one suite run.
925
+
926
+ Refcounting hides it: the local dies when the method returns and CPython
927
+ finalizes immediately, which is why the suite was green. It stops hiding
928
+ the moment anything else holds a frame alive -- a traceback, a debugger,
929
+ a coverage tracer. Under `pytest --cov` the v3->v4 and v4->v5 rollback
930
+ drills failed with `PermissionError: [WinError 32]`: Windows will not
931
+ unlink a file with an open handle, and rollback REPLACES the live
932
+ database. The recovery path was depending on collector timing.
933
+
934
+ The methods that manage their own transaction keep using `_connect` with
935
+ an explicit `try/finally: connection.close()` -- they were already
936
+ correct, and wrapping them here would add a second commit they did not
937
+ ask for.
938
+ """
939
+ connection = self._connect()
940
+ try:
941
+ with connection:
942
+ yield connection
943
+ finally:
944
+ connection.close()
945
+
946
+ def initialize(self) -> None:
947
+ _secure_repository_directory(self.root, self.path.parent)
948
+ _validate_database_file(self.path)
949
+ self._migrate_pre_v6_provider_widening()
950
+ self._migrate_v6_to_v7()
951
+ self._migrate_v7_to_v8()
952
+ connection: sqlite3.Connection | None = None
953
+ try:
954
+ connection = self._connect()
955
+ connection.execute("BEGIN IMMEDIATE")
956
+ connection.execute(_SCHEMA[0])
957
+ existing_version = connection.execute(
958
+ "SELECT value FROM schema_meta WHERE key = 'schema_version'"
959
+ ).fetchone()
960
+ if existing_version is not None and existing_version[0] not in {
961
+ "1", "2", "3", "4", "5", "6", "7", SCHEMA_VERSION
962
+ }:
963
+ raise StorageError("storage_schema_unsupported")
964
+ if existing_version is not None and existing_version[0] == SCHEMA_VERSION:
965
+ self._validate_v6_schema(connection)
966
+ if existing_version is not None and existing_version[0] == "5":
967
+ self._validate_v5_schema(connection)
968
+ if existing_version is not None and existing_version[0] == "1":
969
+ self._migrate_v1_attempts(connection)
970
+ if existing_version is not None and existing_version[0] in {"1", "2"}:
971
+ self._migrate_v2_digest(connection)
972
+ if existing_version is not None and existing_version[0] == "3":
973
+ self._create_schema_v3_backup()
974
+ self._migrate_v3_cli_cutover(connection)
975
+ if existing_version is not None and existing_version[0] == "4":
976
+ self._validate_v4_schema(connection)
977
+ self._create_schema_v4_backup()
978
+ for statement in _SCHEMA[1:]:
979
+ connection.execute(statement)
980
+ # _migrate_v7_to_v8 only drops this superseded trigger when the
981
+ # on-disk version is exactly "7" at the moment it runs. Stores
982
+ # migrating from v6-or-earlier never pass through that check --
983
+ # _migrate_v6_to_v7 (and the pre-v6 widening) rebuild tables
984
+ # without stamping schema_version, so it stays at the origin
985
+ # version until this transaction's INSERT below. Drop it here,
986
+ # unconditionally and idempotently, so every origin converges on
987
+ # only the v8 guard (which is a strict superset: same protection
988
+ # plus carried-identity acceptance) instead of leaving the two
989
+ # triggers to coexist and the old one to reject valid carries.
990
+ connection.execute(
991
+ "DROP TRIGGER IF EXISTS lifecycle_approval_binding_insert_guard"
992
+ )
993
+ connection.execute(
994
+ """INSERT INTO schema_meta(key, value) VALUES('schema_version', ?)
995
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value""",
996
+ (SCHEMA_VERSION,),
997
+ )
998
+ connection.commit()
999
+ result = connection.execute("PRAGMA integrity_check").fetchone()
1000
+ if result is None or result[0] != "ok":
1001
+ raise StorageError("storage_corrupt")
1002
+ except StorageError:
1003
+ if connection is not None and connection.in_transaction:
1004
+ connection.rollback()
1005
+ raise
1006
+ except sqlite3.Error as exc:
1007
+ if connection is not None and connection.in_transaction:
1008
+ connection.rollback()
1009
+ raise _translate_database_error(exc) from exc
1010
+ finally:
1011
+ if connection is not None:
1012
+ connection.close()
1013
+ _validate_database_file(self.path)
1014
+ _secure_file(self.path)
1015
+
1016
+ @staticmethod
1017
+ def _file_sha256(path: Path) -> str:
1018
+ digest = hashlib.sha256()
1019
+ try:
1020
+ with path.open("rb") as stream:
1021
+ while chunk := stream.read(1024 * 1024):
1022
+ digest.update(chunk)
1023
+ except OSError as exc:
1024
+ raise StorageError("storage_backup_failed") from exc
1025
+ return digest.hexdigest()
1026
+
1027
+ @staticmethod
1028
+ def _atomic_private_write(path: Path, payload: bytes) -> None:
1029
+ temporary = path.with_name(f".{path.name}.{secrets.token_hex(8)}.tmp")
1030
+ descriptor: int | None = None
1031
+ try:
1032
+ descriptor = os.open(
1033
+ temporary,
1034
+ os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0),
1035
+ 0o600,
1036
+ )
1037
+ written = os.write(descriptor, payload)
1038
+ if written != len(payload):
1039
+ raise StorageError("storage_backup_failed")
1040
+ os.fsync(descriptor)
1041
+ os.close(descriptor)
1042
+ descriptor = None
1043
+ os.replace(temporary, path)
1044
+ _secure_file(path)
1045
+ except StorageError:
1046
+ raise
1047
+ except OSError as exc:
1048
+ raise StorageError("storage_backup_failed") from exc
1049
+ finally:
1050
+ if descriptor is not None:
1051
+ os.close(descriptor)
1052
+ try:
1053
+ temporary.unlink(missing_ok=True)
1054
+ except OSError:
1055
+ pass
1056
+
1057
+ def _create_schema_v3_backup(self) -> None:
1058
+ self._create_schema_backup(
1059
+ source_version="3",
1060
+ backup=self.schema_v3_backup_path,
1061
+ marker=self.schema_v3_backup_marker_path,
1062
+ )
1063
+
1064
+ def _create_schema_v4_backup(self) -> None:
1065
+ self._create_schema_backup(
1066
+ source_version="4",
1067
+ backup=self.schema_v4_backup_path,
1068
+ marker=self.schema_v4_backup_marker_path,
1069
+ )
1070
+
1071
+ def _create_schema_backup(
1072
+ self, *, source_version: str, backup: Path, marker: Path
1073
+ ) -> None:
1074
+ _secure_repository_directory(self.root, backup.parent)
1075
+ temporary = backup.with_name(f".{backup.name}.{secrets.token_hex(8)}.tmp")
1076
+ try:
1077
+ _validate_database_file(backup)
1078
+ with closing(
1079
+ sqlite3.connect(self.path, timeout=self.busy_timeout_ms / 1_000)
1080
+ ) as source:
1081
+ with closing(sqlite3.connect(temporary)) as destination:
1082
+ source.backup(destination)
1083
+ integrity = destination.execute("PRAGMA integrity_check").fetchone()
1084
+ version = destination.execute(
1085
+ "SELECT value FROM schema_meta WHERE key='schema_version'"
1086
+ ).fetchone()
1087
+ if integrity != ("ok",) or version != (source_version,):
1088
+ raise StorageError("storage_backup_failed")
1089
+ _secure_file(temporary)
1090
+ os.replace(temporary, backup)
1091
+ _secure_file(backup)
1092
+ evidence = json.dumps(
1093
+ {
1094
+ "backup_sha256": self._file_sha256(backup),
1095
+ "schema_version": source_version,
1096
+ },
1097
+ sort_keys=True,
1098
+ separators=(",", ":"),
1099
+ ).encode("utf-8")
1100
+ self._atomic_private_write(marker, evidence)
1101
+ except StorageError:
1102
+ raise
1103
+ except sqlite3.Error as exc:
1104
+ raise StorageError("storage_backup_failed") from exc
1105
+ except OSError as exc:
1106
+ raise StorageError("storage_backup_failed") from exc
1107
+ finally:
1108
+ try:
1109
+ temporary.unlink(missing_ok=True)
1110
+ except OSError:
1111
+ pass
1112
+
1113
+ @staticmethod
1114
+ def _migrate_v3_cli_cutover(connection: sqlite3.Connection) -> None:
1115
+ required = {
1116
+ "attempt_id",
1117
+ "status",
1118
+ "failure_reason",
1119
+ "inventory_digest",
1120
+ }
1121
+ exists = connection.execute(
1122
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name='execution_attempts'"
1123
+ ).fetchone()
1124
+ if exists is None:
1125
+ raise StorageError("storage_migration_quarantined")
1126
+ columns = {
1127
+ str(row[1]) for row in connection.execute("PRAGMA table_info(execution_attempts)")
1128
+ }
1129
+ if not required <= columns:
1130
+ raise StorageError("storage_migration_quarantined")
1131
+ connection.execute(
1132
+ """UPDATE execution_attempts
1133
+ SET status='legacy_unrecoverable', failure_reason='legacy_provider_retired'
1134
+ WHERE status IN ('pending', 'persistence_failed')"""
1135
+ )
1136
+
1137
+ @staticmethod
1138
+ def _validate_v4_schema(connection: sqlite3.Connection) -> None:
1139
+ required_tables = {
1140
+ "capability_snapshots",
1141
+ "task_worktrees",
1142
+ "cli_execution_attempts",
1143
+ "validation_results",
1144
+ "review_links",
1145
+ }
1146
+ existing_tables = {
1147
+ str(row[0])
1148
+ for row in connection.execute(
1149
+ "SELECT name FROM sqlite_master WHERE type='table'"
1150
+ )
1151
+ }
1152
+ if not required_tables <= existing_tables:
1153
+ raise StorageError("storage_rollback_required")
1154
+ cli_columns = {
1155
+ str(row[1])
1156
+ for row in connection.execute("PRAGMA table_info(cli_execution_attempts)")
1157
+ }
1158
+ if not {
1159
+ "provider",
1160
+ "effective_model",
1161
+ "capability_snapshot_digest",
1162
+ "worktree_id",
1163
+ "status",
1164
+ } <= cli_columns:
1165
+ raise StorageError("storage_rollback_required")
1166
+ lifecycle_tables = {
1167
+ "lifecycle_snapshot_bindings",
1168
+ "lifecycle_approval_bindings",
1169
+ "lifecycle_attempt_bindings",
1170
+ }
1171
+ if lifecycle_tables & existing_tables:
1172
+ raise StorageError("storage_rollback_required")
1173
+
1174
+ @staticmethod
1175
+ def _validate_v5_schema(connection: sqlite3.Connection) -> None:
1176
+ RepositoryStore._validate_v4_core_schema(connection)
1177
+ required_tables = {
1178
+ "lifecycle_snapshot_bindings",
1179
+ "lifecycle_approval_bindings",
1180
+ "lifecycle_attempt_bindings",
1181
+ }
1182
+ existing_tables = {
1183
+ str(row[0])
1184
+ for row in connection.execute(
1185
+ "SELECT name FROM sqlite_master WHERE type='table'"
1186
+ )
1187
+ }
1188
+ if not required_tables <= existing_tables:
1189
+ raise StorageError("storage_rollback_required")
1190
+
1191
+ @staticmethod
1192
+ def _validate_v6_schema(connection: sqlite3.Connection) -> None:
1193
+ RepositoryStore._validate_v5_schema(connection)
1194
+ for table in _PRE_V6_WIDENED_TABLES:
1195
+ row = connection.execute(
1196
+ "SELECT sql FROM sqlite_master WHERE type='table' AND name=?",
1197
+ (table,),
1198
+ ).fetchone()
1199
+ if row is None or "'openrouter'" not in str(row[0]):
1200
+ raise StorageError("storage_rollback_required")
1201
+
1202
+ def _migrate_pre_v6_provider_widening(self) -> None:
1203
+ """Rebuild the two provider-constrained tables to admit 'openrouter'.
1204
+
1205
+ Runs on a dedicated connection before the main initialize transaction
1206
+ because both tables are foreign-key parents: the rebuild copies rows
1207
+ aside, recreates each table under its original name (so child
1208
+ REFERENCES clauses are never rewritten), and restores the rows, with
1209
+ foreign-key enforcement off for the duration and a full
1210
+ foreign_key_check before commit. Guard triggers dropped with the old
1211
+ tables are recreated by the normal schema pass that follows.
1212
+ """
1213
+ if not self.path.exists():
1214
+ return
1215
+ connection: sqlite3.Connection | None = None
1216
+ try:
1217
+ connection = sqlite3.connect(
1218
+ self.path,
1219
+ timeout=self.busy_timeout_ms / 1_000,
1220
+ isolation_level=None,
1221
+ )
1222
+ try:
1223
+ version = connection.execute(
1224
+ "SELECT value FROM schema_meta WHERE key='schema_version'"
1225
+ ).fetchone()
1226
+ except sqlite3.Error:
1227
+ return
1228
+ if version is None or version[0] not in {"4", "5"}:
1229
+ return
1230
+ table_sql: dict[str, str] = {}
1231
+ for table in _PRE_V6_WIDENED_TABLES:
1232
+ row = connection.execute(
1233
+ "SELECT sql FROM sqlite_master WHERE type='table' AND name=?",
1234
+ (table,),
1235
+ ).fetchone()
1236
+ if row is None:
1237
+ return
1238
+ table_sql[table] = str(row[0])
1239
+ if all("'openrouter'" in sql for sql in table_sql.values()):
1240
+ return
1241
+ self._create_schema_backup(
1242
+ source_version=version[0],
1243
+ backup=self._pre_v6_backup_path(version[0]),
1244
+ marker=self._pre_v6_backup_marker_path(version[0]),
1245
+ )
1246
+ connection.execute("PRAGMA foreign_keys=OFF")
1247
+ connection.execute("BEGIN IMMEDIATE")
1248
+ for table, (ddl, columns) in _PRE_V6_WIDENED_TABLES.items():
1249
+ before = connection.execute(
1250
+ f"SELECT COUNT(*) FROM {table}"
1251
+ ).fetchone()[0]
1252
+ connection.execute(
1253
+ f"CREATE TABLE {table}_pre_v6_copy AS SELECT * FROM {table}"
1254
+ )
1255
+ connection.execute(f"DROP TABLE {table}")
1256
+ connection.execute(ddl)
1257
+ connection.execute(
1258
+ f"INSERT INTO {table} ({columns}) "
1259
+ f"SELECT {columns} FROM {table}_pre_v6_copy"
1260
+ )
1261
+ connection.execute(f"DROP TABLE {table}_pre_v6_copy")
1262
+ after = connection.execute(
1263
+ f"SELECT COUNT(*) FROM {table}"
1264
+ ).fetchone()[0]
1265
+ if before != after:
1266
+ raise StorageError("storage_migration_quarantined")
1267
+ if connection.execute("PRAGMA foreign_key_check").fetchall():
1268
+ raise StorageError("storage_migration_quarantined")
1269
+ connection.execute("COMMIT")
1270
+ if connection.execute("PRAGMA integrity_check").fetchone() != ("ok",):
1271
+ raise StorageError("storage_corrupt")
1272
+ except StorageError:
1273
+ if connection is not None and connection.in_transaction:
1274
+ connection.execute("ROLLBACK")
1275
+ raise
1276
+ except sqlite3.Error as exc:
1277
+ if connection is not None and connection.in_transaction:
1278
+ connection.execute("ROLLBACK")
1279
+ raise _translate_database_error(exc) from exc
1280
+ finally:
1281
+ if connection is not None:
1282
+ connection.close()
1283
+
1284
+ def _migrate_v6_to_v7(self) -> None:
1285
+ """Rebuild provider-CHECK tables to admit 'zai' (schema v6->v7)."""
1286
+ if not self.path.exists():
1287
+ return
1288
+ connection: sqlite3.Connection | None = None
1289
+ try:
1290
+ connection = sqlite3.connect(
1291
+ self.path,
1292
+ timeout=self.busy_timeout_ms / 1_000,
1293
+ isolation_level=None,
1294
+ )
1295
+ try:
1296
+ version = connection.execute(
1297
+ "SELECT value FROM schema_meta WHERE key='schema_version'"
1298
+ ).fetchone()
1299
+ except sqlite3.Error:
1300
+ return
1301
+ if version is None or version[0] != "6":
1302
+ return
1303
+ table_sql: dict[str, str] = {}
1304
+ for table in _PRE_V6_WIDENED_TABLES:
1305
+ row = connection.execute(
1306
+ "SELECT sql FROM sqlite_master WHERE type='table' AND name=?",
1307
+ (table,),
1308
+ ).fetchone()
1309
+ if row is None:
1310
+ return
1311
+ table_sql[table] = str(row[0])
1312
+ if all("'zai'" in sql for sql in table_sql.values()):
1313
+ return
1314
+ self._create_schema_backup(
1315
+ source_version=version[0],
1316
+ backup=self._pre_v7_backup_path(version[0]),
1317
+ marker=self._pre_v7_backup_marker_path(version[0]),
1318
+ )
1319
+ connection.execute("PRAGMA foreign_keys=OFF")
1320
+ connection.execute("BEGIN IMMEDIATE")
1321
+ for table, (ddl, columns) in _PRE_V6_WIDENED_TABLES.items():
1322
+ before = connection.execute(
1323
+ f"SELECT COUNT(*) FROM {table}"
1324
+ ).fetchone()[0]
1325
+ connection.execute(
1326
+ f"CREATE TABLE {table}_pre_v7_copy AS SELECT * FROM {table}"
1327
+ )
1328
+ connection.execute(f"DROP TABLE {table}")
1329
+ connection.execute(ddl)
1330
+ connection.execute(
1331
+ f"INSERT INTO {table} ({columns}) "
1332
+ f"SELECT {columns} FROM {table}_pre_v7_copy"
1333
+ )
1334
+ connection.execute(f"DROP TABLE {table}_pre_v7_copy")
1335
+ after = connection.execute(
1336
+ f"SELECT COUNT(*) FROM {table}"
1337
+ ).fetchone()[0]
1338
+ if before != after:
1339
+ raise StorageError("storage_migration_quarantined")
1340
+ if connection.execute("PRAGMA foreign_key_check").fetchall():
1341
+ raise StorageError("storage_migration_quarantined")
1342
+ connection.execute("COMMIT")
1343
+ if connection.execute("PRAGMA integrity_check").fetchone() != ("ok",):
1344
+ raise StorageError("storage_corrupt")
1345
+ except StorageError:
1346
+ if connection is not None and connection.in_transaction:
1347
+ connection.execute("ROLLBACK")
1348
+ raise
1349
+ except sqlite3.Error as exc:
1350
+ if connection is not None and connection.in_transaction:
1351
+ connection.execute("ROLLBACK")
1352
+ raise _translate_database_error(exc) from exc
1353
+ finally:
1354
+ if connection is not None:
1355
+ connection.close()
1356
+
1357
+ def _migrate_v7_to_v8(self) -> None:
1358
+ """Add the carry table era: swap the approval insert guard (schema v7->v8)."""
1359
+ if not self.path.exists():
1360
+ return
1361
+ connection: sqlite3.Connection | None = None
1362
+ try:
1363
+ connection = sqlite3.connect(
1364
+ self.path,
1365
+ timeout=self.busy_timeout_ms / 1_000,
1366
+ isolation_level=None,
1367
+ )
1368
+ try:
1369
+ version = connection.execute(
1370
+ "SELECT value FROM schema_meta WHERE key='schema_version'"
1371
+ ).fetchone()
1372
+ except sqlite3.Error:
1373
+ return
1374
+ if version is None or version[0] != "7":
1375
+ return
1376
+ self._create_schema_backup(
1377
+ source_version=version[0],
1378
+ backup=self._pre_v8_backup_path(version[0]),
1379
+ marker=self._pre_v8_backup_marker_path(version[0]),
1380
+ )
1381
+ except sqlite3.Error as exc:
1382
+ if connection is not None and connection.in_transaction:
1383
+ connection.rollback()
1384
+ raise _translate_database_error(exc) from exc
1385
+ finally:
1386
+ if connection is not None:
1387
+ connection.close()
1388
+
1389
+ @staticmethod
1390
+ def _validate_v4_core_schema(connection: sqlite3.Connection) -> None:
1391
+ required_tables = {
1392
+ "capability_snapshots",
1393
+ "task_worktrees",
1394
+ "cli_execution_attempts",
1395
+ "validation_results",
1396
+ "review_links",
1397
+ }
1398
+ existing_tables = {
1399
+ str(row[0])
1400
+ for row in connection.execute(
1401
+ "SELECT name FROM sqlite_master WHERE type='table'"
1402
+ )
1403
+ }
1404
+ if not required_tables <= existing_tables:
1405
+ raise StorageError("storage_rollback_required")
1406
+ cli_columns = {
1407
+ str(row[1])
1408
+ for row in connection.execute("PRAGMA table_info(cli_execution_attempts)")
1409
+ }
1410
+ if not {
1411
+ "provider",
1412
+ "effective_model",
1413
+ "capability_snapshot_digest",
1414
+ "worktree_id",
1415
+ "status",
1416
+ } <= cli_columns:
1417
+ raise StorageError("storage_rollback_required")
1418
+
1419
+ @staticmethod
1420
+ def _migrate_v1_attempts(connection: sqlite3.Connection) -> None:
1421
+ exists = connection.execute(
1422
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name='execution_attempts'"
1423
+ ).fetchone()
1424
+ if exists is None:
1425
+ return
1426
+ columns = {
1427
+ str(row[1]) for row in connection.execute("PRAGMA table_info(execution_attempts)")
1428
+ }
1429
+ if {"max_input_tokens", "max_output_tokens", "expected_prompt_hash"} <= columns:
1430
+ return
1431
+ connection.execute("ALTER TABLE execution_attempts RENAME TO execution_attempts_v1")
1432
+ connection.execute(
1433
+ """CREATE TABLE execution_attempts (
1434
+ attempt_id TEXT PRIMARY KEY,
1435
+ approval_id TEXT NOT NULL UNIQUE REFERENCES approvals(approval_id) ON DELETE RESTRICT,
1436
+ task_id TEXT NOT NULL REFERENCES tasks(task_id) ON DELETE RESTRICT,
1437
+ decision_id TEXT NOT NULL REFERENCES decisions(decision_id) ON DELETE RESTRICT,
1438
+ manifest_hash TEXT NOT NULL,
1439
+ graph_fingerprint TEXT NOT NULL,
1440
+ model_id TEXT NOT NULL,
1441
+ effort TEXT NOT NULL,
1442
+ reserved_tokens INTEGER NOT NULL,
1443
+ max_input_tokens INTEGER,
1444
+ max_output_tokens INTEGER,
1445
+ expected_prompt_hash TEXT,
1446
+ status TEXT NOT NULL CHECK(status IN (
1447
+ 'pending', 'completed', 'failed', 'persistence_failed', 'legacy_unrecoverable'
1448
+ )),
1449
+ failure_reason TEXT,
1450
+ execution_id TEXT UNIQUE REFERENCES executions(execution_id) ON DELETE RESTRICT,
1451
+ created_at INTEGER NOT NULL,
1452
+ updated_at INTEGER NOT NULL,
1453
+ CHECK(
1454
+ status IN ('completed', 'failed', 'legacy_unrecoverable')
1455
+ OR (
1456
+ max_input_tokens IS NOT NULL AND max_input_tokens > 0
1457
+ AND max_output_tokens IS NOT NULL AND max_output_tokens > 0
1458
+ AND expected_prompt_hash IS NOT NULL
1459
+ )
1460
+ )
1461
+ )"""
1462
+ )
1463
+ connection.execute(
1464
+ """INSERT INTO execution_attempts(
1465
+ attempt_id, approval_id, task_id, decision_id, manifest_hash,
1466
+ graph_fingerprint, model_id, effort, reserved_tokens,
1467
+ max_input_tokens, max_output_tokens, expected_prompt_hash,
1468
+ status, failure_reason, execution_id, created_at, updated_at
1469
+ )
1470
+ SELECT attempt_id, approval_id, task_id, decision_id, manifest_hash,
1471
+ graph_fingerprint, model_id, effort, reserved_tokens,
1472
+ NULL, NULL, NULL,
1473
+ CASE WHEN status IN ('pending', 'persistence_failed')
1474
+ THEN 'legacy_unrecoverable' ELSE status END,
1475
+ CASE WHEN status IN ('pending', 'persistence_failed')
1476
+ THEN 'legacy_attempt_bindings_missing' ELSE failure_reason END,
1477
+ execution_id, created_at, updated_at
1478
+ FROM execution_attempts_v1"""
1479
+ )
1480
+ connection.execute("DROP TABLE execution_attempts_v1")
1481
+
1482
+ @staticmethod
1483
+ def _migrate_v2_digest(connection: sqlite3.Connection) -> None:
1484
+ exists = connection.execute(
1485
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name='execution_attempts'"
1486
+ ).fetchone()
1487
+ if exists is None:
1488
+ return
1489
+ attempt_columns = {
1490
+ str(row[1]) for row in connection.execute("PRAGMA table_info(execution_attempts)")
1491
+ }
1492
+ if "inventory_digest" not in attempt_columns:
1493
+ connection.execute("ALTER TABLE execution_attempts ADD COLUMN inventory_digest TEXT")
1494
+ connection.execute(
1495
+ """UPDATE execution_attempts
1496
+ SET status = 'legacy_unrecoverable',
1497
+ failure_reason = 'legacy_attempt_digest_missing'
1498
+ WHERE status IN ('pending', 'persistence_failed')
1499
+ AND inventory_digest IS NULL"""
1500
+ )
1501
+ staged_exists = connection.execute(
1502
+ "SELECT 1 FROM sqlite_master "
1503
+ "WHERE type='table' AND name='staged_execution_receipts'"
1504
+ ).fetchone()
1505
+ if staged_exists is not None:
1506
+ staged_columns = {
1507
+ str(row[1])
1508
+ for row in connection.execute("PRAGMA table_info(staged_execution_receipts)")
1509
+ }
1510
+ if "inventory_digest" not in staged_columns:
1511
+ connection.execute(
1512
+ "ALTER TABLE staged_execution_receipts ADD COLUMN inventory_digest TEXT"
1513
+ )
1514
+
1515
+ def integrity_check(self) -> str:
1516
+ try:
1517
+ with self._connection() as connection:
1518
+ row = connection.execute("PRAGMA integrity_check").fetchone()
1519
+ except sqlite3.Error as exc:
1520
+ raise _translate_database_error(exc) from exc
1521
+ if row is None or row[0] != "ok":
1522
+ raise StorageError("storage_corrupt")
1523
+ return "ok"
1524
+
1525
+ def pragma_state(self) -> dict[str, int | str]:
1526
+ with self._connection() as connection:
1527
+ foreign_keys = int(connection.execute("PRAGMA foreign_keys").fetchone()[0])
1528
+ journal_mode = str(connection.execute("PRAGMA journal_mode").fetchone()[0]).casefold()
1529
+ busy_timeout = int(connection.execute("PRAGMA busy_timeout").fetchone()[0])
1530
+ return {
1531
+ "foreign_keys": foreign_keys,
1532
+ "journal_mode": journal_mode,
1533
+ "busy_timeout": busy_timeout,
1534
+ }
1535
+
1536
+ def record_task(
1537
+ self,
1538
+ task_id: str,
1539
+ category: str,
1540
+ risk: str,
1541
+ objective_hash: str,
1542
+ created_at: int,
1543
+ ) -> bool:
1544
+ task_id = _identifier(task_id, "task_id_invalid")
1545
+ category = TaskCategory(category).value
1546
+ risk = RiskTier(risk).value
1547
+ if not _HEX_64.fullmatch(objective_hash):
1548
+ raise ValueError("objective_hash_invalid")
1549
+ created_at = _nonnegative_integer(created_at, "created_at_invalid")
1550
+ try:
1551
+ with self._connection() as connection:
1552
+ cursor = connection.execute(
1553
+ "INSERT OR IGNORE INTO tasks(task_id, category, risk, objective_hash, created_at) "
1554
+ "VALUES (?, ?, ?, ?, ?)",
1555
+ (task_id, category, risk, objective_hash, created_at),
1556
+ )
1557
+ return cursor.rowcount == 1
1558
+ except sqlite3.Error as exc:
1559
+ raise _translate_database_error(exc) from exc
1560
+
1561
+ def record_decision(
1562
+ self,
1563
+ decision_id: str,
1564
+ task_id: str,
1565
+ model_id: str | None,
1566
+ effort: str | None,
1567
+ policy_version: str,
1568
+ evidence_version: str,
1569
+ created_at: int,
1570
+ ) -> bool:
1571
+ values = (
1572
+ _identifier(decision_id, "decision_id_invalid"),
1573
+ _identifier(task_id, "task_id_invalid"),
1574
+ None if model_id is None else _identifier(model_id, "model_id_invalid"),
1575
+ None if effort is None else Effort(effort).value,
1576
+ _identifier(policy_version, "policy_version_invalid"),
1577
+ _identifier(evidence_version, "evidence_version_invalid"),
1578
+ _nonnegative_integer(created_at, "created_at_invalid"),
1579
+ )
1580
+ try:
1581
+ with self._connection() as connection:
1582
+ cursor = connection.execute(
1583
+ """INSERT OR IGNORE INTO decisions(
1584
+ decision_id, task_id, model_id, effort, policy_version,
1585
+ evidence_version, created_at
1586
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)""",
1587
+ values,
1588
+ )
1589
+ return cursor.rowcount == 1
1590
+ except sqlite3.Error as exc:
1591
+ raise _translate_database_error(exc) from exc
1592
+
1593
+ def insert_execution(
1594
+ self,
1595
+ *,
1596
+ execution_id: str,
1597
+ idempotency_key: str,
1598
+ task_id: str | None,
1599
+ decision_id: str | None,
1600
+ approval_id: str | None,
1601
+ model_id: str,
1602
+ effort: str,
1603
+ status: str,
1604
+ reserved_tokens: int,
1605
+ created_at: int,
1606
+ ) -> bool:
1607
+ values = {
1608
+ "execution_id": _identifier(execution_id, "execution_id_invalid"),
1609
+ "idempotency_key": _identifier(idempotency_key, "idempotency_key_invalid"),
1610
+ "task_id": None if task_id is None else _identifier(task_id, "task_id_invalid"),
1611
+ "decision_id": None if decision_id is None else _identifier(decision_id, "decision_id_invalid"),
1612
+ "approval_id": None if approval_id is None else _identifier(approval_id, "approval_id_invalid"),
1613
+ "model_id": _identifier(model_id, "model_id_invalid"),
1614
+ "effort": Effort(effort).value,
1615
+ "status": _identifier(status, "status_invalid"),
1616
+ "reserved_tokens": _nonnegative_integer(reserved_tokens, "reserved_tokens_invalid"),
1617
+ "created_at": _nonnegative_integer(created_at, "created_at_invalid"),
1618
+ }
1619
+ connection: sqlite3.Connection | None = None
1620
+ try:
1621
+ connection = self._connect()
1622
+ connection.execute("BEGIN IMMEDIATE")
1623
+ existing = connection.execute(
1624
+ "SELECT execution_id FROM executions WHERE idempotency_key = ?",
1625
+ (values["idempotency_key"],),
1626
+ ).fetchone()
1627
+ if existing is not None:
1628
+ connection.rollback()
1629
+ return False
1630
+ connection.execute(
1631
+ """INSERT INTO executions(
1632
+ execution_id, idempotency_key, task_id, decision_id, approval_id,
1633
+ model_id, effort, status, reserved_tokens, created_at
1634
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
1635
+ tuple(values.values()),
1636
+ )
1637
+ connection.commit()
1638
+ return True
1639
+ except sqlite3.Error as exc:
1640
+ if connection is not None and connection.in_transaction:
1641
+ connection.rollback()
1642
+ raise _translate_database_error(exc) from exc
1643
+ finally:
1644
+ if connection is not None:
1645
+ connection.close()
1646
+
1647
+ def record_outcome(
1648
+ self,
1649
+ outcome_id: str,
1650
+ execution_id: str,
1651
+ provenance: str,
1652
+ success: bool,
1653
+ severe_failure: bool,
1654
+ recorded_at: int,
1655
+ ) -> bool:
1656
+ outcome_id = _identifier(outcome_id, "outcome_id_invalid")
1657
+ execution_id = _identifier(execution_id, "execution_id_invalid")
1658
+ if provenance not in {"machine_verified", "ci_imported", "human", "pairwise", "reversion", "ambiguous"}:
1659
+ raise ValueError("provenance_invalid")
1660
+ if not isinstance(success, bool) or not isinstance(severe_failure, bool):
1661
+ raise ValueError("outcome_invalid")
1662
+ recorded_at = _nonnegative_integer(recorded_at, "recorded_at_invalid")
1663
+ try:
1664
+ with self._connection() as connection:
1665
+ cursor = connection.execute(
1666
+ "INSERT OR IGNORE INTO outcomes(outcome_id, execution_id, provenance, success, severe_failure, recorded_at) "
1667
+ "VALUES (?, ?, ?, ?, ?, ?)",
1668
+ (outcome_id, execution_id, provenance, int(success), int(severe_failure), recorded_at),
1669
+ )
1670
+ return cursor.rowcount == 1
1671
+ except sqlite3.Error as exc:
1672
+ raise _translate_database_error(exc) from exc
1673
+
1674
+ def link_execution_evidence(
1675
+ self,
1676
+ execution_id: str,
1677
+ task_id: str,
1678
+ decision_id: str,
1679
+ graph_fingerprint: str,
1680
+ ) -> bool:
1681
+ values = (
1682
+ _identifier(execution_id, "execution_id_invalid"),
1683
+ _identifier(task_id, "task_id_invalid"),
1684
+ _identifier(decision_id, "decision_id_invalid"),
1685
+ graph_fingerprint,
1686
+ )
1687
+ if not _HEX_64.fullmatch(graph_fingerprint):
1688
+ raise ValueError("graph_fingerprint_invalid")
1689
+ try:
1690
+ with self._connection() as connection:
1691
+ cursor = connection.execute(
1692
+ """INSERT OR IGNORE INTO execution_evidence(
1693
+ execution_id, task_id, decision_id, graph_fingerprint
1694
+ ) VALUES (?, ?, ?, ?)""",
1695
+ values,
1696
+ )
1697
+ return cursor.rowcount == 1
1698
+ except sqlite3.Error as exc:
1699
+ raise _translate_database_error(exc) from exc
1700
+
1701
+ def execution_evidence(self, execution_id: str) -> dict[str, str] | None:
1702
+ execution_id = _identifier(execution_id, "execution_id_invalid")
1703
+ with self._connection() as connection:
1704
+ row = connection.execute(
1705
+ """SELECT ee.task_id, ee.decision_id, ee.graph_fingerprint
1706
+ FROM execution_evidence ee
1707
+ JOIN executions e ON e.execution_id = ee.execution_id
1708
+ WHERE ee.execution_id = ?
1709
+ AND e.task_id = ee.task_id AND e.decision_id = ee.decision_id""",
1710
+ (execution_id,),
1711
+ ).fetchone()
1712
+ return None if row is None else dict(row)
1713
+
1714
+ def create_execution_attempt(
1715
+ self,
1716
+ *,
1717
+ attempt_id: str,
1718
+ approval_id: str,
1719
+ task_id: str,
1720
+ decision_id: str,
1721
+ manifest_hash: str,
1722
+ graph_fingerprint: str,
1723
+ model_id: str,
1724
+ effort: str,
1725
+ reserved_tokens: int,
1726
+ max_input_tokens: int,
1727
+ max_output_tokens: int,
1728
+ expected_prompt_hash: str,
1729
+ inventory_digest: str,
1730
+ created_at: int,
1731
+ ) -> bool:
1732
+ values = (
1733
+ _identifier(attempt_id, "attempt_id_invalid"),
1734
+ _identifier(approval_id, "approval_id_invalid"),
1735
+ _identifier(task_id, "task_id_invalid"),
1736
+ _identifier(decision_id, "decision_id_invalid"),
1737
+ manifest_hash,
1738
+ graph_fingerprint,
1739
+ _identifier(model_id, "model_id_invalid"),
1740
+ Effort(effort).value,
1741
+ _nonnegative_integer(reserved_tokens, "reserved_tokens_invalid"),
1742
+ _nonnegative_integer(max_input_tokens, "max_input_tokens_invalid"),
1743
+ _nonnegative_integer(max_output_tokens, "max_output_tokens_invalid"),
1744
+ expected_prompt_hash,
1745
+ inventory_digest,
1746
+ _nonnegative_integer(created_at, "created_at_invalid"),
1747
+ )
1748
+ if not _HEX_64.fullmatch(manifest_hash):
1749
+ raise ValueError("manifest_hash_invalid")
1750
+ if not _HEX_64.fullmatch(graph_fingerprint):
1751
+ raise ValueError("graph_fingerprint_invalid")
1752
+ if not _HEX_64.fullmatch(expected_prompt_hash):
1753
+ raise ValueError("prompt_hash_invalid")
1754
+ if not isinstance(inventory_digest, str) or not _HEX_64.fullmatch(inventory_digest):
1755
+ raise ValueError("inventory_digest_invalid")
1756
+ if values[9] < 1 or values[10] < 1 or values[9] + values[10] != values[8]:
1757
+ raise ValueError("token_bounds_invalid")
1758
+ connection: sqlite3.Connection | None = None
1759
+ try:
1760
+ connection = self._connect()
1761
+ connection.execute("BEGIN IMMEDIATE")
1762
+ existing = connection.execute(
1763
+ "SELECT attempt_id, task_id, decision_id, manifest_hash, "
1764
+ "graph_fingerprint, model_id, effort, reserved_tokens, max_input_tokens, "
1765
+ "max_output_tokens, expected_prompt_hash, inventory_digest "
1766
+ "FROM execution_attempts WHERE approval_id = ?",
1767
+ (values[1],),
1768
+ ).fetchone()
1769
+ if existing is not None:
1770
+ expected = (values[0], *values[2:13])
1771
+ if tuple(existing) != expected:
1772
+ raise StorageError("execution_attempt_conflict")
1773
+ connection.rollback()
1774
+ return False
1775
+ approval = connection.execute(
1776
+ "SELECT manifest_hash, reserved_tokens, status FROM approvals "
1777
+ "WHERE approval_id = ?",
1778
+ (values[1],),
1779
+ ).fetchone()
1780
+ decision = connection.execute(
1781
+ "SELECT task_id, model_id, effort FROM decisions WHERE decision_id = ?",
1782
+ (values[3],),
1783
+ ).fetchone()
1784
+ if (
1785
+ approval is None
1786
+ or tuple(approval) != (values[4], values[8], "issued")
1787
+ or decision is None
1788
+ or tuple(decision) != (values[2], values[6], values[7])
1789
+ ):
1790
+ raise StorageError("execution_attempt_conflict")
1791
+ connection.execute(
1792
+ """INSERT INTO execution_attempts(
1793
+ attempt_id, approval_id, task_id, decision_id, manifest_hash,
1794
+ graph_fingerprint, model_id, effort, reserved_tokens,
1795
+ max_input_tokens, max_output_tokens, expected_prompt_hash,
1796
+ inventory_digest, status,
1797
+ failure_reason, execution_id, created_at, updated_at
1798
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
1799
+ 'pending', NULL, NULL, ?, ?)""",
1800
+ (*values[:13], values[13], values[13]),
1801
+ )
1802
+ connection.commit()
1803
+ return True
1804
+ except StorageError:
1805
+ if connection is not None and connection.in_transaction:
1806
+ connection.rollback()
1807
+ raise
1808
+ except sqlite3.IntegrityError as exc:
1809
+ if connection is not None and connection.in_transaction:
1810
+ connection.rollback()
1811
+ raise StorageError("execution_attempt_conflict") from exc
1812
+ except sqlite3.Error as exc:
1813
+ if connection is not None and connection.in_transaction:
1814
+ connection.rollback()
1815
+ raise _translate_database_error(exc) from exc
1816
+ finally:
1817
+ if connection is not None:
1818
+ connection.close()
1819
+
1820
+ def mark_execution_attempt_failed(
1821
+ self,
1822
+ attempt_id: str,
1823
+ reason: str,
1824
+ *,
1825
+ persistence_failed: bool = False,
1826
+ updated_at: int,
1827
+ ) -> bool:
1828
+ attempt_id = _identifier(attempt_id, "attempt_id_invalid")
1829
+ reason = _identifier(reason, "failure_reason_invalid")
1830
+ updated_at = _nonnegative_integer(updated_at, "updated_at_invalid")
1831
+ status = "persistence_failed" if persistence_failed else "failed"
1832
+ try:
1833
+ with self._connection() as connection:
1834
+ cursor = connection.execute(
1835
+ """UPDATE execution_attempts
1836
+ SET status = ?, failure_reason = ?, updated_at = ?
1837
+ WHERE attempt_id = ? AND status IN ('pending', 'persistence_failed')""",
1838
+ (status, reason, updated_at, attempt_id),
1839
+ )
1840
+ if cursor.rowcount == 1:
1841
+ return True
1842
+ row = connection.execute(
1843
+ "SELECT status, failure_reason FROM execution_attempts WHERE attempt_id = ?",
1844
+ (attempt_id,),
1845
+ ).fetchone()
1846
+ if row is None:
1847
+ raise StorageError("execution_attempt_missing")
1848
+ if tuple(row) == (status, reason):
1849
+ return False
1850
+ raise StorageError("execution_attempt_conflict")
1851
+ except StorageError:
1852
+ raise
1853
+ except sqlite3.Error as exc:
1854
+ raise _translate_database_error(exc) from exc
1855
+
1856
+ @staticmethod
1857
+ def _validate_receipt_shape(receipt: ExecutionReceipt) -> None:
1858
+ if not isinstance(receipt, ExecutionReceipt):
1859
+ raise ValueError("execution_receipt_invalid")
1860
+ if (
1861
+ receipt.outcome is not ExecutionOutcome.SUCCEEDED
1862
+ or receipt.input_tokens is None
1863
+ or receipt.output_tokens is None
1864
+ or receipt.response_hash is None
1865
+ or receipt.failure_reason is not None
1866
+ ):
1867
+ raise ValueError("execution_receipt_invalid")
1868
+
1869
+ @staticmethod
1870
+ def _receipt_tuple(receipt: ExecutionReceipt) -> tuple[Any, ...]:
1871
+ return (
1872
+ receipt.approval_id, receipt.model_id, receipt.effort.value,
1873
+ receipt.outcome.value, receipt.input_tokens, receipt.output_tokens,
1874
+ receipt.latency_ms, receipt.prompt_hash, receipt.response_hash, None,
1875
+ )
1876
+
1877
+ @staticmethod
1878
+ def _receipt_from_row(row: sqlite3.Row) -> ExecutionReceipt:
1879
+ return ExecutionReceipt(
1880
+ execution_id=str(row["execution_id"]),
1881
+ approval_id=str(row["approval_id"]),
1882
+ model_id=str(row["model_id"]),
1883
+ effort=Effort(str(row["effort"])),
1884
+ outcome=ExecutionOutcome(str(row["outcome"])),
1885
+ input_tokens=int(row["input_tokens"]),
1886
+ output_tokens=int(row["output_tokens"]),
1887
+ latency_ms=int(row["latency_ms"]),
1888
+ prompt_hash=str(row["prompt_hash"]),
1889
+ response_hash=str(row["response_hash"]),
1890
+ failure_reason=None,
1891
+ )
1892
+
1893
+ @staticmethod
1894
+ def _validate_attempt_binding(attempt: sqlite3.Row, receipt: ExecutionReceipt) -> None:
1895
+ if (
1896
+ attempt["approval_id"] != receipt.approval_id
1897
+ or attempt["model_id"] != receipt.model_id
1898
+ or attempt["effort"] != receipt.effort.value
1899
+ or attempt["max_input_tokens"] is None
1900
+ or attempt["max_output_tokens"] is None
1901
+ or attempt["expected_prompt_hash"] is None
1902
+ or receipt.input_tokens > attempt["max_input_tokens"]
1903
+ or receipt.output_tokens > attempt["max_output_tokens"]
1904
+ or receipt.input_tokens + receipt.output_tokens > attempt["reserved_tokens"]
1905
+ or receipt.prompt_hash != attempt["expected_prompt_hash"]
1906
+ ):
1907
+ raise StorageError("execution_attempt_conflict")
1908
+
1909
+ @staticmethod
1910
+ def _assert_approval_consumed(
1911
+ connection: sqlite3.Connection, attempt: sqlite3.Row
1912
+ ) -> None:
1913
+ approval = connection.execute(
1914
+ "SELECT status, manifest_hash, reserved_tokens FROM approvals WHERE approval_id = ?",
1915
+ (attempt["approval_id"],),
1916
+ ).fetchone()
1917
+ decision = connection.execute(
1918
+ "SELECT task_id, model_id, effort FROM decisions WHERE decision_id = ?",
1919
+ (attempt["decision_id"],),
1920
+ ).fetchone()
1921
+ if approval is None or approval["status"] != "consumed":
1922
+ raise StorageError("approval_not_consumed")
1923
+ if (
1924
+ approval["manifest_hash"] != attempt["manifest_hash"]
1925
+ or approval["reserved_tokens"] != attempt["reserved_tokens"]
1926
+ or decision is None
1927
+ or tuple(decision)
1928
+ != (attempt["task_id"], attempt["model_id"], attempt["effort"])
1929
+ ):
1930
+ raise StorageError("execution_attempt_conflict")
1931
+
1932
+ @staticmethod
1933
+ def _finalize_rows(
1934
+ connection: sqlite3.Connection,
1935
+ attempt: sqlite3.Row,
1936
+ receipt: ExecutionReceipt,
1937
+ completed_at: int,
1938
+ ) -> None:
1939
+ connection.execute(
1940
+ """INSERT INTO executions(
1941
+ execution_id, idempotency_key, task_id, decision_id, approval_id,
1942
+ model_id, effort, status, reserved_tokens, actual_input_tokens,
1943
+ actual_output_tokens, created_at, completed_at
1944
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
1945
+ (
1946
+ receipt.execution_id, attempt["attempt_id"], attempt["task_id"],
1947
+ attempt["decision_id"], receipt.approval_id, receipt.model_id,
1948
+ receipt.effort.value, receipt.outcome.value, attempt["reserved_tokens"],
1949
+ receipt.input_tokens, receipt.output_tokens, attempt["created_at"],
1950
+ completed_at,
1951
+ ),
1952
+ )
1953
+ connection.execute(
1954
+ """INSERT INTO execution_receipts(
1955
+ execution_id, approval_id, model_id, effort, outcome, input_tokens,
1956
+ output_tokens, latency_ms, prompt_hash, response_hash, failure_reason,
1957
+ completed_at
1958
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)""",
1959
+ (
1960
+ receipt.execution_id,
1961
+ *RepositoryStore._receipt_tuple(receipt)[:-1],
1962
+ completed_at,
1963
+ ),
1964
+ )
1965
+ connection.execute(
1966
+ """INSERT INTO execution_evidence(
1967
+ execution_id, task_id, decision_id, graph_fingerprint
1968
+ ) VALUES (?, ?, ?, ?)""",
1969
+ (
1970
+ receipt.execution_id, attempt["task_id"], attempt["decision_id"],
1971
+ attempt["graph_fingerprint"],
1972
+ ),
1973
+ )
1974
+ connection.execute(
1975
+ """UPDATE execution_attempts
1976
+ SET status = 'completed', failure_reason = NULL, execution_id = ?, updated_at = ?
1977
+ WHERE attempt_id = ?""",
1978
+ (receipt.execution_id, completed_at, attempt["attempt_id"]),
1979
+ )
1980
+ connection.execute(
1981
+ "DELETE FROM staged_execution_receipts WHERE attempt_id = ?",
1982
+ (attempt["attempt_id"],),
1983
+ )
1984
+
1985
+ def finalize_execution_attempt(
1986
+ self,
1987
+ *,
1988
+ attempt_id: str,
1989
+ receipt: ExecutionReceipt,
1990
+ inventory_digest: str,
1991
+ completed_at: int,
1992
+ ) -> bool:
1993
+ attempt_id = _identifier(attempt_id, "attempt_id_invalid")
1994
+ self._validate_receipt_shape(receipt)
1995
+ if not isinstance(inventory_digest, str) or not _HEX_64.fullmatch(inventory_digest):
1996
+ raise ValueError("inventory_digest_invalid")
1997
+ completed_at = _nonnegative_integer(completed_at, "completed_at_invalid")
1998
+ connection: sqlite3.Connection | None = None
1999
+ try:
2000
+ connection = self._connect()
2001
+ connection.execute("BEGIN IMMEDIATE")
2002
+ attempt = connection.execute(
2003
+ "SELECT * FROM execution_attempts WHERE attempt_id = ?",
2004
+ (attempt_id,),
2005
+ ).fetchone()
2006
+ if attempt is None:
2007
+ raise StorageError("execution_attempt_missing")
2008
+ if attempt["status"] == "legacy_unrecoverable":
2009
+ raise StorageError(str(attempt["failure_reason"]))
2010
+ if attempt["inventory_digest"] != inventory_digest:
2011
+ raise StorageError("execution_attempt_conflict")
2012
+ self._assert_approval_consumed(connection, attempt)
2013
+ if attempt["status"] == "completed":
2014
+ stored = connection.execute(
2015
+ "SELECT approval_id, model_id, effort, outcome, input_tokens, "
2016
+ "output_tokens, latency_ms, prompt_hash, response_hash, failure_reason "
2017
+ "FROM execution_receipts WHERE execution_id = ?",
2018
+ (receipt.execution_id,),
2019
+ ).fetchone()
2020
+ expected = self._receipt_tuple(receipt)
2021
+ if attempt["execution_id"] != receipt.execution_id or tuple(stored or ()) != expected:
2022
+ raise StorageError("execution_attempt_conflict")
2023
+ connection.rollback()
2024
+ return False
2025
+ if attempt["status"] not in {"pending", "persistence_failed"}:
2026
+ raise StorageError("execution_attempt_conflict")
2027
+ self._validate_attempt_binding(attempt, receipt)
2028
+ staged = connection.execute(
2029
+ "SELECT * FROM staged_execution_receipts WHERE attempt_id = ?",
2030
+ (attempt_id,),
2031
+ ).fetchone()
2032
+ if (
2033
+ staged is not None
2034
+ and self._receipt_tuple(self._receipt_from_row(staged))
2035
+ != self._receipt_tuple(receipt)
2036
+ ):
2037
+ raise StorageError("execution_attempt_conflict")
2038
+ self._finalize_rows(connection, attempt, receipt, completed_at)
2039
+ connection.commit()
2040
+ return True
2041
+ except StorageError:
2042
+ if connection is not None and connection.in_transaction:
2043
+ connection.rollback()
2044
+ raise
2045
+ except sqlite3.Error as exc:
2046
+ if connection is not None and connection.in_transaction:
2047
+ connection.rollback()
2048
+ raise _translate_database_error(exc) from exc
2049
+ finally:
2050
+ if connection is not None:
2051
+ connection.close()
2052
+
2053
+ def stage_execution_receipt(
2054
+ self,
2055
+ *,
2056
+ attempt_id: str,
2057
+ receipt: ExecutionReceipt,
2058
+ inventory_digest: str,
2059
+ staged_at: int,
2060
+ ) -> bool:
2061
+ attempt_id = _identifier(attempt_id, "attempt_id_invalid")
2062
+ self._validate_receipt_shape(receipt)
2063
+ if not isinstance(inventory_digest, str) or not _HEX_64.fullmatch(inventory_digest):
2064
+ raise ValueError("inventory_digest_invalid")
2065
+ staged_at = _nonnegative_integer(staged_at, "staged_at_invalid")
2066
+ connection: sqlite3.Connection | None = None
2067
+ try:
2068
+ connection = self._connect()
2069
+ connection.execute("BEGIN IMMEDIATE")
2070
+ attempt = connection.execute(
2071
+ "SELECT * FROM execution_attempts WHERE attempt_id = ?", (attempt_id,)
2072
+ ).fetchone()
2073
+ if attempt is None:
2074
+ raise StorageError("execution_attempt_missing")
2075
+ if attempt["status"] == "legacy_unrecoverable":
2076
+ raise StorageError(str(attempt["failure_reason"]))
2077
+ if attempt["inventory_digest"] != inventory_digest:
2078
+ raise StorageError("execution_attempt_conflict")
2079
+ self._assert_approval_consumed(connection, attempt)
2080
+ if attempt["status"] not in {"pending", "persistence_failed"}:
2081
+ raise StorageError("execution_attempt_conflict")
2082
+ self._validate_attempt_binding(attempt, receipt)
2083
+ existing = connection.execute(
2084
+ "SELECT * FROM staged_execution_receipts WHERE attempt_id = ?",
2085
+ (attempt_id,),
2086
+ ).fetchone()
2087
+ if existing is not None:
2088
+ if (
2089
+ existing["inventory_digest"] != inventory_digest
2090
+ or self._receipt_tuple(self._receipt_from_row(existing))
2091
+ != self._receipt_tuple(receipt)
2092
+ ):
2093
+ raise StorageError("execution_attempt_conflict")
2094
+ connection.rollback()
2095
+ return False
2096
+ connection.execute(
2097
+ """INSERT INTO staged_execution_receipts(
2098
+ attempt_id, execution_id, approval_id, model_id, effort, outcome,
2099
+ input_tokens, output_tokens, latency_ms, prompt_hash, response_hash,
2100
+ inventory_digest, failure_reason, staged_at
2101
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)""",
2102
+ (
2103
+ attempt_id, receipt.execution_id,
2104
+ *self._receipt_tuple(receipt)[:-1], inventory_digest, staged_at,
2105
+ ),
2106
+ )
2107
+ connection.execute(
2108
+ """UPDATE execution_attempts SET status = 'persistence_failed',
2109
+ failure_reason = 'execution_persistence_failed', updated_at = ?
2110
+ WHERE attempt_id = ?""",
2111
+ (staged_at, attempt_id),
2112
+ )
2113
+ connection.commit()
2114
+ return True
2115
+ except StorageError:
2116
+ if connection is not None and connection.in_transaction:
2117
+ connection.rollback()
2118
+ raise
2119
+ except sqlite3.Error as exc:
2120
+ if connection is not None and connection.in_transaction:
2121
+ connection.rollback()
2122
+ raise _translate_database_error(exc) from exc
2123
+ finally:
2124
+ if connection is not None:
2125
+ connection.close()
2126
+
2127
+ def recoverable_attempts(
2128
+ self,
2129
+ *,
2130
+ limit: int = DEFAULT_RECOVERY_PAGE_SIZE,
2131
+ after: str | None = None,
2132
+ ) -> RecoverableAttemptPage:
2133
+ if (
2134
+ isinstance(limit, bool)
2135
+ or not isinstance(limit, int)
2136
+ or not 1 <= limit <= MAX_RECOVERY_PAGE_SIZE
2137
+ ):
2138
+ raise ValueError("recovery_limit_invalid")
2139
+ if after is not None:
2140
+ after = _identifier(after, "recovery_cursor_invalid")
2141
+ query = """SELECT a.attempt_id FROM execution_attempts a
2142
+ JOIN staged_execution_receipts s ON s.attempt_id = a.attempt_id
2143
+ WHERE a.status = 'persistence_failed'"""
2144
+ parameters: list[Any] = []
2145
+ if after is not None:
2146
+ query += " AND a.attempt_id > ?"
2147
+ parameters.append(after)
2148
+ query += " ORDER BY a.attempt_id LIMIT ?"
2149
+ parameters.append(limit + 1)
2150
+ try:
2151
+ with self._connection() as connection:
2152
+ rows = connection.execute(query, parameters).fetchall()
2153
+ except sqlite3.Error as exc:
2154
+ raise _translate_database_error(exc) from exc
2155
+ try:
2156
+ validated = tuple(_identifier(row[0], "storage_corrupt") for row in rows)
2157
+ except ValueError as exc:
2158
+ raise StorageError("storage_corrupt") from exc
2159
+ has_more = len(validated) > limit
2160
+ attempt_ids = validated[:limit]
2161
+ return RecoverableAttemptPage(
2162
+ attempt_ids=attempt_ids,
2163
+ next_cursor=attempt_ids[-1] if has_more else None,
2164
+ has_more=has_more,
2165
+ )
2166
+
2167
+ def reconcile_execution_attempt(
2168
+ self, attempt_id: str, *, completed_at: int
2169
+ ) -> ExecutionReceipt:
2170
+ attempt_id = _identifier(attempt_id, "attempt_id_invalid")
2171
+ completed_at = _nonnegative_integer(completed_at, "completed_at_invalid")
2172
+ connection: sqlite3.Connection | None = None
2173
+ try:
2174
+ connection = self._connect()
2175
+ connection.execute("BEGIN IMMEDIATE")
2176
+ attempt = connection.execute(
2177
+ "SELECT * FROM execution_attempts WHERE attempt_id = ?", (attempt_id,)
2178
+ ).fetchone()
2179
+ if attempt is None:
2180
+ raise StorageError("execution_attempt_missing")
2181
+ if attempt["status"] == "legacy_unrecoverable":
2182
+ raise StorageError(str(attempt["failure_reason"]))
2183
+ self._assert_approval_consumed(connection, attempt)
2184
+ if attempt["status"] == "completed":
2185
+ row = connection.execute(
2186
+ "SELECT * FROM execution_receipts WHERE execution_id = ?",
2187
+ (attempt["execution_id"],),
2188
+ ).fetchone()
2189
+ evidence = connection.execute(
2190
+ "SELECT 1 FROM execution_evidence WHERE execution_id = ?",
2191
+ (attempt["execution_id"],),
2192
+ ).fetchone()
2193
+ if row is None or evidence is None:
2194
+ raise StorageError("execution_attempt_conflict")
2195
+ receipt = self._receipt_from_row(row)
2196
+ self._validate_attempt_binding(attempt, receipt)
2197
+ connection.rollback()
2198
+ return receipt
2199
+ if attempt["status"] != "persistence_failed":
2200
+ raise StorageError("execution_attempt_conflict")
2201
+ staged = connection.execute(
2202
+ "SELECT * FROM staged_execution_receipts WHERE attempt_id = ?",
2203
+ (attempt_id,),
2204
+ ).fetchone()
2205
+ if staged is None:
2206
+ raise StorageError("execution_attempt_conflict")
2207
+ if staged["inventory_digest"] != attempt["inventory_digest"]:
2208
+ raise StorageError("execution_attempt_conflict")
2209
+ receipt = self._receipt_from_row(staged)
2210
+ self._validate_attempt_binding(attempt, receipt)
2211
+ self._finalize_rows(connection, attempt, receipt, completed_at)
2212
+ connection.commit()
2213
+ return receipt
2214
+ except StorageError:
2215
+ if connection is not None and connection.in_transaction:
2216
+ connection.rollback()
2217
+ raise
2218
+ except sqlite3.Error as exc:
2219
+ if connection is not None and connection.in_transaction:
2220
+ connection.rollback()
2221
+ raise _translate_database_error(exc) from exc
2222
+ finally:
2223
+ if connection is not None:
2224
+ connection.close()
2225
+
2226
+ def execution_attempt(self, attempt_id: str) -> dict[str, Any] | None:
2227
+ attempt_id = _identifier(attempt_id, "attempt_id_invalid")
2228
+ with self._connection() as connection:
2229
+ row = connection.execute(
2230
+ "SELECT * FROM execution_attempts WHERE attempt_id = ?",
2231
+ (attempt_id,),
2232
+ ).fetchone()
2233
+ return None if row is None else dict(row)
2234
+
2235
+ def outcome_evidence_rows(self) -> list[dict[str, Any]]:
2236
+ with self._connection() as connection:
2237
+ rows = connection.execute(
2238
+ """SELECT o.outcome_id, o.execution_id, o.provenance, o.success,
2239
+ o.severe_failure, o.recorded_at, e.model_id, e.effort,
2240
+ t.category, t.risk
2241
+ FROM outcomes o
2242
+ JOIN executions e ON e.execution_id = o.execution_id
2243
+ JOIN tasks t ON t.task_id = e.task_id
2244
+ ORDER BY o.recorded_at, o.outcome_id"""
2245
+ ).fetchall()
2246
+ return [dict(row) for row in rows]
2247
+
2248
+ def close_incident(self, execution_id: str, reviewed_at: int) -> bool:
2249
+ execution_id = _identifier(execution_id, "execution_id_invalid")
2250
+ reviewed_at = _nonnegative_integer(reviewed_at, "reviewed_at_invalid")
2251
+ try:
2252
+ with self._connection() as connection:
2253
+ cursor = connection.execute(
2254
+ "INSERT OR IGNORE INTO incident_reviews(execution_id, reviewed_at) VALUES (?, ?)",
2255
+ (execution_id, reviewed_at),
2256
+ )
2257
+ return cursor.rowcount == 1
2258
+ except sqlite3.Error as exc:
2259
+ raise _translate_database_error(exc) from exc
2260
+
2261
+ def latest_incident_review(self) -> int | None:
2262
+ with self._connection() as connection:
2263
+ row = connection.execute("SELECT MAX(reviewed_at) FROM incident_reviews").fetchone()
2264
+ return None if row is None or row[0] is None else int(row[0])
2265
+
2266
+ def reserve_shadow_budget(
2267
+ self,
2268
+ entry_id: str,
2269
+ token_amount: int,
2270
+ quota: int,
2271
+ now: int,
2272
+ ) -> bool:
2273
+ entry_id = _identifier(entry_id, "entry_id_invalid")
2274
+ token_amount = _nonnegative_integer(token_amount, "token_amount_invalid")
2275
+ quota = _nonnegative_integer(quota, "quota_invalid")
2276
+ now = _nonnegative_integer(now, "created_at_invalid")
2277
+ connection: sqlite3.Connection | None = None
2278
+ try:
2279
+ connection = self._connect()
2280
+ connection.execute("BEGIN IMMEDIATE")
2281
+ used = int(connection.execute(
2282
+ """SELECT COALESCE(SUM(token_amount), 0) FROM budget_ledger
2283
+ WHERE entry_type = 'shadow_reservation' AND created_at >= ?""",
2284
+ (max(0, now - 86_400),),
2285
+ ).fetchone()[0])
2286
+ if token_amount > quota or used + token_amount > quota:
2287
+ connection.rollback()
2288
+ return False
2289
+ connection.execute(
2290
+ """INSERT INTO budget_ledger(
2291
+ entry_id, approval_id, execution_id, entry_type, token_amount, created_at
2292
+ ) VALUES (?, NULL, NULL, 'shadow_reservation', ?, ?)""",
2293
+ (entry_id, token_amount, now),
2294
+ )
2295
+ connection.commit()
2296
+ return True
2297
+ except sqlite3.Error as exc:
2298
+ if connection is not None and connection.in_transaction:
2299
+ connection.rollback()
2300
+ raise _translate_database_error(exc) from exc
2301
+ finally:
2302
+ if connection is not None:
2303
+ connection.close()
2304
+
2305
+ def create_blind_comparison(
2306
+ self,
2307
+ *,
2308
+ comparison_id: str,
2309
+ primary_execution_id: str,
2310
+ shadow_execution_id: str,
2311
+ label_a_hash: str,
2312
+ label_b_hash: str,
2313
+ label_a_is_shadow: bool,
2314
+ created_at: int,
2315
+ ) -> None:
2316
+ for value, code in (
2317
+ (comparison_id, "comparison_id_invalid"),
2318
+ (primary_execution_id, "execution_id_invalid"),
2319
+ (shadow_execution_id, "execution_id_invalid"),
2320
+ ):
2321
+ _identifier(value, code)
2322
+ if not _HEX_64.fullmatch(label_a_hash) or not _HEX_64.fullmatch(label_b_hash):
2323
+ raise ValueError("response_hash_invalid")
2324
+ if not isinstance(label_a_is_shadow, bool):
2325
+ raise ValueError("comparison_invalid")
2326
+ created_at = _nonnegative_integer(created_at, "created_at_invalid")
2327
+ try:
2328
+ with self._connection() as connection:
2329
+ connection.execute(
2330
+ """INSERT INTO blind_comparisons(
2331
+ comparison_id, primary_execution_id, shadow_execution_id,
2332
+ label_a_hash, label_b_hash, label_a_is_shadow, created_at
2333
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)""",
2334
+ (comparison_id, primary_execution_id, shadow_execution_id,
2335
+ label_a_hash, label_b_hash, int(label_a_is_shadow), created_at),
2336
+ )
2337
+ except sqlite3.Error as exc:
2338
+ raise _translate_database_error(exc) from exc
2339
+
2340
+ def record_blind_verdict(self, comparison_id: str, verdict: str, recorded_at: int) -> bool:
2341
+ comparison_id = _identifier(comparison_id, "comparison_id_invalid")
2342
+ if verdict not in {"a", "b", "tie", "reject_both"}:
2343
+ raise ValueError("pairwise_verdict_invalid")
2344
+ recorded_at = _nonnegative_integer(recorded_at, "recorded_at_invalid")
2345
+ with self._connection() as connection:
2346
+ cursor = connection.execute(
2347
+ """UPDATE blind_comparisons SET verdict = ?, recorded_at = ?
2348
+ WHERE comparison_id = ? AND verdict IS NULL""",
2349
+ (verdict, recorded_at, comparison_id),
2350
+ )
2351
+ return cursor.rowcount == 1
2352
+
2353
+ def blind_comparison(self, comparison_id: str) -> dict[str, Any] | None:
2354
+ comparison_id = _identifier(comparison_id, "comparison_id_invalid")
2355
+ with self._connection() as connection:
2356
+ row = connection.execute(
2357
+ "SELECT * FROM blind_comparisons WHERE comparison_id = ?",
2358
+ (comparison_id,),
2359
+ ).fetchone()
2360
+ return None if row is None else dict(row)
2361
+
2362
+ def purge_outcomes_before(self, cutoff: int) -> int:
2363
+ cutoff = _nonnegative_integer(cutoff, "cutoff_invalid")
2364
+ connection: sqlite3.Connection | None = None
2365
+ try:
2366
+ connection = self._connect()
2367
+ connection.execute("BEGIN IMMEDIATE")
2368
+ deleted = connection.execute(
2369
+ "DELETE FROM outcomes WHERE recorded_at < ?",
2370
+ (cutoff,),
2371
+ ).rowcount
2372
+ connection.execute("DELETE FROM confidence_stats")
2373
+ connection.execute(
2374
+ """INSERT INTO confidence_stats(
2375
+ model_id, effort, category, risk, sample_count,
2376
+ success_count, severe_failure_count
2377
+ )
2378
+ SELECT e.model_id, e.effort, t.category, t.risk,
2379
+ COUNT(*), SUM(o.success), SUM(o.severe_failure)
2380
+ FROM outcomes o
2381
+ JOIN executions e ON e.execution_id = o.execution_id
2382
+ JOIN tasks t ON t.task_id = e.task_id
2383
+ WHERE o.provenance IN ('machine_verified', 'ci_imported')
2384
+ GROUP BY e.model_id, e.effort, t.category, t.risk"""
2385
+ )
2386
+ connection.commit()
2387
+ return int(deleted)
2388
+ except sqlite3.Error as exc:
2389
+ if connection is not None and connection.in_transaction:
2390
+ connection.rollback()
2391
+ raise _translate_database_error(exc) from exc
2392
+ finally:
2393
+ if connection is not None:
2394
+ connection.close()
2395
+
2396
+ def confidence_rows(self) -> list[dict[str, Any]]:
2397
+ with self._connection() as connection:
2398
+ rows = connection.execute(
2399
+ """SELECT model_id, effort, category, risk, sample_count,
2400
+ success_count, severe_failure_count
2401
+ FROM confidence_stats
2402
+ ORDER BY model_id, effort, category, risk"""
2403
+ ).fetchall()
2404
+ return [dict(row) for row in rows]
2405
+
2406
+ def create_task_worktree_record(
2407
+ self,
2408
+ *,
2409
+ worktree_id: str,
2410
+ task_id: str,
2411
+ baseline_commit: str,
2412
+ canonical_root_hash: str,
2413
+ created_at: int,
2414
+ ) -> bool:
2415
+ values = (
2416
+ _identifier(worktree_id, "worktree_id_invalid"),
2417
+ _identifier(task_id, "task_id_invalid"),
2418
+ baseline_commit,
2419
+ canonical_root_hash,
2420
+ _nonnegative_integer(created_at, "created_at_invalid"),
2421
+ )
2422
+ if not _GIT_OBJECT.fullmatch(baseline_commit):
2423
+ raise ValueError("baseline_commit_invalid")
2424
+ if not _HEX_64.fullmatch(canonical_root_hash):
2425
+ raise ValueError("canonical_root_hash_invalid")
2426
+ try:
2427
+ with self._connection() as connection:
2428
+ existing = connection.execute(
2429
+ "SELECT worktree_id,task_id,baseline_commit,canonical_root_hash,created_at "
2430
+ "FROM task_worktrees WHERE worktree_id=?",
2431
+ (values[0],),
2432
+ ).fetchone()
2433
+ expected = (*values,)
2434
+ if existing is not None:
2435
+ if tuple(existing) != expected:
2436
+ raise StorageError("worktree_conflict")
2437
+ return False
2438
+ connection.execute(
2439
+ "INSERT INTO task_worktrees(worktree_id,task_id,baseline_commit,"
2440
+ "canonical_root_hash,status,created_at,updated_at) "
2441
+ "VALUES (?,?,?,?,'prepared',?,?)",
2442
+ (*values, values[-1]),
2443
+ )
2444
+ return True
2445
+ except StorageError:
2446
+ raise
2447
+ except sqlite3.Error as exc:
2448
+ raise _translate_database_error(exc) from exc
2449
+
2450
+ def transition_task_worktree(
2451
+ self, worktree_id: str, *, expected_status: str, new_status: str, updated_at: int
2452
+ ) -> bool:
2453
+ worktree_id = _identifier(worktree_id, "worktree_id_invalid")
2454
+ allowed = {"prepared", "executed", "accepted", "rejected", "quarantined", "cleaned"}
2455
+ if expected_status not in allowed or new_status not in allowed:
2456
+ raise ValueError("worktree_status_invalid")
2457
+ updated_at = _nonnegative_integer(updated_at, "updated_at_invalid")
2458
+ try:
2459
+ with self._connection() as connection:
2460
+ cursor = connection.execute(
2461
+ "UPDATE task_worktrees SET status=?,updated_at=? "
2462
+ "WHERE worktree_id=? AND status=?",
2463
+ (new_status, updated_at, worktree_id, expected_status),
2464
+ )
2465
+ if cursor.rowcount == 1:
2466
+ return True
2467
+ row = connection.execute(
2468
+ "SELECT status FROM task_worktrees WHERE worktree_id=?", (worktree_id,)
2469
+ ).fetchone()
2470
+ if row is None:
2471
+ raise StorageError("worktree_missing")
2472
+ if str(row[0]) == new_status:
2473
+ return False
2474
+ raise StorageError("worktree_transition_invalid")
2475
+ except StorageError:
2476
+ raise
2477
+ except sqlite3.Error as exc:
2478
+ raise _translate_database_error(exc) from exc
2479
+
2480
+ def task_worktree_record(self, worktree_id: str) -> dict[str, Any] | None:
2481
+ worktree_id = _identifier(worktree_id, "worktree_id_invalid")
2482
+ with self._connection() as connection:
2483
+ row = connection.execute(
2484
+ "SELECT * FROM task_worktrees WHERE worktree_id=?", (worktree_id,)
2485
+ ).fetchone()
2486
+ return None if row is None else dict(row)
2487
+
2488
+ def latest_task_worktree_record(self, task_id: str) -> dict[str, Any] | None:
2489
+ task_id = _identifier(task_id, "task_id_invalid")
2490
+ with self._connection() as connection:
2491
+ row = connection.execute(
2492
+ "SELECT * FROM task_worktrees WHERE task_id=? "
2493
+ "ORDER BY created_at DESC,worktree_id DESC LIMIT 1",
2494
+ (task_id,),
2495
+ ).fetchone()
2496
+ return None if row is None else dict(row)
2497
+
2498
+ def worktree_validation_record(self, worktree_id: str) -> dict[str, Any] | None:
2499
+ worktree_id = _identifier(worktree_id, "worktree_id_invalid")
2500
+ with self._connection() as connection:
2501
+ row = connection.execute(
2502
+ """SELECT v.*,a.attempt_id,a.execution_id,a.status AS attempt_status,
2503
+ a.provider,a.task_id,a.decision_id,a.worktree_id,t.risk,t.category
2504
+ FROM cli_execution_attempts a
2505
+ JOIN validation_results v ON v.attempt_id=a.attempt_id
2506
+ JOIN tasks t ON t.task_id=a.task_id
2507
+ WHERE a.worktree_id=?
2508
+ ORDER BY a.created_at DESC,a.attempt_id DESC LIMIT 1""",
2509
+ (worktree_id,),
2510
+ ).fetchone()
2511
+ return None if row is None else dict(row)
2512
+
2513
+ def create_review_link_record(
2514
+ self,
2515
+ *,
2516
+ review_attempt_id: str,
2517
+ primary_attempt_id: str,
2518
+ primary_diff_hash: str,
2519
+ created_at: int,
2520
+ ) -> bool:
2521
+ values = (
2522
+ _identifier(review_attempt_id, "review_attempt_id_invalid"),
2523
+ _identifier(primary_attempt_id, "primary_attempt_id_invalid"),
2524
+ primary_diff_hash,
2525
+ _nonnegative_integer(created_at, "created_at_invalid"),
2526
+ )
2527
+ if values[0] == values[1]:
2528
+ raise ValueError("review_attempt_not_independent")
2529
+ if not _HEX_64.fullmatch(primary_diff_hash):
2530
+ raise ValueError("primary_diff_hash_invalid")
2531
+ try:
2532
+ with self._connection() as connection:
2533
+ cursor = connection.execute(
2534
+ "INSERT OR IGNORE INTO review_links VALUES (?,?,?,?)", values
2535
+ )
2536
+ if cursor.rowcount == 1:
2537
+ return True
2538
+ row = connection.execute(
2539
+ "SELECT review_attempt_id,primary_attempt_id,primary_diff_hash,created_at "
2540
+ "FROM review_links WHERE review_attempt_id=?",
2541
+ (values[0],),
2542
+ ).fetchone()
2543
+ if tuple(row or ()) == values:
2544
+ return False
2545
+ raise StorageError("review_link_conflict")
2546
+ except StorageError:
2547
+ raise
2548
+ except sqlite3.Error as exc:
2549
+ raise _translate_database_error(exc) from exc
2550
+
2551
+ def create_cli_execution_attempt_record(
2552
+ self,
2553
+ *,
2554
+ attempt_id: str,
2555
+ approval_id: str,
2556
+ task_id: str,
2557
+ decision_id: str,
2558
+ worktree_id: str,
2559
+ provider: str,
2560
+ requested_model: str,
2561
+ effective_model: str,
2562
+ effort: str,
2563
+ cli_version: str,
2564
+ executable_sha256: str,
2565
+ capability_snapshot_digest: str,
2566
+ manifest_hash: str,
2567
+ expected_prompt_hash: str,
2568
+ reserved_tokens: int,
2569
+ created_at: int,
2570
+ ) -> bool:
2571
+ values = (
2572
+ _identifier(attempt_id, "attempt_id_invalid"),
2573
+ _identifier(approval_id, "approval_id_invalid"),
2574
+ _identifier(task_id, "task_id_invalid"),
2575
+ _identifier(decision_id, "decision_id_invalid"),
2576
+ _identifier(worktree_id, "worktree_id_invalid"),
2577
+ ProviderId(provider).value,
2578
+ _model_identifier(requested_model, "requested_model_invalid"),
2579
+ _model_identifier(effective_model, "effective_model_invalid"),
2580
+ Effort(effort).value,
2581
+ _semantic_version(cli_version, "cli_version_invalid"),
2582
+ executable_sha256,
2583
+ capability_snapshot_digest,
2584
+ manifest_hash,
2585
+ expected_prompt_hash,
2586
+ _positive_integer(reserved_tokens, "reserved_tokens_invalid"),
2587
+ _nonnegative_integer(created_at, "created_at_invalid"),
2588
+ )
2589
+ for value, code in (
2590
+ (executable_sha256, "executable_sha256_invalid"),
2591
+ (capability_snapshot_digest, "capability_snapshot_digest_invalid"),
2592
+ (manifest_hash, "manifest_hash_invalid"),
2593
+ (expected_prompt_hash, "prompt_hash_invalid"),
2594
+ ):
2595
+ if not _HEX_64.fullmatch(value):
2596
+ raise ValueError(code)
2597
+ try:
2598
+ with self._connection() as connection:
2599
+ cursor = connection.execute(
2600
+ """INSERT OR IGNORE INTO cli_execution_attempts(
2601
+ attempt_id,approval_id,task_id,decision_id,worktree_id,provider,
2602
+ requested_model,effective_model,effort,cli_version,executable_sha256,
2603
+ capability_snapshot_digest,manifest_hash,expected_prompt_hash,
2604
+ reserved_tokens,status,failure_reason,execution_id,created_at,updated_at
2605
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,'pending',NULL,NULL,?,?)""",
2606
+ (*values, values[-1]),
2607
+ )
2608
+ if cursor.rowcount == 1:
2609
+ return True
2610
+ raise StorageError("cli_attempt_conflict")
2611
+ except StorageError:
2612
+ raise
2613
+ except sqlite3.Error as exc:
2614
+ raise _translate_database_error(exc) from exc
2615
+
2616
+ def mark_cli_execution_attempt(
2617
+ self,
2618
+ attempt_id: str,
2619
+ *,
2620
+ status: str,
2621
+ failure_reason: str | None,
2622
+ execution_id: str | None = None,
2623
+ updated_at: int,
2624
+ ) -> bool:
2625
+ attempt_id = _identifier(attempt_id, "attempt_id_invalid")
2626
+ if status not in {"completed", "failed", "persistence_failed", "quarantined"}:
2627
+ raise ValueError("cli_attempt_status_invalid")
2628
+ if failure_reason is not None:
2629
+ failure_reason = _identifier(failure_reason, "failure_reason_invalid")
2630
+ if execution_id is not None:
2631
+ execution_id = _identifier(execution_id, "execution_id_invalid")
2632
+ updated_at = _nonnegative_integer(updated_at, "updated_at_invalid")
2633
+ with self._connection() as connection:
2634
+ cursor = connection.execute(
2635
+ "UPDATE cli_execution_attempts SET status=?,failure_reason=?,execution_id=?,"
2636
+ "updated_at=? WHERE attempt_id=? AND status IN ('pending','persistence_failed')",
2637
+ (status, failure_reason, execution_id, updated_at, attempt_id),
2638
+ )
2639
+ if cursor.rowcount == 1:
2640
+ return True
2641
+ row = connection.execute(
2642
+ "SELECT status,failure_reason,execution_id FROM cli_execution_attempts "
2643
+ "WHERE attempt_id=?",
2644
+ (attempt_id,),
2645
+ ).fetchone()
2646
+ if row is None:
2647
+ raise StorageError("cli_attempt_missing")
2648
+ if tuple(row) == (status, failure_reason, execution_id):
2649
+ return False
2650
+ raise StorageError("cli_attempt_transition_invalid")
2651
+
2652
+ def record_validation_result(
2653
+ self,
2654
+ *,
2655
+ attempt_id: str,
2656
+ diff_hash: str,
2657
+ changed_file_count: int,
2658
+ changed_byte_count: int,
2659
+ outcome: str,
2660
+ recorded_at: int,
2661
+ ) -> bool:
2662
+ values = (
2663
+ _identifier(attempt_id, "attempt_id_invalid"),
2664
+ diff_hash,
2665
+ _nonnegative_integer(changed_file_count, "changed_file_count_invalid"),
2666
+ _nonnegative_integer(changed_byte_count, "changed_byte_count_invalid"),
2667
+ outcome,
2668
+ _nonnegative_integer(recorded_at, "recorded_at_invalid"),
2669
+ )
2670
+ if not _HEX_64.fullmatch(diff_hash):
2671
+ raise ValueError("diff_hash_invalid")
2672
+ if outcome not in {"passed", "failed", "blocked", "not_run"}:
2673
+ raise ValueError("validation_outcome_invalid")
2674
+ try:
2675
+ with self._connection() as connection:
2676
+ cursor = connection.execute(
2677
+ "INSERT OR IGNORE INTO validation_results VALUES (?,?,?,?,?,?)", values
2678
+ )
2679
+ if cursor.rowcount == 1:
2680
+ return True
2681
+ row = connection.execute(
2682
+ "SELECT attempt_id,diff_hash,changed_file_count,changed_byte_count,"
2683
+ "outcome,recorded_at FROM validation_results WHERE attempt_id=?",
2684
+ (values[0],),
2685
+ ).fetchone()
2686
+ if tuple(row or ()) == values:
2687
+ return False
2688
+ raise StorageError("validation_result_conflict")
2689
+ except StorageError:
2690
+ raise
2691
+ except sqlite3.Error as exc:
2692
+ raise _translate_database_error(exc) from exc
2693
+
2694
+ def finalize_cli_execution(
2695
+ self,
2696
+ *,
2697
+ attempt_id: str,
2698
+ receipt: ExecutionReceipt,
2699
+ graph_fingerprint: str,
2700
+ completed_at: int,
2701
+ ) -> bool:
2702
+ attempt_id = _identifier(attempt_id, "attempt_id_invalid")
2703
+ if not isinstance(receipt, ExecutionReceipt):
2704
+ raise ValueError("execution_receipt_invalid")
2705
+ if (
2706
+ receipt.outcome is not ExecutionOutcome.SUCCEEDED
2707
+ or receipt.input_tokens is None
2708
+ or receipt.output_tokens is None
2709
+ or receipt.response_hash is None
2710
+ or receipt.failure_reason is not None
2711
+ ):
2712
+ raise ValueError("execution_receipt_invalid")
2713
+ if not _HEX_64.fullmatch(graph_fingerprint):
2714
+ raise ValueError("graph_fingerprint_invalid")
2715
+ completed_at = _nonnegative_integer(completed_at, "completed_at_invalid")
2716
+ connection: sqlite3.Connection | None = None
2717
+ try:
2718
+ connection = self._connect()
2719
+ connection.execute("BEGIN IMMEDIATE")
2720
+ attempt = connection.execute(
2721
+ "SELECT * FROM cli_execution_attempts WHERE attempt_id=?", (attempt_id,)
2722
+ ).fetchone()
2723
+ if attempt is None:
2724
+ raise StorageError("cli_attempt_missing")
2725
+ if attempt["status"] == "completed":
2726
+ if attempt["execution_id"] != receipt.execution_id:
2727
+ raise StorageError("cli_attempt_conflict")
2728
+ connection.rollback()
2729
+ return False
2730
+ if attempt["status"] != "pending":
2731
+ raise StorageError("cli_attempt_transition_invalid")
2732
+ approval = connection.execute(
2733
+ "SELECT status,manifest_hash,reserved_tokens FROM approvals WHERE approval_id=?",
2734
+ (attempt["approval_id"],),
2735
+ ).fetchone()
2736
+ if (
2737
+ approval is None
2738
+ or approval["status"] != "consumed"
2739
+ or approval["manifest_hash"] != attempt["manifest_hash"]
2740
+ or approval["reserved_tokens"] != attempt["reserved_tokens"]
2741
+ or receipt.approval_id != attempt["approval_id"]
2742
+ or receipt.model_id != attempt["requested_model"]
2743
+ or receipt.effective_model != attempt["effective_model"]
2744
+ or receipt.effort.value != attempt["effort"]
2745
+ or receipt.cli_version != attempt["cli_version"]
2746
+ or receipt.prompt_hash != attempt["expected_prompt_hash"]
2747
+ or receipt.input_tokens + receipt.output_tokens > attempt["reserved_tokens"]
2748
+ ):
2749
+ raise StorageError("cli_attempt_conflict")
2750
+ connection.execute(
2751
+ """INSERT INTO executions(
2752
+ execution_id,idempotency_key,task_id,decision_id,approval_id,
2753
+ model_id,effort,status,reserved_tokens,actual_input_tokens,
2754
+ actual_output_tokens,created_at,completed_at
2755
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)""",
2756
+ (
2757
+ receipt.execution_id,
2758
+ attempt_id,
2759
+ attempt["task_id"],
2760
+ attempt["decision_id"],
2761
+ receipt.approval_id,
2762
+ receipt.model_id,
2763
+ receipt.effort.value,
2764
+ receipt.outcome.value,
2765
+ attempt["reserved_tokens"],
2766
+ receipt.input_tokens,
2767
+ receipt.output_tokens,
2768
+ attempt["created_at"],
2769
+ completed_at,
2770
+ ),
2771
+ )
2772
+ connection.execute(
2773
+ """INSERT INTO execution_receipts(
2774
+ execution_id,approval_id,model_id,effort,outcome,input_tokens,
2775
+ output_tokens,latency_ms,prompt_hash,response_hash,failure_reason,completed_at
2776
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,NULL,?)""",
2777
+ (
2778
+ receipt.execution_id,
2779
+ receipt.approval_id,
2780
+ receipt.model_id,
2781
+ receipt.effort.value,
2782
+ receipt.outcome.value,
2783
+ receipt.input_tokens,
2784
+ receipt.output_tokens,
2785
+ receipt.latency_ms,
2786
+ receipt.prompt_hash,
2787
+ receipt.response_hash,
2788
+ completed_at,
2789
+ ),
2790
+ )
2791
+ connection.execute(
2792
+ "INSERT INTO execution_evidence VALUES (?,?,?,?)",
2793
+ (
2794
+ receipt.execution_id,
2795
+ attempt["task_id"],
2796
+ attempt["decision_id"],
2797
+ graph_fingerprint,
2798
+ ),
2799
+ )
2800
+ connection.execute(
2801
+ "UPDATE cli_execution_attempts SET status='completed',failure_reason=NULL,"
2802
+ "execution_id=?,updated_at=? WHERE attempt_id=?",
2803
+ (receipt.execution_id, completed_at, attempt_id),
2804
+ )
2805
+ connection.commit()
2806
+ return True
2807
+ except StorageError:
2808
+ if connection is not None and connection.in_transaction:
2809
+ connection.rollback()
2810
+ raise
2811
+ except sqlite3.IntegrityError as exc:
2812
+ if connection is not None and connection.in_transaction:
2813
+ connection.rollback()
2814
+ raise StorageError("cli_attempt_conflict") from exc
2815
+ except sqlite3.Error as exc:
2816
+ if connection is not None and connection.in_transaction:
2817
+ connection.rollback()
2818
+ raise _translate_database_error(exc) from exc
2819
+ finally:
2820
+ if connection is not None:
2821
+ connection.close()
2822
+
2823
+ @staticmethod
2824
+ def _cli_telemetry_values(
2825
+ payload: dict[str, Any],
2826
+ ) -> tuple[str, str, int]:
2827
+ if not isinstance(payload, dict) or set(payload) != _CLI_TELEMETRY_KEYS:
2828
+ raise ValueError("cli_telemetry_payload_invalid")
2829
+ try:
2830
+ ProviderId(payload["provider"])
2831
+ Effort(payload["effort"])
2832
+ TaskCategory(payload["category"])
2833
+ RiskTier(payload["risk"])
2834
+ EvidenceProvenance(payload["provenance"])
2835
+ _model_identifier(payload["requested_model"], "cli_telemetry_payload_invalid")
2836
+ _model_identifier(payload["effective_model"], "cli_telemetry_payload_invalid")
2837
+ except (TypeError, ValueError) as exc:
2838
+ raise ValueError("cli_telemetry_payload_invalid") from exc
2839
+ if (
2840
+ not isinstance(payload["capability_snapshot_digest"], str)
2841
+ or _HEX_64.fullmatch(payload["capability_snapshot_digest"]) is None
2842
+ or payload["cost_status"] != "unknown"
2843
+ or payload["validation_outcome"] not in {"passed", "failed", "blocked", "not_run"}
2844
+ or payload["human_verdict"] not in {None, "accepted", "rejected", "rework"}
2845
+ or (
2846
+ payload["diff_sha256"] is not None
2847
+ and (
2848
+ not isinstance(payload["diff_sha256"], str)
2849
+ or _HEX_64.fullmatch(payload["diff_sha256"]) is None
2850
+ )
2851
+ )
2852
+ ):
2853
+ raise ValueError("cli_telemetry_payload_invalid")
2854
+ defects = payload["review_defect_classes"]
2855
+ allowed_defects = {
2856
+ "correctness", "security", "reliability", "maintainability",
2857
+ "performance", "test_coverage", "requirement_miss",
2858
+ }
2859
+ if (
2860
+ not isinstance(defects, list)
2861
+ or any(not isinstance(item, str) for item in defects)
2862
+ or len(defects) != len(set(defects))
2863
+ or not set(defects) <= allowed_defects
2864
+ ):
2865
+ raise ValueError("cli_telemetry_payload_invalid")
2866
+ for name, maximum, optional in (
2867
+ ("latency_ms", 86_400_000, True),
2868
+ ("input_tokens", 100_000_000, True),
2869
+ ("output_tokens", 100_000_000, True),
2870
+ ("changed_file_count", 100_000, False),
2871
+ ("changed_byte_count", 10**12, False),
2872
+ ("rework_count", 1_000, False),
2873
+ ("observed_at", 10**12, False),
2874
+ ):
2875
+ value = payload[name]
2876
+ if optional and value is None:
2877
+ continue
2878
+ _nonnegative_integer(value, "cli_telemetry_payload_invalid", maximum)
2879
+ try:
2880
+ canonical = json.dumps(
2881
+ payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True,
2882
+ allow_nan=False,
2883
+ )
2884
+ except (TypeError, ValueError) as exc:
2885
+ raise ValueError("cli_telemetry_payload_invalid") from exc
2886
+ if len(canonical.encode("utf-8")) > 8192:
2887
+ raise ValueError("cli_telemetry_payload_invalid")
2888
+ observed_at = _nonnegative_integer(payload["observed_at"], "observed_at_invalid")
2889
+ event_hash = hashlib.sha256(canonical.encode("utf-8")).hexdigest()
2890
+ return event_hash, canonical, observed_at
2891
+
2892
+ def record_cli_telemetry(self, payload: dict[str, Any]) -> bool:
2893
+ """Persist one immutable, allowlisted telemetry event."""
2894
+ event_hash, canonical, observed_at = self._cli_telemetry_values(payload)
2895
+ try:
2896
+ with self._connection() as connection:
2897
+ cursor = connection.execute(
2898
+ "INSERT OR IGNORE INTO cli_telemetry_events VALUES (?,?,?)",
2899
+ (event_hash, canonical, observed_at),
2900
+ )
2901
+ if cursor.rowcount == 1:
2902
+ return True
2903
+ row = connection.execute(
2904
+ "SELECT payload_json,observed_at FROM cli_telemetry_events "
2905
+ "WHERE event_hash=?", (event_hash,),
2906
+ ).fetchone()
2907
+ if tuple(row or ()) == (canonical, observed_at):
2908
+ return False
2909
+ raise StorageError("cli_telemetry_conflict")
2910
+ except StorageError:
2911
+ raise
2912
+ except sqlite3.Error as exc:
2913
+ raise _translate_database_error(exc) from exc
2914
+
2915
+ def promote_bound_capability_snapshot_record(
2916
+ self,
2917
+ *,
2918
+ source_snapshot_digest: str,
2919
+ snapshot: CapabilitySnapshot,
2920
+ lifecycle_identity_digest: str,
2921
+ bound_at: int,
2922
+ telemetry_payload: dict[str, Any],
2923
+ ) -> bool:
2924
+ """Atomically persist one validated edit-smoke promotion and its audit event."""
2925
+ if not _HEX_64.fullmatch(source_snapshot_digest):
2926
+ raise ValueError("source_snapshot_digest_invalid")
2927
+ if not isinstance(snapshot, CapabilitySnapshot) or (
2928
+ snapshot.profile.permission_mode is not PermissionMode.WORKSPACE_WRITE
2929
+ ):
2930
+ raise ValueError("capability_snapshot_invalid")
2931
+ if not _HEX_64.fullmatch(lifecycle_identity_digest):
2932
+ raise ValueError("lifecycle_identity_digest_invalid")
2933
+ bound_at = _nonnegative_integer(bound_at, "bound_at_invalid")
2934
+ event_hash, canonical, observed_at = self._cli_telemetry_values(
2935
+ telemetry_payload
2936
+ )
2937
+ if (
2938
+ telemetry_payload["capability_snapshot_digest"] != snapshot.digest
2939
+ or observed_at != bound_at
2940
+ ):
2941
+ raise ValueError("cli_telemetry_payload_invalid")
2942
+ payload_json = json.dumps(
2943
+ snapshot.to_dict(),
2944
+ sort_keys=True,
2945
+ separators=(",", ":"),
2946
+ ensure_ascii=True,
2947
+ )
2948
+ if (
2949
+ len(snapshot.profile.supported_efforts) != 1
2950
+ or len(payload_json.encode("utf-8")) > 64 * 1024
2951
+ ):
2952
+ raise ValueError("capability_snapshot_invalid")
2953
+ values = (
2954
+ snapshot.digest,
2955
+ snapshot.profile.provider.value,
2956
+ snapshot.profile.requested_model,
2957
+ snapshot.profile.effective_model,
2958
+ snapshot.profile.supported_efforts[0].value,
2959
+ snapshot.identity.executable_sha256,
2960
+ snapshot.identity.cli_version,
2961
+ snapshot.identity.adapter_protocol_version,
2962
+ snapshot.profile.permission_mode.value,
2963
+ payload_json,
2964
+ snapshot.verified_at,
2965
+ snapshot.expires_at,
2966
+ )
2967
+ connection: sqlite3.Connection | None = None
2968
+ try:
2969
+ connection = self._connect()
2970
+ connection.execute("BEGIN IMMEDIATE")
2971
+ source = connection.execute(
2972
+ "SELECT provider,requested_model,effective_model,effort,"
2973
+ "executable_sha256,cli_version,adapter_protocol_version,"
2974
+ "permission_mode,expires_at FROM capability_snapshots "
2975
+ "WHERE capability_snapshot_digest=?",
2976
+ (source_snapshot_digest,),
2977
+ ).fetchone()
2978
+ binding = connection.execute(
2979
+ "SELECT lifecycle_identity_digest FROM lifecycle_snapshot_bindings "
2980
+ "WHERE capability_snapshot_digest=?",
2981
+ (source_snapshot_digest,),
2982
+ ).fetchone()
2983
+ expected_source = (
2984
+ snapshot.profile.provider.value,
2985
+ snapshot.profile.requested_model,
2986
+ snapshot.profile.effective_model,
2987
+ snapshot.profile.supported_efforts[0].value,
2988
+ snapshot.identity.executable_sha256,
2989
+ snapshot.identity.cli_version,
2990
+ snapshot.identity.adapter_protocol_version,
2991
+ )
2992
+ if (
2993
+ source is None
2994
+ or tuple(source[:7]) != expected_source
2995
+ or source[7] != PermissionMode.READ_ONLY.value
2996
+ or int(source[8]) <= bound_at
2997
+ or binding is None
2998
+ or binding[0] != lifecycle_identity_digest
2999
+ ):
3000
+ raise StorageError("capability_promotion_source_invalid")
3001
+ existing = connection.execute(
3002
+ "SELECT capability_snapshot_digest,provider,requested_model,"
3003
+ "effective_model,effort,executable_sha256,cli_version,"
3004
+ "adapter_protocol_version,permission_mode,payload_json,verified_at,"
3005
+ "expires_at FROM capability_snapshots "
3006
+ "WHERE capability_snapshot_digest=?",
3007
+ (snapshot.digest,),
3008
+ ).fetchone()
3009
+ if existing is not None:
3010
+ if tuple(existing) != values:
3011
+ raise StorageError("storage_corrupt")
3012
+ existing_binding = connection.execute(
3013
+ "SELECT lifecycle_identity_digest,bound_at "
3014
+ "FROM lifecycle_snapshot_bindings "
3015
+ "WHERE capability_snapshot_digest=?",
3016
+ (snapshot.digest,),
3017
+ ).fetchone()
3018
+ existing_event = connection.execute(
3019
+ "SELECT payload_json,observed_at FROM cli_telemetry_events "
3020
+ "WHERE event_hash=?",
3021
+ (event_hash,),
3022
+ ).fetchone()
3023
+ if tuple(existing_binding or ()) != (
3024
+ lifecycle_identity_digest,
3025
+ bound_at,
3026
+ ) or tuple(existing_event or ()) != (canonical, observed_at):
3027
+ raise StorageError("capability_promotion_conflict")
3028
+ connection.rollback()
3029
+ return False
3030
+ connection.execute(
3031
+ """INSERT INTO capability_snapshots(
3032
+ capability_snapshot_digest,provider,requested_model,effective_model,
3033
+ effort,executable_sha256,cli_version,adapter_protocol_version,
3034
+ permission_mode,payload_json,verified_at,expires_at
3035
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)""",
3036
+ values,
3037
+ )
3038
+ self._save_lifecycle_binding(
3039
+ connection,
3040
+ table="lifecycle_snapshot_bindings",
3041
+ key_column="capability_snapshot_digest",
3042
+ key_value=snapshot.digest,
3043
+ lifecycle_identity_digest=lifecycle_identity_digest,
3044
+ bound_at=bound_at,
3045
+ )
3046
+ connection.execute(
3047
+ "INSERT INTO cli_telemetry_events VALUES (?,?,?)",
3048
+ (event_hash, canonical, observed_at),
3049
+ )
3050
+ connection.commit()
3051
+ return True
3052
+ except StorageError:
3053
+ if connection is not None and connection.in_transaction:
3054
+ connection.rollback()
3055
+ raise
3056
+ except sqlite3.Error as exc:
3057
+ if connection is not None and connection.in_transaction:
3058
+ connection.rollback()
3059
+ raise _translate_database_error(exc) from exc
3060
+ finally:
3061
+ if connection is not None:
3062
+ connection.close()
3063
+
3064
+ def cli_telemetry_records(self, *, limit: int = 1000) -> tuple[dict[str, Any], ...]:
3065
+ if isinstance(limit, bool) or not isinstance(limit, int) or not 1 <= limit <= 10_000:
3066
+ raise ValueError("cli_telemetry_limit_invalid")
3067
+ with self._connection() as connection:
3068
+ rows = connection.execute(
3069
+ "SELECT payload_json FROM cli_telemetry_events "
3070
+ "ORDER BY observed_at DESC,event_hash DESC LIMIT ?", (limit,),
3071
+ ).fetchall()
3072
+ try:
3073
+ return tuple(json.loads(str(row[0])) for row in rows)
3074
+ except (TypeError, ValueError) as exc:
3075
+ raise StorageError("storage_corrupt") from exc
3076
+
3077
+ @staticmethod
3078
+ def _policy_signature_payload(
3079
+ policy_version: str,
3080
+ parent_version: str | None,
3081
+ payload_hash: str,
3082
+ evidence_hash: str,
3083
+ ) -> bytes:
3084
+ return json.dumps(
3085
+ {
3086
+ "evidence_hash": evidence_hash,
3087
+ "parent_version": parent_version,
3088
+ "payload_hash": payload_hash,
3089
+ "policy_version": policy_version,
3090
+ },
3091
+ sort_keys=True, separators=(",", ":"), ensure_ascii=True,
3092
+ ).encode("utf-8")
3093
+
3094
+ def record_cli_policy_candidate(
3095
+ self,
3096
+ *,
3097
+ policy_version: str,
3098
+ parent_version: str | None,
3099
+ payload: dict[str, Any],
3100
+ evidence_hash: str,
3101
+ signature: str,
3102
+ signing_key: bytes,
3103
+ created_at: int,
3104
+ ) -> bool:
3105
+ """Record a signed candidate; recording never activates it."""
3106
+ policy_version = _identifier(policy_version, "policy_version_invalid")
3107
+ if parent_version is not None:
3108
+ parent_version = _identifier(parent_version, "parent_policy_version_invalid")
3109
+ if not isinstance(payload, dict) or set(payload) != _CLI_POLICY_KEYS:
3110
+ raise ValueError("cli_policy_payload_invalid")
3111
+ if (
3112
+ payload["schema_version"] != 1
3113
+ or payload["autonomy"] is not False
3114
+ or payload["allowed_providers"] != ["claude-code", "codex"]
3115
+ or payload["permission_ceiling"] != "workspace-write"
3116
+ or payload["risk_ceilings"] != {"claude-code": "medium", "codex": "medium"}
3117
+ or payload["source_policy_version"] != "3"
3118
+ ):
3119
+ raise ValueError("cli_policy_guardrail_invalid")
3120
+ weights = payload["scoring_weights"]
3121
+ if (
3122
+ not isinstance(weights, dict)
3123
+ or not weights
3124
+ or not set(weights) <= {
3125
+ "reliability", "tokens", "latency", "scarcity",
3126
+ "effort", "confidence", "recency",
3127
+ }
3128
+ ):
3129
+ raise ValueError("cli_policy_payload_invalid")
3130
+ for value in weights.values():
3131
+ _nonnegative_integer(value, "cli_policy_payload_invalid", 1_000)
3132
+ minimum_samples = _nonnegative_integer(
3133
+ payload["minimum_samples"], "cli_policy_payload_invalid", 10_000
3134
+ )
3135
+ half_life = _nonnegative_integer(
3136
+ payload["recency_half_life_days"], "cli_policy_payload_invalid", 365
3137
+ )
3138
+ if minimum_samples < 20 or half_life < 7:
3139
+ raise ValueError("cli_policy_payload_invalid")
3140
+ if not isinstance(signing_key, bytes) or len(signing_key) < 32:
3141
+ raise ValueError("policy_signing_key_invalid")
3142
+ if not isinstance(evidence_hash, str) or not _HEX_64.fullmatch(evidence_hash):
3143
+ raise ValueError("evidence_hash_invalid")
3144
+ if not isinstance(signature, str) or not _HEX_64.fullmatch(signature):
3145
+ raise ValueError("policy_signature_invalid")
3146
+ created_at = _nonnegative_integer(created_at, "created_at_invalid")
3147
+ try:
3148
+ canonical = json.dumps(
3149
+ payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True,
3150
+ allow_nan=False,
3151
+ )
3152
+ except (TypeError, ValueError) as exc:
3153
+ raise ValueError("cli_policy_payload_invalid") from exc
3154
+ if len(canonical.encode("utf-8")) > 16384:
3155
+ raise ValueError("cli_policy_payload_invalid")
3156
+ payload_hash = hashlib.sha256(canonical.encode("utf-8")).hexdigest()
3157
+ expected = hmac.new(
3158
+ signing_key,
3159
+ self._policy_signature_payload(
3160
+ policy_version, parent_version, payload_hash, evidence_hash
3161
+ ),
3162
+ hashlib.sha256,
3163
+ ).hexdigest()
3164
+ if not hmac.compare_digest(expected, signature):
3165
+ raise ValueError("policy_signature_invalid")
3166
+ values = (
3167
+ policy_version, parent_version, canonical, payload_hash,
3168
+ signature, evidence_hash, created_at,
3169
+ )
3170
+ try:
3171
+ with self._connection() as connection:
3172
+ connection.execute("BEGIN IMMEDIATE")
3173
+ cursor = connection.execute(
3174
+ "INSERT OR IGNORE INTO cli_policy_versions VALUES (?,?,?,?,?,?,?)", values
3175
+ )
3176
+ if cursor.rowcount != 1:
3177
+ row = connection.execute(
3178
+ "SELECT policy_version,parent_version,payload_json,payload_hash,"
3179
+ "signature,evidence_hash,created_at FROM cli_policy_versions "
3180
+ "WHERE policy_version=?", (policy_version,),
3181
+ ).fetchone()
3182
+ if tuple(row or ()) == values:
3183
+ connection.rollback()
3184
+ return False
3185
+ raise StorageError("cli_policy_version_conflict")
3186
+ connection.execute(
3187
+ "INSERT INTO cli_policy_events(policy_version,action,prior_version,actor,created_at) "
3188
+ "VALUES (?,'candidate',?,'human',?)",
3189
+ (policy_version, parent_version, created_at),
3190
+ )
3191
+ connection.commit()
3192
+ return True
3193
+ except StorageError:
3194
+ raise
3195
+ except sqlite3.Error as exc:
3196
+ raise _translate_database_error(exc) from exc
3197
+
3198
+ @staticmethod
3199
+ def _active_cli_policy(connection: sqlite3.Connection) -> str | None:
3200
+ row = connection.execute(
3201
+ "SELECT policy_version FROM cli_policy_events "
3202
+ "WHERE action IN ('promote','rollback') ORDER BY event_id DESC LIMIT 1"
3203
+ ).fetchone()
3204
+ return None if row is None else str(row[0])
3205
+
3206
+ def activate_cli_policy(
3207
+ self,
3208
+ policy_version: str,
3209
+ *,
3210
+ action: str,
3211
+ authority_granted: bool,
3212
+ created_at: int,
3213
+ ) -> bool:
3214
+ """Append a human promotion or rollback without rewriting evidence."""
3215
+ policy_version = _identifier(policy_version, "policy_version_invalid")
3216
+ if action not in {"promote", "rollback"}:
3217
+ raise ValueError("policy_action_invalid")
3218
+ if authority_granted is not True:
3219
+ raise ValueError("human_authority_required")
3220
+ created_at = _nonnegative_integer(created_at, "created_at_invalid")
3221
+ try:
3222
+ with self._connection() as connection:
3223
+ connection.execute("BEGIN IMMEDIATE")
3224
+ exists = connection.execute(
3225
+ "SELECT 1 FROM cli_policy_versions WHERE policy_version=?",
3226
+ (policy_version,),
3227
+ ).fetchone()
3228
+ if exists is None:
3229
+ raise StorageError("cli_policy_version_missing")
3230
+ prior = self._active_cli_policy(connection)
3231
+ if prior == policy_version:
3232
+ connection.rollback()
3233
+ return False
3234
+ if action == "rollback":
3235
+ previously_active = connection.execute(
3236
+ "SELECT 1 FROM cli_policy_events WHERE policy_version=? "
3237
+ "AND action IN ('promote','rollback') LIMIT 1", (policy_version,),
3238
+ ).fetchone()
3239
+ if previously_active is None:
3240
+ raise StorageError("cli_policy_rollback_target_invalid")
3241
+ connection.execute(
3242
+ "INSERT INTO cli_policy_events(policy_version,action,prior_version,actor,created_at) "
3243
+ "VALUES (?,?,?,'human',?)",
3244
+ (policy_version, action, prior, created_at),
3245
+ )
3246
+ connection.commit()
3247
+ return True
3248
+ except StorageError:
3249
+ raise
3250
+ except sqlite3.Error as exc:
3251
+ raise _translate_database_error(exc) from exc
3252
+
3253
+ def active_cli_policy_version(self) -> str | None:
3254
+ with self._connection() as connection:
3255
+ return self._active_cli_policy(connection)
3256
+
3257
+ def cli_policy_event_records(self) -> tuple[dict[str, Any], ...]:
3258
+ with self._connection() as connection:
3259
+ rows = connection.execute(
3260
+ "SELECT policy_version,action,prior_version,actor,created_at "
3261
+ "FROM cli_policy_events ORDER BY event_id"
3262
+ ).fetchall()
3263
+ return tuple(dict(row) for row in rows)
3264
+
3265
+ def row_count(self, table: str) -> int:
3266
+ if table not in _TABLES:
3267
+ raise ValueError("table_invalid")
3268
+ with self._connection() as connection:
3269
+ return int(connection.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0])
3270
+
3271
+ def save_capability_snapshot_record(self, snapshot: CapabilitySnapshot) -> bool:
3272
+ if not isinstance(snapshot, CapabilitySnapshot):
3273
+ raise ValueError("capability_snapshot_invalid")
3274
+ if len(snapshot.profile.supported_efforts) != 1:
3275
+ raise ValueError("capability_snapshot_invalid")
3276
+ payload_json = json.dumps(
3277
+ snapshot.to_dict(), sort_keys=True, separators=(",", ":"), ensure_ascii=True
3278
+ )
3279
+ if len(payload_json.encode("utf-8")) > 64 * 1024:
3280
+ raise ValueError("capability_snapshot_invalid")
3281
+ values = (
3282
+ snapshot.digest,
3283
+ snapshot.profile.provider.value,
3284
+ snapshot.profile.requested_model,
3285
+ snapshot.profile.effective_model,
3286
+ snapshot.profile.supported_efforts[0].value,
3287
+ snapshot.identity.executable_sha256,
3288
+ snapshot.identity.cli_version,
3289
+ snapshot.identity.adapter_protocol_version,
3290
+ snapshot.profile.permission_mode.value,
3291
+ payload_json,
3292
+ snapshot.verified_at,
3293
+ snapshot.expires_at,
3294
+ )
3295
+ try:
3296
+ with self._connection() as connection:
3297
+ existing = connection.execute(
3298
+ "SELECT capability_snapshot_digest,provider,requested_model,"
3299
+ "effective_model,effort,executable_sha256,cli_version,"
3300
+ "adapter_protocol_version,permission_mode,payload_json,verified_at,expires_at "
3301
+ "FROM capability_snapshots WHERE capability_snapshot_digest=?",
3302
+ (snapshot.digest,),
3303
+ ).fetchone()
3304
+ if existing is not None:
3305
+ if tuple(existing) != values:
3306
+ raise StorageError("storage_corrupt")
3307
+ return False
3308
+ connection.execute(
3309
+ """INSERT INTO capability_snapshots(
3310
+ capability_snapshot_digest,provider,requested_model,effective_model,
3311
+ effort,executable_sha256,cli_version,adapter_protocol_version,
3312
+ permission_mode,payload_json,verified_at,expires_at
3313
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)""",
3314
+ values,
3315
+ )
3316
+ return True
3317
+ except StorageError:
3318
+ raise
3319
+ except sqlite3.Error as exc:
3320
+ raise _translate_database_error(exc) from exc
3321
+
3322
+ def capability_snapshot_records(self, *, limit: int) -> tuple[dict[str, Any], ...]:
3323
+ if isinstance(limit, bool) or not isinstance(limit, int) or not 1 <= limit <= 64:
3324
+ raise ValueError("capability_snapshot_limit_invalid")
3325
+ try:
3326
+ with self._connection() as connection:
3327
+ rows = connection.execute(
3328
+ "SELECT capability_snapshot_digest,payload_json FROM capability_snapshots "
3329
+ "ORDER BY capability_snapshot_digest LIMIT ?",
3330
+ (limit + 1,),
3331
+ ).fetchall()
3332
+ except sqlite3.Error as exc:
3333
+ raise _translate_database_error(exc) from exc
3334
+ if len(rows) > limit:
3335
+ raise StorageError("capability_snapshot_limit")
3336
+ result: list[dict[str, Any]] = []
3337
+ for row in rows:
3338
+ payload_json = row["payload_json"]
3339
+ if not isinstance(payload_json, str) or len(payload_json.encode("utf-8")) > 64 * 1024:
3340
+ raise StorageError("storage_corrupt")
3341
+ try:
3342
+ payload = json.loads(payload_json)
3343
+ except (json.JSONDecodeError, RecursionError) as exc:
3344
+ raise StorageError("storage_corrupt") from exc
3345
+ result.append({"digest": str(row["capability_snapshot_digest"]), "payload": payload})
3346
+ return tuple(result)
3347
+
3348
+ @staticmethod
3349
+ def _save_lifecycle_binding(
3350
+ connection: sqlite3.Connection,
3351
+ *,
3352
+ table: str,
3353
+ key_column: str,
3354
+ key_value: str,
3355
+ lifecycle_identity_digest: str,
3356
+ bound_at: int,
3357
+ capability_snapshot_digest: str | None = None,
3358
+ ) -> bool:
3359
+ columns = [key_column]
3360
+ values: list[object] = [key_value]
3361
+ if capability_snapshot_digest is not None:
3362
+ columns.append("capability_snapshot_digest")
3363
+ values.append(capability_snapshot_digest)
3364
+ columns.extend(("lifecycle_identity_digest", "bound_at"))
3365
+ values.extend((lifecycle_identity_digest, bound_at))
3366
+ existing = connection.execute(
3367
+ f"SELECT {','.join(columns[1:])} FROM {table} WHERE {key_column}=?",
3368
+ (key_value,),
3369
+ ).fetchone()
3370
+ if existing is not None:
3371
+ if tuple(existing) != tuple(values[1:]):
3372
+ raise StorageError("lifecycle_binding_changed")
3373
+ return False
3374
+ placeholders = ",".join("?" for _ in columns)
3375
+ connection.execute(
3376
+ f"INSERT INTO {table}({','.join(columns)}) VALUES({placeholders})",
3377
+ tuple(values),
3378
+ )
3379
+ return True
3380
+
3381
+ def save_lifecycle_snapshot_binding(
3382
+ self,
3383
+ *,
3384
+ capability_snapshot_digest: str,
3385
+ lifecycle_identity_digest: str,
3386
+ bound_at: int,
3387
+ ) -> bool:
3388
+ if not _HEX_64.fullmatch(capability_snapshot_digest):
3389
+ raise ValueError("capability_snapshot_digest_invalid")
3390
+ if not _HEX_64.fullmatch(lifecycle_identity_digest):
3391
+ raise ValueError("lifecycle_identity_digest_invalid")
3392
+ bound_at = _nonnegative_integer(bound_at, "bound_at_invalid")
3393
+ try:
3394
+ with self._connection() as connection:
3395
+ return self._save_lifecycle_binding(
3396
+ connection,
3397
+ table="lifecycle_snapshot_bindings",
3398
+ key_column="capability_snapshot_digest",
3399
+ key_value=capability_snapshot_digest,
3400
+ lifecycle_identity_digest=lifecycle_identity_digest,
3401
+ bound_at=bound_at,
3402
+ )
3403
+ except StorageError:
3404
+ raise
3405
+ except sqlite3.Error as exc:
3406
+ raise _translate_database_error(exc) from exc
3407
+
3408
+ def save_lifecycle_approval_binding(
3409
+ self,
3410
+ *,
3411
+ approval_id: str,
3412
+ capability_snapshot_digest: str,
3413
+ lifecycle_identity_digest: str,
3414
+ bound_at: int,
3415
+ ) -> bool:
3416
+ approval_id = _identifier(approval_id, "approval_id_invalid")
3417
+ if not _HEX_64.fullmatch(capability_snapshot_digest):
3418
+ raise ValueError("capability_snapshot_digest_invalid")
3419
+ if not _HEX_64.fullmatch(lifecycle_identity_digest):
3420
+ raise ValueError("lifecycle_identity_digest_invalid")
3421
+ bound_at = _nonnegative_integer(bound_at, "bound_at_invalid")
3422
+ try:
3423
+ with self._connection() as connection:
3424
+ return self._save_lifecycle_binding(
3425
+ connection,
3426
+ table="lifecycle_approval_bindings",
3427
+ key_column="approval_id",
3428
+ key_value=approval_id,
3429
+ capability_snapshot_digest=capability_snapshot_digest,
3430
+ lifecycle_identity_digest=lifecycle_identity_digest,
3431
+ bound_at=bound_at,
3432
+ )
3433
+ except StorageError:
3434
+ raise
3435
+ except sqlite3.Error as exc:
3436
+ raise _translate_database_error(exc) from exc
3437
+
3438
+ def save_lifecycle_attempt_binding(
3439
+ self,
3440
+ *,
3441
+ attempt_id: str,
3442
+ lifecycle_identity_digest: str,
3443
+ bound_at: int,
3444
+ ) -> bool:
3445
+ attempt_id = _identifier(attempt_id, "attempt_id_invalid")
3446
+ if not _HEX_64.fullmatch(lifecycle_identity_digest):
3447
+ raise ValueError("lifecycle_identity_digest_invalid")
3448
+ bound_at = _nonnegative_integer(bound_at, "bound_at_invalid")
3449
+ try:
3450
+ with self._connection() as connection:
3451
+ return self._save_lifecycle_binding(
3452
+ connection,
3453
+ table="lifecycle_attempt_bindings",
3454
+ key_column="attempt_id",
3455
+ key_value=attempt_id,
3456
+ lifecycle_identity_digest=lifecycle_identity_digest,
3457
+ bound_at=bound_at,
3458
+ )
3459
+ except StorageError:
3460
+ raise
3461
+ except sqlite3.Error as exc:
3462
+ raise _translate_database_error(exc) from exc
3463
+
3464
+ def lifecycle_identity_binding(self, *, authority_kind: str, authority_id: str) -> str | None:
3465
+ tables = {
3466
+ "capability_snapshot": (
3467
+ "lifecycle_snapshot_bindings",
3468
+ "capability_snapshot_digest",
3469
+ ),
3470
+ "approval": ("lifecycle_approval_bindings", "approval_id"),
3471
+ "attempt": ("lifecycle_attempt_bindings", "attempt_id"),
3472
+ }
3473
+ try:
3474
+ table, key_column = tables[authority_kind]
3475
+ except (KeyError, TypeError) as exc:
3476
+ raise ValueError("authority_kind_invalid") from exc
3477
+ if authority_kind == "capability_snapshot":
3478
+ authority_id = _identifier(authority_id, "authority_id_invalid")
3479
+ if not _HEX_64.fullmatch(authority_id):
3480
+ raise ValueError("authority_id_invalid")
3481
+ else:
3482
+ authority_id = _identifier(authority_id, "authority_id_invalid")
3483
+ try:
3484
+ with self._connection() as connection:
3485
+ if authority_kind == "capability_snapshot":
3486
+ row = connection.execute(
3487
+ """SELECT COALESCE(
3488
+ (SELECT carry.new_identity_digest
3489
+ FROM lifecycle_binding_carries AS carry
3490
+ WHERE carry.capability_snapshot_digest = ?
3491
+ ORDER BY carry.carry_id DESC LIMIT 1),
3492
+ (SELECT binding.lifecycle_identity_digest
3493
+ FROM lifecycle_snapshot_bindings AS binding
3494
+ WHERE binding.capability_snapshot_digest = ?)
3495
+ )""",
3496
+ (authority_id, authority_id),
3497
+ ).fetchone()
3498
+ if row is None or row[0] is None:
3499
+ return None
3500
+ return str(row[0])
3501
+ row = connection.execute(
3502
+ f"SELECT lifecycle_identity_digest FROM {table} WHERE {key_column}=?",
3503
+ (authority_id,),
3504
+ ).fetchone()
3505
+ except sqlite3.Error as exc:
3506
+ raise _translate_database_error(exc) from exc
3507
+ return None if row is None else str(row[0])
3508
+
3509
+ def save_registry_snapshot(self, payload: dict[str, Any]) -> None:
3510
+ if not isinstance(payload, dict) or set(payload) != {
3511
+ "schema_version",
3512
+ "refreshed_at",
3513
+ "expires_at",
3514
+ "models",
3515
+ }:
3516
+ raise ValueError("registry_snapshot_invalid")
3517
+ refreshed = _nonnegative_integer(payload["refreshed_at"], "refreshed_at_invalid")
3518
+ expires = _nonnegative_integer(payload["expires_at"], "expires_at_invalid")
3519
+ if expires <= refreshed:
3520
+ raise ValueError("expires_at_invalid")
3521
+ models = payload["models"]
3522
+ if not isinstance(models, list) or len(models) > 128:
3523
+ raise ValueError("registry_models_invalid")
3524
+ for model in models:
3525
+ if not isinstance(model, dict) or set(model) != {
3526
+ "model_id",
3527
+ "digest",
3528
+ "context_window_tokens",
3529
+ "capabilities",
3530
+ }:
3531
+ raise ValueError("registry_model_invalid")
3532
+ model_id = model["model_id"]
3533
+ digest = model["digest"]
3534
+ context = model["context_window_tokens"]
3535
+ capabilities = model["capabilities"]
3536
+ if not isinstance(model_id, str) or not _SNAPSHOT_MODEL_ID.fullmatch(model_id):
3537
+ raise ValueError("registry_model_invalid")
3538
+ if not isinstance(digest, str) or not _HEX_64.fullmatch(digest):
3539
+ raise ValueError("registry_model_invalid")
3540
+ _nonnegative_integer(context, "registry_model_invalid", 4_194_304)
3541
+ if (
3542
+ not isinstance(capabilities, list)
3543
+ or len(capabilities) > 16
3544
+ or any(
3545
+ not isinstance(item, str) or not _CAPABILITY.fullmatch(item)
3546
+ for item in capabilities
3547
+ )
3548
+ ):
3549
+ raise ValueError("registry_model_invalid")
3550
+ serialized = json.dumps(payload, sort_keys=True, separators=(",", ":"))
3551
+ if len(serialized.encode("utf-8")) > 2 * 1024 * 1024:
3552
+ raise ValueError("registry_snapshot_too_large")
3553
+ payload_hash = hashlib.sha256(serialized.encode("utf-8")).hexdigest()
3554
+ try:
3555
+ with self._connection() as connection:
3556
+ connection.execute(
3557
+ """INSERT INTO registry_snapshots(
3558
+ snapshot_id, payload_json, payload_hash, refreshed_at, expires_at
3559
+ ) VALUES (1, ?, ?, ?, ?)
3560
+ ON CONFLICT(snapshot_id) DO UPDATE SET
3561
+ payload_json = excluded.payload_json,
3562
+ payload_hash = excluded.payload_hash,
3563
+ refreshed_at = excluded.refreshed_at,
3564
+ expires_at = excluded.expires_at""",
3565
+ (serialized, payload_hash, refreshed, expires),
3566
+ )
3567
+ except sqlite3.Error as exc:
3568
+ raise _translate_database_error(exc) from exc
3569
+
3570
+ def load_registry_snapshot(self) -> dict[str, Any] | None:
3571
+ try:
3572
+ with self._connection() as connection:
3573
+ row = connection.execute(
3574
+ "SELECT payload_json, payload_hash FROM registry_snapshots WHERE snapshot_id = 1"
3575
+ ).fetchone()
3576
+ except sqlite3.Error as exc:
3577
+ raise _translate_database_error(exc) from exc
3578
+ if row is None:
3579
+ return None
3580
+ payload_text = str(row["payload_json"])
3581
+ if hashlib.sha256(payload_text.encode("utf-8")).hexdigest() != row["payload_hash"]:
3582
+ raise StorageError("storage_corrupt")
3583
+ try:
3584
+ payload = json.loads(payload_text)
3585
+ except (json.JSONDecodeError, RecursionError) as exc:
3586
+ raise StorageError("storage_corrupt") from exc
3587
+ if not isinstance(payload, dict):
3588
+ raise StorageError("storage_corrupt")
3589
+ return payload
3590
+
3591
+ def save_approval_record(
3592
+ self,
3593
+ *,
3594
+ approval_id: str,
3595
+ task_id: str | None,
3596
+ decision_id: str | None,
3597
+ nonce_hash: str,
3598
+ manifest_hash: str,
3599
+ expires_at: int,
3600
+ reserved_tokens: int,
3601
+ ) -> None:
3602
+ approval_id = _identifier(approval_id, "approval_id_invalid")
3603
+ if task_id is not None:
3604
+ task_id = _identifier(task_id, "task_id_invalid")
3605
+ if decision_id is not None:
3606
+ decision_id = _identifier(decision_id, "decision_id_invalid")
3607
+ if not _HEX_64.fullmatch(nonce_hash) or not _HEX_64.fullmatch(manifest_hash):
3608
+ raise ValueError("approval_hash_invalid")
3609
+ expires_at = _nonnegative_integer(expires_at, "expires_at_invalid")
3610
+ reserved_tokens = _nonnegative_integer(reserved_tokens, "reserved_tokens_invalid")
3611
+ try:
3612
+ with self._connection() as connection:
3613
+ existing = connection.execute(
3614
+ "SELECT nonce_hash, manifest_hash, expires_at, reserved_tokens FROM approvals WHERE approval_id = ?",
3615
+ (approval_id,),
3616
+ ).fetchone()
3617
+ if existing is not None:
3618
+ if tuple(existing) != (nonce_hash, manifest_hash, expires_at, reserved_tokens):
3619
+ raise StorageError("approval_manifest_changed")
3620
+ return
3621
+ connection.execute(
3622
+ """INSERT INTO approvals(
3623
+ approval_id, task_id, decision_id, nonce_hash, manifest_hash,
3624
+ status, expires_at, reserved_tokens
3625
+ ) VALUES (?, ?, ?, ?, ?, 'issued', ?, ?)""",
3626
+ (
3627
+ approval_id,
3628
+ task_id,
3629
+ decision_id,
3630
+ nonce_hash,
3631
+ manifest_hash,
3632
+ expires_at,
3633
+ reserved_tokens,
3634
+ ),
3635
+ )
3636
+ except StorageError:
3637
+ raise
3638
+ except sqlite3.Error as exc:
3639
+ raise _translate_database_error(exc) from exc
3640
+
3641
+ def consume_approval_record(
3642
+ self,
3643
+ *,
3644
+ approval_id: str,
3645
+ nonce_hash: str,
3646
+ manifest_hash: str,
3647
+ now: int,
3648
+ token_amount: int,
3649
+ repository_quota: int,
3650
+ ) -> None:
3651
+ approval_id = _identifier(approval_id, "approval_id_invalid")
3652
+ now = _nonnegative_integer(now, "now_invalid")
3653
+ token_amount = _nonnegative_integer(token_amount, "token_amount_invalid")
3654
+ repository_quota = _nonnegative_integer(repository_quota, "repository_quota_invalid")
3655
+ connection: sqlite3.Connection | None = None
3656
+ try:
3657
+ connection = self._connect()
3658
+ connection.execute("BEGIN IMMEDIATE")
3659
+ row = connection.execute(
3660
+ """SELECT nonce_hash, manifest_hash, status, expires_at, reserved_tokens
3661
+ FROM approvals WHERE approval_id = ?""",
3662
+ (approval_id,),
3663
+ ).fetchone()
3664
+ if row is None:
3665
+ raise StorageError("approval_missing")
3666
+ if row["nonce_hash"] != nonce_hash or row["manifest_hash"] != manifest_hash:
3667
+ raise StorageError("approval_manifest_changed")
3668
+ if row["status"] == "invalidated":
3669
+ raise StorageError("approval_lifecycle_stale")
3670
+ if row["status"] != "issued":
3671
+ raise StorageError("approval_reused")
3672
+ if now >= int(row["expires_at"]):
3673
+ raise StorageError("approval_expired")
3674
+ if int(row["reserved_tokens"]) != token_amount:
3675
+ raise StorageError("approval_manifest_changed")
3676
+ current = int(
3677
+ connection.execute(
3678
+ "SELECT COALESCE(SUM(token_amount), 0) FROM budget_ledger WHERE entry_type = 'reservation'"
3679
+ ).fetchone()[0]
3680
+ )
3681
+ if token_amount > repository_quota or current + token_amount > repository_quota:
3682
+ raise StorageError("budget_exhausted")
3683
+ connection.execute(
3684
+ "UPDATE approvals SET status = 'consumed' WHERE approval_id = ? AND status = 'issued'",
3685
+ (approval_id,),
3686
+ )
3687
+ connection.execute(
3688
+ """INSERT INTO budget_ledger(
3689
+ entry_id, approval_id, execution_id, entry_type, token_amount, created_at
3690
+ ) VALUES (?, ?, NULL, 'reservation', ?, ?)""",
3691
+ (f"reserve:{approval_id}", approval_id, token_amount, now),
3692
+ )
3693
+ connection.commit()
3694
+ except StorageError:
3695
+ if connection is not None and connection.in_transaction:
3696
+ connection.rollback()
3697
+ raise
3698
+ except sqlite3.Error as exc:
3699
+ if connection is not None and connection.in_transaction:
3700
+ connection.rollback()
3701
+ raise _translate_database_error(exc) from exc
3702
+ finally:
3703
+ if connection is not None:
3704
+ connection.close()
3705
+
3706
+ def approval_status(self, approval_id: str) -> str | None:
3707
+ approval_id = _identifier(approval_id, "approval_id_invalid")
3708
+ with self._connection() as connection:
3709
+ row = connection.execute(
3710
+ "SELECT status FROM approvals WHERE approval_id = ?",
3711
+ (approval_id,),
3712
+ ).fetchone()
3713
+ return None if row is None else str(row[0])
3714
+
3715
+ def carry_forward_snapshot_bindings(
3716
+ self,
3717
+ *,
3718
+ previous_identity_digest: str,
3719
+ new_identity_digest: str,
3720
+ lifecycle_event_id: str,
3721
+ carried_at: int,
3722
+ ) -> tuple[str, ...]:
3723
+ for value in (previous_identity_digest, new_identity_digest, lifecycle_event_id):
3724
+ if not isinstance(value, str) or not _HEX_64.fullmatch(value):
3725
+ raise ValueError("lifecycle_identity_digest_invalid")
3726
+ if previous_identity_digest == new_identity_digest:
3727
+ raise ValueError("lifecycle_identity_digest_invalid")
3728
+ observed = _nonnegative_integer(carried_at, "carried_at_invalid")
3729
+ connection: sqlite3.Connection | None = None
3730
+ try:
3731
+ connection = self._connect()
3732
+ connection.execute("BEGIN IMMEDIATE")
3733
+ rows = connection.execute(
3734
+ """SELECT binding.capability_snapshot_digest
3735
+ FROM lifecycle_snapshot_bindings AS binding
3736
+ JOIN capability_snapshots AS snapshot
3737
+ ON snapshot.capability_snapshot_digest = binding.capability_snapshot_digest
3738
+ LEFT JOIN (
3739
+ SELECT capability_snapshot_digest, new_identity_digest,
3740
+ MAX(carry_id) AS carry_id
3741
+ FROM lifecycle_binding_carries
3742
+ GROUP BY capability_snapshot_digest
3743
+ ) AS carry
3744
+ ON carry.capability_snapshot_digest = binding.capability_snapshot_digest
3745
+ WHERE COALESCE(carry.new_identity_digest, binding.lifecycle_identity_digest) = ?
3746
+ AND snapshot.expires_at > ?
3747
+ ORDER BY binding.capability_snapshot_digest""",
3748
+ (previous_identity_digest, observed),
3749
+ ).fetchall()
3750
+ carried = tuple(str(row[0]) for row in rows)
3751
+ for digest in carried:
3752
+ connection.execute(
3753
+ """INSERT INTO lifecycle_binding_carries(
3754
+ capability_snapshot_digest,previous_identity_digest,
3755
+ new_identity_digest,lifecycle_event_id,carried_at
3756
+ ) VALUES (?,?,?,?,?)""",
3757
+ (
3758
+ digest,
3759
+ previous_identity_digest,
3760
+ new_identity_digest,
3761
+ lifecycle_event_id,
3762
+ observed,
3763
+ ),
3764
+ )
3765
+ connection.commit()
3766
+ return carried
3767
+ except sqlite3.Error as exc:
3768
+ if connection is not None and connection.in_transaction:
3769
+ connection.rollback()
3770
+ raise _translate_database_error(exc) from exc
3771
+ finally:
3772
+ if connection is not None:
3773
+ connection.close()
3774
+
3775
+ def lifecycle_authority_targets(
3776
+ self, lifecycle_identity_digest: str
3777
+ ) -> tuple[tuple[str, ...], tuple[str, ...]]:
3778
+ if not _HEX_64.fullmatch(lifecycle_identity_digest):
3779
+ raise ValueError("lifecycle_identity_digest_invalid")
3780
+ try:
3781
+ with self._connection() as connection:
3782
+ snapshots = tuple(
3783
+ str(row[0])
3784
+ for row in connection.execute(
3785
+ """SELECT binding.capability_snapshot_digest
3786
+ FROM lifecycle_snapshot_bindings AS binding
3787
+ LEFT JOIN (
3788
+ SELECT capability_snapshot_digest, new_identity_digest,
3789
+ MAX(carry_id) AS carry_id
3790
+ FROM lifecycle_binding_carries
3791
+ GROUP BY capability_snapshot_digest
3792
+ ) AS carry
3793
+ ON carry.capability_snapshot_digest = binding.capability_snapshot_digest
3794
+ WHERE COALESCE(
3795
+ carry.new_identity_digest, binding.lifecycle_identity_digest
3796
+ ) = ?
3797
+ ORDER BY binding.capability_snapshot_digest""",
3798
+ (lifecycle_identity_digest,),
3799
+ ).fetchall()
3800
+ )
3801
+ approvals = tuple(
3802
+ str(row[0])
3803
+ for row in connection.execute(
3804
+ "SELECT binding.approval_id FROM lifecycle_approval_bindings AS binding "
3805
+ "JOIN approvals AS approval ON approval.approval_id=binding.approval_id "
3806
+ "WHERE binding.lifecycle_identity_digest=? AND approval.status='issued' "
3807
+ "ORDER BY binding.approval_id",
3808
+ (lifecycle_identity_digest,),
3809
+ ).fetchall()
3810
+ )
3811
+ except sqlite3.Error as exc:
3812
+ raise _translate_database_error(exc) from exc
3813
+ return snapshots, approvals
3814
+
3815
+ def lifecycle_approval_binding_details(
3816
+ self, approval_id: str
3817
+ ) -> tuple[str, str] | None:
3818
+ approval_id = _identifier(approval_id, "approval_id_invalid")
3819
+ try:
3820
+ with self._connection() as connection:
3821
+ row = connection.execute(
3822
+ "SELECT capability_snapshot_digest,lifecycle_identity_digest "
3823
+ "FROM lifecycle_approval_bindings WHERE approval_id=?",
3824
+ (approval_id,),
3825
+ ).fetchone()
3826
+ except sqlite3.Error as exc:
3827
+ raise _translate_database_error(exc) from exc
3828
+ return None if row is None else (str(row[0]), str(row[1]))
3829
+
3830
+ def invalidate_lifecycle_approvals(
3831
+ self, lifecycle_identity_digest: str
3832
+ ) -> tuple[str, ...]:
3833
+ if not _HEX_64.fullmatch(lifecycle_identity_digest):
3834
+ raise ValueError("lifecycle_identity_digest_invalid")
3835
+ connection: sqlite3.Connection | None = None
3836
+ try:
3837
+ connection = self._connect()
3838
+ connection.execute("BEGIN IMMEDIATE")
3839
+ approval_ids = tuple(
3840
+ str(row[0])
3841
+ for row in connection.execute(
3842
+ "SELECT binding.approval_id FROM lifecycle_approval_bindings AS binding "
3843
+ "JOIN approvals AS approval ON approval.approval_id=binding.approval_id "
3844
+ "WHERE binding.lifecycle_identity_digest=? AND approval.status='issued' "
3845
+ "ORDER BY binding.approval_id",
3846
+ (lifecycle_identity_digest,),
3847
+ ).fetchall()
3848
+ )
3849
+ if approval_ids:
3850
+ placeholders = ",".join("?" for _ in approval_ids)
3851
+ connection.execute(
3852
+ f"UPDATE approvals SET status='invalidated' WHERE status='issued' "
3853
+ f"AND approval_id IN ({placeholders})",
3854
+ approval_ids,
3855
+ )
3856
+ connection.commit()
3857
+ return approval_ids
3858
+ except sqlite3.Error as exc:
3859
+ if connection is not None and connection.in_transaction:
3860
+ connection.rollback()
3861
+ raise _translate_database_error(exc) from exc
3862
+ finally:
3863
+ if connection is not None:
3864
+ connection.close()
3865
+
3866
+ def reserved_token_total(self) -> int:
3867
+ with self._connection() as connection:
3868
+ return int(
3869
+ connection.execute(
3870
+ "SELECT COALESCE(SUM(token_amount), 0) FROM budget_ledger WHERE entry_type = 'reservation'"
3871
+ ).fetchone()[0]
3872
+ )
3873
+
3874
+
3875
+ @dataclass(frozen=True)
3876
+ class AggregateRecord:
3877
+ model_id: str
3878
+ effort: str
3879
+ category: str
3880
+ risk: str
3881
+ outcome: str
3882
+ input_bucket: int
3883
+ output_bucket: int
3884
+ latency_bucket: int
3885
+ policy_version: str
3886
+ recorded_day: int
3887
+
3888
+ def __post_init__(self) -> None:
3889
+ if not isinstance(self.model_id, str) or not _MODEL_ID.fullmatch(self.model_id):
3890
+ raise ValueError("model_id_invalid")
3891
+ object.__setattr__(self, "effort", Effort(self.effort).value)
3892
+ object.__setattr__(self, "category", TaskCategory(self.category).value)
3893
+ object.__setattr__(self, "risk", RiskTier(self.risk).value)
3894
+ object.__setattr__(self, "outcome", ExecutionOutcome(self.outcome).value)
3895
+ for name in ("input_bucket", "output_bucket", "latency_bucket"):
3896
+ object.__setattr__(self, name, _nonnegative_integer(getattr(self, name), f"{name}_invalid", 20))
3897
+ if not isinstance(self.policy_version, str) or not _VERSION.fullmatch(self.policy_version):
3898
+ raise ValueError("policy_version_invalid")
3899
+ object.__setattr__(self, "recorded_day", _nonnegative_integer(self.recorded_day, "recorded_day_invalid", 1_000_000))
3900
+
3901
+ def to_dict(self) -> dict[str, str | int]:
3902
+ return {
3903
+ "model_id": self.model_id,
3904
+ "effort": self.effort,
3905
+ "category": self.category,
3906
+ "risk": self.risk,
3907
+ "outcome": self.outcome,
3908
+ "input_bucket": self.input_bucket,
3909
+ "output_bucket": self.output_bucket,
3910
+ "latency_bucket": self.latency_bucket,
3911
+ "policy_version": self.policy_version,
3912
+ "recorded_day": self.recorded_day,
3913
+ }
3914
+
3915
+
3916
+ def _default_machine_state_dir() -> Path:
3917
+ if os.name == "nt":
3918
+ base = os.environ.get("LOCALAPPDATA")
3919
+ return (Path(base) if base else Path.home() / "AppData" / "Local") / "Graphite" / "routing"
3920
+ xdg = os.environ.get("XDG_STATE_HOME")
3921
+ return (Path(xdg) if xdg else Path.home() / ".local" / "state") / "graphite" / "routing"
3922
+
3923
+
3924
+ class AggregateStore:
3925
+ """Opt-in machine-wide store containing sanitized typed aggregates only."""
3926
+
3927
+ def __init__(
3928
+ self,
3929
+ repository_root: Path,
3930
+ *,
3931
+ opt_in: bool,
3932
+ state_dir: Path | None = None,
3933
+ ) -> None:
3934
+ if not isinstance(opt_in, bool):
3935
+ raise ValueError("aggregate_opt_in_invalid")
3936
+ self.root = _selected_root(repository_root)
3937
+ self.opt_in = opt_in
3938
+ selected_state = (state_dir or _default_machine_state_dir()).resolve(strict=False)
3939
+ try:
3940
+ selected_state.relative_to(self.root)
3941
+ except ValueError:
3942
+ pass
3943
+ else:
3944
+ raise StorageError("aggregate_path_invalid")
3945
+ self.path = selected_state / "aggregate.sqlite3"
3946
+
3947
+ def _initialize(self) -> None:
3948
+ _secure_directory(self.path.parent)
3949
+ try:
3950
+ with closing(sqlite3.connect(self.path, timeout=2.0)) as connection, connection:
3951
+ connection.execute("PRAGMA journal_mode = WAL")
3952
+ connection.execute("PRAGMA busy_timeout = 2000")
3953
+ connection.execute(
3954
+ """CREATE TABLE IF NOT EXISTS aggregate_events (
3955
+ event_id INTEGER PRIMARY KEY AUTOINCREMENT,
3956
+ model_id TEXT NOT NULL,
3957
+ effort TEXT NOT NULL,
3958
+ category TEXT NOT NULL,
3959
+ risk TEXT NOT NULL,
3960
+ outcome TEXT NOT NULL,
3961
+ input_bucket INTEGER NOT NULL,
3962
+ output_bucket INTEGER NOT NULL,
3963
+ latency_bucket INTEGER NOT NULL,
3964
+ policy_version TEXT NOT NULL,
3965
+ recorded_day INTEGER NOT NULL
3966
+ )"""
3967
+ )
3968
+ except sqlite3.Error as exc:
3969
+ raise _translate_database_error(exc) from exc
3970
+ _secure_file(self.path)
3971
+
3972
+ def write(self, record: AggregateRecord) -> bool:
3973
+ if not isinstance(record, AggregateRecord):
3974
+ raise ValueError("aggregate_record_invalid")
3975
+ if not self.opt_in:
3976
+ return False
3977
+ self._initialize()
3978
+ values = record.to_dict()
3979
+ try:
3980
+ with closing(sqlite3.connect(self.path, timeout=2.0)) as connection, connection:
3981
+ connection.execute("PRAGMA busy_timeout = 2000")
3982
+ connection.execute(
3983
+ """INSERT INTO aggregate_events(
3984
+ model_id, effort, category, risk, outcome,
3985
+ input_bucket, output_bucket, latency_bucket,
3986
+ policy_version, recorded_day
3987
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
3988
+ tuple(values.values()),
3989
+ )
3990
+ except sqlite3.Error as exc:
3991
+ raise _translate_database_error(exc) from exc
3992
+ return True
3993
+
3994
+ def row_count(self) -> int:
3995
+ if not self.opt_in or not self.path.exists():
3996
+ return 0
3997
+ try:
3998
+ with closing(sqlite3.connect(self.path, timeout=2.0)) as connection, connection:
3999
+ return int(connection.execute("SELECT COUNT(*) FROM aggregate_events").fetchone()[0])
4000
+ except sqlite3.Error as exc:
4001
+ raise _translate_database_error(exc) from exc