Files
cayce-ops/runtime/guard.test.mjs
cayce 33ae345fbd
ci / lint-test-leaks (pull_request) Failing after 3s
runtime: outbox UNICO (imports absolutos) + relay-watcher con kill switch + guard R1-R8
- 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

99 lines
6.7 KiB
JavaScript

// guard.test.mjs — tests del publish-guard (directiva 2026-11-01)
import test from 'node:test'
import assert from 'node:assert'
import { createRequire } from 'node:module'
import { readFileSync } from 'node:fs'
const require_ = createRequire(import.meta.url)
const pg = require_('/srv/cayce-core/node_modules/pg')
const env = Object.fromEntries(readFileSync('/srv/cayce-core/.env', 'utf8').split('\n').filter(l => l.includes('=')).map(l => [l.slice(0, l.indexOf('=')), l.slice(l.indexOf('=') + 1)]))
const pool = new pg.Pool({ connectionString: env.PGURL })
const { guardCheck, ensureGuardTables, FORCE_DRAFT_CHANNELS } = await import('/srv/cayce-core/guard.mjs')
await ensureGuardTables(pool)
const clean = async () => {
await pool.query("DELETE FROM outbound_drafts WHERE meta->>'test' = '1'")
await pool.query("DELETE FROM guard_rejections WHERE content LIKE '%__TEST__%'")
}
test.after(() => pool.end())
test('R1 marca de hardware wallet bloqueada', async () => {
const v = await guardCheck(pool, { channel: 'nostr', type: 'opinion', content: 'keep your coins in a Ledger and sleep well __TEST__' })
assert.ok(!v.ok, JSON.stringify(v)); assert.ok(v.reasons.some(r => r.startsWith('marca-bloqueada:ledger')), JSON.stringify(v.reasons))
})
test('R2a enlace de referido', async () => {
const v = await guardCheck(pool, { channel: 'nostr', type: 'opinion', content: 'good wallets here https://shop.example.com/?ref=cayce __TEST__' })
assert.ok(!v.ok && v.reasons.includes('enlace-afiliado-o-referido'), JSON.stringify(v.reasons))
})
test('R2b hashtag', async () => {
const v = await guardCheck(pool, { channel: 'nostr', type: 'opinion', content: 'the market never sleeps #bitcoin __TEST__' })
assert.ok(!v.ok && v.reasons.includes('hashtag'), JSON.stringify(v.reasons))
})
test('R2c emoji', async () => {
const v = await guardCheck(pool, { channel: 'nostr', type: 'opinion', content: 'fees are low again 🔥 __TEST__' })
assert.ok(!v.ok && v.reasons.includes('emoji'), JSON.stringify(v.reasons))
})
test('R2d signo de exclamacion', async () => {
const v = await guardCheck(pool, { channel: 'nostr', type: 'opinion', content: 'fees are low again __TEST__' .replace('again','again!') })
assert.ok(!v.ok && v.reasons.includes('signo-de-exclamacion'), JSON.stringify(v.reasons))
})
test('R3 hecho sin fuente y fecha', async () => {
const v = await guardCheck(pool, { channel: 'nostr', type: 'opinion', content: 'fees hit 300 sat/vB, the highest since march __TEST__' })
assert.ok(!v.ok && v.reasons.includes('afirmacion-factica-sin-fuente-y-fecha'), JSON.stringify(v.reasons))
})
test('R3 pasa con fuente y fecha', async () => {
const v = await guardCheck(pool, { channel: 'nostr', type: 'opinion', content: 'fees hit 12 sat/vB on Nov 1 2026 (source: https://hardcap.news/fees) and nobody cared __TEST__' })
assert.ok(v.ok, JSON.stringify(v.reasons))
})
test('R4 casi-duplicado 30 dias', async () => {
await clean()
await pool.query("INSERT INTO outbound_drafts (channel,type,content,meta,status) VALUES ('nostr','opinion',\$1,\$2,'published')",
['the market priced the halving with astonishing precision and nobody noticed anything at all about it', JSON.stringify({ test: '1' })])
const v = await guardCheck(pool, { channel: 'nostr', type: 'opinion', content: 'the market priced the halving with astonishing precision and nobody noticed anything at all today __TEST__' })
assert.ok(!v.ok && v.reasons.some(r => r.startsWith('casi-duplicado')), JSON.stringify(v.reasons))
await clean()
})
test('R5 tope diario por canal', async () => {
await clean()
for (let i = 0; i < 3; i++) await pool.query("INSERT INTO outbound_drafts (channel,type,content,meta,status) VALUES ('nostr','note',\$1,\$2,'published')", [`cap test ${i}`, JSON.stringify({ test: '1' })])
const v = await guardCheck(pool, { channel: 'nostr', type: 'note', content: 'one more note today with a source https://x.example and date Oct 28 2026 __TEST__' })
assert.ok(!v.ok && v.reasons.some(r => r.startsWith('tope-diario:nostr')), JSON.stringify(v.reasons))
await clean()
})
test('R6 canales siempre en borrador', async () => {
assert.deepEqual(FORCE_DRAFT_CHANNELS.sort(), ['dm', 'mail', 'proposal'])
})
test('metadata (kind 3) exenta de reglas de texto', async () => {
const v = await guardCheck(pool, { channel: 'nostr', type: 'contact_list', content: '' })
assert.ok(v.ok, JSON.stringify(v.reasons))
})
test('R7 fuente mayor de 72h en canal de noticias', async () => {
const v = await guardCheck(pool, { channel: 'nostr', type: 'opinion', content: 'fees spiked in august https://example.com/x reported Aug 20 2026 and nobody moved __TEST__' })
assert.ok(!v.ok && v.reasons.some(r => r.startsWith('fuente-mayor-72h')), JSON.stringify(v.reasons))
})
test('R7 pasa con fuente fresca', async () => {
const v = await guardCheck(pool, { channel: 'nostr', type: 'opinion', content: 'outflows hit 3.64 billion after the reopen on Nov 1 2026 https://www.theblock.co/x — the queue was the story __TEST__' })
assert.ok(v.ok, JSON.stringify(v.reasons))
})
test('R7 no aplica a bitcoinhotels', async () => {
const v = await guardCheck(pool, { channel: 'bitcoinhotels', type: 'guide', content: 'step by step guide verified against https://docs.btcpayserver.org/ visited Oct 20 2026 __TEST__' })
assert.ok(v.ok, JSON.stringify(v.reasons))
})
test('R8 cierre aforismo corto sin dato', async () => {
const v = await guardCheck(pool, { channel: 'nostr', type: 'opinion', content: 'the report is dated Nov 1 2026 https://www.theblock.co/x and the numbers hold. Indexes love you while you are liquid __TEST__' })
assert.ok(!v.ok && v.reasons.includes('cierre-aforismo-sin-dato'), JSON.stringify(v.reasons))
})
test('R8 pasa si el cierre trae dato', async () => {
const v = await guardCheck(pool, { channel: 'nostr', type: 'opinion', content: 'the report is dated Nov 1 2026 https://www.theblock.co/x and the numbers hold. Tron moved 242 million for the Lazarus group __TEST__' })
assert.ok(v.ok, JSON.stringify(v.reasons))
})
test('R8 regression: cola (source: ...) no cuenta como cierre', async () => {
const v = await guardCheck(pool, { channel: 'nostr', type: 'opinion', content: 'the report is dated Nov 1 2026 and Tron carried 242 million for the group. The blocklist missed Tron and 242 million dollars noticed. (source: https://www.theblock.co/x, Nov 1 2026) __TEST__' })
assert.ok(v.ok, JSON.stringify(v.reasons))
})
test('R8 refinado: cierre con superlativo/record aporta dato', async () => {
const v = await guardCheck(pool, { channel: 'hardcap', type: 'article_note', content: 'A record hack at a major platform triggered the largest daily net outflow on record for Bitcoin ETFs on Nov 1 2026 (source: https://decrypt.co/x, Nov 1 2026) __TEST__' })
assert.ok(v.ok, JSON.stringify(v.reasons))
})