audit-4: serialized audit chain + legacy genesis + daily Nostr anchoring

- audit() writes are serialized: pg_advisory_xact_lock on Postgres;
  in-process RLock held until session commit/rollback on SQLite
  (test: 4 concurrent writers x 25 entries -> chain verifies, 100 rows)
- legacy rows (entry_hash NULL, pre-0002) are skipped: the chain begins
  GENESIS at the first hashed row; migrated DBs verify clean by design
- monitor anchor: publishes the last chain hash as a signed NIP-01 note
  once per day, logs it in the audit log (self-verified before publish)

tests: concurrent writers; legacy-genesis verification; anchor signs,
logs once per day.
This commit is contained in:
2026-10-20 02:20:56 +00:00
parent d77409148c
commit ffe425320d
2 changed files with 118 additions and 0 deletions
+39
View File
@@ -67,3 +67,42 @@ def test_build_escapes_injected_content(cfg, sess, tmp_path):
page = out.read_text()
assert "<script>x</script>" not in page
assert "&lt;script&gt;x&lt;/script&gt;" in page
def test_anchor_signs_and_logs_once_per_day(cfg, sess, monkeypatch):
from cayceops.components.monitor import monitor
from cayceops.models import AuditEntry
# seed the chain
from cayceops.models import audit as audit_fn
audit_fn(sess, actor="daemon:executor", action="execute_done", detail={})
sess.flush()
published = []
def fake_publish(relays, event, timeout=10.0):
published.append(event)
return [{"relay": r, "ok": "true"} for r in relays]
import cayceops.components.publisher.nostr as nostr_mod
monkeypatch.setattr(nostr_mod, "publish", fake_publish)
config = cfg(
CAYCE_OPS_NOSTR_PRIVATE_KEY="11" * 32,
CAYCE_OPS_NOSTR_RELAYS="wss://r.example.org",
CAYCE_OPS_DEPLOYMENT_ID="itest",
)
code = monitor._anchor(config, sess)
assert code == 0
assert len(published) == 1
content = published[0]["content"]
assert "audit anchor" in content and "itest" in content
entry = sess.query(AuditEntry).filter_by(action="anchor").one()
assert entry.detail["last_hash"]
# second call same day -> skipped, no new nostr event
code2 = monitor._anchor(config, sess)
assert code2 == 0
assert len(published) == 1