<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ronny Cruz</title>
    <description>The latest articles on DEV Community by Ronny Cruz (@candornetwork).</description>
    <link>https://dev.to/candornetwork</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4033883%2Fd495ac32-d2da-4899-98b8-f57d42092255.png</url>
      <title>DEV Community: Ronny Cruz</title>
      <link>https://dev.to/candornetwork</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/candornetwork"/>
    <language>en</language>
    <item>
      <title>The Moderation System That Was Running Perfectly — On Nobody</title>
      <dc:creator>Ronny Cruz</dc:creator>
      <pubDate>Fri, 17 Jul 2026 17:27:37 +0000</pubDate>
      <link>https://dev.to/candornetwork/the-moderation-system-that-was-running-perfectly-on-nobody-akf</link>
      <guid>https://dev.to/candornetwork/the-moderation-system-that-was-running-perfectly-on-nobody-akf</guid>
      <description>&lt;p&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;I'm a solo founder building a social platform where content screening is the whole point. Livestreams get transcribed in real time and fanned out to AI analyzers — extremism detection, crisis detection — and the broadcaster's identity rides along so the system knows whose stream it's screening and can act on it.&lt;/p&gt;

&lt;p&gt;Identity flows through a JWT. Client logs in, gets a token, token accompanies the broadcast session, screening pipeline attributes everything correctly. Standard stuff. It worked in every test I ran during development.&lt;/p&gt;

&lt;p&gt;Here's the thing about this bug: &lt;strong&gt;there was no symptom.&lt;/strong&gt; No crash, no error log, no user complaint. Streams worked. Screening worked — I could see the analyzers firing. Everything looked healthy. That's what makes this one worth writing up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hunt
&lt;/h2&gt;

&lt;p&gt;I run my project under a rule I call §0: &lt;em&gt;"designed," "coded," and "verified working end-to-end" are three different statements, and only probes against the running system count as evidence.&lt;/em&gt; It exists because I once caught my deployed system quietly diverging from its design, one easy shortcut at a time, and I decided never again.&lt;/p&gt;

&lt;p&gt;So during a verification pass on the broadcast path, I wasn't asking "does streaming work?" — it obviously did. I was asking a more §0 question: &lt;strong&gt;prove to me that a logged-in broadcaster's session carries their verified identity into the screening pipeline.&lt;/strong&gt; Show me the actual value, from the actual running system.&lt;/p&gt;

&lt;p&gt;The probe came back wrong. The session that should have been my authenticated test user was attributed to &lt;code&gt;anonymous&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One session, maybe a fluke. I logged in again, fresh. Anonymous. Every single post-login session was anonymous.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dig
&lt;/h2&gt;

