Files
cayce 90e6cfcc19 fix: address external audit — criticals 1-5 + mediums
Critical 1 (approval forgery): audit_log is now hash-chained
(prev_hash/entry_hash, verify_audit_chain); executor approve REQUIRES a
BIP-340 signature over sha256(cayce-ops:approve:<id>) when
CAYCE_OPS_OPERATOR_PUBKEY is set; deploy/roles.sql ships least-privilege
per-daemon DB roles.

Critical 2 (threading dead): sends create real OUTBOUND rows (own
Message-ID, In-Reply-To -> inbound); inbound message_id never overwritten;
maild compose adds the first-contact path; _match_proposal falls back to
inbound chains.

Critical 3 (duplicate sends): per-message transactions in send path —
SMTP failure on message N no longer resurrects 1..N-1; failures audited
(send_failed) and the row stays queued.

Critical 4 (classifier): negated approvals (cannot accept / not approved /
not accepting) classify REJECTION at 0.95 before the bare-keyword rule;
LLM prompt braces escaped (.format no longer KeyError into silent fallback).

Critical 5 (systemd): every daemon has a run subcommand
(maild run = poll+send, publisher run = publish-due, monitor run = build);
deploy README documents the separate daily publisher plan timer.

Mediums: approved_hash binds drafts/captions to the approved text (edited
text refuses to send/publish until re-approved); risk_score renamed
match_score (migration 0002) and auto-approve orders HIGHEST relevance
first; compose binds postgres to 127.0.0.1; IMAP marks \\Seen only after
commit (fetch returns (num, raw), mark post-commit); disabled channels are
terminal (ContentStatus.SKIPPED) and publish failures burn a bounded
attempt budget before abandonment; make hooks installs the pre-push gate.

tests: 12 new — 56 total, all green; ruff 0 findings; gitleaks clean.
2026-10-19 23:06:29 +00:00

1.3 KiB

systemd deployment (VPS)

This is the primary deployment path (docker-compose is for local dev).

sudo useradd -r -m -d /srv/cayce-ops cayce
rsync -a --exclude .venv ./ cayce@vm:/srv/cayce-ops/
sudo -u cayce uv sync --frozen
sudo -u cayce cayceops db upgrade          # alembic to head
sudo cp deploy/systemd/cayce-ops@.service deploy/systemd/cayce-ops@.timer /etc/systemd/system/

# Every daemon has a `run` subcommand now:
#   scanner run · executor run · maild run (poll + send-approved)
#   publisher run (publish-due) · monitor run (build)
# cadence per daemon (edit OnCalendar in each timer copy):
#   scanner  hourly      executor  */10 min      maild  */5 min
#   publisher */5 min    monitor   */1 min
# NOTE: `publisher plan` (fills the calendar) is a SEPARATE timer:
#   OnCalendar=daily  ->  ExecStart=/srv/cayce-ops/.venv/bin/cayceops publisher plan
for d in scanner executor maild publisher monitor; do
  sudo cp deploy/systemd/cayce-ops@.timer /etc/systemd/system/cayce-ops@$d.timer
done
# adjust OnCalendar per timer, then:
sudo systemctl daemon-reload
sudo systemctl enable --now cayce-ops@{scanner,executor,maild,publisher,monitor}.timer

The unit hardens the daemon (NoNewPrivileges, ProtectSystem=strict) and reads secrets only from /srv/cayce-ops/.env (never in the repo).