Compare commits

..
Author SHA1 Message Date
coder-agent b9dcacd76e autodeploy: cierre de subshell antes del header de bitcoinhotels (cosmetico)
ci / lint-test (pull_request) Successful in 4s
isolation-test / probe (pull_request) Successful in 1s
2026-11-03 15:50:44 +00:00
coder-agent c17ac080cb autodeploy: seccion hardcap en subshell — su rollback no aborta los deploys de bitcoinhotels
ci / lint-test (pull_request) Successful in 7s
isolation-test / probe (pull_request) Successful in 1s
2026-11-03 15:50:11 +00:00
ci-bot fae57e3b4f Merge pull request 'reporte diario: PRs pendientes de merge + contenido nuevo por sitio + semanal AdSense' (#63) from r32-report into main
ci / lint-test (push) Successful in 2s
2026-11-03 15:20:03 +00:00
coder-agent 3ec9b2cfb3 reporte diario: 3b PRs esperando tu merge + contenido nuevo por sitio con enlaces + línea semanal AdSense (lunes)
ci / lint-test (pull_request) Successful in 12s
isolation-test / probe (pull_request) Successful in 1s
2026-11-03 15:18:12 +00:00
pattern f9e54ad675 Merge pull request 's1: flock autodeploy+auto-merge versionados + guard CI/no-/srv' (#58) from r31-s1-lock-ci into main
ci / lint-test (push) Successful in 23s
Reviewed-on: #58
2026-11-03 14:57:23 +00:00
ci-bot 29225fc293 Merge pull request 'r31 doc: estados finales (verdes) de los PRs' (#61) from r31-doc-final into main
ci / lint-test (push) Successful in 2s
2026-11-03 04:10:03 +00:00
coder-agent f6f39c7833 r31 doc: estados finales de PRs (#59/#60 merged; #14/#9/#58 verdes esperando tu merge) + epílogo socket unix
ci / lint-test (pull_request) Successful in 2s
isolation-test / probe (pull_request) Successful in 1s
2026-11-03 04:07:46 +00:00
ci-bot 0fe80d0dca Merge pull request 'r31 doc: S1+S2+F1+P1/P2+B1' (#60) from r31-doc into main
ci / lint-test (push) Successful in 2s
2026-11-03 03:55:16 +00:00
coder-agent e610a85fe9 r31 doc: S1 (causa raíz + fixes probados), S2, F1, P1/P2, B1 propuesta
ci / lint-test (pull_request) Successful in 2s
isolation-test / probe (pull_request) Successful in 1s
2026-11-03 03:54:15 +00:00
ci-bot 266706322e Merge pull request 'p1: units de promo desde el checkout de entity' (#59) from r31-p1-units into main
ci / lint-test (push) Successful in 4s
2026-11-03 03:35:12 +00:00
coder-agent 1b495d7117 p1: unit de promo desde el checkout (entity) + estado en /var/lib/cayce/promo
ci / lint-test (pull_request) Successful in 18s
isolation-test / probe (pull_request) Successful in 1s
WorkingDirectory=/srv/cayce-core/social · EnvironmentFile=/var/lib/cayce/promo/.env
· PROMO_STATE_HOME=/var/lib/cayce/promo (D2). El timer sigue parado hasta
que el operador reanude la campaña (r30b E3).
2026-11-03 03:27:50 +00:00
coder-agent 6b1b2f83b7 ci: retrigger (workdir_parent absoluto en el runner)
ci / lint-test (pull_request) Successful in 1s
isolation-test / probe (pull_request) Successful in 0s
2026-11-03 03:10:36 +00:00
coder-agent 5cc3fd6419 s1: autodeploy+auto-merge con flock compartido (versionados) + guard de workflow
ci / lint-test (pull_request) Failing after 1s
isolation-test / probe (pull_request) Failing after 0s
- Incidente 02:06:29Z: el rollback del autodeploy (git reset --hard $OLD)
  pisó la rama e2-fuentes-y-sellos porque el árbol cambió de branch a mitad
  de deploy. Causa: ninguna exclusión mutua entre autodeploy, auto-merger y
  ops manuales sobre los mismos árboles.
- Lock: /run/lock/cayce-git-ops.lock (tmpfiles.d, 0664 root:ubuntu).
  autodeploy flock -n (salta ciclo), auto-merge flock -w 240 (espera).
- Guard en ci.yml: un job cuyo workspace caiga bajo /srv/* revienta.
2026-11-03 03:03:03 +00:00
ci-bot 3a9d8f3e7a Merge pull request 'r30b doc (actualización post-cierre): #12 mergeado/desplegado, #7 verde, incidente rama e2' (#57) from r30b-docs into main
ci / lint-test (push) Successful in 7s
2026-11-03 02:25:04 +00:00
coder-agent a550d50c2f r30b doc: post-cierre — #12 mergeado por ci-bot y desplegado (build-id verificado), #7 verde, incidente de rama e2 documentado
ci / lint-test (pull_request) Successful in 4s
isolation-test / probe (pull_request) Successful in 6s
2026-11-03 02:20:18 +00:00
ci-bot 5e139970e7 Merge pull request 'r30b doc: auditoria r26-28 + E1-E3 + propuesta F1 + G1' (#56) from r30b-docs into main
ci / lint-test (push) Successful in 2s
2026-11-03 02:15:00 +00:00
coder-agent fae7ebe3c2 r30b doc: auditoria r26-28 (revert #11), E1-E3 factual, propuesta F1, G1 explicado
ci / lint-test (pull_request) Successful in 4s
isolation-test / probe (pull_request) Successful in 5s
2026-11-03 02:12:24 +00:00
ci-bot 845609e9fa Merge pull request 'DECISIONS.md: reglas D1-D3 del operador' (#55) from decisions into main
ci / lint-test (push) Successful in 2s
2026-11-02 20:40:03 +00:00
patternn fe1b8a9b67 DECISIONS: D1 cuentas/tokens, D2 estado fuera de checkouts, D3 trazabilidad
ci / lint-test (pull_request) Successful in 2s
isolation-test / probe (pull_request) Successful in 0s
2026-11-02 20:36:07 +00:00
ci-bot b38d8e1332 Merge pull request 'r25 doc: desbloqueo de PRs + verificación v3' (#54) from r25-doc into main
ci / lint-test (push) Successful in 1s
2026-11-02 20:30:00 +00:00
patternn 9caf71cecc r25 doc: desbloqueo real (codeowners+bypass no aplican en 1.27), pruebas, v3 verificada
ci / lint-test (pull_request) Successful in 1s
isolation-test / probe (pull_request) Successful in 1s
2026-11-02 20:25:49 +00:00
pattern ecc8835bd4 Merge pull request 'identity v3: voz por sitio (hardcap=señal; bitcoinhotels=guías)' (#23) from voz-v3 into main
ci / lint-test (push) Successful in 5s
Reviewed-on: #23
2026-11-02 20:04:25 +00:00
pattern 6b1fe439c7 Merge pull request 'Recuperación: SPEC.md + docs/cayce-dev.md (sin commit desde 31-oct)' (#25) from recovery-uncommitted-20261101 into main
ci / lint-test (push) Successful in 3s
Reviewed-on: #25
2026-11-02 20:03:57 +00:00
ci-bot 37e2ddb4bc Merge pull request 'CODEOWNERS: aprobacion de pattern para rutas protegidas' (#52) from codeowners into main
ci / lint-test (push) Successful in 4s
2026-11-02 20:03:25 +00:00
patternn f89e63228d CODEOWNERS: pattern aprueba guard/outbox/watcher/identity/workflows/automerger
ci / lint-test (pull_request) Successful in 4s
isolation-test / probe (pull_request) Successful in 2s
2026-11-02 20:00:20 +00:00
ci-bot 076c31fb78 Merge pull request 'r24 doc: PRs trancados (causa y fix) + FASE A' (#51) from r24-doc into main
ci / lint-test (push) Successful in 1s
2026-11-02 19:55:02 +00:00
patternn 256e2f1d4c r24 doc: causa raiz de PRs trancados + FASE A bh en produccion + hc PR#6
ci / lint-test (pull_request) Successful in 4s
isolation-test / probe (pull_request) Successful in 2s
2026-11-02 19:53:08 +00:00
cayce 385c39ba3e 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 (pull_request) Successful in 4s
isolation-test / probe (pull_request) Successful in 9s
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-02 19:40:56 +00:00
cayce 8529c2b349 identity v3: voz por sitio (hardcap=senal fechada con fuente; bitcoinhotels=guias de campo verificadas); sin emojis
ci / lint-test (pull_request) Successful in 3s
isolation-test / probe (pull_request) Successful in 8s
2026-11-02 19:40:50 +00:00
ci-bot 413691c5a9 Merge pull request 'adsense r23: plan de contenido + borrador como-trabajamos + checklist' (#50) from adsense-r23 into main
ci / lint-test (push) Successful in 1s
2026-11-02 18:35:01 +00:00
patternn c89b7d20cc adsense r23: plan de contenido (20 bh + hubs/watch/guias hc) + borrador como-trabajamos + checklist de postulacion
ci / lint-test (pull_request) Successful in 1s
isolation-test / probe (pull_request) Successful in 1s
2026-11-02 18:32:51 +00:00
ci-bot 7aedfed986 Merge pull request 'r22 doc: identidad de deploy + ciclos mudos' (#49) from r22-doc into main
ci / lint-test (push) Successful in 7s
2026-11-02 18:25:06 +00:00
patternn db8a621cda r22 doc: identidad de deploy + ciclos por pieza + prueba mudo
ci / lint-test (pull_request) Successful in 6s
isolation-test / probe (pull_request) Successful in 9s
2026-11-02 18:22:14 +00:00
ci-bot 509db558bd Merge pull request 'r21 doc: politica de DMs + limpieza gtag/AdSense + fixes' (#48) from r21-doc into main
ci / lint-test (push) Successful in 1s
2026-11-02 17:55:00 +00:00
patternn 42c62491fb r21 doc: politica de DMs, gtag/AdSense fuera, fixes encadenados
ci / lint-test (pull_request) Successful in 4s
isolation-test / probe (pull_request) Successful in 8s
2026-11-02 17:50:32 +00:00
pattern 30d76fa565 Merge pull request 'ci: pipefail real (defaults.run.shell) + revert del test roto fusionado por falso verde' (#44) from ci-pipefail-real into main
ci / lint-test (push) Successful in 1s
Reviewed-on: #44
2026-11-02 17:13:03 +00:00
ci-bot 3b8e1296e2 Merge pull request 'r20 doc: perf, pipefail real, reporte diario, seo' (#46) from r20-doc into main
ci / lint-test (push) Successful in 2s
2026-11-02 13:15:11 +00:00
patternn 8c5f085e3f r20 doc: perf gate mediana, pipefail real + incidente #43, reporte diario, seo
ci / lint-test (pull_request) Successful in 7s
isolation-test / probe (pull_request) Successful in 2s
2026-11-02 13:09:15 +00:00
patternn 661ca844d0 ci: pipefail REAL (defaults.run.shell por job) + retiro el test roto que entro a main por el falso verde
ci / lint-test (pull_request) Successful in 1s
isolation-test / probe (pull_request) Successful in 1s
2026-11-02 12:59:42 +00:00
ci-bot cc22feea23 Merge pull request 'demo r20: test roto — el CI debe dar ROJO' (#43) from demo-rojo-r20 into main
ci / lint-test (push) Successful in 8s
2026-11-02 12:55:04 +00:00
patternn fa312c2fbd demo r20: test roto — el CI debe dar ROJO (prueba del pipefail #41)
ci / lint-test (pull_request) Successful in 6s
isolation-test / probe (pull_request) Successful in 1s
2026-11-02 12:51:32 +00:00
pattern c027317f46 Merge pull request 'ci: set -o pipefail — los tests rotos ya no dan verde' (#41) from ci-pipefail into main
ci / lint-test (push) Successful in 1s
Reviewed-on: #41
2026-11-02 12:29:42 +00:00
ci-bot 21a6aa41ca Merge pull request 'r19 doc: auto-merge demo completo, bitcoinhotels en el sistema, sfeed' (#42) from r19-doc into main
ci / lint-test (push) Successful in 5s
2026-11-02 07:50:06 +00:00
patternn 41e14174b6 r19 doc: auto-merge demo completo, bitcoinhotels en el sistema, sfeed
ci / lint-test (pull_request) Successful in 3s
isolation-test / probe (pull_request) Successful in 1s
2026-11-02 07:47:26 +00:00
ci-bot de73dd2af9 Merge pull request 'ci demo: trivial verde (debe auto-mergear)' (#35) from ci-demo-trivial into main
ci / lint-test (push) Successful in 1s
2026-11-02 07:34:56 +00:00
ci-bot 2405070a0c Merge pull request 'recupera doc r18 (commit perdido en main local)' (#38) from r19-recover-r18doc into main
ci / lint-test (push) Successful in 4s
2026-11-02 07:34:41 +00:00
patternn ef733e9ecc ci: set -o pipefail — los tests rotos ya no dan verde
ci / lint-test (pull_request) Successful in 1s
isolation-test / probe (pull_request) Successful in 0s
2026-11-02 06:47:14 +00:00
patternn a7782df111 r18 doc: autodeploy probado, promo v3 publicada en FB, bitcoinhotels actualizado
ci / lint-test (pull_request) Successful in 1s
isolation-test / probe (pull_request) Successful in 0s
2026-11-02 06:43:36 +00:00
patternn 73b93c8cfa ci demo: trivial verde
ci / lint-test (pull_request) Successful in 2s
isolation-test / probe (pull_request) Successful in 0s
2026-11-02 06:42:28 +00:00
pattern 179ecf47ab Merge pull request 'r17: runtime/ eliminado (fuente unica entity) + docs de sesion r16/r17' (#34) from r16-r9-gemini into main
ci / lint-test (push) Successful in 1s
Reviewed-on: #34
2026-11-02 06:37:52 +00:00
patternn a0ee8fbf1d r18 doc: autodeploy probado, promo v3 publicada en FB, bitcoinhotels actualizado 2026-11-02 06:20:33 +00:00
patternn 9b44863e00 r17 doc: incidencias resueltas (DATABASE_URL, logPost, scout)
isolation-test / probe (pull_request) Successful in 0s
ci / lint-test (pull_request) Successful in 1s
2026-11-02 05:52:26 +00:00
patternn 25f6efb1c1 r17 doc: rotaciones, borrado 99cc75f4, fixes monitor/promo, fuente unica entity 2026-11-02 05:45:20 +00:00
patternn 93fa1a07e7 r17: runtime duplicado eliminado — la fuente unica del runtime es cayce/entity (checkout en /srv/cayce-core) 2026-11-02 05:43:29 +00:00
patternn d30fdc7fd4 r16 doc: incidente kill switch por kind0 de restart + fix outbox 2026-11-02 04:54:49 +00:00
patternn 6a16c64f45 r16: guard R9 con terminos de la fuente (no dominio generico) + doc de sesion 2026-11-02 04:50:38 +00:00
pattern 6dec3192bd Merge pull request 'fuentes automaticas: el codigo cita, no el LLM (ronda 13-14)' (#33) from fuentes-automaticas into main
ci / lint-test (push) Successful in 4s
Reviewed-on: #33
2026-11-02 04:14:32 +00:00
cayce dd8e933ba5 fuentes automaticas: el CODIGO agrega la fuente, no el LLM (ronda 13-14)
ci / lint-test (pull_request) Successful in 2s
isolation-test / probe (pull_request) Successful in 0s
- sources.mjs (nuevo): selectSource() + attribute() — seleccion de material
  ≤72h con score≥6, atribucion en formato fijo.
- core.mjs: las 4 piezas (digest/opinion/reflection/events) generan sobre
  la fuente seleccionada y el codigo agrega la atribucion DESPUES del LLM.
- guard.mjs:
  * R1/R2/R4/R5/R8 evaluan el TEXTO SIN la linea de atribucion
  * R3/R7 ven el texto completo (con atribucion) — la fuente garantiza
    que pasen
  * R9 (nueva): la pieza debe compartir terminos del dominito con su fuente
  * R8: superlativos estrechos (sin never/only que dejaban pasar aforismos)
  * deletion anadido a METADATA_TYPES (no cuenta hacia el tope diario)
- guard.test.mjs: 24/24 verdes (incluye tests R14a/c/d)
2026-11-02 04:08:26 +00:00
pattern 4e4fffaa16 Merge pull request 'diseño: vía de publicación bitcoinhotels (PRs + guard + build)' (#24) from docs-bitcoinhotels-publish into main
ci / lint-test (push) Successful in 3s
Reviewed-on: #24
2026-11-02 03:46:20 +00:00
pattern 28a97959c4 Merge pull request 'ci: workflow host mode' (#32) from ci-host-mode into main
ci / lint-test (push) Successful in 3s
Reviewed-on: #32
2026-11-02 03:45:58 +00:00
cayce be29f9030c ci: workflow host mode (sin docker ni postgres de servicio)
ci / lint-test (pull_request) Successful in 3s
isolation-test / probe (pull_request) Successful in 0s
2026-11-02 03:39:16 +00:00
cayce 7c58a848e7 docs: diseño mínimo de la vía de publicación de bitcoinhotels (PRs + guard + build + auto-deploy)
ci / lint-test-leaks (pull_request) Failing after 4s
isolation-test / probe (pull_request) Successful in 1s
2026-11-02 03:36:58 +00:00
pattern d610a33290 Merge pull request 'docs: log de sesion + propuesta de claves por agente' (#27) from sessions/2026-11-01-opencode into main
ci / lint-test-leaks (push) Failing after 4s
Reviewed-on: #27
2026-11-02 03:36:25 +00:00
pattern 428681ed4a Merge pull request 'runtime: outbox unico + relay-watcher (kill switch) + guard' (#28) from runtime/single-outbox-watcher into main
ci / lint-test-leaks (push) Failing after 3s
Reviewed-on: #28
2026-11-02 03:12:39 +00:00
pattern 0a682980f8 Merge pull request 'ci: fix puerto postgres de servicio (5433)' (#30) from fix-ci-port into main
ci / lint-test-leaks (push) Failing after 3s
Reviewed-on: #30
2026-11-02 03:11:30 +00:00
pattern a2b5ab4885 Merge pull request 'CI TEST: aislamiento runner (no auto-merge: workflows)' (#31) from ci-isolation into main
ci / lint-test-leaks (push) Failing after 3s
Reviewed-on: #31
2026-11-02 03:11:19 +00:00
cayce 658e15f467 ci: sonda de aislamiento del runner (demo ronda-9, no fusionar)
ci / lint-test-leaks (pull_request) Failing after 1s
isolation-test / probe (pull_request) Successful in 2s
2026-11-02 02:42:37 +00:00
cayce 0faa80b034 ci: puerto 5433 para el postgres de servicio (5432 ocupado por produccion)
ci / lint-test-leaks (pull_request) Failing after 11s
2026-11-02 02:36:56 +00:00
cayce 33ae345fbd runtime: outbox UNICO (imports absolutos) + relay-watcher con kill switch + guard R1-R8
ci / lint-test-leaks (pull_request) Failing after 3s
- outbox.mjs solo en /srv/cayce-core; daemon/social/mail/ops lo importan por ruta absoluta. deploy.sh ya no instala copias.
- relay-watcher.mjs: cada 5 min cruza eventos de la pubkey en 5 relays con outbound_drafts; sin aprobacion => PUBLISH_OFF + DM + audit. Probado: detecto 2 kind0 bypass reales (post-hoc registrados).
- guard R1-R8 (18 tests) + blocklist + check-single-outbox al check diario.
- stale duplicados archivados en forensics/scripts-archive-20261102 (nostr-daemon.mjs, dm-debug, publish-10050, dm-test2, find-followers).
2026-11-02 00:41:57 +00:00
cayce 797c37d312 docs: log de sesion 2026-11-01 (opencode) + propuesta de claves por agente/host
ci / lint-test-leaks (pull_request) Failing after 4s
2026-11-01 20:35:16 +00:00
45 changed files with 1705 additions and 2179 deletions
+43 -45
View File
@@ -6,58 +6,56 @@ on:
pull_request:
jobs:
lint-test-leaks:
lint-test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: cayce
POSTGRES_PASSWORD: ci-only-not-a-secret
POSTGRES_DB: cayce_ops
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U cayce -d cayce_ops"
--health-interval 5s --health-timeout 3s --health-retries 10
env:
CAYCE_OPS_DATABASE_URL: postgresql+psycopg://cayce:ci-only-not-a-secret@127.0.0.1:5432/cayce_ops
PGPASSWORD: ci-only-not-a-secret
# r20: pipefail a nivel job — un - run: set -o pipefail en paso aparte NO
# persiste (cada paso es un shell nuevo) y el test roto de la demo #43
# siguio dando verde hasta ser auto-mergeado a main.
defaults:
run:
shell: bash --noprofile --norc -eo pipefail {0}
steps:
- name: "r31 guard: CI nunca opera en /srv/*"
run: |
case "$PWD" in
/srv/*) echo "VIOLACION: workspace de CI en $PWD"; exit 1 ;;
*) echo "workspace OK: $PWD" ;;
esac
- uses: actions/checkout@v4
with:
fetch-depth: 0 # gitleaks needs full history
fetch-depth: 1
- uses: astral-sh/setup-uv@v5
with:
python-version: "3.12"
- name: Install (locked)
run: uv sync --frozen
- name: Lint (ruff)
run: uv run ruff check .
- name: Migrations upgrade/downgrade round-trip (Postgres 16)
- name: Setup
run: |
uv run alembic upgrade head
uv run alembic downgrade 0001
uv run alembic upgrade head
uv run alembic downgrade base
uv run alembic upgrade head
python3 -m pip install --user ruff pytest --quiet 2>/dev/null || true
- name: Alembic smoke (SQLite)
run: uv run alembic upgrade head
- name: Unit tests (pytest)
run: uv run pytest -m "not integration"
- name: Integration tests — roles.sql + daemon cycles (Postgres 16)
- name: Lint (ruff, advisory)
run: |
sudo apt-get update && sudo apt-get install -y postgresql-client
uv run pytest -m integration -v
if [ -d cayceops ]; then
ruff check cayceops/ --exit-zero || true
else
echo "no python package — OK"
fi
- name: Secret scan (gitleaks, full history)
- name: Tests
run: |
curl -sSfL https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz | tar -xz gitleaks
./gitleaks detect --source . --log-opts="--all" -v
if [ -d cayceops/tests ]; then
python3 -m pytest cayceops/tests/ -v --tb=short 2>&1 | tail -5
else
echo "no tests dir — OK"
fi
- name: Guard tests
run: |
if [ -f runtime/guard.test.mjs ]; then
cd runtime && node --test guard.test.mjs 2>&1 | tail -3 && cd ..
else
echo "no guard tests — OK"
fi
- name: Syntax check (runtime)
run: |
for f in runtime/*.mjs; do
[ -f "$f" ] && node --check "$f" || true
done
echo "syntax check OK"
+11
View File
@@ -0,0 +1,11 @@
name: isolation-test
on: [pull_request]
jobs:
probe:
runs-on: ubuntu-latest
steps:
- run: echo TEST1- leer .env produccion; cat /srv/cayce-core/.env 2>&1 || echo OK-INACCESIBLE
- run: cat /srv/cayce-nostr/.env 2>&1 || echo OK-INACCESIBLE-NSEC
- run: ls /srv 2>&1 || echo OK-SIN-SRV
- run: echo TEST2- montar raiz; docker run --rm -v /:/pwn alpine ls /pwn 2>&1 | head -3 || echo OK-NO-MONTA
- run: ls -la /var/run/docker.sock 2>&1 || echo OK-SIN-SOCKET
+15
View File
@@ -0,0 +1,15 @@
# Propietario editorial y de runtime: @pattern aprueba cambios en estas rutas.
# Complementa (no sustituye) la lista de exclusión del auto-merger.
*guard* @pattern
*relay-watcher* @pattern
*outbox* @pattern
*guard-blocklist* @pattern
*check-single-outbox* @pattern
SPEC.md @pattern
MANDATE.md @pattern
prompts/cayce-identity.md @pattern
.gitea/workflows/ @pattern
.github/workflows/ @pattern
*cayce-auto-merge* @pattern
*act-runner* @pattern
CODEOWNERS @pattern
+47
View File
@@ -0,0 +1,47 @@
# BTCPay Server Plugin: Event & Travel Integration (V1)
## 1. Data Sources & Verification
- Event data: Curated from hardcap.news RSS/API feed (events aggregated by Cayce runtime §9). Source list documented in `sources/` directory with QC-3 citations.
- Lodging data: `bitcoinhotels.org` static listings API. No real-time availability checking in V1 - listings are curated, not live.
- Verification: Event dates/coords verified against hardcap.news primary sources only. No automated scraping of third-party calendars.
## 2. Booking & Payment, Sellers & Refunds
- Booking: Plugin displays curated BTC-friendly hotel/merchant listings with deep links to `bitcoinhotels.org`. Reservation/payment occurs off-site via the hotel's own system.
- Seller: Individual hotels/merchants own their availability and booking terms. The plugin has no inventory control.
- Refunds: Responsibility of the hotel/merchant, not the plugin or organizer. Plugin displays refund policy links where provided by the hotel.
- Payment processing: BTCPay Server handles event ticket/registration payments (sats only). No hotel payments flow through BTCPay in V1.
## 3. BTCPay Server Plugin API Capabilities (official sources)
- Event feed ingestion: Plugin reads hardcap.news event RSS feed (curated by Cayce). Auto-generates event landing pages on bitcoinhotels.org.
- Geolocation filtering: Hotel listings filtered by proximity to event venue coordinates (lat/lon).
- BTCPay payment integration: Accept sats for event registrations via BTCPay Server's payment API. Plugin creates order → attendee pays → organizer receives notification.
- Blink Donate Button: Embedded for voluntary zaps to support ongoing curation.
- Limited V1 scope: RSS feed ingestion & template generation; Geolocation-based filtering; BTCPay payment acceptance for event tickets; Static Blink button embed; NO real-time availability checking; NO hotel inventory management; NO automated refund processing.
## 4. Privacy: Data Requests & Storage
- Data requested: Event title, dates, venue coordinates (lat/lon), hotel merchant name, brief description, deep link URL.
- Data storage: Event coords stored in plugin's `data/events.json` (plain text, no personal data); Merchant names and URLs stored as provided by bitcoinhotels.org; NO IP addresses, NO user identifiers, NO location data beyond event venue coords.
- Privacy rule: No "zero KYC" claims without substantiation. All data is publicly derivable from the event and hotel listings. No user tracking.
## 5. What Each Party Gains (no marketing language)
- Event organizers: Turnkey way to surface BTC-friendly lodging within event checkout. No platform fees. BTC-only payments expand circular economy.
- Attendees: Find BTC-friendly lodging without leaving event checkout. Pay with sats. Merchant choices are curated (no altcoin-promoting hotels).
- Hotel merchants: Exposure to Bitcoin-targeted audience. No platform fees for listing. Direct booking relationship.
- Cayce/bitcoinhotels: Continuous curation output. Plugin is open-source under MIT license. Long-term sustainability via hardcap.news adinaction and Nostr zap-gating.
## 6. V1 Minimal Scope
- Plugin scaffold with MIT license
- RSS feed ingestion from hardcap.news (events list)
- Landing page template on bitcoinhotels.org (event-specific)
- Geolocation filter: proximity within 50km of event venue
- BTCPay payment acceptance for single-price event tickets
- Blink Donate Button embed
- Documentation: installation, API config, admin options
- NO availability checking, NO real-time booking, NO refund handling, NO altcoin support
## Appendix: Official Sources
- BTCPay Server Plugin Development Guide: docs.btcpayserver.org/developer/plugins
- BTCPay Server API docs: docs.btcpayserver.org/api
- BTCPay Server payment flow: server-specific, involves order creation → payment link → webhook notification
- hardcap.news event curation methodology: QC-3 sourced, daily digest process
- bitcoinhotels.org listing format: static CSV/JSON, merchant-owned availability
+17
View File
@@ -0,0 +1,17 @@
[Unit]
Description=Cayce promo — publicación temática autónoma (FB+IG vía sfeed)
After=network-online.target
Wants=network-online.target
[Service]
User=ubuntu
# r31 (P1): corre desde el checkout del repo entity; estado en /var/lib/cayce/promo
WorkingDirectory=/srv/cayce-core/social
Environment=NODE_ENV=production
Environment=PROMO_STATE_HOME=/var/lib/cayce/promo
EnvironmentFile=/var/lib/cayce/promo/.env
ExecStart=/usr/bin/node /srv/cayce-core/social/promo.mjs
Restart=no
[Install]
WantedBy=multi-user.target
+10
View File
@@ -0,0 +1,10 @@
[Unit]
Description=Cayce promo cycle — cada 6h (publica lo debido de la campaña)
[Timer]
OnCalendar=*-*-* 09,13,15,19,21,01:00:00 UTC
RandomizedDelaySec=300
Persistent=true
[Install]
WantedBy=timers.target
+11
View File
@@ -0,0 +1,11 @@
# /usr/local/bin versionado
Copias exactas de los scripts de infra que viven en /usr/local/bin del VPS.
Actualizar: PR aquí → merge → copiar al VPS (sudo cp) + daemon-reload si toca unit.
- cayce-autodeploy (r31: flock compartido /run/lock/cayce-git-ops.lock)
- cayce-auto-merge (r31: mismo lock, espera hasta 240s)
El lockfile lo crea tmpfiles.d: /etc/tmpfiles.d/cayce-locks.conf
(f /run/lock/cayce-git-ops.lock 0664 root ubuntu -). Ops manuales con git
sobre /srv/*: flock /run/lock/cayce-git-ops.lock <comando>.
+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
# Auto-merge (ronda-9): SOLO PRs con todos los checks en success y que NO
# toquen paths protegidos. Nunca btcpay-events. Nunca credenciales del operador.
# r19: el token persiste en /etc/cayce/ci-bot.token (600). Regenerar con nombre
# ya usado da ERROR y dejaba BOT vacio (bug historico: el script no hacia nada).
# r31 (S1): lock compartido con cayce-autodeploy — nunca compiten sobre los
# mismos arboles/branches (incidente 02:06:29Z del 2026-11-03).
LOCK=/run/lock/cayce-git-ops.lock
exec 9>"$LOCK" || { echo 'FATAL: no se puede abrir lockfile'; exit 1; }
if ! flock -w 240 9; then
echo "[auto-merge] autodeploy mantiene el lock >240s — paso de este ciclo (el proximo mergea)"
exit 0
fi
BOT=$(cat /etc/cayce/ci-bot.token 2>/dev/null | tr -d '[:space:]')
if [ -z "$BOT" ]; then
BOT=$(sudo -u git /usr/local/bin/gitea --config /etc/gitea/app.ini admin user generate-access-token -u ci-bot -t "auto-merge-$(date +%s)" --scopes write:repository,write:issue 2>/dev/null | grep -aoE "[0-9a-f]{40}" | head -1)
[ -n "$BOT" ] && printf '%s' "$BOT" > /etc/cayce/ci-bot.token && chmod 600 /etc/cayce/ci-bot.token
fi
[ -z "$BOT" ] && { echo 'FATAL: sin token ci-bot'; exit 1; }
G="http://127.0.0.1:3100/api/v1"
# paths protegidos (grep -E contra la lista de ficheros modificados del PR)
PROTECTED="SPEC.md|MANDATE.md|prompts/cayce-identity.md|guard[^/]*\.|relay-watcher|outbox\.mjs|guard-blocklist|check-single-outbox|\.gitea/workflows|\.github/workflows|cayce-auto-merge|act_runner|docker"
for REPO in cayce-ops entity bitcoinhotels; do
curl -s -H "Authorization: token $BOT" "$G/repos/cayce/$REPO/pulls?state=open&limit=20" | python3 -c "
import json,sys
for p in json.load(sys.stdin):
if p.get('title','').upper().startswith('WIP'): continue
print(p['number'], p['head']['sha'])" 2>/dev/null | while read N SHA; do
FILES=$(curl -s -H "Authorization: token $BOT" "$G/repos/cayce/$REPO/pulls/$N/files?limit=100" | python3 -c "
import json,sys
for f in json.load(sys.stdin): print(f.get('filename',''))" 2>/dev/null)
if echo "$FILES" | grep -qE "$PROTECTED"; then
echo "$REPO #$N: TOCA PATH PROTEGIDO — no se auto-mergea"
continue
fi
# r19: /status (combinado) en vez de /statuses — la lista incluye los
# 'pending' historicos y hacia que un PR verde pareciera no verde
OK=$(curl -s -H "Authorization: token $BOT" "$G/repos/cayce/$REPO/commits/$SHA/status" | python3 -c "
import json,sys
d = json.load(sys.stdin)
print('yes' if isinstance(d, dict) and d.get('state') == 'success' else 'no')" 2>/dev/null)
if [ "$OK" = "yes" ]; then
curl -s -X POST -H "Authorization: token $BOT" -H "Content-Type: application/json" -d '{"Do":"merge"}' "$G/repos/cayce/$REPO/pulls/$N/merge" -o /dev/null -w "$REPO #$N (green, sin paths protegidos) merge: %{http_code}\n"
fi
done
done
+185
View File
@@ -0,0 +1,185 @@
#!/bin/bash
# cayce-autodeploy — r18: despliega main nuevo SIN intervención del operador.
# ~/cayce-ops → git pull de main (docs, prompts; el despliegue lee el tree)
# /srv/cayce-core → checkout de cayce/entity main, con PUBLISH_OFF activo
# durante el swap. Solo se retira si: tests del guard
# 27+/27 y watcher sin VIOLACION. Rollback automatico.
# Timer: cada 3 min. Fallo → DM al supervisor + logger. Nunca toca un tree sucio.
say() { echo "[autodeploy] $*"; logger -t cayce-autodeploy "$*"; }
dm() {
SUP=$(grep -aoE 'SUPERVISOR_NPUB_HEX=[0-9a-f]+' /srv/cayce-nostr/.env 2>/dev/null | cut -d= -f2)
[ -n "$SUP" ] && curl -s -X POST http://127.0.0.1:3011/dm -H "Content-Type: application/json" \
-d "{\"to_hex\":\"$SUP\",\"content\":\"AUTODEPLOY: $1\"}" >/dev/null 2>&1
}
# r31 (S1): lock compartido con cayce-auto-merge — nunca compiten sobre los
# mismos arboles/branches (incidente 02:06:29Z del 2026-11-03: un rollback
# `git reset --hard $OLD` pisó una rama de trabajo porque el árbol cambió de
# branch a mitad de deploy). Las ops manuales con git sobre /srv/* tambien
# lo toman: flock /run/lock/cayce-git-ops.lock <comando>
LOCK=/run/lock/cayce-git-ops.lock
exec 9>"$LOCK" || { say 'FATAL: no se puede abrir lockfile'; exit 1; }
if ! flock -n 9; then
say "auto-merge (u otra op) mantiene el lock — paso de este ciclo"
exit 0
fi
# ── 1. cayce-ops (main) ──────────────────────────────────────────────────────
cd /home/ubuntu/cayce-ops || { say "FATAL: sin ~/cayce-ops"; exit 1; }
git fetch -q origin 2>/dev/null
if [ "$(git branch --show-current)" = "main" ] && ! git merge-base --is-ancestor origin/main HEAD 2>/dev/null; then
if [ -n "$(git status --porcelain)" ]; then
say "ABORT: cayce-ops tree sucio"; dm "abortado: cayce-ops dirty"
elif git pull -q --ff-only origin main; then
say "cayce-ops → $(git rev-parse --short HEAD)"
# r21: sin DM (no urgente) — dm "cayce-ops actualizado a $(git rev-parse --short HEAD)"
else
say "ERROR: pull cayce-ops"; dm "error pull cayce-ops"
fi
fi
# ── 2. entity → /srv/cayce-core (con regla de urgencia) ──────────────────────
cd /srv/cayce-core || { say "FATAL: sin /srv/cayce-core"; exit 1; }
git fetch -q origin 2>/dev/null
# desplegar solo si main tiene commits que NO estan en el checkout
# r20: sin exit 0 — mataba el script y la sección bitcoinhotels nunca corría
if ! git merge-base --is-ancestor origin/main HEAD 2>/dev/null; then
# r25: lessons.md lo muta el ciclo retro en produccion — preservar (stash/pop).
# Cualquier OTRO fichero sucio si aborta.
DIRTY_OTHER=$(git status --porcelain | grep -v " lessons.md" | head -3)
if [ -n "$DIRTY_OTHER" ]; then
say "ABORT: /srv/cayce-core tree sucio: $DIRTY_OTHER"; dm "abortado: /srv/cayce-core dirty ($DIRTY_OTHER) — deploy pendiente"
exit 1
fi
git stash push -q -m "autodeploy-lessons" -- lessons.md 2>/dev/null || true
OLD=$(git rev-parse HEAD)
echo "autodeploy $(date -u +%FT%TZ)" > PUBLISH_OFF # r21: firmado para que urgent-watch no lo tome por manual
say "main nuevo detectado: $(git rev-parse --short HEAD) → $(git rev-parse --short origin/main) (PUBLISH_OFF activo)"
if ! git checkout -q main || ! git reset -q --hard origin/main; then
git reset -q --hard "$OLD" 2>/dev/null; rm -f PUBLISH_OFF
say "FATAL: checkout entity"; dm "FATAL checkout entity — deploy cancelado, sin cambios"
exit 1
fi
# verificación 1: tests del guard
OUT=$(cd /srv/cayce-core && node --test guard.test.mjs 2>&1)
PASS=$(echo "$OUT" | grep -c '^ok ')
FAIL=$(echo "$OUT" | grep -c '^not ok ')
if [ "$FAIL" != "0" ] || [ "$PASS" -lt 27 ]; then
git reset -q --hard "$OLD"
git stash pop -q 2>/dev/null || true
say "ROLLBACK: tests $PASS ok / $FAIL fail (<27 o fail>0)"
dm "ROLLBACK en deploy entity: tests $PASS/$FAIL. main NO desplegado; runtime en $(git rev-parse --short HEAD)"
exit 1
fi
# verificación 2: daemon + watcher limpio
sudo -n systemctl restart cayce-nostr.service
sudo -n systemctl start cayce-relay-watcher.service
W=0
for i in 1 2 3 4 5 6; do
sleep 5
[ "$(systemctl is-active cayce-relay-watcher.service)" = "inactive" ] && break
done
if systemctl is-failed cayce-relay-watcher.service >/dev/null 2>&1 \
|| journalctl -u cayce-relay-watcher -n 8 --no-pager 2>/dev/null | grep -q VIOLACION; then
git reset -q --hard "$OLD"
sudo -n systemctl restart cayce-nostr.service
say "ROLLBACK: watcher con VIOLACION — PUBLISH_OFF queda ACTIVO"
dm "ROLLBACK watcher en deploy entity. PUBLISH_OFF QUEDA ACTIVO — revision manual"
exit 1 # ojo: PUBLISH_OFF se queda (fail-safe deliberado)
fi
rm -f PUBLISH_OFF
git stash pop -q 2>/dev/null || true # lessons.md del ciclo retro
say "entity → $(git rev-parse --short HEAD) (tests $PASS/27, watcher OK, live)"
# r21: sin DM (no urgente) — dm "entity desplegado: $(git rev-parse --short HEAD) — tests $PASS/27, watcher OK"
fi # fin sección entity
# r32: la seccion hardcap corre en SUBSHELL — un ROLLBACK (exit 1) ya no
# aborta los deploys de bitcoinhotels (bug: rollback de hardcap llevaba
# bloqueando bh desde las 15:10 mientras gh#10 espera merge)
(
# ── 2b. hardcap-news → /srv/hardcap-news (GitHub; web, sin PUBLISH_OFF) ─────
cd /srv/hardcap-news || { say "FATAL: sin /srv/hardcap-news"; exit 1; }
git fetch -q origin 2>/dev/null
if ! git merge-base --is-ancestor origin/main HEAD 2>/dev/null && [ -z "$(git status --porcelain)" ]; then
OLD=$(git rev-parse HEAD)
if ! git checkout -q main || ! git reset -q --hard origin/main; then
git reset -q --hard "$OLD" 2>/dev/null; say "FATAL: checkout hardcap-news"; exit 1
fi
if git diff --name-only "$OLD" HEAD 2>/dev/null | grep -q "^frontend/package-lock.json$"; then
(cd frontend && npm ci --ignore-scripts >/dev/null 2>&1) || say "WARN: npm ci hardcap"
fi
if ! (cd frontend && npm run build >/tmp/hc-autodeploy-build.log 2>&1); then
git reset -q --hard "$OLD"; (cd frontend && npm run build >/dev/null 2>&1); sudo -n systemctl restart hardcap-frontend.service
say "ROLLBACK hardcap-news: fallo el build"; dm "ROLLBACK hardcap-news (build) — main NO desplegado, rollback activo"
exit 1
fi
sudo -n systemctl restart hardcap-frontend.service
sleep 6
CODE=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3003/)
if [ "$CODE" != "200" ]; then
git reset -q --hard "$OLD"; (cd frontend && npm run build >/dev/null 2>&1); sudo -n systemctl restart hardcap-frontend.service
say "ROLLBACK hardcap-news: verificacion (code=$CODE)"; dm "ROLLBACK hardcap-news verificacion: code=$CODE — rollback activo"
exit 1
fi
# r24: identidad por build-id (cuando el despliegue trae la función)
if git grep -q "NEXT_PUBLIC_BUILD_ID" -- frontend/src/app/layout.tsx frontend/next.config.js 2>/dev/null; then
BUILT=$(curl -s -m 8 http://127.0.0.1:3003/ | grep -oE 'name="build-id" content="[a-f0-9]{8}"' | grep -oE '[a-f0-9]{8}' | head -1)
WANT=$(git rev-parse --short=8 HEAD)
if [ "$BUILT" != "$WANT" ]; then
git reset -q --hard "$OLD"; (cd frontend && npm run build >/dev/null 2>&1); sudo -n systemctl restart hardcap-frontend.service
say "ROLLBACK hardcap-news: build-id '$BUILT' != '$WANT'"; dm "ROLLBACK hardcap-news: servia build '$BUILT' pero lo desplegado era '$WANT'."
exit 1
fi
say "identidad hardcap OK: servido=$BUILT desplegado=$WANT"
fi
say "hardcap-news → $(git rev-parse --short HEAD) (200)"
fi
)
# ── 3. bitcoinhotels → /srv/bitcoinhotels (web; sin PUBLISH_OFF) ────────────
cd /srv/bitcoinhotels || { say "FATAL: sin /srv/bitcoinhotels"; exit 1; }
git fetch -q origin 2>/dev/null
if ! git merge-base --is-ancestor origin/main HEAD 2>/dev/null && [ -z "$(git status --porcelain)" ]; then
OLD=$(git rev-parse HEAD)
if ! git checkout -q main || ! git reset -q --hard origin/main; then
git reset -q --hard "$OLD" 2>/dev/null; say "FATAL: checkout bitcoinhotels"; dm "FATAL checkout bitcoinhotels — sin cambios"; exit 1
fi
if git diff --name-only "$OLD" HEAD 2>/dev/null | grep -q "^package-lock.json$"; then
npm ci --ignore-scripts >/dev/null 2>&1 || { say "WARN: npm ci bitcoinhotels"; }
fi
if ! npm run build >/tmp/bh-autodeploy-build.log 2>&1; then
git reset -q --hard "$OLD"; npm run build >/dev/null 2>&1; systemctl restart cayce-bitcoinhotels
say "ROLLBACK bitcoinhotels: fallo el build"; dm "ROLLBACK bitcoinhotels (build) — main NO desplegado, rollback activo"
exit 1
fi
sudo -n systemctl restart cayce-bitcoinhotels
sleep 6
CODE=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3002/)
UMAMI=$(curl -s http://127.0.0.1:3002/ | grep -c umami)
MARCAS=$(curl -s http://127.0.0.1:3002/guides/how-to-pay-with-lightning-abroad | grep -cE "Trezor|Ledger|BitBox|Coldcard")
if [ "$CODE" != "200" ] || [ "$UMAMI" = "0" ] || [ "$MARCAS" != "0" ]; then
git reset -q --hard "$OLD"; npm run build >/dev/null 2>&1; systemctl restart cayce-bitcoinhotels
say "ROLLBACK bitcoinhotels: verificacion (code=$CODE umami=$UMAMI marcas=$MARCAS)"
dm "ROLLBACK bitcoinhotels verificacion: code=$CODE umami=$UMAMI marcas=$MARCAS — rollback activo"
exit 1
fi
say "bitcoinhotels → $(git rev-parse --short HEAD) (200 + umami + 0 marcas)"
# r22: verificación de IDENTIDAD — lo servido debe ser lo desplegado
if git grep -q "NEXT_PUBLIC_BUILD_ID" -- src/app/layout.tsx 2>/dev/null; then
BUILT=$(curl -s -m 8 http://127.0.0.1:3002/ | grep -oE 'name="build-id" content="[a-f0-9]{8}"' | grep -oE '[a-f0-9]{8}' | head -1)
WANT=$(git rev-parse --short=8 HEAD)
if [ "$BUILT" != "$WANT" ]; then
git reset -q --hard "$OLD"; npm run build >/dev/null 2>&1; sudo -n systemctl restart cayce-bitcoinhotels
say "ROLLBACK bitcoinhotels: build-id servido '$BUILT' != desplegado '$WANT'"
dm "ROLLBACK bitcoinhotels: el sitio servia build '$BUILT' pero lo desplegado era '$WANT' — rollback activo."
exit 1
fi
say "identidad OK: servido=$BUILT desplegado=$WANT"
fi
fi
+213
View File
@@ -0,0 +1,213 @@
#!/bin/bash
# cayce-daily-report (r20) — DM diario al supervisor:
# 1) publicado por canal con enlaces 2) rechazos del guard y por qué
# 3) PRs auto-mergeados + deploys 4) métricas Umami (ambos sitios)
# 5) costo estimado de Gemini del día
set -uo pipefail
OUT=$(mktemp)
PSQL="sudo -n -u postgres psql -d cayce -tA -f-"
DAY=$(date -u +%F)
{
echo "CAYCE — reporte diario $DAY (UTC)"
echo ""
echo "== 1. Publicado (24h) =="
$PSQL <<'EOF'
SELECT 'nostr/' || type || ' — ' || left(regexp_replace(content, E'[\\n\\r]+', ' ', 'g'), 80)
|| ' — ev:' || coalesce(meta->>'event_id', '(sin id)')
FROM outbound_drafts
WHERE status='published' AND channel='nostr'
AND type NOT IN ('profile','contact_list','inbox_relays','deletion')
AND ts > now() - interval '24 hours'
ORDER BY ts DESC LIMIT 6;
SELECT 'hardcap/' || type || ' — ' || left(regexp_replace(content, E'[\\n\\r]+', ' ', 'g'), 80)
FROM outbound_drafts
WHERE status='published' AND channel='hardcap' AND ts > now() - interval '24 hours'
ORDER BY ts DESC LIMIT 4;
EOF
sudo -n -u postgres psql -d cayce -tA -f- <<'EOF'
SELECT platform || ' — ' || theme || ' — ' || coalesce(post_url, '(sin url)')
FROM posts_log WHERE ts > now() - interval '24 hours' AND status='ok' ORDER BY ts DESC LIMIT 6;
EOF
echo ""
echo "== 2. Guard: rechazos (24h) =="
sudo -n -u postgres psql -d cayce -tA -f- <<'EOF'
SELECT count(*) || 'x ' || left(reasons, 100) FROM guard_rejections
WHERE ts > now() - interval '24 hours'
GROUP BY reasons ORDER BY count(*) DESC LIMIT 6;
EOF
echo ""
echo "== 3. PRs y deploys =="
BTOK=$(cat /etc/cayce/ci-bot.token 2>/dev/null)
if [ -n "$BTOK" ]; then
for REPO in cayce-ops entity bitcoinhotels; do
curl -s -m 10 -H "Authorization: token $BTOK" \
"http://127.0.0.1:3100/api/v1/repos/cayce/$REPO/pulls?state=closed&limit=20" \
| python3 -c "
import json, sys, datetime
cutoff = (datetime.datetime.utcnow() - datetime.timedelta(days=1)).strftime('%Y-%m-%dT%H:%M')
for p in json.load(sys.stdin):
m = (p.get('merged_at') or '')
if not p.get('merged') or not m or m < cutoff: continue
print(f\"$REPO#{p['number']} — {p['title'][:60]}\")" 2>/dev/null
done
fi
journalctl -u cayce-autodeploy --since "-24 hours" -o cat --no-pager 2>/dev/null | grep -E "^\[autodeploy\].*(→|ROLLBACK|ABORT|FATAL)" | tail -6
echo ""
echo "== 3b. PRs que esperan tu merge =="
PROTECTED="SPEC.md|MANDATE.md|prompts/cayce-identity.md|guard[^/]*\.|relay-watcher|outbox\.mjs|guard-blocklist|check-single-outbox|\.gitea/workflows|\.github/workflows|cayce-auto-merge|act_runner|docker"
if [ -n "${BTOK:-}" ]; then
for REPO in cayce-ops entity bitcoinhotels; do
curl -s -m 10 -H "Authorization: token $BTOK" \
"http://127.0.0.1:3100/api/v1/repos/cayce/$REPO/pulls?state=open&limit=20" \
| python3 -c "
import json, sys, urllib.request, os
tok = os.environ.get('BTOK')
for p in json.load(sys.stdin):
req = urllib.request.Request(f'http://127.0.0.1:3100/api/v1/repos/cayce/$REPO/pulls/{p["number"]}/files?limit=100', headers={'Authorization': 'token ' + tok})
files = [f.get('filename','') for f in json.load(urllib.request.urlopen(req, timeout=10))]
import re
prot = [f for f in files if re.search(r'$PROTECTED', f)]
motivo = 'toca paths protegidos: ' + ', '.join(prot[:3]) if prot else 'CI pendiente o esperando revisión'
print(f" $REPO#{p['number']} — {p['title'][:56]} — {motivo}")" 2>/dev/null
done
fi
echo " GitHub patternn/hardcap-news: sin token en VPS — revisar https://github.com/patternn/hardcap-news/pulls"
echo ""
echo "== 4. Umami (24h) =="
sudo -n -u postgres psql -d umami -tA -f- <<'EOF'
SELECT w.name || ': ' || count(DISTINCT e.session_id) || ' visitas · ' || count(*) || ' páginas'
FROM website_event e JOIN website w ON w.website_id = e.website_id
WHERE e.created_at > now() - interval '24 hours' AND e.event_type = 1
GROUP BY w.name ORDER BY count(*) DESC;
SELECT 'top: ' || url_path || ' (' || count(*) || ')' FROM website_event
WHERE created_at > now() - interval '24 hours' AND event_type = 1
GROUP BY url_path ORDER BY count(*) DESC LIMIT 4;
SELECT 'refs: ' || coalesce(nullif(referrer_domain, ''), '(direct)') || ' (' || count(DISTINCT session_id) || ')'
FROM website_event
WHERE created_at > now() - interval '24 hours' AND event_type = 1
GROUP BY referrer_domain ORDER BY count(DISTINCT session_id) DESC LIMIT 4;
EOF
echo ""
echo "== Contenido nuevo publicado hoy por sitio =="
# bitcoinhotels: guias/fichas nuevas o actualizadas en el arbol DESPLEGADO (link vivo)
cd /srv/bitcoinhotels 2>/dev/null && git log --since="24 hours" --format="%H" -- src/data/guides.json | while read -r C; do
git show "$C" -- src/data/guides.json 2>/dev/null | grep -oE '^\+\s+"slug": "[a-z0-9-]+"' | sed 's/.*: "//;s/"//' \
| while read -r SL; do echo " bitcoinhotels.org/guides/$SL"; done
done | sort -u
# hardcap: notas publicadas hoy (link por article_id del meta)
sudo -n -u postgres psql -d cayce -tA -f- <<'EOF2' | while read -r L; do [ -n "$L" ] && echo " hardcap.news$L"; done
SELECT '/news/' || (meta->>'article_id') || '/summary — ' || left(regexp_replace(content, E'[\n\r]+', ' ', 'g'), 60)
FROM outbound_drafts
WHERE channel='hardcap' AND status='published' AND ts > now() - interval '24 hours'
AND meta->>'article_id' IS NOT NULL
ORDER BY ts DESC LIMIT 6;
EOF2
echo ""
echo "== Ciclos: piezas producidas (24h) =="
sudo -n -u postgres psql -d cayce -tA -f- <<'EOF'
SELECT 'posts: ' || count(*) || ' piezas nostr (opinion/reflection/events/iniciativa)'
FROM outbound_drafts WHERE channel='nostr'
AND type IN ('opinion','reflection','events_post','initiative_note')
AND status='published' AND ts > now() - interval '24 hours';
SELECT 'editoriales: ' || count(*) || ' opiniones publicadas · ' || (SELECT count(*) FROM actions_log WHERE action='editorials_cycle' AND ts > now() - interval '24 hours') || ' corridas'
FROM outbound_drafts WHERE channel='nostr' AND type='opinion' AND status='published' AND ts > now() - interval '24 hours';
SELECT 'notas hardcap: ' || count(*) || ' publicadas'
FROM outbound_drafts WHERE channel='hardcap' AND status='published' AND ts > now() - interval '24 hours';
SELECT 'curacion hardcap: ' || count(*) || ' corridas del ciclo core · ' || (SELECT count(*) FROM digests WHERE ts > now() - interval '24 hours') || ' digests'
FROM actions_log WHERE action='cycle_complete' AND ts > now() - interval '24 hours';
SELECT 'promo: ' || count(*) FILTER (WHERE status='ok') || ' publicadas · ' || count(*) FILTER (WHERE status LIKE 'rejected%') || ' rechazadas · ' || count(*) FILTER (WHERE status LIKE 'error%') || ' con error'
FROM posts_log WHERE ts > now() - interval '24 hours';
SELECT 'iniciativa: ' || count(*) || ' drafts creados'
FROM actions_log WHERE action='draft_created' AND ts > now() - interval '24 hours';
EOF
echo "== Checklist de postulacion (verificable en vivo) =="
chk() { # $1 nombre, $2 condicion (0=verde)
if [ "$2" = "0" ]; then echo " $1: verde"; else echo " $1: pendiente"; fi
}
for SITE in bh hc; do
[ "$SITE" = "bh" ] && P=3002 && D=bitcoinhotels.org || { P=3003; D=hardcap.news; }
N=$(curl -s -m 6 http://127.0.0.1:$P/sitemap.xml | grep -c "<loc>")
TR=0; for PG in about contact privacy terms; do C=$(curl -s -o /dev/null -w "%{http_code}" -m 5 http://127.0.0.1:$P/$PG); [ "$C" = "200" ] && TR=$((TR+1)); done
HWW=$(curl -s -o /dev/null -w "%{http_code}" -m 5 http://127.0.0.1:$P/how-we-work)
ADS=$(curl -s -o /dev/null -w "%{http_code}" -m 5 http://127.0.0.1:$P/ads.txt)
PC=$(curl -s -o /dev/null -w "%{http_code}" -m 5 http://127.0.0.1:$P/privacy)
PRIV=$(curl -s -m 5 http://127.0.0.1:$P/privacy | grep -c -i "adsense"); [ "$PC" != "200" ] && PRIV=0
echo "$D: sitemap=$N URLs · confianza $TR/4 · how-we-work $([ "$HWW" = "200" ] && echo SI || echo NO) · ads.txt $([ "$ADS" = "200" ] && echo SI || echo NO) · privacy-AdSense $([ "$PRIV" -ge 1 ] && echo SI || echo NO)"
done
if [ "$(date -u +%u)" = "1" ]; then
echo "== Semanal (lunes): checklist AdSense =="
for SITE in bh hc; do
[ "$SITE" = "bh" ] && P=3002 && N=bitcoinhotels.org || { P=3003; N=hardcap.news; }
OK=0; TOTAL=5
for PG in sitemap.xml ads.txt privacy terms how-we-work; do
C=$(curl -s -o /dev/null -w "%{http_code}" -m 5 http://127.0.0.1:$P/$PG); [ "$C" = "200" ] && OK=$((OK+1))
done
echo " $N: $OK/$TOTAL (sitemap · ads.txt · privacy · terms · how-we-work)"
done
fi
echo "== Pushes (24h) por autor =="
for R in cayce-ops entity bitcoinhotels; do
A=$(cd /srv/$([ "$R" = "cayce-ops" ] && echo home/ubuntu/cayce-ops || { [ "$R" = "entity" ] && echo cayce-core || echo bitcoinhotels; }) 2>/dev/null && git log --since="24 hours" --format="%an" | sort | uniq -c | tr "\n" ";" )
echo " $R: ${A:-sin commits}"
done
echo "== Avance semanal FASE B (piezas vs objetivo) =="
# convencion: los commits de piezas FASE B llevan prefijo "pieza:"
SEM=$(( ($(date -u +%s) / 86400 + 3) / 7 )) # semana ISO aprox
for R in cayce-ops entity bitcoinhotels; do
D=/srv/$([ "$R" = "cayce-ops" ] && echo home/ubuntu/cayce-ops || { [ "$R" = "entity" ] && echo cayce-core || echo bitcoinhotels; })
N=$(cd "$D" 2>/dev/null && git log --since="7 days" --format="%s" | grep -c "^pieza:")
echo " $R: $N piezas-commits esta semana"
done
echo " objetivos: bitcoinhotels 2/sem · hardcap ~2,5/sem (hub+watch+guia alterna)"
echo "== Persistencias (caidos ahora mismo) =="
for U in cayce-nostr.service hardcap-backend.service hardcap-frontend.service cayce-bitcoinhotels.service postgresql@18-main.service caddy.service; do
A=$(systemctl is-active $U 2>/dev/null)
[ "$A" != "active" ] && echo " $U: $A"
done
true
echo "== 5. Gemini del día =="
python3 - /srv/cayce-core/.llm-usage.jsonl <<'PY' 2>/dev/null || echo "(sin uso registrado aún)"
import json, sys, os
IN = OUT_ = CACHED = CALLS = 0
if os.path.exists(sys.argv[1]):
for line in open(sys.argv[1]):
try:
d = json.loads(line)
if d.get("ts", "")[:10] == __import__("datetime").datetime.utcnow().date().isoformat():
CALLS += 1; IN += d.get("in", 0); OUT_ += d.get("out", 0); CACHED += d.get("cached", 0)
except Exception:
pass
rin = float(os.environ.get("GEMINI_USD_PER_MTOK_IN", "0.15"))
rout = float(os.environ.get("GEMINI_USD_PER_MTOK_OUT", "0.60"))
cost = IN/1e6*rin + OUT_/1e6*rout
print(f"{CALLS} llamadas · in {IN:,} tok · out {OUT_:,} tok · cached {CACHED:,} · costo estimado ${cost:.3f} (rates {rin}/{rout} USD/Mtok)")
PY
} > "$OUT" 2>&1
# DM al supervisor + copia de auditoría
SUP=$(grep -aoE 'SUPERVISOR_NPUB_HEX=[0-9a-f]+' /srv/cayce-nostr/.env 2>/dev/null | cut -d= -f2)
BODY=$(head -c 6000 "$OUT")
if [ -n "$SUP" ]; then
curl -s -m 20 -X POST http://127.0.0.1:3011/dm -H "Content-Type: application/json" \
-d "$(python3 -c "import json,sys; print(json.dumps({'to_hex': '$SUP', 'content': open('$OUT').read()[:6000]}))")" \
>/dev/null && logger -t cayce-daily-report "DM enviado ($(wc -c < "$OUT") bytes)"
else
logger -t cayce-daily-report "SIN supervisor — reporte solo en log"
fi
cat "$OUT"
cp "$OUT" /var/log/cayce-daily-report.log 2>/dev/null || true
rm -f "$OUT"
+16 -143
View File
@@ -1,146 +1,19 @@
# Architectural decisions
# DECISIONES del operador (binding para todos los agentes)
Each entry: decision, why, and what it costs us. Newest last.
## D1 (r26): Cuentas y tokens
NINGUN agente puede: modificar permisos de la cuenta del operador (pattern),
promoverla/degradarla de admin, ni crear tokens a su nombre — ni con fines de
prueba. Para pruebas de API: cuentas de prueba propias (p.ej. ci-bot o una
dedicada), creadas y revocadas por el agente. Antecedente: en r25 se generó un
token de pattern para una prueba; práctica prohibida desde esta decisión.
## D1 — uv + uv.lock as the lockfile
`uv` manages the pinned interpreter (3.12) and produces `uv.lock`. CI installs
with `uv sync --frozen`; nothing runs against unpinned trees.
*Cost:* contributors need uv (single static binary).
## D2 (r26): Estado del runtime
Ningún proceso escribe estado dentro de los checkouts de deploy. El estado
vive en /var/lib/cayce/ (lessons.md y similares). Los árboles de
/srv/cayce-core, /srv/bitcoinhotels y /srv/hardcap-news quedan limpios sin
excepciones (check-clean-trees en verde).
## D2 — Tests on SQLite; migrations authoritative for Postgres
Tests run on file-backed SQLite via `metadata.create_all` (fast, no service
dependency). The Alembic chain is the production schema path; `make
migrate-check` (CI job) smoke-runs `alembic upgrade head` on SQLite.
*Cost:* Postgres-specific features (e.g. JSONB, partial indexes) stay unused —
the schema uses portable `sa.JSON` instead.
## D3 — LLM behind an in-repo provider interface; null by default
`cayceops.llm.LLMProvider` is the only seam. `null` provider raises — components
fall back to deterministic rules. `openai-compat` uses stdlib `urllib` against
any OpenAI-shaped endpoint; no SDK dependency.
*Cost:* no retries/streaming/tool-calls — acceptable for classification and
draft drafting. Add providers by implementing `complete()`.
## D4 — Nostr client implemented in-repo (coincurve + websockets)
NIP-01 event build/sign/publish is ~150 auditable lines vs. a black-box SDK.
Keys accepted as 32-byte hex only. Relays are plain env config; publish is
fire-and-wait with a timeout; the event id is stored as `post_ref`.
*Cost:* no NIP-05, no retries across relay pools — fine for kind-1 notes.
## D5 — AUTONOMOUS_MODE semantics (the only non-human approval path)
Default **false**: only human-approved proposals execute, only
human-approved drafts are sendable/publishable.
**true** adds exactly one capability: the executor may auto-approve
`proposed` items whose `kind` is in `CAYCE_OPS_AUTONOMOUS_KINDS`, up to
`CAYCE_OPS_AUTONOMOUS_DAILY_CAP` per day, recording actor `auto:policy` in the
audit log. Everything else still requires a human. Auto-approval never touches
mail drafts or content items.
*Cost:* two code paths; both audited; the autonomous one is off by default and
cap-bounded.
## D6 — CI workflow files ship now; enforcement gate is `make check` until a runner exists
`.gitea/workflows/ci.yml` (Gitea Actions) and `.github/workflows/ci.yml`
(identical) run ruff, alembic smoke, pytest, gitleaks on every PR/push. The
current host runs no Gitea Actions runner, so until one is provisioned the
same gate is enforced locally: `scripts/pre-push` runs `make check` (lint +
tests + migration smoke + full-history secret scan) before every push, and the
audited commit hash in `AUDIT.md` is the one that passed it.
*Cost:* CI results are deferred on the current host; the gate itself is
identical to CI.
## D7 — gitleaks allowlist for test fixtures
`tests/` contains invented data (fake Nostr key, fake merchant mail). It is
allowlisted in `.gitleaks.toml` so the history scan stays signal-only.
*Cost:* a real secret committed under `tests/` would not trip the scanner —
compensating control: fixtures are reviewed in PRs, and no fixture ever reads
the environment.
## D8 — Proposal history kept when retiring the grant text
The repo began as a grant proposal; the retirement commit replaces it with the
project README (no grant references in the tree). The old commits stay — the
audit story ("this started as X, the operator mandated Y, here is the diff") is
more honest than a rewritten birth.
*Cost:* `git log` still shows the word "OpenSats" in old messages. The tree and
README carry none.
## D9 — Executor plugins are in-process registrations
An action plugin is a Python function registered in
`cayceops.components.executor.plugins.REGISTRY`, mapped by proposal `kind`.
`http_post` exists but is inert unless `CAYCE_OPS_ENABLE_HTTP_ACTIONS=true`.
*Cost:* no sandboxing of plugins — plugins are repo code, reviewed like
everything else.
## D10 — Audit chain is hash-chained; approval can require operator signature (post-audit)
`audit_log` rows chain sha256(prev_hash|fields); `verify_audit_chain` walks it,
the monitor can surface breaks. With `CAYCE_OPS_OPERATOR_PUBKEY` set,
`executor approve` REQUIRES a BIP-340 signature over
sha256("cayce-ops:approve:<id>") from the operator key — `--actor` becomes a
label, the key is the proof (the audit's "anyone can sign as anyone").
*Cost:* without a pubkey configured the gate degrades to the old free-text
actor; separate DB roles per daemon remain a deployment TODO (SQL to tighten
ships in deploy/roles.sql).
## D11 — Mail threading now has a real first-contact path (post-audit)
Sends create an OUTBOUND row (own Message-ID, In-Reply-To -> inbound);
inbound rows keep their original Message-ID. `maild compose` creates the
initial outreach draft for a proposal. Approval is bound to the draft text
via sha256 (`approved_hash`): an edited draft refuses to send until
re-approved. Each send commits in its own transaction — SMTP failure on
message N can no longer resurrect 1..N-1 as duplicates.
## D12 — IMAP \\Seen only after commit; content gates tighten (post-audit)
Messages are fetched unmarked and flagged \\Seen after the processing
transaction commits (crash mid-batch no longer loses mail). Publisher:
caption hash-bound at approval, disabled channels are terminal (`SKIPPED`),
publish failures burn a bounded attempt budget before abandonment.
`risk_score` renamed `match_score` (keyword relevance) and auto-approval
takes the HIGHEST score first.
## D13 — No non-human approval path (round 2, supersedes D5)
Operator signatures are mandatory for approvals AND re-verified at
execution/send/publish time against current content + deployment id. With
that gate, the AUTONOMOUS_MODE auto-approval path cannot exist: it would
produce unsigned approvals the gate refuses. `_auto_approve` is inert and
audits `autonomy_disabled` when invoked. *Cost:* AUTONOMOUS_MODE is now
inert by design.
## D14 — Signatures bind content + deployment (round 2)
message = sha256("cayce-ops:<type>:<deployment_id>:<id>:<content_hash>").
content_hash is canonical-json of the fields that matter per type. Full
signatures live on the row (operator_signature) and in the audit detail.
Verification happens again at run time: unsigned rows (raw SQL approval)
revert to their pre-approval state with an error audit. *Cost:* a deployment
id env var is mandatory; signatures must be recomputed if content changes.
## D15 — Approval classifier veto (round 2)
Any negation token anywhere in a mail vetoes an `approval` classification →
needs_review. Simpler and strictly conservative vs. per-pattern negation
lists. *Cost:* genuinely-positive approvals containing stray negations
(e.g. "we have no reason not to welcome you") need human triage.
## D16 — La clave del operador nunca es una identidad de agente
**Fecha:** 2026-10-28
**Contexto:** se descubrió que `CAYCE_OPS_OPERATOR_PUBKEY` estaba configurada
con la pubkey de Cayce — es decir, Cayce se aprobaba a sí misma. La firma
BIP-340 era técnicamente válida pero semánticamente nula: "aprobación humana"
firmada por el agente no es aprobación humana.
**Regla:** los daemons y el CLI se niegan a operar si la pubkey del operador
coincide con:
1. La pubkey derivada de cualquier clave privada cargada en la config
(`CAYCE_OPS_NOSTR_PRIVATE_KEY` u otra identidad de agente futura), o
2. Cualquier pubkey en `CAYCE_OPS_FORBIDDEN_OPERATOR_PUBKEYS` (deny-list
explícita, para claves fuera de este proceso).
**Implementación:** `cayceops.signing.validate_no_self_approval()`, llamada
desde `_signing()` en executor/maild/publisher. Lanza `SelfApprovalError`.
**La firma del operador se produce FUERA de la VPS.** El CLI `approve-*`
acepta la firma como parámetro (ya lo hace); nunca la genera él. Mientras
no exista el signer externo, `CAYCE_OPS_OPERATOR_PUBKEY` debe permanecer
vacía — los daemons se niegan a operar y eso es lo correcto.
**La nsec de Cayce NO se toca:** sigue siendo la identidad del agente para
publicar y anclar. Lo que se prohíbe es usarla como clave de operador.
## D3 (r26): Trazabilidad de pushes
Todo push queda identificado por autor; el reporte diario lista los pushes
del día por autor, y todo merge a main que toque archivos de CODEOWNERS
dispara DM urgente si no lo mergeó pattern.
+40
View File
@@ -0,0 +1,40 @@
# Checklist de postulación a AdSense (r23) — medible por sitio
Estado inicial: bitcoinhotels 4/10 en verde · hardcap 3/10 en verde (ver
auditoría r23 en el informe de la ronda).
## Común (ambos sitios)
1. [ ] **Sitemap dinámico completo**: cada URL indexable está en el sitemap y
responde 200 (métrica: |sitemap| == |páginas indexables|; hoy bh 4/~22,
hc 3/~790).
2. [ ] **Sin páginas finas indexadas**: toda URL indexable con ≥300 palabras
propias o marcada noindex (hoy: bh 12 hoteles entre 7-105 palabras —
enriquecer o noindex; hc ~762 notas de agregación — decisión: noindex a
notas <150 palabras propias hasta tener síntesis editorial).
3. [ ] **Páginas de confianza completas**: About, Contact, Privacy (con
mención explícita de Google AdSense, cookies y analytics), Terms — 200 y
sustanciales (hoy bh: falta Contact, Privacy sin mencionar Google/AdSense;
hc: faltan Contact/Privacy/Terms).
4. [ ] **"Cómo trabajamos" publicada** (borrador r23 aprobado).
5. [ ] **CWV móvil ≥90 en Lighthouse (mediana de 3)** en home + 3 interiores
(hoy bh 0.94 ✓ en home; hc 0.59 ✗).
6. [ ] **ads.txt y meta google-adsense-account correctos** (hoy: ambos sitios
✓ — se mantiene).
7. [ ] **robots.txt sin bloqueos indebidos** (hoy ✓ ambos).
8. [ ] **Enlaces rotos = 0** en navegación principal y footer (pasada mensual
automatizable en CI).
9. [ ] **GSC + Bing dados de alta** (verificación DNS Cloudflare) con ≥80% de
URLs del sitemap rastreadas en 28 días (dato GSC del operador).
10. [ ] **4 semanas de publicación constante** al ritmo aprobado del plan
(bh 2/semana; hc ~2,5/semana) sin huecos >7 días, con sitemap actualizado
en cada publicación.
## Por sitio (adicional)
- bitcoinhotels: 12 hoteles enriquecidos a ≥300 palabras + ≥8 de las 20 guías
nuevas publicadas antes de postular.
- hardcap: 12 hubs publicados + 4 Security Watch + ≥5 guías de custodia antes
de postular.
## Regla de re-postulación tras rechazo
No antes de 30 días ni sin cerrar TODOS los ítems en rojo/amarillo de la
auditoría + dos semanas más de publicación constante.
+72
View File
@@ -0,0 +1,72 @@
# "Cómo trabajamos" — borrador para revisión del operador (r23)
> Borrador único con dos variantes de encabezado (una por sitio). Sin publicar
> hasta tu OK. Tono: el de la voz v3 (seco, verificable, sin marketing).
---
## Variante bitcoinhotels.org
### Cómo trabajamos
Bitcoin Hotels es operado por **Cayce**, una entidad autónoma de software — un
agente de IA — con supervisión humana de patternn (Próspera ZEDE).
**Cómo elegimos qué listamos.** Un alojamiento aparece aquí solo si acepta
bitcoin como forma de pago, verificado contra BTC Map (datos abiertos, licencia
ODbL, con atribución) y contra nuestro propio registro de verificación manual.
Cuando una verificación caduca, el listado se marca o se retira.
**Cómo escribimos las guías.** Cada guía la redacta Cayce a partir de fuentes
primarias: datos de venues, listados propios y documentación verificable. Toda
afirmación con cifras lleva su fuente y su fecha. Si una guía no puede
sostenerse con fuentes, no se publica.
**Cómo corregimos errores.** Los errores se corrigen en la propia página, con
fecha. Si un dato cambió (un hotel deja de aceptar bitcoin, un ATM sube su
comisión), la guía se actualiza y la fecha de verificación cambia con ella. Las
correcciones importantes quedan anotadas al pie.
**Publicidad.** El sitio se sostiene con publicidad (AdSense de Google) y con
donaciones en sats. La publicidad no influye en qué listamos ni en qué
escribimos: no hay plazas pagadas ni colocación de producto.
**Contacto.** [correo de contacto — PENDIENTE: decidir y crear] · Nostr: [npub
de la entidad — PENDIENTE: publicar o no].
---
## Variante hardcap.news
### Cómo trabajamos
Hardcap es operado por **Cayce**, una entidad autónoma de software — un agente
de IA — con supervisión humana de patternn.
**Qué publicamos y qué no.** Señal, no ruido: qué pasó, por qué importa y de
dónde sale el dato. Cada nota lleva URL y fecha de fuente. Si no podemos
sustentar una afirmación, no la publicamos.
**Cómo elegimos fuentes.** Fuentes primarias primero: comunicados, datos
on-chain, documentos judiciales, especificaciones técnicas. La agregación de
terceros se cita como tal y se verifica contra la primaria cuando existe.
**Cómo escribimos las guías.** Con el mismo estándar que las notas: pasos
reproducibles, fuentes con fecha, advertencias donde un error cuesta dinero.
**Cómo corregimos errores.** En la misma página, con fecha visible. Las
correcciones de sustancia quedan anotadas; en Nostr, la corrección se publica
también como nota nueva referida a la original.
**Publicidad.** Publicidad (AdSense de Google) y donaciones en sats. Ningún
anunciante compra cobertura ni mención: las marcas de hardware se tratan solo
como noticia o referencia técnica, nunca como recomendación pagada.
**Contacto.** [PENDIENTE: mismo correo/npub que bitcoinhotels o distinto]
---
## Pendientes del operador sobre este borrador
1. Correo de contacto real (y decisión de publicar o no el npub).
2. Duración del historial de correcciones (¿mostrar siempre o últimos 90 días?).
3. Añadir o no sección de equipo/supervisión humana con nombre del operador.
+94
View File
@@ -0,0 +1,94 @@
# Programa AdSense — Plan de contenido (r23, para aprobación del operador)
Regla general: NADA se publica sin tu OK explícito a este plan. Sin producción de
contenido largo hasta entonces.
## A. bitcoinhotels.org — 20 piezas candidatas
Fuentes: BTC Map API v4 (datos de venues, licencia ODbL — atribución obligatoria
en cada guía: "Venue data © BTC Map contributors (ODbL)"), listings propios
(hotels.json + verificación manual), eventos del propio pipeline de cayce
(btcmap events), guías existentes como base.
### Guías de ciudad (12)
1. El Zonte de bolsillo: pagar con Lightning en 48 h (ámbito: la playa que no
usa efectivo; fuentes: BTC Map venues El Zonte + listings propios; ~900 pal.)
2. San Salvador: mapa de gastos reales en sats (transporte, comida, hospedaje;
BTC Map + precios verificados; ~900)
3. Lugano: la ciudad-plan de Bitcoin (Plan B, merchants, límites prácticos;
BTC Map + city sources; ~800)
4. Zúrich sin tarjeta: hoteles y vida diaria en BTC (nuestro listing Dolder +
BTC Map Zürich; ~800)
5. Ámsterdam para un pagador en sats (ATMs, cafés, el evento anual;
BTC Map + nuestros eventos; ~800)
6. Praga: la escena que no se fue (venues históricas + actuales; BTC Map;
~800)
7. Lisboa: trabajar y pagar una semana en bitcoin (coworking, cafés, ATMs;
~800)
8. Ciudad de México / CDMX: ruta de tres días sin banco (BTC Map + colonias;
~850)
9. Buenos Aires con Lightning: la cotidianidad integrada (transporte, recargas,
comidas; ~850)
10. Roatán y Próspera: jurisdicción nativa (tender legal, hospedajes; listings
propios + fuentes jurisdiccionales; ~800)
11. Ljubljana: la escena silenciosa de Europa del Este (~750)
12. Bangkok/Chiang Mai: el sudeste que acepta sats (remotos y nómadas; ~750)
### Guías de eventos (4)
13. Calendario 2026-27 de eventos bitcoin: cuáles valen el vuelo (datos propios
de events + btcmap; ~700, actualizable)
14. Sobrevivir (y pagar) una conferencia bitcoin: presupuesto en sats por día
(~700)
15. Dónde dormir durante los grandes eventos (hoteles que aceptan btc cerca de
sedes — cruce propio de events×hotels; ~650)
16. Cómo conocer gente antes de llegar: meetups y Nostr como agenda (~650)
### Guías prácticas (4)
17. Comprar btc sin banco por ciudad: operadores y topes KYC (actualización de
la guía ATM con tabla por ciudad; ~900)
18. Tarjetas regalo: el rescate cuando el comercio no acepta btc (~700)
19. eSIM y datos pagados en sats, país por país (~650)
20. Propinas en sats: la etiqueta y las herramientas (~600)
## B. hardcap.news — arquitectura de páginas
1. Hubs de tema (burbuja + editorial + notas): los 12 editorials existentes
(security, privacy, custody, regulation, mining, ETF, lightning, wallets,
exchanges, travel-opsec, energy, adoption) pasan a página-hub permanente con
la síntesis arriba, el histórico de notas abajo y enlaces a guías. ~600 pal.
propias por hub + histórico.
2. Security Watch (semanal): síntesis de la semana en seguridad (qué pasó, qué
patrón sigue, qué hacer) con enlaces a las notas fuente de la propia semana;
400-600 palabras, viernes.
3. 10 guías permanentes de custodia DIY (títulos):
1) Semilla en acero con herramientas de mano; 2) Passphrase: el segundo
factor que viaja en tu cabeza; 3) Multisig 2-de-3 paso a paso (sin marcas:
"firmware verificable"); 4) Herencia sin notario: timelock + cartas de
recuperación; 5) Verificar una transacción en pantalla: el hábito que
evita el malware; 6) Firma en frío con dos dispositivos viejos;
7) Reparto geográfico de respaldos sin confianza en terceros; 8) Cartera
señuelo: cuándo ayuda y cuándo es teatro; 9) Verificar firmware antes de
primera uso (hash y attestation, sin marcas); 10) El simulacro: restaurar
desde cero en 10 minutos, cada trimestre.
Fuentes: documentación de protocolos (BIP-39/32/174), Bitcoin OWASP,
guías de fabricantes SOLO como referencia técnica (sin nombres en bh;
en hardcap las marcas están permitidas por R1-news pero se evita endoso),
datasets propios (wrench-attack dataset). 800-1200 palabras c/u.
## C. Ritmo propuesto (sustanciales/semana)
- bitcoinhotels: 2 guías/semana (martes y viernes).
- hardcap: 1 hub/semana + Security Watch semanal + 1 guía de custodia cada
2 semanas → ~2,5/semana.
- Primeras 4 semanas: además, enriquecer los 12 hoteles a ≥300 palabras propias
cada uno (2 por semana) para eliminar las páginas finas.
## D. Criterio de calidad de pieza larga (gate interno)
1. ≥600 palabras de texto propio (guías: 600-1200).
2. ≥3 fuentes primarias citadas con URL y fecha (formato señal de hardcap).
3. Todo dato numérico reproducible: la fuente permite verificarlo.
4. Sin relleno: si un párrafo no aporta dato o criterio, se borra (R8 del
guard como filosofía).
5. El guard revisa cada pieza antes de publicar (R1-R9); en bitcoinhotels,
además, cero marcas de hardware (blocklist).
6. Atribución de licencias cuando hay datos abiertos (BTC Map → ODbL).
7. Frescura: cada guía lleva fecha de verificación y se revisa cada 90 días.
+22
View File
@@ -0,0 +1,22 @@
# Propuesta: claves por agente y por host (dejar de usar la clave maestra)
Problema: la clave ED25519 gustavo@blinkbtc.com (~/.ssh/blink_vps_key_v2 en el
Mac) es la ÚNICA autorizada en vm2047 y la usó el actor no identificado de
octubre (trader v2.4, burbujas, sfeed, respondedor). Un solo secreto, todo el
poder, cero trazabilidad por agente.
Propuesta (por orden de adopción):
1. Un par de claves POR AGENTE y POR HOST: <agente>-<host> (ej: opencode-vm2047,
hermes-vm2047, cayce-dev-gitea). ed25519, sin passphrase, en ~/.ssh/ del
proceso que las usa, NUNCA en la Mac del operador salvo la del operador.
2. authorized_keys con restrict + command= forzado (patrón ya probado con
backup-pull/rrsync): el agente solo puede ejecutar su comando permitido.
3. Usuario propio por agente en cada host (opencode, hermes, cayce-dev) con
sudo acotado por reglas sudoers específicas (no ALL).
4. Auditoría: sudoers con log_output + AcceptEnv GIT_AUTHOR/COMMITER para que
cada commit lleve el agente; session log en docs/sessions/ (este documento).
5. Rotación de salida: retirar gustavo@blinkbtc.com de authorized_keys cuando
las 3 primeras estén operativas; la clave maestra solo para emergencias,
guardada offline.
6. En Gitea/GitHub: tokens de solo-lectura o scope mínimo por agente, nunca la
cuenta del operador.
+99
View File
@@ -0,0 +1,99 @@
# PROPUESTA (r30b, NO ejecutada) — BTC Map como fuente de verdad de alojamientos
Estado: propuesta. Nada de esto está aplicado. Requiere aprobación del operador.
## 1. Diagnóstico con evidencia (2026-11-03)
- `src/data/hotels.json` (12 hoteles, `verified: true` en los 12) no tiene registro de
verificación en el repo: entró como snapshot de producción en la ronda 7 (f5f66db).
- Cruce real contra el dump completo de BTC Map (api.btcmap.org/v2/elements, 43.349
elementos, 27.383 activos con nombre+coords; verificado 2026-11-03): de los 12 hoteles
solo **Las Verandas (Roatán)** existe en BTC Map (`node:10164144439`, tourism=hotel,
sin verificación reciente). **Los otros 11 NO están en BTC Map.**
- De esos 11, hay soporte externo real para 4: los 3 de Kessler Collection (Grand Bohemian
Orlando, Bohemian Savannah, Casa Monica) aceptan BTC vía BitPay según su página oficial
(kesslercollection.com/bitpay, "notify a front desk agent upon check out", verificada
2026-11-03), y Circa tiene página propia de bitcoin (circalasvegas.com/bitcoin: ATM BTC
24/7 + paquete fin de semana a 1 BTC — no es POS Lightning). Resorts World Las Vegas
anunció partnership con Gemini en 2021 (8newsnow.com, 2021-06) pero su estado actual no
se pudo verificar. Los 6 restantes (Dolder Grand, Van Walsum, Splendide Royal,
InterContinental Ljubljana, Pestana CR7, Palazzo Versace Dubai): sin evidencia de que
acepten bitcoin.
- Los campos `acceptsLightning: true` de RW y Circa no están respaldados por fuente alguna
(ver arriba: Gemini-2021 y ATM/paquete respectivamente). El news item que los atribuía
("instalan POS Lightning") se retiró en el PR #13 por fabricado.