&lt;p&gt;Tracing it backwards through the running system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The client wasn't sending the token.&lt;/strong&gt; The broadcast token request went out without an &lt;code&gt;Authorization: Bearer&lt;/code&gt; header. The session token existed in the app — it just never got attached to this particular request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The server didn't mind.&lt;/strong&gt; The token route did no server-side JWT verification of its own. No token? No problem. It fell through to a default:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;broadcaster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;verifiedUser&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;anonymous&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;And there was a third layer:&lt;/strong&gt; even when auth &lt;em&gt;did&lt;/em&gt; work, the client wasn't hydrating the session from storage after a page reload — so any refreshed session lost its identity anyway and fell into the same anonymous path.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Three small, defensible-looking decisions. A missing header. A convenient fallback. A skipped hydration step. Each one written with good intentions, none of them logging anything, and together they meant: &lt;strong&gt;after login, every session silently fell back to anonymous.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why does that matter beyond bookkeeping? Because anonymous sessions took a different code path — one that bypassed identity-attributed screening entirely. The moderation stack was deployed, healthy, and fully operational. It just wasn't in the loop for the sessions that mattered. A safety system in the "on" position, wired to nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Three parts, matching the three failures:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Server-side JWT verification on the token route.&lt;/strong&gt; The server now verifies the token itself, cryptographically, before issuing anything. Client claims are not trusted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client sends &lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;&lt;/code&gt;&lt;/strong&gt; on the token request. The obvious missing piece, made explicit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;localStorage hydration on load&lt;/strong&gt;, so a refreshed session recovers its verified identity instead of silently downgrading.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And then the part I care about most — the fix behind the fix:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The fallback died.&lt;/strong&gt; Not just this one — the &lt;em&gt;pattern&lt;/em&gt;. My platform now runs a fail-loud rule: no silent defaults on anything security- or safety-relevant, anywhere. A missing secret at boot is &lt;code&gt;process.exit(1)&lt;/code&gt;, not &lt;code&gt;|| 'change-me'&lt;/code&gt;. A verification failure is a refusal, not a downgrade to a friendlier identity. If the system can't establish who you are, it doesn't guess — it stops, loudly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Verified after the fix the same way it was caught: probes against the live system, logged with dates. Authenticated session in, verified identity out, screening attributed correctly, on the running box — not in my head, not in the code review.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this bug taught me
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Silent fallbacks are camouflage.&lt;/strong&gt; &lt;code&gt;|| 'anonymous'&lt;/code&gt; looks like defensive programming. It's actually a machine for converting failures into fake success. The system didn't degrade visibly — it degraded into something that &lt;em&gt;looked identical to working.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"It works" and "I verified it works" are different sentences.&lt;/strong&gt; Every test I ran during development passed, because development tests exercise the paths you thought of. The bug lived in the gap between the design in my head and the system on the box, and only a probe of the running system — asking it to prove its own behavior — could see into that gap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The worst bugs don't throw.&lt;/strong&gt; Sentry-style error monitoring is essential, but this class of bug never generates an error to monitor — every code path "succeeded." The only detection mechanism is refusing to let failure be expressible as success: fail loud, fail closed on the paths that matter, and make silence itself impossible.&lt;/p&gt;

&lt;p&gt;This bug ultimately changed my platform's architecture — I rebuilt the write path so that this entire &lt;em&gt;class&lt;/em&gt; of bypass can't exist, which I wrote up separately in &lt;a href="https://dev.to/candornetwork/how-we-made-moderation-architecturally-impossible-to-skip-7lo"&gt;How We Made Moderation Architecturally Impossible to Skip&lt;/a&gt;. But the architecture came second. First came the humbling: my safety system spent its early life running perfectly, on nobody, and nothing anywhere was ever going to tell me.&lt;/p&gt;

&lt;p&gt;Now something does. Loudly.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
    </item>
    <item>
      <title>#bugsmash tag</title>
      <dc:creator>Ronny Cruz</dc:creator>
      <pubDate>Fri, 17 Jul 2026 17:15:43 +0000</pubDate>
      <link>https://dev.to/candornetwork/bugsmash-tag-31ld</link>
      <guid>https://dev.to/candornetwork/bugsmash-tag-31ld</guid>
      <description></description>
    </item>
    <item>
      <title>Cómo hicimos que saltarse la moderación fuera arquitectónicamente imposible</title>
      <dc:creator>Ronny Cruz</dc:creator>
      <pubDate>Fri, 17 Jul 2026 16:37:40 +0000</pubDate>
      <link>https://dev.to/candornetwork/como-hicimos-que-saltarse-la-moderacion-fuera-arquitectonicamente-imposible-1l8h</link>
      <guid>https://dev.to/candornetwork/como-hicimos-que-saltarse-la-moderacion-fuera-arquitectonicamente-imposible-1l8h</guid>
      <description>&lt;p&gt;Cómo hicimos que saltarse la moderación fuera arquitectónicamente imposible&lt;/p&gt;

&lt;p&gt;Por Ronny Cruz Alvarez, fundador de Open Feed Network (Candor Network). Fundador en solitario, una plataforma en producción, y muchas opiniones sobre rutas de escritura.&lt;/p&gt;

&lt;p&gt;El bug que me convenció&lt;/p&gt;

&lt;p&gt;Hace unas semanas encontré un bug en mi propia plataforma que debería quitarle el sueño a cualquier ingeniero de confianza y seguridad (trust &amp;amp; safety).&lt;/p&gt;

&lt;p&gt;Candor filtra las transmisiones en vivo. La identidad del transmisor viaja en un JWT para que el pipeline de filtrado sepa quién está transmitiendo. En algún punto de esa ruta, la verificación del token podía fallar — y cuando fallaba, el código recurría a una identidad por defecto. Algo así:&lt;/p&gt;

