Commit Graph
59 Commits
Author SHA1 Message Date
cayce 78725eee85 recupera 2 ficheros sin commit del respondedor (31-oct): SPEC.md (draft plugin BTCPay) y docs/cayce-dev.md (diseño subagente)
ci / lint-test-leaks (pull_request) Waiting to run
Ambos estaban untracked en el working tree desde el 31-oct 18:46-18:52Z.
Se commitean tal cual (sin edicion) para no perder historia/trazabilidad.
2026-11-01 19:08:35 +00:00
cayce 398d01fd4c Merge: regla permanente de secretos (anti-filtración)
ci / lint-test-leaks (push) Canceled after 0s
2026-10-31 18:00:41 +00:00
cayce 6ddf76983b regla permanente de secretos: test anti-patrones + hook de journald
ci / lint-test-leaks (pull_request) Canceled after 0s
Nueva regla del operador (2026-10-31): los ficheros de secretos solo se
inspeccionan con herramientas que no muestran valores (jq keys, grep -c,
stat). Prohibido cat/grep -r sobre .env y volcar errores con DSN.

- tests/test_no_credential_patterns.py: 3 tests — DSN con credencial
  literal fuera de placeholders, nsec bech32 versionado, tokens hex de
  64 chars fuera de tests/.
- scripts/check-credential-leaks.sh: hook que falla si journald
  reciente contiene DSN con contraseña o nsec (redactando el valor en
  la salida). Sin timer propio (no autorizado); ejecutar manual o desde
  cron del operador.

Contexto: la rotación del rol hardcap y el DSN filtrado a journald por
el test del gate (corregido; la credencial filtrada ya está rotada).
2026-10-31 18:00:26 +00:00
cayce 88b2ad2980 hardcap: auth middleware en /admin — token obligatorio, comparación en tiempo constante
ci / lint-test-leaks (push) Canceled after 0s
ci / lint-test-leaks (pull_request) Canceled after 0s
- adminAuth: exige Authorization: Bearer <HARDCAP_ADMIN_TOKEN> (o
  X-Admin-Token). Comparación timingSafeEqual sobre sha256 de ambos
  valores (sin leak de longitud ni timing). Fail-closed: sin token
  configurado, TODAS las rutas admin responden 503.
- 401s ahora se loguean con método, ruta e IP (primera trazabilidad de
  acceso que tiene el admin API desde que existe).
- token generado (32 bytes hex) y almacenado SOLO en
  /srv/hardcap-backend/.env — nunca impreso ni versionado.
- test-admin-auth.mjs: 6 tests (sin token, incorrecto, vacío, correcto,
  header alternativo, misma-longitud-distinto) — todos pasan en la VPS.
- desplegado en src + dist; servicio reiniciado y activo.

Nota de contexto: el admin API carecía de auth desde su creación
(index.ts: \x27TODO: add auth middleware before production\x27) y estuvo
alcanzable sin credenciales en :3001 hasta la contención de hoy.
2026-10-31 17:39:38 +00:00
cayce 34ee25b32f Merge: gate de publicación hardcap + outbox auto-suficiente
ci / lint-test-leaks (push) Canceled after 0s
2026-10-31 17:12:08 +00:00
cayce 87a2829b8a containment: hardcap admin API publish gate + outbox self-sufficient pool
ci / lint-test-leaks (pull_request) Canceled after 0s
- hardcap PATCH /admin/articles/:id → PUBLISHED now stages into
  outbound_drafts (channel hardcap) instead of publishing, when
  HARDCAP_PUBLISH_MODE=draft (default). Fail-closed. Verified E2E:
  PATCH on a QUEUED article returned {staged:true, draft_id:7} and the
  article stayed QUEUED.
- bitcoinhotels.org: audited — frontend only (Next.js), no publishing
  path; nothing to contain.
- outbox.mjs: self-sufficient pool (createRequire from
  /srv/cayce-core/node_modules/pg — hosts may lack pg; found by the
  promo simulation which now works end-to-end).
- runtime/hardcap/admin.ts: versioned copy of the patched route
  (hardcap-backend itself is not a git repo).
