containment: every third-party send becomes a draft (outbox), identity file versioned
ci / lint-test-leaks (pull_request) Canceled after 0s
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.
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { loadIdentityBlock, identityFingerprint } from './outbox.mjs'
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
const core = readFileSync('/srv/cayce-core/core.mjs', 'utf8')
|
||||
const ed = readFileSync('/srv/cayce-core/editorials.mjs', 'utf8')
|
||||
|
||||
function blockFromOriginal(src, start, endMark) {
|
||||
const i = src.indexOf(start)
|
||||
if (i === -1) return null
|
||||
return src.slice(i, src.indexOf(endMark, i) + endMark.length)
|
||||
}
|
||||
|
||||
const checks = []
|
||||
const dOrig = blockFromOriginal(core, 'You are Cayce, autonomous entity. Voice: dry, declarative, short sentences, zero corporate vocabulary', 'Stories already include URLs; keep them intact.')
|
||||
checks.push(['digest', loadIdentityBlock('digest') === dOrig])
|
||||
|
||||
const eOrig = blockFromOriginal(ed, 'You are Cayce — autonomous entity, Próspera ZEDE', 'End with one sharp line of judgment.')
|
||||
checks.push(['editorial', loadIdentityBlock('editorial') === eOrig])
|
||||
|
||||
// opinion/reflection/events: la parte estática original termina antes del separador dinámico
|
||||
const oStart = 'You are Cayce, autonomous entity on Nostr. Voice: dry, declarative, short sentences, pattern-vs-apophenia as lens'
|
||||
const oEnd = 'End with the article URL.'
|
||||
checks.push(['opinion', loadIdentityBlock('opinion') === blockFromOriginal(core, oStart, oEnd)])
|
||||
|
||||
const rStart = 'You are Cayce, autonomous entity on Nostr. Write ONE reflective post'
|
||||
const rEnd = '(use, ignore, or push against):'
|
||||
checks.push(['reflection', loadIdentityBlock('reflection') === blockFromOriginal(core, rStart, rEnd)])
|
||||
|
||||
const vStart = 'You are Cayce, autonomous entity on Nostr. Voice: dry, declarative, short sentences, no hashtags'
|
||||
const vEnd = 'If an event has a URL, include it.'
|
||||
checks.push(['events', loadIdentityBlock('events') === blockFromOriginal(core, vStart, vEnd)])
|
||||
|
||||
let all = true
|
||||
for (const [k, ok] of checks) { console.log(k, ok ? 'IDENTICO ✓' : 'DIFIERE ✗'); if (!ok) all = false }
|
||||
console.log('fingerprint:', JSON.stringify(identityFingerprint()))
|
||||
process.exit(all ? 0 : 1)
|
||||
Reference in New Issue
Block a user