&lt;p&gt;jsconst broadcaster = verifiedUser || 'anonymous';&lt;/p&gt;

&lt;p&gt;Un solo ||. Escrito a la defensiva, con buenas intenciones, probablemente a la 1 de la madrugada. El efecto: después del login, cada sesión caía silenciosamente en "anonymous" — y las sesiones anónimas tomaban una ruta de código que evadía todo el sistema de filtrado. No un clasificador. Todos.&lt;/p&gt;

&lt;p&gt;Nada se rompió. Nada registró un error. El sistema de moderación estaba completamente desplegado, completamente funcional, y no estaba corriendo.&lt;/p&gt;

&lt;p&gt;Y aquí está lo importante: este no es un bug raro. Es el modo de fallo normal de cómo toda la industria conecta la moderación. Y es la razón por la que dejé de confiar en la arquitectura estándar y reconstruí la mía alrededor de un solo invariante.&lt;/p&gt;

&lt;p&gt;La arquitectura estándar es una promesa, no una garantía&lt;/p&gt;

&lt;p&gt;Casi todas las plataformas — sin importar qué proveedor de moderación usen — funcionan así:&lt;/p&gt;

&lt;p&gt;contenido del usuario → código de aplicación → [llamar al API de moderación, ojalá] → base de datos → feed&lt;/p&gt;

&lt;p&gt;El filtrado y el almacenamiento son dos sistemas separados, conectados por código de aplicación. Lo que significa que la garantía de "todo se filtra" es tan fuerte como cada ruta de código que pueda escribir en la base de datos. Cada endpoint. Cada trabajo en segundo plano. Cada script de migración. Cada fallback bien intencionado escrito a la 1 de la madrugada.&lt;/p&gt;

&lt;p&gt;Esa garantía se degrada de maneras que nunca aparecen en un demo:&lt;/p&gt;

&lt;p&gt;Un ingeniero agrega una nueva ruta de escritura y olvida la llamada al filtrado.&lt;br&gt;
Una condición de carrera sirve el contenido antes de que regrese el filtrado asíncrono.&lt;br&gt;
El servicio de moderación está caído y el código "falla abierto" para no bloquear a los usuarios.&lt;br&gt;
Un fallback silencioso (ver arriba) le da la vuelta a la verificación por completo.&lt;/p&gt;

&lt;p&gt;Cuando algo eventualmente se cuela y alguien pregunta "¿tu moderación corrió sobre esta publicación?", lo único que tienes es un log de aplicación afirmando que la verificación ocurrió. Los logs se contradicen con otros logs. Un log es una afirmación, no una prueba.&lt;/p&gt;

&lt;p&gt;El invariante&lt;/p&gt;

&lt;p&gt;El feed de Candor está construido sobre una sola oración, y cada decisión arquitectónica se verifica contra ella:&lt;/p&gt;

&lt;p&gt;La base de datos acepta únicamente contenido filtrado, porque la base de datos no olvida nada.&lt;/p&gt;

&lt;p&gt;Dos propiedades, y una fuerza a la otra:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;El almacén es de solo-anexar (append-only) e inmutable. Las entradas del feed forman una cadena criptográfica de hashes: cada entrada compromete el hash de la anterior. Alterar cualquier entrada pasada rompe todos los hashes subsiguientes. La manipulación no está prohibida por política — es matemáticamente detectable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Como no existe el "lo borro después", el filtrado tiene que correr antes de la escritura. En un almacén inmutable, guardar-y-luego-filtrar no es un orden peor. Es un orden prohibido, por definición. La puerta es la guardiana de un acto irreversible.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Así que la arquitectura no es "llama al API de moderación antes de guardar, y con code review nos aseguramos de que todos lo hagan". Es: la puerta de filtrado y la ruta de escritura al almacén son el mismo sistema. No existe ruta de escritura hacia el almacén del feed que no haya pasado ya por la puerta — no como convención, como propiedad estructural. La clase de bug del || 'anonymous' ya no puede evadir el filtrado, porque no hay nada del otro lado del filtrado a donde caer.&lt;/p&gt;

&lt;p&gt;"Espera — ¿inmutable? ¿Y las órdenes legales de eliminación?"&lt;/p&gt;

