movingparticle@caja-negra RODRIGO POLO EC, GYE ~/reto-2/DESIGN_DOC ← demo en vivo --:--:-- EN

movingparticle@caja-negra:~$ cat ./SYSTEM_DESIGN.txt

FRONTIER Bogotá 2026 · Reto 2 · hito 12:00.
Un servicio lleva 10 años en producción y nadie sabe cómo funciona. Vamos a descubrir su comportamiento observándolo y construir una réplica que responda exactamente igual — errores incluidos — sin llamarlo nunca en ejecución.

01 // qué

Reemplazar un sistema sin código ni documentación por una réplica determinista, byte a byte, frente a casos que no conocemos.

02 // cómo

Probing guiado por hipótesis para inferir la regla real, y un Worker de Cloudflare (con Durable Object para un estado único) que emula comandos, estados y errores.

03 // prueba

Comparación diferencial oráculo vs. réplica, batería adversarial y paridad exacta de status y cuerpo.

fidelidad status+body active learning node esm · 0 deps reset atómico sin overfitting
documentación arquitectura ↗
explicación

Resumen

Lectura en 30 segundos. El detalle técnico está más abajo, en el mismo documento.

El problema

El interior es desconocido. Solo vemos INPUT → CAJA → OUTPUT. Hay que reconstruir la regla, no memorizar ejemplos.

La solución

Un harness de descubrimiento que pregunta al original, y un entrypoint HTTP (Cloudflare Worker + Durable Object) que responde igual.

La evidencia

Cada comportamiento cita una línea del log. Cada respuesta se compara contra el original. Nada se afirma sin prueba.

PRINCIPIO. Una función que reproduce los ejemplos conocidos no reproduce necesariamente la regla real. Diseñamos para generalizar, no para aprobar el set visible.

Puntos críticos

punto críticoestadoresolución
Réplica no implementadaresuelto6 comandos + 17 errores en src/engine.mjs; paridad 20/20.
Algoritmo del control desconocidoresueltoISO 7064 Mod 97-10 (16/16 casos + probes de borde).
Estado no aislado / multi-instanciaresueltoDurable Object único global-caja-negra (un solo estado).
Entrypoint no desplegadoresueltoCloudflare Workers: https://caja-negra-reemplazo.parsec-ai-labs.workers.dev.
Incidente de las 15:30abiertoBaseline congelado + adaptación sin regresión (INCIDENT.md).
Notas de contrato (supuestos). GET /health exige token: sin él → 401 E001 (el arnés manda token en todas las llamadas). El cuerpo de /__reset es {"ok":true} y no es comparado por el arnés. Ambos quedan como supuestos hasta verificar contra el original.
datos observados

Stats

Dos fuentes lado a lado: verde = 20 ejemplos públicos (pruebas/ejemplos.jsonl) · cian = 14 sondas en vivo contra el oráculo (experimentos.jsonl). Barras de bloque, ordenadas, con n visible. Sin tortas.

20
ejemplos
13
respuestas 2xx
7
errores
6
comandos conocidos
1
ruta de negocio

status http baseline · 20 ejemplos

n = 20 · ordenado por frecuencia

200 OK
11
400 BAD
4
201 NEW
2
422 LIM
2
404 MISS
1

status http pruebas hechas · 14 sondas

n = 14 · sondas adversariales en vivo

400 BAD
9
404 MISS
3
200 OK
2

familias de error baseline · 20 ejemplos

n = 7 · E2xx forma · E3xx comando · E4xx recurso · E5xx negocio

E2xx
2
E3xx
2
E5xx
2
E4xx
1

familias de error pruebas hechas · 12 errores

n = 12 · aparece E1xx (forma de mensaje), que el baseline no alcanzaba

E3xx
6
E4xx
3
E1xx
2
E2xx
1

comandos baseline · 20 ejemplos

n = 20 · el comando es case-insensitive (CONSULTa fue aceptado)

CONSULTA
5
ALTA
4
TRANSFER
4
DEPOSITO
3
ANULA
2
PING
1
UNKNOWN
1

comandos pruebas hechas · 14 sondas

n = 14 · sesgo adversarial: TRANSFER y ANULA para forzar bordes

TRANSFER
4
ANULA
3
DEPOSITO
2
PING
1
ALTA
1
CONSULTA
1
RETIRO
1
(vacío)
1
BASELINE (20)          n    barra        PRUEBAS (14)        n    barra
200 OK                11   ███████████  400 BAD             9   █████████
400 BAD                4   ████         404 MISS            3   ███
201 NEW                2   ██           200 OK              2   ██
422 LIM                2   ██
404 MISS               1   █
Verde = baseline (20 ejemplos públicos) · cian = pruebas hechas (14 sondas en vivo, ids 21–34 en experimentos.jsonl). El probing fijó la precedencia de errores (E303→E304→E401→E506), los límites de monto y el algoritmo de control (checksum mod 97).
system design · entendimiento