- deploy.sh: hardcap env wiring (HARDCAP_PUBLISH_MODE, IDENTITY_PATH,
  CAYCE_PGURL appended server-side).

🔴 UNRELATED CRITICAL FINDING (needs operator decision, not patched
without approval): hardcap admin API has NO authentication and binds
*:3001 — reachable from the public internet (HTTP 200 from outside,
including DELETE /admin/articles/all). Recommend: auth middleware +
bind 127.0.0.1 + firewall, plus rotation of credentials that appeared
in journald (hardcap DB role DSN leaked via a psql error line during
testing — fixed in the same patch, but journald retains it).
2026-10-31 17:11:57 +00:00
cayce f618574043 Merge PR #19: contención 2/2 — 10050, resumen de borradores, BTCPay prioridad 1
ci / lint-test-leaks (push) Canceled after 0s
2026-10-31 16:49:22 +00:00
cayce 058126b80d containment 2/2: kind 10050 (inbox relays) + drafts summary in supervisor DM + grants suspended, BTCPay priority-1
ci / lint-test-leaks (pull_request) Canceled after 0s
- publish-10050.mjs: publishes Cayce kind 10050 (NIP-65 message-relays/
  inbox) listing the 6 relays where the listener actually reads DMs.
  EXECUTED once with operator authorization: event id
  188ef397824e8f29b5e1bea661ae65b1f8532ee97617747b9fbde57c6e8dfce9,
  accepted 6/6 relays.
- initiative.mjs: the cycle DM to the supervisor now includes a summary
  of outbound_drafts from the last 24h (id, type, first 15 words) so the
  operator reviews what the agent WANTED to publish.