&lt;p&gt;Esta es la primera objeción que todo el mundo levanta, y es buena. El derecho al olvido del GDPR, las órdenes judiciales y los contenidos que genuinamente se cuelan son reales. Un feed inmutable que no puede cumplir con la ley de remoción de contenido no sirve.&lt;/p&gt;

&lt;p&gt;La respuesta es la redacción por lápida (tombstone redaction). Cada entrada almacena permanentemente hash(contenido) — un compromiso de contenido — separado del contenido mismo. Cuando la remoción es legalmente requerida, el contenido se elimina, pero el espacio de la entrada, su hash comprometido y sus enlaces de cadena permanecen, reemplazados por una lápida: aquí existió una entrada; removida por la razón X; fecha Y; autoridad Z.&lt;/p&gt;

&lt;p&gt;La validación de la cadena corre contra el hash comprometido, así que redactar una entrada nunca rompe la verificabilidad de ninguna otra. Obtienes ambas cosas: el contenido desaparece de verdad, y el registro de que existió, fue filtrado y fue removido bajo autoridad declarada es permanente y demostrable. Este fue el componente más delicado de construir.&lt;/p&gt;

&lt;p&gt;Fallar cerrado — pero no en todo, y ese es el punto&lt;/p&gt;

&lt;p&gt;"Fallar cerrado" (fail-closed) es un gran eslogan y una pésima política universal. Si cada error de filtrado bloqueara cada publicación, una sola dependencia inestable tumbaría la plataforma, y el arreglo de la guardia terminaría siendo… un parche que falla abierto. Felicidades: reinventaste el bypass silencioso, ahora con pasos extra.&lt;/p&gt;

&lt;p&gt;Así que la puerta tiene una matriz de fallos por severidad:&lt;/p&gt;

&lt;p&gt;Ruta de filtradoAnte un errorPor quéContenido terrorista / extremismo violentoFalla cerrado — no se almacenaRuta de daño catastróficoCSAM (comparación de hashes)Falla cerrado — no se almacenaLegal + catastróficoPuntuación de credibilidadPuede fallar abierto — se almacena sin puntuaciónUna puntuación faltante degrada una función; no daña a nadieVerificación de discurso protegidoSolo-degrada — puede reducir la severidad de un veredicto, nunca puede bloquear, nunca puede saltarse el filtradoPreviene la sobre-censura y evita que "la propaganda disfrazada" use la verificación de contexto como bypass&lt;/p&gt;

&lt;p&gt;Una puntuación de credibilidad perdida y una verificación de CSAM perdida no son el mismo fallo. Tratarlas igual es como terminas siendo o inusable o inseguro.&lt;/p&gt;

&lt;p&gt;La regla compañera es fallar ruidosamente (fail-loud): en Candor, un secreto faltante al arrancar es process.exit(1), nunca un valor por defecto. Nada de || 'change-me'. Nada de || 'anonymous'. Esa regla me ha atrapado personalmente bugs que de otra forma habrían sido invisibles — incluyendo uno donde un manejador de errores devolvía un valor con apariencia segura de "ninguna señal detectada" que en realidad estaba enmascarando fallos del API upstream. Un falso "todo despejado" es la peor salida posible de un sistema de seguridad, y la única defensa confiable que he encontrado es negarse a que cualquier ruta de código pueda fabricar uno.&lt;/p&gt;

&lt;p&gt;Limitaciones honestas&lt;/p&gt;

&lt;p&gt;Algunas cosas que esta arquitectura no resuelve mágicamente, porque los artículos de arquitectura que declaran victoria total están mintiendo:&lt;/p&gt;