Problema

arc42 §1–3 y el criterio de entendimiento de la rúbrica: objetivo, límites, supuestos, lo que queda fuera y por qué.

Objetivo

Réplica autónoma, determinista y pública que responda igual que el original — mismos status y cuerpos — ante un set oculto de 100 tests.

Inputs / Outputs

  • In: POST /msg con {"m":"CMD;arg;...;ctrl"} + Bearer token.
  • Out: status HTTP exacto + cuerpo JSON o texto exacto.
  • Control: GET /health, POST /__reset.

Restricciones explícitas

  • El reemplazo no puede llamar al original ni a otro reemplazo.
  • 5 s por paso; 10 req/s y 8.000/día por token.
  • Repo público sin material privado del organizador.
  • Fuzzers propios: permitidos y esperados.

Restricciones implícitas

  • Un solo proceso implica estado por instancia.
  • El servicio puede cambiar durante el día (incidente 15:30).
  • Sin token o con token ajeno, comportarse como el original.

Supuestos

  • Máquina de estados determinista.
  • Argumentos posicionales separados por ;.
  • El último campo valida integridad del mensaje.
  • Estado efímero: /__reset vuelve a cero.

Out of scope

  • Persistencia: el contrato pide reset, no durabilidad.
  • Proxy al original: la regla lo pone en 0.
  • UI en runtime: no suma paridad.

Edge cases

cuerpo vacíocase-insensitive E302 aridadE201 control E202 controlE401 cuenta E506 límiteE507 saldo ya anuladaruta inexistente payload grandecodificación inválida

Failure modes

  • Overfitting a 20 ejemplos, falla lo nuevo.
  • Orden de validación mal inferido, error equivocado.
  • Estado compartido sin reset, cascada.
  • Schema change del incidente, respuestas rotas.
  • Latencia > 5 s, timeout = fallo.
system design · arquitectura

Arquitectura

Producción en Cloudflare Workers con un Durable Object que garantiza un estado único global; el mismo motor corre en un servidor Node local. Integridad con ISO 7064 Mod 97-10.

Entrypoint público: https://caja-negra-reemplazo.parsec-ai-labs.workers.dev

→ abrir architecture.html (C4 nivel 1 y 2)

trade-offs · madr 4.0

Decisiones

Cada ADR dice qué se descartó, por qué, qué se gana, qué se sacrifica y cuándo se cambiaría.

ACEPTADA ADR-0001 · Node.js ESM, cero dependencias

contextoReproducibilidad y arranque en cualquier host de evaluación.
driversReproducibilidad · superficie de ataque · tiempo de arranque
opciones(A) Fastify/Express + TypeScript + Zod · (B) node:http nativo
Elegimos (B). Sin árbol de dependencias, el despliegue es idéntico en cualquier máquina.
+ determinismo, portabilidad, arranque < 50 ms
- routing y validación escritos a mano
cambiar si> 25.000 req/s con esquemas dinámicos: Fastify.
confirmaciónnpm ci && npm test && npm start en entorno limpio.

ACEPTADA ADR-0002 · Probing por hipótesis

contextoPresupuesto corto: 10 req/s, 8.000/día.
driversInformación por probe · trazabilidad SPEC/LOG
opciones(A) fuzzing aleatorio · (B) probes que separan hipótesis
Elegimos (B). Cada probe intenta falsar una hipótesis y queda en el LOG.
+ semántica, evidencia citable, anti-overfitting
- más lento para basura puramente sintáctica
cambiar siAparecen campos libres o JSON profundo: añadir mutadores.

ACEPTADA ADR-0003 · Estado único con Durable Object + reset atómico

contextoEl juez llama /__reset antes de cada uno de 100 tests y asume un único proceso/estado.
driversEstado único global · aislamiento entre tests · latencia de reset
opciones(A) SQLite/Redis clásico · (B) instancias sin estado · (C) Durable Object con nombre fijo
Elegimos (C): un Durable Object global-caja-negra garantiza un solo estado lógico, con reset en memoria (< 1 ms) sin disco.
+ estado único entre instancias, reset instantáneo, deploy trivial
- el estado vive en el DO y requiere binding (no es un proceso Node plano)
cambiar siSe pidiera crash-recovery o estado compartido multi-región.

ACEPTADA ADR-0004 · Adapter para el incidente

contextoA las 15:30 el servicio puede cambiar de formato.
opciones(A) reescribir el motor · (B) adapter de normalización
Elegimos (B). El cambio vive en un solo punto. Lo que ya pasaba, sigue pasando.
+ patch mínimo, regresión congelada
- una indirección más
cambiar siEl incidente invalida el modelo de dominio, no solo el formato.
plan para medir

Medición

Cómo sabremos que funciona antes de construirlo. Sin número, no hay defensa.

Baseline

