"""Local metadata-only trajectories; raw values are deliberately not persisted. Hashes can still reveal low-entropy secrets via guessing. Persist HMACs instead, using a per-store key kept outside the database. Rich training records require a separate reviewed, redacted ingestion path. """ import hashlib import hmac import json from pathlib import Path import secrets import sqlite3 from time import time class Recorder: def __init__(self, path): path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) keyfile = path.with_suffix('.key') try: with keyfile.open('xb') as handle: handle.write(secrets.token_bytes(32)) keyfile.chmod(0o600) except FileExistsError: pass self.key = keyfile.read_bytes() if len(self.key) != 32: raise ValueError('invalid recording key') self.db = sqlite3.connect(path) self.db.execute('''CREATE TABLE IF NOT EXISTS events ( id INTEGER PRIMARY KEY, created REAL, task TEXT, epoch INTEGER, sequence INTEGER, state_key TEXT, action_key TEXT, kind TEXT, status TEXT, code TEXT, wall_ms REAL, cpu_ms REAL)''') def private_key(self, value): return hmac.new(self.key, value.encode('utf-8'), hashlib.sha256).hexdigest() def record(self, decision, state, outcome): ticket = decision.ticket self.db.execute('INSERT INTO events VALUES (NULL,?,?,?,?,?,?,?,?,?,?,?)', (time(), ticket.task_id, ticket.epoch, ticket.sequence, self.private_key(state.state_hash if state else ''), self.private_key(decision.action.encode()), decision.action.kind.name, outcome.status, outcome.code, outcome.wall_ms, outcome.cpu_ms)) self.db.commit() def close(self): self.db.close()