English · Español
Teoría 00 — La integración como disciplina; el DoD cerrado¶
Integrar no es escribir glue code. Es verificar contratos. Cada módulo del repo declaró sus entradas y salidas en un BLUEPRINT.md; la fase 39 audita que cada productor y cada consumidor coincidan. Y "done enough" no es una sensación: es una lista cerrada de verificaciones binarias que un script reproduce en frío.
Por qué importa esta teoría¶
De todas las cosas que la ingeniería de software intenta enseñar, dos se enseñan habitualmente mal: la integración y la definición de hecho (DoD). La primera parece fácil ("solo llama a la función B desde la función A") pero produce el 80% de los incidentes en producción. La segunda suena rigurosa ("nuestros criterios de aceptación") pero suele ser una lista de aspiraciones disfrazadas de checks. La Fase 39 arregla las dos siendo concreta.
Definiremos:
- Qué es un contrato de integración, y por qué cada llamada cruzada entre módulos en
lynx-cortextiene uno (lo supiera el autor o no). - Cómo detectar un contrato roto antes de que muerda en producción (type checks, schema tests, fuzz tests, replay).
- Qué es una lista de DoD cerrada — cada fila binaria, cada fila automatizable, sin aspiraciones.
- El vocabulario de "done enough": ¿enough para qué? Para la demo. Para 90 segundos. Para que un desconocido vea la columna vertebral del currículo.
Parte 1 — La integración es el cumplimiento de contratos¶
El contrato de un módulo tiene cuatro partes¶
Toma cualquier módulo en lynx-cortex — por ejemplo src/minigpt/ (Fase 17). Su BLUEPRINT.md declara:
- Entradas. La forma del tensor que consume:
(batch, seq_len)deint64con token ids en[0, |V|). Más un objeto de configuración. - Salidas. La forma que produce:
(batch, seq_len, |V|)defloat32con logits. - Invariantes. La promesa que hace sobre el comportamiento bajo invariantes: "dados los mismos inputs y la misma semilla, produce los mismos outputs (bit-identical)". "La salida suma uno tras softmax en el último eje" (después de la head, no antes).
- Efectos secundarios. Lo que cambia fuera de su valor de retorno: quizá nada (función pura); quizá un incremento de un counter de Prometheus; quizá una línea de log. Los efectos secundarios no documentados son bugs.
El consumidor downstream (src/miniserve/handlers.py) tiene el contrato espejo: se compromete a proporcionar (batch, seq_len) de int64, espera recibir (batch, seq_len, |V|) de float32, depende del invariante bit-identical para los tests de reproducibilidad, espera exactamente los efectos secundarios documentados.
Integración = esos dos contratos coinciden. No "la llamada a la función compila". No "la demo no peta". Los contratos coinciden, cada vez, bajo cada input que hemos pensado en testear.
Dónde se rompen los contratos¶
Seis modos de fallo, todos observados en sistemas reales (y en fases anteriores de este repo cuando faltaban los checks):
- Type drift. El productor cambia su salida de
float32afloat16para ahorrar memoria; el cálculo downstream del consumidor underflowa enfloat16y produce NaNs en la cola larga. Los tests unitarios siguen pasando porque los fixtures de test no tienen los valores de la cola larga. - Schema drift. El productor añade una clave nueva a una salida JSON; el consumidor la ignora. Seis semanas después, un consumidor distinto lee la clave y obtiene datos obsoletos porque el productor original nunca la pobló. (Este es el patrón del bug del manifest del corpus de la Fase 12.)
- Encoding drift. El productor escribe UTF-8 sin BOM; el consumidor lee con el encoding por default de la plataforma (CP1252 en Windows). Caracteres especiales mutilados. Los tests pasan solo en Linux.
- Time drift. El productor emite timestamps en la zona horaria local del productor; el consumidor asume UTC. Desfase de 1, 2 u 8 horas, según el usuario.
- Concurrency drift. El productor era single-threaded; el consumidor asumió que podía llamarlo con seguridad desde varias coroutines. El productor se "optimizó" para usar estado mutable compartido; ahora se corrompe bajo concurrencia.
- Version drift. El productor fijó
numpy==1.26.0en el momento del contrato; el entorno del consumidor terminó connumpy==2.1.0debido a churn del lockfile; cambios sutiles de API rompieron el contrato silenciosamente.
La auditoría de la Fase 39 recorre los seis modos de fallo contra cada llamada cruzada entre módulos en la ruta de la demo.
Detectar contratos rotos: cuatro técnicas¶
- Type checks.
mypy --strictsobresrc/desde la Fase 0. Atrapa el drift de firmas pero no los invariantes a nivel de valor. - Schema tests. Cada módulo que emite JSON tiene un directorio
tests/schema/con un JSON Schema + al menos 5 payloads de ejemplo. Las lecturas del lado consumidor incluyen unjsonschema.validate(payload, schema). Atrapa el schema drift. - Property tests.
hypothesis(introducido en la Fase 8 para autograd de tensores). Genera inputs aleatorios que satisfacen precondiciones, afirma el invariante. Atrapa drift de concurrencia + numérico. - Replay tests. Un corpus fijo de inputs pasados (uno por modo de fallo conocido) se re-ejecuta en cada PR. Atrapa regresiones sobre bugs ya descubiertos.
Para el capstone, el test de integración en lab/01 ejecuta los cuatro contra la ruta de la demo. Eso es lo que significa "el capstone es composición, no código nuevo" — el artefacto nuevo es el test de integración, no un módulo nuevo.
Parte 2 — "Done enough", operacionalizado¶
Los dos modos de fallo de "done"¶
- DoD aspiracional. "El sistema debería ser robusto y fácil de mantener." No testeable. Será declarado-hecho por gente con deadlines.
- DoD tautológico. "Los tests pasan." Verdadero pero inútil — los tests pueden escribirse para pasar trivialmente.
Una DoD real debe ser (a) binaria (pasa o falla, sin juicio), (b) automatizada (un script responde la pregunta, no un humano), © reproducible (la respuesta es la misma en una máquina nueva), (d) cerrada (una lista finita).
Los 8 checks binarios del capstone¶
La lista de DoD está en PHASE_39_PLAN.md §7 y se commitea al arranque de fase como docs/DONE_ENOUGH.md. Cada fila tiene un check automatizado. Reformulada aquí para énfasis:
| # | Check | Cómo se automatiza |
|---|---|---|
| 1 | just demo desde host limpio tiene éxito en ≤ 3 minutos |
.github/workflows/demo-smoke.yml corre en cada PR |
| 2 | just demo-cold levanta la pila completa, corre, la baja, sale con 0 |
El mismo workflow de CI |
| 3 | CI está verde en 5 PRs consecutivos | Visible en la UI de GitHub Actions |
| 4 | Cada panel de Grafana se rellena en 60s de la primera petición | lab/01 tiene un script de cobertura de panels |
| 5 | ≥ 3 filas de security/THREATS.md anotadas Phase 39 demo: verified |
Grep en security/THREATS.md por la anotación |
| 6 | docs/ARCHITECTURE.md renderiza en mkdocs sin parches manuales |
just docs sale con 0 |
| 7 | Cada fila de docs/DONE_ENOUGH.md pasa en una ejecución limpia |
La propia lista se chequea a sí misma |
| 8 | PHASE_39_REPORT.md commiteado con la tabla de mapeo por fase rellena |
Inspección visual (esta no está automatizada; documentar por qué) |
Ocho filas, no veinte. Más pequeño es más fuerte. Una DoD de 50 filas tiene 50 sitios para mentirte a ti mismo. Una DoD de 8 filas en la que cada fila pasa es una señal real.
Qué significa "done enough" en alcance¶
"Done enough" depende del contexto. Para la Fase 39 significa:
- Done enough para que un desconocido vea el sistema funcionando de punta a punta. No done enough para soportar 1.000 RPS. No done enough para ser un producto de pago. No done enough para sobrevivir un año de abandono.
- Done enough para que el currículo sea enseñable. Un visitante leyendo
docs/README.md+ viendo la demo puede responder a "¿qué aporta aquí la Fase 23?" La tabla de mapeo fuerza esto. - Done enough para defenderse contra los tres ataques más pedagógicos. No las 12+ filas del modelo de amenazas — esas son la auditoría de la Fase 40. Tres filas, demostradas en vivo.
Parte 3 — Los dos escollos¶
Escollo 1: gold-plating¶
El capstone es el sitio donde cada ingeniero quiere refactorizar "ya que estoy aquí". Resiste. La DoD es binaria; el refactor es ilimitado. Cada refactor no estrictamente requerido por una fila de DoD es candidato a la Fase 40. Anótalo en PHASE_39_REPORT.md § carry-overs y sigue. La regla provista por Borja (CLAUDE.md §0) — "microscopic scope" — se aplica con la mayor estrictez aquí.
Escollo 2: saltarse la tabla de mapeo¶
La tabla de mapeo por fase en PHASE_39_REPORT.md es el único artefacto que hace el currículo visible en el capstone. Sin ella, un lector ve una demo funcionando y un árbol src/ largo y no tiene mapa entre ambos. Con ella, cada Fase de la 0 a la 38 tiene una línea que dice "la demo toca esto en el archivo X en el paso Y". Esa tabla es el producto del currículo. Saltársela convierte 40 fases de aprendizaje estructurado en "construí un chatbot, creo".
Ejemplo trabajado: el contrato del "embedding lookup"¶
Toma una llamada concreta en la ruta de la demo: Embedding.forward(token_ids) de la Fase 13, invocada por Mini-GPT.forward de la Fase 17.
- Contrato del productor (
BLUEPRINTdesrc/minigpt/embedding.py): entrada(batch, seq_len)deint64en[0, |V|); salida(batch, seq_len, d_model)defloat32; función pura; sin efectos secundarios; bit-identical dados los mismos parámetros e inputs. - Contrato del consumidor (
BLUEPRINTdeMini-GPT.forward): proporciona token idsint64desde el BPE tokenizer, esperafloat32(d_model=128 para el grammar tutor); depende del bit-identical para el eval harness. - Auditoría: mypy verifica los tipos. Un property test (
tests/integration/test_embedding_contract.py) genera(batch ∈ [1, 8], seq_len ∈ [1, 64])aleatorios con token ids válidos, llama aEmbedding.forward, chequea forma y dtype de salida, llama dos veces y chequea bit-identity. Un replay test alimenta las 200 frases de eval de la Fase 20 y verifica que los hashes del output del embedding coincidan con el hash commiteado. - Qué cambió en la Fase 39: el schema test se añadió (no existía en la Fase 17). Ese es el trabajo del capstone: elevar contratos implícitos a explícitos.
El mismo patrón aplica a cada llamada cruzada en la demo. La Fase 39 lab 01 recorre los 12 contratos de la ruta de la demo.
Qué NO cubre esta teoría¶
- Cómo escribir el
BLUEPRINT.mdde cada módulo. Se hace en la fase de origen (p. ej., la Fase 17 escribió el blueprint de Mini-GPT). - Las matemáticas de un módulo concreto. Ya derivadas en la teoría de la fase de origen.
- Contratos de sistemas distribuidos. La demo es de un solo proceso; los contratos multi-proceso (subproceso del sandbox, MLflow sobre HTTP) se mencionan pero no se derivan aquí. Ver
theory/04-security-and-threat-model-closeout.mdpara la frontera del sandbox específicamente. - Configuración de CI en detalle. Cubierta en
lab/00-cold-start-bringup.mdylab/04-demo-script.md.
Siguiente: theory/01-architecture-of-the-tutor.md — el recorrido por el modelo C4, identificando cada contenedor del sistema del grammar tutor y los contratos en cada frontera.