Tras /__reset, los 20 ejemplos dan 20/20 en status y cuerpo (test automatizado).

Métricas

  • Paridad status + body
  • Paso < 5 s
  • Reset < 1 ms
  • Suite 11/11 en verde

Éxito

  • 100% en lo conocido
  • 0 regresiones post-incidente
  • Reset aislado
  • Contrato de entrypoint válido
testqué intenta romperesperadoprio
happy pathPING → ALTA → DEPOSITO → CONSULTAstatus y saldos exactosP0
vacío/msg sin cuerpomismo error que el originalP0
desconocidoSALDOS;...400 E301P0
aridaddemasiados o pocos ;400 E302P0
controlcampo no numérico400 E201 / E202P0
cuentaCONSULTA;AC-9999404 E401P0
límitetransferencia sobre umbral422 E506P1
saldotransferir más de lo que hay422 E507P1
anula x2ANULA de operación ya anuladaok:false, ya anuladaP1
ruta malaGET /msg, path desconocidomismo 404/405P1
payloadcuerpo grande o encoding rotorechazo con el código exactoP1
reset/__reset sin tokencomo el original, sin filtrar el mecanismoP0
incidenteel caso que cambie a las 15:30adaptado, sin regresiónP0
latenciacada paso< 5 sP2
hidden tests

Hipótesis

confirmado se repite · probable se vio una vez o se infiere · supuesto es el default del reemplazo.

idcomportamientoevidenciaconfianza
C1PING → {pong:true} sin estado#1confirmado
C2comando case-insensitive#11 CONSULTaconfirmado
C3ALTA crea AC-#### incremental#3 #4confirmado
C4el control se valida antes de actuar#2 #17probable
C5DEPOSITO acredita y devuelve operacionId#6 #18 #19confirmado
C6TRANSFER mueve saldo#7 #8confirmado
C7hay tope E506 y saldo E507#9 #16confirmado
C8ANULA es idempotente#13 #14confirmado
C9comando desconocido → 400 E301 literal#12confirmado
C10aridad inválida → 400 E302#20confirmado
C11control = ISO 7064 Mod 97-10 (A=1…Z=26 sin padding, CD = 98 − (N·100) mod 97)16/16 + probesconfirmado
H1el control es un dígito de verificación del payloadconfirmado como ISO 7064confirmado
H2orden: forma → control → recurso → negocio#15 vs #9probable
H3IDs por proceso, se reinician con reset#3 #6probable
S1payload grande o encoding inválido → error establecontratosupuesto
S2ruta inexistente → mismo 404contratosupuesto
Anti-overfitting: por cada regla confirmada, un probe donde dos hipótesis predicen distinto. Se ejecuta el de mayor ganancia de información.
implementación

Plan

orden

  • P0 servidor: /health /__reset /*
  • P0 motor + store con reset atómico
  • P0 probing guiado (experimentos.jsonl)
  • P1 batería adversarial
  • P1 incidente 15:30: detectar, aislar, patch, regresión
  • P2 README + video 2 min

abierto

  • Resuelto: control = ISO 7064 Mod 97-10.
  • Resuelto: precedencia E303 > E304 > E401 > E506 > E507.
  • ¿Qué cambia exactamente a las 15:30?
  • ¿El rate-limit trae un cuerpo que haya que emular?
  • ¿Rutas alternas (ej. /saldo) existen en el original?
debrief

Defensa

Patrón: elegimos X por A y B; descartamos Y por C; el trade-off es D; si cambia E, pasamos a Y.

¿Por qué esta arquitectura?
Separa descubrimiento offline de ejecución online. El reemplazo nunca llama al original.
¿Qué rechazaron?
Un proxy al original (el set vale 0) y un fuzzer ciego (no modela estado).
¿La parte más débil?
La precedencia exacta entre errores de forma (fue necesario un par de probes extra) y el estado único, que dependió de usar un Durable Object.
¿Qué rompe a 10x?
Varias instancias, cada una con su RAM. El siguiente paso sería estado compartido con lease.
¿Cómo saben que funciona?
Diff oráculo vs réplica en los 20 ejemplos y en la batería adversarial. Mismo status, mismo cuerpo.
¿Qué asumen?
Máquina de estados determinista, comandos posicionales, estado reiniciable.
¿Si cae una dependencia?
El runtime no tiene dependencias. Si cae el oráculo, para el descubrimiento; la réplica sigue con lo ya inferido.
¿Qué hizo la IA y qué cambiaron?
La IA propuso estructura y fórmulas candidatas. Las aceptamos o las tiramos con probes. El orden de validación generado se corrigió.
¿Con más tiempo?
1) cerrar el control. 2) estado multi-instancia. 3) health y métricas de operación.
¿Por qué generaliza?
Modelamos la regla, no la secuencia de ejemplos. Cada comportamiento se falsó contra una hipótesis rival.
teclas 1234 5678 saltan sección