&lt;p&gt;Las transmisiones en vivo no se pueden pre-filtrar. No puedes poner una puerta a audio/video en vivo antes de que exista. Los streams corren un modelo distinto: transcripción en tiempo real distribuida a los analizadores, con detección en línea y respuesta a nivel del stream. La garantía de inmutabilidad-en-ingreso aplica al feed y a los mensajes; el contenido en vivo se gobierna por velocidad de detección, y sigo activamente reduciendo esa latencia.&lt;br&gt;
La garantía es estructural, y la prueba criptográfica externa está en desarrollo. La cadena de hashes hace la manipulación internamente detectable hoy; publicar los hashes de la cabeza de la cadena en un ancla externa — para que la prueba sea verificable por terceros que no confían en mí — está diseñado y en progreso, no terminado.&lt;br&gt;
La calidad del filtrado sigue siendo un problema de clasificadores. Esta arquitectura garantiza que el filtrado corrió; no hace al clasificador perfecto. Lo que cambia es la clase de fallo: de "la verificación silenciosamente no ocurrió" a "la verificación ocurrió y aquí está el registro".&lt;br&gt;
Soy un fundador en solitario con un solo despliegue en producción — mi propia plataforma, que lleva corriendo esto en producción con una beta en vivo desde el 4 de julio. Esa es la escala honesta de la evidencia hoy. Cada capacidad que afirmo está verificada contra el sistema corriendo, con sondas fechadas, no inferida del código ni de la intención; esa disciplina existe porque una vez atrapé a mi propio sistema desplegado desviándose de su diseño, silenciosamente, un atajo fácil a la vez. El análisis de patente sobre la arquitectura está en curso con asesoría legal.&lt;/p&gt;

&lt;p&gt;Una nota más, específica para esta versión: la plataforma se construyó bilingüe, inglés y español, desde el diseño — la detección de crisis y la transcripción de streams operan en ambos idiomas. La moderación en español es una carencia documentada de toda la industria; para nosotros no es una traducción añadida después, es parte del sistema.&lt;/p&gt;

&lt;p&gt;Por qué creo que este orden gana&lt;/p&gt;

&lt;p&gt;La regulación se está moviendo de "¿moderaste?" a "demuestra que moderaste". La Ley de Servicios Digitales (DSA) de la Unión Europea ya apunta en esa dirección. Cuando esa pregunta llegue a tu plataforma, un log de aplicación es una respuesta débil. Una capa de almacenamiento que estructuralmente no puede aceptar contenido sin filtrar — con una cadena a prueba de manipulación mostrando exactamente qué se filtró, cuándo, y qué se removió bajo qué autoridad — es otro tipo de respuesta.&lt;/p&gt;

&lt;p&gt;Guardar-y-luego-filtrar tenía sentido cuando el almacenamiento era mutable y la moderación era una idea de último momento. En un almacén inmutable, filtrar-antes-de-guardar no es una preferencia. Es el único orden legal. Esa restricción resultó ser la decisión de diseño más clarificadora que he tomado.&lt;/p&gt;

&lt;p&gt;Candor Network es el despliegue de referencia. Los módulos individuales de filtrado (detección de crisis, clasificación de extremismo, puntuación de desinformación, detección de sextorsión) también están disponibles como APIs independientes — en inglés y español. Si administras una plataforma o comunidad y algo de esto se parece a un problema que tienes — o quieres decirme por qué estoy equivocado sobre la matriz de fallos — escríbeme a &lt;a href="mailto:candortheopenfeednetwork@gmail.com"&gt;candortheopenfeednetwork@gmail.com&lt;/a&gt;. Contesto todo yo mismo, en el idioma que prefieras.&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>architecture</category>
      <category>microservices</category>
      <category>ai</category>
    </item>
    <item>
      <title>How We Made Moderation Architecturally Impossible to Skip</title>
      <dc:creator>Ronny Cruz</dc:creator>
      <pubDate>Fri, 17 Jul 2026 13:14:16 +0000</pubDate>
      <link>https://dev.to/candornetwork/how-we-made-moderation-architecturally-impossible-to-skip-7lo</link>
      <guid>https://dev.to/candornetwork/how-we-made-moderation-architecturally-impossible-to-skip-7lo</guid>
      <description>&lt;p&gt;How We Made Moderation Architecturally Impossible to Skip&lt;/p&gt;

&lt;p&gt;By Ronny Cruz Alvarez, founder of Open Feed Network (Candor Network). Solo founder, one production platform, a lot of opinions about write paths.&lt;/p&gt;

&lt;p&gt;The bug that convinced me&lt;/p&gt;

&lt;p&gt;A few weeks ago I found a bug in my own platform that should keep every trust-and-safety engineer up at night.&lt;/p&gt;

