audit-1 (critical 1): mandatory operator signatures bound to content+deployment

- signing module: canonical content hashes + approval_message
  sha256(cayce-ops:<type>:<deployment_id>:<id>:<content_hash>)
- approve/approve-draft/publisher-approve REQUIRE signature; without
  CAYCE_OPS_OPERATOR_PUBKEY+DEPLOYMENT_ID the daemons refuse to
  execute/send/publish (no degraded mode)
- run-time re-verification at execute/send/publish: unsigned rows (raw SQL
  approval) or edited content revert to pre-approval state, audited as error
- FULL signatures stored on rows (operator_signature, migration 0003) and
  in audit detail — never truncated
- signature failures audited from CLI wrappers too (1f)
- AUTONOMOUS_MODE approval path removed (D13): no non-human approvals

tests: SQL-approved row refused at execute/send/publish; to_addr change
blocks send; foreign deployment/content signatures refused; full-signature
persistence. 65 tests green.
This commit is contained in:
2026-10-20 02:20:09 +00:00
parent 61d5cfd67d
commit 40453aadb4
13 changed files with 1209 additions and 273 deletions
+32
View File
@@ -110,3 +110,35 @@ def test_audit_chain_and_verification(sess):
first.actor = "tampered"
ok, broken = verify_audit_chain(sess)
assert not ok and broken == first.id
def test_concurrent_writers_keep_chain_verifiable(db_engine):
"""Round-2 point 4: serialized writes -> no forked chains."""
import threading
import cayceops.db as appdb
errors: list[Exception] = []
def writer(worker: int, n: int):
try:
for i in range(n):
with appdb.session_scope() as s:
audit(s, actor=f"w{worker}", action=f"step{i}", detail={"worker": worker})
except Exception as exc: # noqa: BLE001
errors.append(exc)
threads = [threading.Thread(target=writer, args=(w, 25)) for w in range(4)]
for t in threads:
t.start()
for t in threads:
t.join()
assert not errors, errors
s = appdb.session_factory()()
total = s.query(AuditEntry).count()
assert total == 100
from cayceops.models import verify_audit_chain
ok, broken = verify_audit_chain(s)
assert ok and broken is None