- initiative.mjs: supervisor directive (CAYCE_DIRECTIVE_BTCPAY=1,
  reversible) injected into the cycle prompt: grants/opportunity search
  SUSPENDED, single priority = consolidate the BTCPay Server proposal
  (opportunity #106 / draft #33) ready for HUMAN sending. MANDATE text
  untouched (only the supervisor can change it).
- initiative.mjs: add_opportunity now rejects duplicates (the agent had
  re-registered OpenSats ×10, HRF ×6).
- opportunity #106 status -> priority-1.
- check-relay-silence.mjs: queries the relays for Cayce-signed events
  since a timestamp; allows only kind 10050 and supervisor DMs; exits 1
  on any violation. Verified: 0 events, 0 violations since deployment.
- deploy.sh: sudo for the daemon restart, optional files tolerated.

Gate: make check green (96 tests).
2026-10-31 16:48:54 +00:00
cayce e06cb85ce8 Merge PR #18: contención de salidas a terceros + identidad versionada
ci / lint-test-leaks (push) Canceled after 0s
2026-10-31 16:45:06 +00:00
cayce b81f7f3625 containment: every third-party send becomes a draft (outbox), identity file versioned
ci / lint-test-leaks (pull_request) Canceled after 0s
Problem: Cayce publishes straight to relays with no approval gate —
cayce-initiative publish_note, core.mjs digest/opinion/reflection/events,
mention auto-replies, social promo (Facebook/Instagram via sfeed), and
mail — while the operator believed approvals existed (they never did;
ADR-0005 Telegram queue was never implemented).

Mechanism (outbox.mjs, deployed to all runtimes):
- CAYCE_PUBLISH_MODE=draft (default): gateOutbound() stages EVERY outbound
  artifact into outbound_drafts (entity DB) with identity_path +
  identity_hash (sha256 of prompts/cayce-identity.md) before anything
  else happens. Fail-closed: if staging fails, the event is discarded,
  never published.
- CAYCE_PUBLISH_MODE=live: original behavior, unchanged (reversible with
  one line per service .env).
- Chokepoint: the nostr daemon /publish endpoint gates ALL callers
  (initiative publish_note, core.mjs ×4, banner kind 0); the daemon own
  mention-reply path gates too, storing original_event for context;
  startup profile publish gates as well. DM-to-supervisor (/dm) is
  internal and stays live.

Identity (point 8b):
- prompts/cayce-identity.md: the 5 hardcoded voice blocks extracted
  VERBATIM (verified byte-identical by runtime test) with block markers.
- core.mjs / editorials.mjs now load their voice blocks from this file
  (loadIdentityBlock); identity hash recorded on every draft.
- runtime/ holds the versioned copies of the previously-unversioned
  runtime scripts + deploy.sh installer.

No timers enabled or disabled. No production data modified.
2026-10-31 16:44:57 +00:00
cayce 8e59e1d4da Merge PR #17: fix redacción de credenciales en log del backup
ci / lint-test-leaks (push) Canceled after 0s
2026-10-28 21:10:55 +00:00
cayce ab301039ba fix(backup): log redaction leaked credentials — strip userinfo correctly
ci / lint-test-leaks (pull_request) Canceled after 0s
The previous redaction (${PG_URL%%@*@}) never matched when the URL has a
single @, so the full DSN (user:password@host) went to the log/journal.
Now: scheme + ***:***@ + hostport/db only.

Found during the point-3 E2E test; the leaked value in that transcript
was the already-public test DB credential (itest probe DB).
2026-10-28 21:10:47 +00:00
cayce b1dd5a4f98 Merge PR #16: chequeo de frescura de backups
ci / lint-test-leaks (push) Canceled after 0s
2026-10-28 21:10:08 +00:00
cayce df36126617 backup: freshness check — alert when last backup is older than 26h (Tanda B, point 4)
ci / lint-test-leaks (pull_request) Canceled after 0s
/usr/local/bin/cayce-check-backup-freshness (from
deploy/backup/check-freshness.sh): exits 1 if the newest
/var/backups/cayce/YYYY-MM-DD/ dir has mtime older than 26h, or if no
backups exist while the age recipient IS configured (backups expected).
Info-exit-0 when backups have never been configured. Not attached to a
timer (only cayce-backup.timer was authorized); documented for manual
or cron use.

Verified: fresh dir => OK 0h exit 0; dir with 2-day mtime => ALERT 48h
exit 1; no-dir + recipient => ALERT; no-dir + no-recipient => INFO.
2026-10-28 21:09:59 +00:00
cayce 161fdfa8d1 Merge PR #15: RESTORE.md + prueba E2E de restauración
ci / lint-test-leaks (push) Canceled after 0s
2026-10-28 21:08:05 +00:00
cayce d7156358f6 backup: RESTORE.md runbook + E2E restore test evidence (Tanda B, point 3)
ci / lint-test-leaks (pull_request) Canceled after 0s
RESTORE.md documents the exact from-zero recovery on a fresh VPS:
download+verify (sha256 over encrypted artifacts, no private key needed
on the pull side), decrypt on the operator Mac, Postgres pg_restore,
Gitea unzip+migrate, Caddy/systemd/.env config restore, repo re-clone,
backup pipeline reinstall with the SAME age recipient, and a
post-restore checklist.

E2E test executed on the VPS (no production touched — cayce-ops has no
production DB; a probe DB was created and dropped):
- manifest.sha256: 3/3 artifacts OK (verified WITHOUT any private key)
- age decrypt roundtrip with a throwaway keypair (destroyed afterwards)
- pg_restore TOC valid; full restore into a scratch DB: 5000/5000 rows
- gitea zip valid, config tar valid (34 entries)
- all test material removed: probe DB dropped, throwaway key deleted,
  recipient file removed, test backup deleted (was encrypted to the
  destroyed throwaway key)
2026-10-28 21:07:58 +00:00
cayce 508371184d Merge PR #14: usuario backup-pull de solo lectura
ci / lint-test-leaks (push) Canceled after 0s
2026-10-28 21:01:50 +00:00
cayce fc2dcd4fca backup: backup-pull user — read-only rsync-only SSH access (Tanda B, point 2)
ci / lint-test-leaks (pull_request) Canceled after 0s
System user backup-pull created on the VPS (no sudo, locked password).
authorized_keys restricted with restrict + forced command
rrsync -ro /var/backups/cayce. Verified with a since-deleted throwaway
key: no shell, no arbitrary commands, no sftp/scp, no reads outside
/var/backups/cayce, no writes; the positive rsync pull works.

authorized_keys is left EMPTY — the operator adds their real key with
the exact line documented in deploy/backup/backup-pull-setup.md.
2026-10-28 21:01:44 +00:00
cayce 121183ac83 Merge PR #13: generación de backups cifrados
ci / lint-test-leaks (push) Canceled after 0s
2026-10-28 20:59:35 +00:00
cayce b87a51afc0 backup: encrypted daily backups — VPS generates, Mac pulls (Tanda B, point 1)
ci / lint-test-leaks (pull_request) Canceled after 0s
deploy/backup/make-backup.sh (installed as /usr/local/bin/cayce-make-backup):
- pg_dump custom-format of the cayce-ops DB (URL from env or .env,
  SQLAlchemy driver qualifier stripped, credentials never logged)
- gitea dump (repos + sqlite + attachments), live; consistency notes in RESTORE.md
- config tar: Caddyfile(+d), gitea app.ini + internal_token, cayce-* units, .env
- EACH artifact encrypted with age BEFORE leaving the tmpdir; recipient
  read from /etc/cayce-backup/age-recipient.pub (public key only). The
  private age key never touches this server.
- plaintext exists ONLY in a mktemp dir removed by an EXIT trap; a partial
  day directory is removed on failure; any step failure => exit != 0 =>
  journald entry
- manifest.sha256 over the encrypted artifacts (pull side verifies
  integrity WITHOUT the age private key)
- destination /var/backups/cayce/YYYY-MM-DD/ (750 root:backup-pull, 640)
- retention: 7 daily dirs, pruned by mtime

systemd: cayce-backup.service (oneshot, hardened: ProtectSystem=strict,
NoNewPrivileges, ProtectHome=read-only) + cayce-backup.timer (daily
03:15 UTC, Persistent). Timer ENABLED on the VPS per operator instruction;
runs fail loudly (exit 1, clear journald message) until the age recipient
is provided — that is the designed not-configured state.
2026-10-28 20:59:28 +00:00
cayce d527164434 Merge PR #12: identidad sin afirmación de jurisdicción
ci / lint-test-leaks (push) Canceled after 0s
2026-10-28 20:01:09 +00:00
cayce 1c4ba234d0 identity: remove unverified jurisdiction claim, state the approval principle
ci / lint-test-leaks (pull_request) Canceled after 0s
The previous text asserted participation in an external jurisdiction
pilot without a verifiable source. Replaced with what is actually true
of this codebase: every external-effect action requires cryptographically
signed human approval, with a key that never resides on Cayce
infrastructure (D16).
2026-10-28 20:01:02 +00:00
cayce 64f89df289 Merge PR #11: clasificador con negaciones en español
ci / lint-test-leaks (push) Canceled after 0s
2026-10-28 19:59:50 +00:00
cayce f5b026070b audit-4: classifier understands Spanish negations (veto unchanged)
ci / lint-test-leaks (pull_request) Canceled after 0s
Merchants write in Spanish; negations like nunca, lamentablemente or
no podemos must veto approval exactly like their English counterparts.
Previously an all-Spanish rejection mail could fall through to OTHER,
and a mixed mail with the approval word in English approved despite a
Spanish negation.

- negation list adds: nunca, jamas, tampoco, ningun/ninguna, nadie,
  nada, no podemos, no es posible, no aceptamos, rechaz*, descartad*,
  lamentablemente, no ha sido aprobad*, no procede
- new Spanish REJECTION rule (rechaz*, solicitud denegada, no cumple
  los requisitos) and Spanish APPROVAL rule (aprob*, aceptad*,
  bienvenid*, encantados de trabajar)
- veto rule unchanged: any negation anywhere -> needs_review
- tests: 15 new (12 veto cases incl. 2 mixed-language, pure-Spanish
  approval still approves, Spanish rejection classifies rejection)

Gate: ruff 0 · 96 unit + 1 PG integration · gitleaks clean.
2026-10-28 19:59:44 +00:00
cayce 60300a2f1a Merge PR #10: cadena de auditoría a prueba de dueño
ci / lint-test-leaks (push) Canceled after 0s
2026-10-28 19:56:57 +00:00
cayce 09fe9199b3 audit-3: append-only audit log + strict legacy-prefix rule + anchor cross-check
ci / lint-test-leaks (pull_request) Canceled after 0s
- verify_audit_chain(): rows with entry_hash IS NULL are only tolerated
  BEFORE the first hashed row (documented legacy prefix). A NULL hash
  AFTER the chain started is tampering and fails verification at that
  row id — previously any NULL row anywhere was silently skipped, so an
  attacker with UPDATE could erase intermediate evidence undetected.
- migration 0004: audit_log becomes append-only. SQLite: BEFORE UPDATE/
  DELETE triggers RAISE(ABORT). Postgres: BEFORE triggers RAISE EXCEPTION.
  Closes the hole for the table owner and direct SQL connections.
- monitor.verify_chain_against_anchors(): compares the chain tail against
  anchors published to Nostr. Explicit statuses: no_anchors (never
  anchored / notes missing on relays), local_only (no fetcher), ok
  (relay note attests the tail or the last local anchor), mismatch.
- tests: NULL-before-genesis ok, NULL-in-middle fails at that id, NULL
  late fails, UPDATE rejected, DELETE rejected, INSERT still allowed,
  no-anchors explicit, local-only explicit, mismatch detected, match ok.

Gate: ruff 0 · 81 unit + 1 PG integration · alembic 0001→0004 on PG ·
gitleaks clean · make check exit 0.
2026-10-28 19:56:50 +00:00
cayce 029e4f77d7 Merge PR #9: D16 — desacoplar clave de operador de identidad de agente
ci / lint-test-leaks (push) Canceled after 0s
2026-10-28 19:51:21 +00:00
cayce 0d24309433 audit-D16: operator key must never be an agent identity
ci / lint-test-leaks (pull_request) Canceled after 0s
Background: CAYCE_OPS_OPERATOR_PUBKEY was set to Cayce nostr pubkey —
Cayce was approving itself. Signature was technically valid but semantically
null: human approval signed by the agent is not human approval.

- signing.py: validate_no_self_approval() rejects the operator pubkey when
  it matches (a) the pubkey derived from any private key in the config,
  or (b) any pubkey in CAYCE_OPS_FORBIDDEN_OPERATOR_PUBKEYS deny-list.
- executor/maild/publisher _signing() now calls the validation, so every
  daemon and every CLI approve-* path refuses to operate (D16).
- tests: 6 new (agent-key refusal, human key passes, empty passes,
  explicit denylist, end-to-end daemon refusal, derivation roundtrip).
- docs/DECISIONS.md: D16.

The operator signature is now produced OUTSIDE the VPS. CAYCE_OPS_OPERATOR_PUBKEY
must stay empty until the external signer exists — daemons refuse, which is
correct. The Cayce nsec itself is NOT touched (still the agent identity for
publishing and anchoring).
2026-10-28 19:51:04 +00:00
cayce 53ea15ee51 Merge PR #11: identity — Próspera agentic-companies pilot framing
ci / lint-test-leaks (push) Canceled after 0s
Personal names removed from the public project; approver actors are
generic labels. Gate green.
2026-10-20 02:30:01 +00:00
cayce a2de419467 identity: Próspera agentic-companies pilot framing — no personal names
- README: the stack belongs to Cayce, an autonomous entity operating under
  the Próspera ZEDE agentic-companies jurisdiction pilot; the experiment is
  managed by Cayce itself
- tests: approver actor renamed to the generic label operator
- zero occurrences of personal names in the tree (verified with grep)
2026-10-20 02:29:54 +00:00
cayce 13db3996d9 audit: round-2 response — finding -> commit -> test table (audited: post-merge)
ci / lint-test-leaks (push) Canceled after 0s
2026-10-20 02:22:36 +00:00
cayce aba42a4210 Merge PR #10: audit round 2 — signed approvals, chain serialization, roles, anchoring
ci / lint-test-leaks (push) Canceled after 0s
One commit per point (audit-1..audit-7). Gate at merge: ruff 0 ·
65 unit tests + 1 PG integration test · alembic 0001→0003 with PG
round-trip · gitleaks clean.
2026-10-20 02:21:58 +00:00
cayce 3bde785261 audit-7 + docs: plan systemd units, D13-D15
- cayce-ops-plan.service/.timer (daily): the @ template cannot express
  publisher plan, so it gets its own unit
- DECISIONS: D13 no non-human approvals; D14 signatures bind content +
  deployment; D15 negation veto classifier
2026-10-20 02:21:36 +00:00
cayce c855f01da5 audit-6: CI runs migrations + integration against Postgres 16
ci.yml gains a postgres:16 service; alembic upgrade/downgrade/upgrade
round-trip runs on PG, unit tests run -m "not integration", and the
roles.sql integration suite runs with a postgresql-client install.
(Gate executed locally against Postgres 18.6 — see PR description.)
2026-10-20 02:21:36 +00:00
cayce 56b35d3a63 audit-5: CLI send path uses the tested code, failures audited, non-zero exit
Note: the send-path refactor (send_approved CLI -> _send_approved with
factory, send_failed audits in their own transaction, DaemonRun record,
non-zero exit on any failure class) landed within the audit-1 commit —
maild.py was staged whole. This empty commit marks the point in the
review trail; the behavior is covered by test_send_failures_audited_and_
exit_code.
2026-10-20 02:21:36 +00:00
cayce ffe425320d 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.
2026-10-20 02:20:56 +00:00
cayce d77409148c audit-3 (critical 1 deploy half): roles.sql rewritten + PG integration test
- one least-privilege role per daemon; cayce_approver is the ONLY writer of
  status=APPROVED / operator_signature / approved columns
- per-table BEFORE triggers reject approval writes from daemon roles
- no literal passwords: refuses to run without the six -v password vars
  (psql exits 3 via missing-table SELECT guard)
- idempotent: revokes + drops trigger/function before recreating
- tests/integration/test_pg_roles.py: applies roles.sql via psql -v, runs a
  full cycle per daemon role (propose -> approve -> execute; compose ->
  approve -> send; plan -> approve -> publish; anchor) and asserts the
  denials; runs the alembic upgrade->downgrade->upgrade round-trip on
  Postgres (point 6). Skips cleanly without a reachable PG.
2026-10-20 02:20:26 +00:00
cayce 903c591a06 audit-2 (critical 4): approval-veto classifier — any negation vetoes approval
Replaces per-pattern negation lists: if ANY negation token (not, no, never,
cannot, cannot, will not, has not, is not, no longer, unable, decline,
regret, unfortunately) appears anywhere in the mail, an approval
classification downgrades to needs_review. Approval requires a mail with
zero negations. The LLM prompt braces stay escaped (round-1 fix retained).

tests: the six mandated sentences all classify non-approval.
2026-10-20 02:20:26 +00:00
cayce 40453aadb4 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.
2026-10-20 02:20:09 +00:00
cayce 61d5cfd67d audit: response section — all findings addressed at c9bd441
ci / lint-test-leaks (push) Canceled after 0s
2026-10-19 23:07:17 +00:00
cayce c9bd4411e8 Merge PR #9: audit fixes — criticals 1-5 + mediums (56 tests, gate green)
ci / lint-test-leaks (push) Canceled after 0s
2026-10-19 23:06:30 +00:00
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
cayce 2da5f3ff24 Merge PR #8: audit update — audited commit pinned, gap disclosed
ci / lint-test-leaks (push) Canceled after 0s
2026-10-19 22:04:35 +00:00
cayce 2423cd10b8 audit: pin audited commit d500d88 + disclose lint-gate gap and fix
ci / lint-test-leaks (pull_request) Canceled after 0s
2026-10-19 22:04:34 +00:00
cayce d500d88870 Merge PR #7: lint-fix — make check fully green (E501/F841/F821/E731)
ci / lint-test-leaks (push) Canceled after 0s
Closes the gate gap flagged during final verification: prior per-branch
runs read ruff tail lines incorrectly. This commit is the audited one.
2026-10-19 22:03:55 +00:00
cayce a282aedb94 lint: wrap long lines (E501), fix F841/F821/E731 — make check fully green
All 32 findings resolved: regex literals kept single-line with noqa
(pattern IS the reviewed artifact), HTML rows string-concatenated for
line length, walrus tuple in BIP-340 verify split into named generator,
unused pytest import dropped. No behavior changes; 44/44 tests pass.
Full-gate discipline restored: ruff exit 0.
2026-10-19 22:03:53 +00:00
cayce abbaf7e4e2 Merge PR #6: audit — AUDIT.md for operator review
ci / lint-test-leaks (push) Canceled after 0s
2026-10-19 21:58:47 +00:00
cayce 6d77b3eff6 audit: AUDIT.md for operator review
ci / lint-test-leaks (pull_request) Canceled after 0s
Component map + data flow, every external call (where/what/trigger),
human-gate vs AUTONOMOUS_MODE matrix, env+permissions per component,
pinned deps with purpose, known risks (8, each with mitigation),
test+stack instructions. Audited-commit hash lands in the issue.
2026-10-19 21:58:46 +00:00
cayce 3f07362b7a Merge PR #5: monitor
ci / lint-test-leaks (push) Canceled after 0s
Static status page with privacy assertions and full escaping. 44 tests green.
2026-10-19 21:57:32 +00:00
cayce f61372e28e monitor: static status page — health, queue, gates, audit tail
ci / lint-test-leaks (pull_request) Canceled after 0s
- self-contained HTML (no external assets/JS), everything html-escaped
- daemon health from daemon_runs (last status + last run per daemon)
- proposal queue counts by state; human-gate counters (mail drafts awaiting
  approval, content draft/approved)
- audit tail: actor/action/result only — no detail payloads, no addresses,
  no subjects, no content bodies (tests assert the negatives)
- DaemonRun bookkeeping like every other daemon

tests: 3 new (sections render, leak-negatives, XSS escape) — 44 total
2026-10-19 21:57:31 +00:00
cayce 25c7db3a31 Merge PR #4: publisher
ci / lint-test-leaks (push) Canceled after 0s
Themed calendar + NIP-01 nostr (deterministic schnorr, self-verify),
FB/IG adapters disabled by default, hard human gate. 41 tests green.
2026-10-19 21:54:48 +00:00
cayce aabd6bafc7 publisher: themed calendar, NIP-01 nostr adapter, gated brand images
ci / lint-test-leaks (pull_request) Canceled after 0s
- calendar: one draft per theme/channel (idempotent), rotated schedule
- human gate: approve() audited; AUTONOMOUS_MODE has no effect here by design
- nostr (primary): in-repo NIP-01 client (D4) — canonical serialization,
  sha256 id, BIP-340 schnorr sign via coincurve with zero aux randomness
  (deterministic), pure-python reference verification before publish,
  per-relay publish with isolated errors, event id stored as post_ref
- facebook/instagram adapters: exist but refuse to run unless explicitly
  enabled via env (default deployment never touches Meta)
- images: Pillow brand templates (bh gold / hc orange), 1080x1350,
  template-driven typeset design (not AI-generated media)
- failure isolation: config errors audited, item stays approved

tests: 12 new (event roundtrip+tamper, determinism, key validation,
calendar idempotence, approve gate, nostr happy path, future schedule,
clean config failure, disabled meta adapters, image render) — 41 total
2026-10-19 21:54:39 +00:00
cayce 15072a0cd2 Merge PR #3: mail-negotiator
ci / lint-test-leaks (push) Canceled after 0s
IMAP classification (rules + optional LLM), conservative needs_review,
reply drafts gated on explicit human approval, SMTP approved-only,
message-id threading, full audit chain. 29 tests green.
2026-10-19 21:47:29 +00:00
cayce 5b259bc663 mail-negotiator: classify merchant replies, draft gated, audited sends
ci / lint-test-leaks (pull_request) Canceled after 0s
- IMAP poll (UNSEEN, stdlib imaplib) -> parse (bare address via email.utils)
- threading: In-Reply-To -> outbound sent-draft message-id -> proposal
- classify: deterministic rules; optional LLM behind provider interface;
  below-threshold confidence -> needs_review (never guessed)
- unthreadable approvals flagged needs_review (suspicious by definition)
- merchant_status recorded on proposal detail; proposal lifecycle untouched
- reply drafts generated from templates (drafted), NEVER auto-sent
- SMTP sends only draft_status=approved (human approve_draft, audited);
  sent drafts store their Message-ID for future threading
- idempotent on Message-ID; DaemonRun wrapper for monitor

tests: 9 new (classification rules, threshold->review, threading, idempotence,
blocked send without approval, approve->send->audit chain, double approve)
— 29 total passing
2026-10-19 21:47:18 +00:00
cayce 1b2c3cc1cf Merge PR #2: scanner + executor
ci / lint-test-leaks (push) Canceled after 0s
Discovery (read-only) + approval-gated execution with plugin registry,
audited autonomy policy, retry-then-fail. Gate: ruff clean, 20 tests green,
migration smoke ok, no leaks (full history).
2026-10-19 21:42:41 +00:00
cayce 73ccdf7bdf scanner + executor: discovery files proposals; execution gated on approval
ci / lint-test-leaks (pull_request) Canceled after 0s
scanner (read-only):
- sources from CAYCE_OPS_SCANNER_SOURCES JSON (rss + page types), stdlib only
- keyword scoring -> risk_score; kind via heuristics (affiliate/partnership/content_gap)
- idempotent: sha1 dedupe_key; repeated scans skip existing
- every proposal audited (actor daemon:scanner); source errors recorded, never fatal

executor (human gate):
- approve/reject audited with operator identity; only proposed -> approved
- AUTONOMOUS_MODE=true adds exactly one path: policy auto-approval
  (kinds allowlist + daily cap, actor auto:policy) — DECISIONS D5
- plugin registry by kind; unknown kinds fall back to log_action (D9)
- retries with max_attempts, then failed; every transition audited
- http_post plugin inert unless CAYCE_OPS_ENABLE_HTTP_ACTIONS=true

tests: 13 new (dedupe, keyword filter, autonomy bounds, retry-then-fail,
blocked http_post, fallback plugin) — 20 total passing
2026-10-19 21:42:34 +00:00
cayce 00fa3777ab Merge PR #1: skeleton — structure, schema, CI, deploy
ci / lint-test-leaks (push) Canceled after 0s
Component wiring, ORM schema + state machines, env-only config, alembic 0001,
Typer CLI, CI (ruff/pytest/alembic/gitleaks), compose + systemd, DECISIONS D1-D9.
Gate: make check green (ruff 0 errors, 7 tests pass, migration smoke ok, no leaks).
2026-10-19 21:38:06 +00:00
cayce 34a5f331ac skeleton: package, schema, config, CLI, alembic, CI, deploy, docs
ci / lint-test-leaks (pull_request) Canceled after 0s
- cayceops core: env-only config (CAYCE_OPS_*), engine/session, ORM models
  with state machines (proposal/mail/content) and audited writes
- provider-agnostic LLM interface (null provider default, stdlib openai-compat)
- Typer CLI with lazy component loading (scanner/executor/maild/publisher/monitor)
- alembic 0001 initial schema; sqlite-portable JSON columns
- tests: 7 passing on throwaway sqlite; fixtures invented, never real data
- ci: ruff + alembic smoke + pytest + gitleaks(full history) in
  .gitea/workflows + .github/workflows; local gate = make check
- deploy: docker-compose (postgres 16) + hardened systemd units/timers
- docs: DECISIONS D1-D9; README project (grant text retired)
2026-10-19 21:35:50 +00:00
cayce dbabb26c22 OpenSats proposal v1 — Cayce autonomous entity 2026-10-19 19:29:50 +00:00