Generadores de la documentación

Qué se genera, desde qué fuente, con qué generador, qué valida cada uno, cómo se lee un rojo de bun run docs:check y cómo se añade uno nuevo.

EspecificadoNo implementado
track/docsdec-0124dec-0116dec-0120dec-0103dec-0118track/docs:DC2track/docs:DC3track/docs:DC4track/docs:DC5track/docs:DC6track/docs:DC10

Especificado. Los generadores existen y bun run docs:check corre, pero track/docs sigue queued hasta el lock de dec-0124: por la tabla de estados de su §5.1 esta página no sube a implementado antes. Lo que dice obliga a la implementación.

Toda la sección Referencia se genera. Nadie la edita a mano: el esfuerzo va a la fuente (TSDoc, ///, descripciones de contratos y del esquema de configuración), que además mejora el código.

Principio

Todo lo que se puede generar, se genera. Un generador por fuente, y ninguno inventa contenido: proyecta una fuente canónica. La referencia que sale de ahí se versiona (se lee en GitHub sin construir nada) y una comprobación detecta si se ha desviado de su fuente.

bun run docs:gen      # escribe la referencia generada
bun run docs:check    # regenera en memoria y falla si algo difiere o sobra

Qué se genera de qué

Fuente canónicaGeneradorSalida en el sitio
Rutas Elysia de cada servicio y esquemas de @styx/api-contractsgen-openapi.tsreferencia/http/<servicio>/
TSDoc de los paquetes públicosgen-sdk.ts (TypeDoc)referencia/sdk/<paquete>/
/// y //! de los módulos Zig públicos y de los SDK consumidosgen-zig-docs.ts y el extractor Zigreferencia/zig/<módulo>/
BUS_ROUTES y los contratos del busgen-bus-docs.tsreferencia/bus/
protocols/* (especificación humana y vectores)sync-protocols.tsreferencia/protocolos/
El esquema único de configuración de cada servicio y del daemongen-config.tsreferencia/configuracion/
Tipos de problema y códigos de errorgen-errors.tsreferencia/errores/
Roles, permisos y políticas de @styx/authzgen-permissions.tsreferencia/permisos/
El registro de comandos del CLIel propio CLIreferencia/cli/
styx.model.yml y docs/decisions/gen-roadmap-adrs.tsroadmap/ y decisiones/

Los scripts viven en scripts/docs/; el extractor de Zig está en native/zig/tools/.

Cómo se garantiza que no hay drift

  • Salida determinista: claves ordenadas, sin fechas, sin rutas absolutas, final de línea fijo. Dos ejecuciones dan los mismos bytes.
  • Comparación byte a byte: docs:check no usa una heurística, regenera y compara. Un fichero que falta, difiere o sobra en un directorio que el generador posee es drift.
  • inputHash: cada página generada declara en su frontmatter el hash de su entrada. Ese hash no sirve para detectar drift sin ejecutar el generador cuando la entrada es derivada: en gen-roadmap-adrs.ts se calcula sobre la proyección que el propio generador hace del model y de las páginas que citan cada nodo o ADR, no sobre los bytes de un fichero fuente, así que recalcularlo exige ejecutar casi todo el generador. Sirve para ver en un diff qué páginas cambiaron de entrada; la detección de drift es la regeneración completa (scripts/docs/gen-roadmap-adrs.ts --check), que corre en CI dentro de bun run check:roadmap. Un generador nuevo sólo puede ofrecer detección por hash sin regenerar si su hash se recalcula desde los bytes de la fuente, y lo declara.
  • Los generadores validan su fuente: una ruta sin esquema de respuesta, un export público sin TSDoc, un pub de Zig sin ///, un protocolo sin schemaVersion o sin política de compatibilidad es una violación del propio generador, no un hueco silencioso.
  • Trinquetes: donde ya existe deuda (por ejemplo, exports públicos sin TSDoc) vive en un baseline por generador; ver Trinquetes.

Las vistas de roadmap y decisiones

gen-roadmap-adrs.ts proyecta styx.model.yml y docs/decisions/ en el sitio:

  • una página por nodo de primer nivel, con sus hitos, los items de su gate con su veredicto tal cual, la cola de tickets y qué páginas de la documentación lo citan;
  • una página por decisión, con su estado, sus enmiendas y qué páginas y nodos la citan.

No hay contadores agregados ni se copia prosa de docs/decisions/README.md. Las propias páginas generadas citan track/docs y no el nodo o la decisión que describen, para que su existencia no cuente como cobertura de nadie: la cobertura la dan las páginas escritas a mano, que es justo lo que esas vistas hacen visible.

Los dos comandos

  • bun run docs:gen regenera todo y escribe.
  • bun run docs:check regenera en memoria y falla (exit 1) si un fichero difiere, falta o sobra, o si algún generador encuentra una violación. Es el mismo patrón que gen:bus / check:bus.
  • bun scripts/docs/gen-all.ts --only openapi,bus [--check] limita a unos generadores.

Qué genera cada uno

GeneradorFuente canónicaSalidaRojo si…
scripts/docs/gen-openapi.tsLa app de captura de cada servicio (apps/<svc>/src/app.aot.ts) con serviceOpenApi de @styx/service-http (toOpenAPISchema + problems en application/problem+json). Sin infra ni puertos.apps/docs/generated/openapi/<svc>.json + referencia/httpUna ruta registrada que no está en el OpenAPI sin detail.hide + x-styx-hide-reason (siempre), o una operación nueva sin operationId, sin summary, sin errores, o con algún 4xx/5xx que no declara application/problem+json (se juzga cada status; única exención explícita, GET /ready 503; trinquete scripts/docs/openapi.baseline.json; la página de una operación con esa deuda la avisa arriba con los status).
scripts/docs/gen-sdk.tsEl TSDoc de los entrypoints de exports de los paquetes públicos y de @spire/bus (TypeDoc 0.28 + typedoc-plugin-markdown).referencia/sdkUna exportación pública nueva sin TSDoc o un enlace roto (trinquete scripts/docs/tsdoc.baseline.json). @spire/bus no bloquea: su cobertura se exige en su repo.
scripts/docs/gen-zig-docs.ts/// y //! por AST (native/zig/tools/docs_extract.zig, zig run) de los módulos públicos y de zkit, spire y conduit en el hash de build.zig.zon.apps/docs/generated/zig/*.json + referencia/zigUn pub nuevo sin /// o un fichero sin //! en un módulo de styx (trinquete scripts/docs/zig-doc.baseline.json).
scripts/docs/gen-bus-docs.tsLos contratos de @styx/api-contracts/bus y BUS_ROUTES / BUS_EMITS / BUS_STREAMS.referencia/busUn contrato sin TSDoc, o sin ruta ni emisor.
scripts/docs/sync-protocols.tsprotocols/* (la spec se copia sin reescribir).referencia/protocolosUn directorio de protocols/ sin registrar, o una spec nueva sin versión, compatibilidad o fixtures (trinquete scripts/docs/protocols.baseline.json).
scripts/docs/gen-config.tsEl esquema único scripts/docs/env.schema.ts cruzado con el inventario de lecturas reales (scripts/docs/env-scan.ts).referencia/configuracionEl código lee una variable que no está en el esquema, el esquema tiene una que nadie lee, o el código lee el entorno de una forma que el inventario no convierte en nombres (nombre no literal, ...resto, el entorno entero pasado a algo que no está en ENV_CONSUMERS, un helper exportado usado como valor en otro fichero, también export const f = helper o export default helper; sólo un import o un re-export lo nombra sin usarlo). Los helpers con el nombre como parámetro se resuelven en todo el repo: la llamada cuenta aunque el helper esté definido en otro fichero. Es por expresiones regulares, no por AST: una forma que ninguna regla reconoce no se ve (deuda DC6 hasta check:env-schema).

Trinquetes

Un trinquete es un fichero scripts/docs/*.baseline.json con la deuda conocida. Una violación que no está en el baseline es rojo, y una entrada que ya no se da también lo es (hay que borrarla, para que no vuelva a entrar en silencio). Se documenta la fuente y se borra la línea en el mismo cambio.

No es un bloqueo contra la deuda nueva. docs:check compara lo encontrado con el baseline tal como está en el árbol, no con el de la rama base: si el mismo cambio añade la violación y su entrada en el baseline, sale verde. Añadir deuda es un acto visible en el diff del baseline (como el de check-gates o check-consumer), y quien revisa el cambio es quien lo frena.

Estado de las páginas generadas

Cada página lleva el frontmatter de contrato de dec-0124 §5 con generated: { source, generator, inputHash }. Su status sale del model: implementado si algún nodo citado está in_progress o done, si no especificado. Una página generada nunca es verificado por sí misma.

Añadir un generador

  1. Define la fuente canónica y por qué es la única.
  2. Escribe una función pura sobre esa fuente que devuelva los ficheros, los directorios que posee y sus violaciones. El cargador de disco va aparte para poder probarla sin infraestructura.
  3. Emite el frontmatter de contrato con generated: { source, generator, inputHash }. Su estado se hereda del nodo dueño de la fuente y nunca pasa de implementado por sí mismo.
  4. Regístralo en el orquestador de docs:gen y docs:check.
  5. Escribe los mutantes: cambiar la fuente sin regenerar debe dar rojo, y un fichero de más también.
  6. Si la referencia es de una interfaz, la página escrita a mano que la explica enlaza la referencia en lugar de repetirla.