&lt;p&gt;Candor screens livestream broadcasts. The broadcaster's identity flows through a JWT so the screening pipeline knows who's streaming. Somewhere in that path, a token verification could fail — and when it did, the code fell back to a default identity. Something like:&lt;/p&gt;

&lt;p&gt;jsconst broadcaster = verifiedUser || 'anonymous';&lt;/p&gt;

&lt;p&gt;One ||. Written defensively, with good intentions, probably at 1am. The effect: after login, every session silently fell back to anonymous — and anonymous sessions took a code path that bypassed the entire screening stack. Not one classifier. All of them.&lt;/p&gt;

&lt;p&gt;Nothing crashed. Nothing logged an error. The moderation system was fully deployed, fully functional, and not running.&lt;/p&gt;

&lt;p&gt;Here's the part that matters: this is not an unusual bug. It's the normal failure mode of how the entire industry wires up moderation. And it's why I stopped trusting the standard architecture and rebuilt mine around a single invariant.&lt;/p&gt;

&lt;p&gt;The standard architecture is a promise, not a guarantee&lt;/p&gt;

&lt;p&gt;Almost every platform — regardless of which moderation vendor they use — works like this:&lt;/p&gt;

&lt;p&gt;user content → application code → [call the moderation API, hopefully] → database → feed&lt;/p&gt;

&lt;p&gt;Screening and storage are two separate systems, connected by application code. Which means the guarantee "everything gets screened" is only as strong as every code path that could ever write to the database. Every endpoint. Every background job. Every migration script. Every well-intentioned fallback written at 1am.&lt;/p&gt;

&lt;p&gt;That guarantee degrades in ways that never show up in a demo:&lt;/p&gt;

&lt;p&gt;An engineer adds a new write path and forgets the screening call.&lt;br&gt;
A race condition serves content before the async screen returns.&lt;br&gt;
The moderation service is down and the code "fails open" so users aren't blocked.&lt;br&gt;
A silent fallback (see above) routes around the check entirely.&lt;/p&gt;

&lt;p&gt;When something eventually slips through and someone asks "did your moderation run on this post?", all you have is an application log claiming a check happened. Logs get contradicted by other logs. A log is a claim, not a proof.&lt;/p&gt;

&lt;p&gt;The invariant&lt;/p&gt;

&lt;p&gt;Candor's feed is built on one sentence, and every architectural decision is checked against it:&lt;/p&gt;

&lt;p&gt;The database accepts only screened content, because the database forgets nothing.&lt;/p&gt;

&lt;p&gt;Two properties, and they force each other:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The store is append-only and immutable. Feed entries form a cryptographic hash-chain: each entry commits the hash of the previous one. Altering any past entry breaks every subsequent hash. Tampering isn't forbidden by policy — it's mathematically detectable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Because there is no "remove it later," screening must run before the write. On an immutable store, store-then-screen isn't a worse ordering. It's a prohibited one, by definition. The gate is the gatekeeper of an irreversible act.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the architecture is not "call the moderation API before saving, and we'll code-review to make sure everyone does." It's: the screening gate and the storage write path are the same system. There is no write path into the feed store that hasn't already passed the gate — not as a convention, as a structural property. The || 'anonymous' class of bug can't bypass screening anymore, because there's nothing on the other side of screening to fall through to.&lt;/p&gt;

&lt;p&gt;"Wait — immutable? What about legal takedowns?"&lt;/p&gt;

&lt;p&gt;This is the first objection everyone raises, and it's a good one. GDPR erasure, court orders, and genuine slip-throughs are real. An immutable feed that can't comply with removal law is a non-starter.&lt;/p&gt;

&lt;p&gt;The answer is tombstone redaction. Every entry permanently stores hash(content) — a content commitment — separately from the content payload. When removal is legally required, the payload is deleted, but the entry's slot, its committed hash, and its chain links remain, replaced with a tombstone: entry existed here; removed for reason X; date Y; authority Z.&lt;/p&gt;

&lt;p&gt;Chain validation runs against the committed hash, so redacting one entry never breaks verifiability for any other entry. You get both: the content is genuinely gone, and the record that it existed, was screened, and was removed under stated authority is permanent and provable. This was the single most delicate component to build, and it was built and tested first, before anything depended on it.&lt;/p&gt;

&lt;p&gt;Fail-closed — but not everywhere, and that's the point&lt;/p&gt;

&lt;p&gt;"Fail closed" makes a great slogan and a terrible universal policy. If every screening error blocked every post, one flaky dependency would take the platform down, and the on-call fix would inevitably be… a fail-open patch. Congratulations, you've reinvented the silent bypass, now with extra steps.&lt;/p&gt;

&lt;p&gt;So the gate has a severity-tiered failure matrix instead:&lt;/p&gt;

&lt;p&gt;Screening pathOn errorWhyTerrorism/violent-extremism contentFail closed — do not storeCatastrophic-harm pathCSAM (hash-matching)Fail closed — do not storeLegal + catastrophicCredibility scoringMay fail open — store without a scoreA missing score degrades a feature; it harms no oneProtected-speech context checkDowngrade-only — can reduce a verdict's severity, can never block, can never skip screeningPrevents over-censorship and prevents "propaganda in costume" from using the context check as a bypass&lt;/p&gt;

&lt;p&gt;A missed credibility score and a missed CSAM check are not the same failure. Treating them the same is how you end up either unusable or unsafe.&lt;/p&gt;

&lt;p&gt;The companion rule is fail-loud: on Candor, a missing secret at boot is process.exit(1), never a default value. No || 'change-me'. No || 'anonymous'. That rule has personally caught bugs for me that would otherwise have been invisible — including one where an error handler returned a safe-looking "no signal detected" value that was actually masking upstream API failures. A fake all-clear is the worst possible output of a safety system, and the only reliable defense I've found is refusing to let any code path manufacture one.&lt;/p&gt;

&lt;p&gt;Honest limitations&lt;/p&gt;

&lt;p&gt;A few things this architecture does not magically solve, because architecture posts that claim total victory are lying:&lt;/p&gt;

&lt;p&gt;Livestreams can't be pre-screened. You cannot gate live audio/video before it exists. Streams run a different model: real-time transcription fanned out to analyzers, with in-line detection and stream-level response. The immutable-at-ingress guarantee applies to the feed and messages; live media is governed by detection speed, and I'm still actively working that latency down.&lt;br&gt;
The guarantee is structural, and the cryptographic external proof is in development. The hash-chain makes tampering internally detectable today; publishing chain-head hashes to an external anchor — so the proof is verifiable by third parties who don't trust me — is designed and in progress, not done.&lt;br&gt;
Screening quality is still a classifier problem. This architecture guarantees screening ran; it does not make the classifier perfect. What it changes is the failure class: from "the check silently didn't happen" to "the check happened and here's the record."&lt;br&gt;
I'm a solo founder with one production deployment — my own platform, which has been running this in production with a live beta since July 4th. That's the honest scale of the evidence today. Every capability claim I make is verified against the running system with dated probes, not inferred from code or intent; that discipline exists because I once caught my own deployed system diverging from its design, silently, one easy shortcut at a time. Patent analysis on the architecture is underway with counsel.&lt;/p&gt;

&lt;p&gt;Why I think this ordering wins&lt;/p&gt;

&lt;p&gt;Regulation is moving from "did you moderate?" to "prove you moderated." The EU's DSA already points that way. When that question arrives at your platform, an application log is a weak answer. A storage layer that structurally cannot accept unscreened content — with a tamper-evident chain showing exactly what was screened, when, and what was removed under what authority — is a different kind of answer.&lt;/p&gt;

&lt;p&gt;Store-then-screen made sense when storage was mutable and moderation was an afterthought. On an immutable store, screen-before-store isn't a preference. It's the only legal ordering. That constraint turned out to be the most clarifying design decision I've made.&lt;/p&gt;

&lt;p&gt;Candor Network is the reference deployment. The individual screening modules (crisis detection, extremism classification, disinformation scoring, sextortion detection) are also available as standalone APIs. If you run a platform or community and any of this maps to a problem you have — or you want to tell me why I'm wrong about the failure matrix — I'm at &lt;a href="mailto:candortheopenfeednetwork@gmail.com"&gt;candortheopenfeednetwork@gmail.com&lt;/a&gt;, and I answer everything myself.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>opensource</category>
      <category>architecture</category>
      <category>node</category>
    </item>
  </channel>
</rss>
