@imolko/ultra-agent 0.1.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/README.md +93 -0
  2. package/bin/cli.js +2 -0
  3. package/dist-node/bin.d.ts +2 -0
  4. package/dist-node/bin.d.ts.map +1 -0
  5. package/dist-node/bin.js +5 -0
  6. package/dist-node/bin.js.map +1 -0
  7. package/dist-node/cli.d.ts +41 -0
  8. package/dist-node/cli.d.ts.map +1 -0
  9. package/dist-node/cli.js +170 -0
  10. package/dist-node/cli.js.map +1 -0
  11. package/dist-node/index.d.ts +14 -0
  12. package/dist-node/index.d.ts.map +1 -0
  13. package/dist-node/index.js +40 -0
  14. package/dist-node/index.js.map +1 -0
  15. package/dist-node/init-lint.d.ts +43 -0
  16. package/dist-node/init-lint.d.ts.map +1 -0
  17. package/dist-node/init-lint.js +84 -0
  18. package/dist-node/init-lint.js.map +1 -0
  19. package/dist-node/manifest.d.ts +33 -0
  20. package/dist-node/manifest.d.ts.map +1 -0
  21. package/dist-node/manifest.js +59 -0
  22. package/dist-node/manifest.js.map +1 -0
  23. package/dist-node/materializer.d.ts +52 -0
  24. package/dist-node/materializer.d.ts.map +1 -0
  25. package/dist-node/materializer.js +194 -0
  26. package/dist-node/materializer.js.map +1 -0
  27. package/dist-node/package-root.d.ts +5 -0
  28. package/dist-node/package-root.d.ts.map +1 -0
  29. package/dist-node/package-root.js +17 -0
  30. package/dist-node/package-root.js.map +1 -0
  31. package/dist-node/payload.d.ts +22 -0
  32. package/dist-node/payload.d.ts.map +1 -0
  33. package/dist-node/payload.js +49 -0
  34. package/dist-node/payload.js.map +1 -0
  35. package/dist-node/pointer-hub.d.ts +22 -0
  36. package/dist-node/pointer-hub.d.ts.map +1 -0
  37. package/dist-node/pointer-hub.js +50 -0
  38. package/dist-node/pointer-hub.js.map +1 -0
  39. package/dist-node/publication.cli.d.ts +9 -0
  40. package/dist-node/publication.cli.d.ts.map +1 -0
  41. package/dist-node/publication.cli.js +17 -0
  42. package/dist-node/publication.cli.js.map +1 -0
  43. package/dist-node/publication.d.ts +23 -0
  44. package/dist-node/publication.d.ts.map +1 -0
  45. package/dist-node/publication.js +47 -0
  46. package/dist-node/publication.js.map +1 -0
  47. package/dist-node/reconcile.cli.d.ts +11 -0
  48. package/dist-node/reconcile.cli.d.ts.map +1 -0
  49. package/dist-node/reconcile.cli.js +39 -0
  50. package/dist-node/reconcile.cli.js.map +1 -0
  51. package/dist-node/reconcile.d.ts +30 -0
  52. package/dist-node/reconcile.d.ts.map +1 -0
  53. package/dist-node/reconcile.js +51 -0
  54. package/dist-node/reconcile.js.map +1 -0
  55. package/dist-node/ship/prompts/fix.md +22 -0
  56. package/dist-node/ship/prompts/implement.md +34 -0
  57. package/dist-node/ship/prompts/review.md +28 -0
  58. package/dist-node/ship/ship.agent.d.ts +22 -0
  59. package/dist-node/ship/ship.agent.d.ts.map +1 -0
  60. package/dist-node/ship/ship.agent.js +142 -0
  61. package/dist-node/ship/ship.agent.js.map +1 -0
  62. package/dist-node/ship/ship.args.d.ts +15 -0
  63. package/dist-node/ship/ship.args.d.ts.map +1 -0
  64. package/dist-node/ship/ship.args.js +93 -0
  65. package/dist-node/ship/ship.args.js.map +1 -0
  66. package/dist-node/ship/ship.context.d.ts +12 -0
  67. package/dist-node/ship/ship.context.d.ts.map +1 -0
  68. package/dist-node/ship/ship.context.js +3 -0
  69. package/dist-node/ship/ship.context.js.map +1 -0
  70. package/dist-node/ship/ship.d.ts +4 -0
  71. package/dist-node/ship/ship.d.ts.map +1 -0
  72. package/dist-node/ship/ship.js +221 -0
  73. package/dist-node/ship/ship.js.map +1 -0
  74. package/dist-node/ship/ship.mr.d.ts +9 -0
  75. package/dist-node/ship/ship.mr.d.ts.map +1 -0
  76. package/dist-node/ship/ship.mr.js +171 -0
  77. package/dist-node/ship/ship.mr.js.map +1 -0
  78. package/dist-node/ship/ship.output.d.ts +9 -0
  79. package/dist-node/ship/ship.output.d.ts.map +1 -0
  80. package/dist-node/ship/ship.output.js +21 -0
  81. package/dist-node/ship/ship.output.js.map +1 -0
  82. package/dist-node/ship/ship.tracker.d.ts +37 -0
  83. package/dist-node/ship/ship.tracker.d.ts.map +1 -0
  84. package/dist-node/ship/ship.tracker.js +220 -0
  85. package/dist-node/ship/ship.tracker.js.map +1 -0
  86. package/dist-node/version.cli.d.ts +9 -0
  87. package/dist-node/version.cli.d.ts.map +1 -0
  88. package/dist-node/version.cli.js +30 -0
  89. package/dist-node/version.cli.js.map +1 -0
  90. package/dist-node/version.d.ts +24 -0
  91. package/dist-node/version.d.ts.map +1 -0
  92. package/dist-node/version.js +76 -0
  93. package/dist-node/version.js.map +1 -0
  94. package/docs/adr/ADR-0000-proceso-y-formato-de-adrs.md +115 -0
  95. package/docs/adr/foundational/A-001-clean-architecture-4-capas.md +88 -0
  96. package/docs/adr/foundational/A-002-cqrs-semantico.md +58 -0
  97. package/docs/adr/foundational/A-003-tdd.md +97 -0
  98. package/docs/adr/foundational/A-005-either-return-custom-methods.md +80 -0
  99. package/docs/adr/foundational/A-006-feature-completion-criteria.md +47 -0
  100. package/docs/adr/foundational/A-007-data-contract-fail-fast.md +109 -0
  101. package/docs/adr/foundational/A-008-bounded-context-decomposition.md +130 -0
  102. package/docs/adr/foundational/A-009-use-case-taxonomy.md +99 -0
  103. package/docs/adr/foundational/A-010-propiedad-emision-eventos-dominio.md +95 -0
  104. package/docs/adr/foundational/A-011-characterization-first-refactoring.md +90 -0
  105. package/docs/adr/foundational/A-012-helm-mecanismo-primario-despliegue.md +67 -0
  106. package/docs/adr/foundational/A-013-contrato-settings-unica-fuente-configuracion.md +74 -0
  107. package/docs/adr/foundational/A-014-generacion-artefactos-despliegue-desde-contrato-settings.md +75 -0
  108. package/docs/adr/foundational/A-015-cloudevents-estandar-eventos.md +85 -0
  109. package/docs/adr/foundational/A-016-cicd-gitlab-pipeline-raiz-pipelines-modulares.md +92 -0
  110. package/docs/adr/foundational/A-017-taxonomia-repositorios-librerias-sistemas.md +93 -0
  111. package/docs/adr/foundational/A-018-practicas-build-imagenes-docker.md +85 -0
  112. package/docs/adr/foundational/A-019-convenciones-autoria-helm-charts.md +97 -0
  113. package/docs/adr/foundational/A-020-entorno-desarrollo-estandarizado-devcontainer.md +58 -0
  114. package/docs/adr/foundational/A-022-formato-reporte-tests-ctrf.md +55 -0
  115. package/docs/adr/foundational/A-023-composicion-bloques-prompt-agentes.md +65 -0
  116. package/docs/adr/foundational/A-024-features-slices-verticales-portables.md +41 -0
  117. package/docs/adr/foundational/A-025-anatomia-mecanica-feature-puertos-adaptadores-drivers.md +104 -0
  118. package/docs/adr/foundational/A-026-infraestructura-compartida.md +44 -0
  119. package/docs/adr/foundational/A-027-patron-wiring-entry-point.md +67 -0
  120. package/docs/adr/foundational/A-029-verbos-intencion-metodos-lado-negocio.md +54 -0
  121. package/docs/adr/foundational/A-030-global-development-tooling.md +52 -0
  122. package/docs/adr/foundational/A-031-ubiquitous-language.md +64 -0
  123. package/docs/adr/foundational/A-032-domain-artifact-documentation-format.md +128 -0
  124. package/docs/adr/foundational/A-033-estandar-mensajes-commit.md +127 -0
  125. package/docs/adr/foundational/A-034-canonical-skill-set.md +64 -0
  126. package/docs/adr/foundational/A-035-captura-ideas-reanclaje-candies.md +51 -0
  127. package/docs/adr/foundational/A-036-adopcion-wayfinder-ortogonalidad-labels.md +37 -0
  128. package/docs/adr/foundational/A-037-formalizacion-taxonomia-labels.md +98 -0
  129. package/docs/adr/foundational/A-038-issues-referencian-skills-nombre.md +54 -0
  130. package/docs/adr/foundational/A-039-pipeline-ship-issues-hijos-spec.md +64 -0
  131. package/docs/adr/foundational/A-040-punteros-configuracion-repo-skills.md +44 -0
  132. package/docs/adr/foundational/A-041-estrategia-de-ramas.md +45 -0
  133. package/docs/adr/foundational/A-042-formato-documentos-issue-tracker.md +78 -0
  134. package/docs/adr/foundational/A-043-vocabulario-etiquetas-adrs.md +42 -0
  135. package/docs/adr/foundational/A-044-composicion-funcional-procesos-operaciones.md +44 -0
  136. package/docs/adr/foundational/A-045-guardrails-aplicables-no-directrices.md +48 -0
  137. package/docs/adr/foundational/A-046-metricas-de-forma-y-legibilidad-del-codigo.md +50 -0
  138. package/docs/adr/foundational/A-047-dependencias-explicitas-sin-globales-ambientales.md +40 -0
  139. package/docs/adr/foundational/A-048-cobertura-minima-codigo.md +39 -0
  140. package/docs/adr/foundational/A-049-materializacion-bucle-guardrails-extension-nativa-pi.md +49 -0
  141. package/docs/adr/foundational/A-050-el-caso-de-uso-pide-al-dominio-no-lee-su-representacion.md +50 -0
  142. package/docs/adr/foundational/A-051-orden-imports-por-distancia.md +47 -0
  143. package/docs/adr/foundational/A-052-distribucion-metodologia-paquete-versionado.md +42 -0
  144. package/docs/adr/foundational/A-053-errores-compartidos-entre-features-dos-hogares-por-capa.md +43 -0
  145. package/docs/adr/foundational/A-054-contrato-costura-driver-port-raw-data-canal-error.md +65 -0
  146. package/docs/adr/foundational/index.md +98 -0
  147. package/docs/adr/index.md +19 -0
  148. package/docs/adr/python/P-001-dependencias-por-feature-fastapi-depends.md +63 -0
  149. package/docs/adr/python/P-002-inyeccion-token-por-request-sin-persistirlo.md +35 -0
  150. package/docs/adr/python/P-003-sqlalchemy-async-connection-pooling.md +59 -0
  151. package/docs/adr/python/P-004-serializer-json-custom-modelos-pydantic.md +47 -0
  152. package/docs/adr/python/P-005-patrones-pitfalls-nicegui.md +148 -0
  153. package/docs/adr/python/P-006-convenciones-pytest.md +96 -0
  154. package/docs/adr/python/P-007-ci-python-alineacion-poetry-version-unica.md +55 -0
  155. package/docs/adr/python/P-008-empaquetado-infraestructura-compartida-por-tecnologia.md +38 -0
  156. package/docs/adr/python/index.md +14 -0
  157. package/docs/adr/typescript/T-001-ddd-artifact-validation-zod-primitives.md +136 -0
  158. package/docs/adr/typescript/T-002-test-documentation-jsdoc.md +133 -0
  159. package/docs/adr/typescript/T-003-readme-formatting-domain-artifacts.md +51 -0
  160. package/docs/adr/typescript/T-005-input-optionality-use-case-not-entity.md +130 -0
  161. package/docs/adr/typescript/T-006-small-enums-inlined-zod-regex.md +77 -0
  162. package/docs/adr/typescript/T-007-controller-input-zod-schema.md +79 -0
  163. package/docs/adr/typescript/T-008-expressive-use-case-pipe.md +207 -0
  164. package/docs/adr/typescript/T-009-mdx-brace-escaping.md +80 -0
  165. package/docs/adr/typescript/T-010-stack-tooling-typescript-dominio.md +53 -0
  166. package/docs/adr/typescript/T-011-ultra-reporter-herramienta-canonica-documentacion.md +52 -0
  167. package/docs/adr/typescript/T-012-npm-workspaces-estructura-monorepo-clean-architecture.md +54 -0
  168. package/docs/adr/typescript/T-013-definicion-eventos-versionados-ultra-ddd.md +45 -0
  169. package/docs/adr/typescript/T-014-publicacion-distribucion-librerias-npm-semver.md +53 -0
  170. package/docs/adr/typescript/T-015-method-placement-definition.md +35 -0
  171. package/docs/adr/typescript/T-016-contrato-settings-zod.md +34 -0
  172. package/docs/adr/typescript/T-017-characterization-tests-golden-files.md +39 -0
  173. package/docs/adr/typescript/T-018-procedencia-campos-frontmatter.md +55 -0
  174. package/docs/adr/typescript/T-019-ultra-generator-herramienta-canonica-generacion.md +35 -0
  175. package/docs/adr/typescript/T-020-ultra-settings-herramienta-canonica-exportacion-despliegue.md +36 -0
  176. package/docs/adr/typescript/T-021-pipe-flujo-datos-operadores-semantica-contexto-inmutable.md +105 -0
  177. package/docs/adr/typescript/T-022-construction-is-execution-single-boundary.md +112 -0
  178. package/docs/adr/typescript/T-023-registry-cli-declara-dependencias-reales.md +84 -0
  179. package/docs/adr/typescript/T-024-paquete-ultra-agent-manifiesto-pi-peers-cli.md +39 -0
  180. package/docs/adr/typescript/T-025-publicacion-npm-trusted-publishing-procedencia.md +59 -0
  181. package/docs/adr/typescript/index.md +30 -0
  182. package/eslint/index.mjs +721 -0
  183. package/package.json +94 -0
  184. package/pi/extensions/handoff/README.md +92 -0
  185. package/pi/extensions/handoff/handoff-prompt.txt +9 -0
  186. package/pi/extensions/handoff/index.ts +296 -0
  187. package/pi/extensions/handoff/package.json +12 -0
  188. package/pi/extensions/qa-guardrail/README.md +108 -0
  189. package/pi/extensions/qa-guardrail/adapters/cli.ts +100 -0
  190. package/pi/extensions/qa-guardrail/adapters/eslint.ts +90 -0
  191. package/pi/extensions/qa-guardrail/adapters/registry.ts +45 -0
  192. package/pi/extensions/qa-guardrail/baseline.ts +74 -0
  193. package/pi/extensions/qa-guardrail/cerco.ts +134 -0
  194. package/pi/extensions/qa-guardrail/check.ts +185 -0
  195. package/pi/extensions/qa-guardrail/config.ts +189 -0
  196. package/pi/extensions/qa-guardrail/delta.ts +52 -0
  197. package/pi/extensions/qa-guardrail/feedback.ts +26 -0
  198. package/pi/extensions/qa-guardrail/index.ts +311 -0
  199. package/pi/extensions/qa-guardrail/nstop.ts +82 -0
  200. package/pi/extensions/qa-guardrail/package.json +12 -0
  201. package/pi/extensions/qa-guardrail/selection.ts +98 -0
  202. package/pi/extensions/qa-guardrail/types.ts +64 -0
  203. package/scripts/copy-assets.sh +10 -0
  204. package/scripts/reconcile-tags.sh +94 -0
  205. package/scripts/release-beta.sh +64 -0
  206. package/scripts/release-build.sh +53 -0
  207. package/scripts/release.sh +51 -0
  208. package/scripts/verify-adr-consistency.py +217 -0
  209. package/skills/code-review/SKILL.md +66 -0
  210. package/skills/codebase-design/SKILL.md +46 -0
  211. package/skills/configure-repo/SKILL.md +33 -0
  212. package/skills/diagnose-bug/SKILL.md +63 -0
  213. package/skills/domain-modeling/SKILL.md +56 -0
  214. package/skills/grill-me/SKILL.md +34 -0
  215. package/skills/i-have-an-idea/SKILL.md +166 -0
  216. package/skills/implement/SKILL.md +22 -0
  217. package/skills/research/SKILL.md +17 -0
  218. package/skills/ship/SKILL.md +26 -0
  219. package/skills/spike/SKILL.md +27 -0
  220. package/skills/tdd/SKILL.md +68 -0
  221. package/skills/tdd/references/deep-modules.md +53 -0
  222. package/skills/tdd/references/interface-design.md +74 -0
  223. package/skills/tdd/references/mocking.md +80 -0
  224. package/skills/tdd/references/refactoring.md +49 -0
  225. package/skills/tdd/references/tests.md +184 -0
  226. package/skills/to-specs/SKILL.md +141 -0
  227. package/skills/to-tasks/SKILL.md +143 -0
  228. package/skills/triage/SKILL.md +73 -0
  229. package/skills/ultra-generator/SKILL.md +263 -0
  230. package/skills/ultra-generator/references/aggregate-root.md +149 -0
  231. package/skills/ultra-generator/references/domain-event.md +174 -0
  232. package/skills/ultra-generator/references/entity.md +133 -0
  233. package/skills/ultra-generator/references/service.md +92 -0
  234. package/skills/ultra-generator/references/value-object.md +135 -0
  235. package/skills/wayfinder/SKILL.md +201 -0
  236. package/skills/workflow/SKILL.md +31 -0
  237. package/skills/writing-for-agents/SKILL-MECHANICS.md +32 -0
  238. package/skills/writing-for-agents/SKILL.md +86 -0
  239. package/skills/writing-for-agents/references/adrs.md +19 -0
@@ -0,0 +1,85 @@
1
+ # A-018: Prácticas de build de imágenes Docker
2
+
3
+ **Estado**: Aceptado
4
+
5
+ **Fecha**: 2026-08-13
6
+
7
+ **originally**: cortex-dockerimage-for-apps
8
+
9
+ **Enriquecido desde**: Cortex doc "dockerimage-for-apps" — buenas prácticas para construir imágenes Docker
10
+
11
+ **tags**: despliegue
12
+
13
+ ## Contexto
14
+
15
+ Las aplicaciones servidor de Imolko se despliegan en Kubernetes mediante Helm ([A-012](./A-012-helm-mecanismo-primario-despliegue.md)) con una imagen construida y publicada por el pipeline de CI/CD ([A-016](./A-016-cicd-gitlab-pipeline-raiz-pipelines-modulares.md)). La velocidad de construcción y el tamaño de la imagen tienen un costo directo: builds lentos demoran el feedback del pipeline y las imágenes sobredimensionadas consumen recursos en el registro y en cada clúster que las descarga.
16
+
17
+ Sin convenciones explícitas, cada app construye su imagen a su manera:
18
+
19
+ - Imágenes base con el tag `latest`, que hacen el build no reproducible.
20
+ - Instalación de dependencias repetida en cada paso del pipeline.
21
+ - Imágenes finales que incluyen compiladores y herramientas que producción no necesita.
22
+ - Contenedores que corren como `root`.
23
+ - Dockerfiles crípticos, sin documentar, difíciles de mantener.
24
+
25
+ ## Decisión
26
+
27
+ Adoptar un conjunto canónico de prácticas de build para **toda imagen de contenedor** de Imolko. Las prácticas aplican por igual a proyectos Python y Node, y no dependen de un lenguaje concreto.
28
+
29
+ ### 1. Construcción multistage
30
+
31
+ Toda imagen se construye con un Dockerfile **multistage**: las etapas intermedias instalan herramientas y compilan, y la etapa final copia únicamente los artefactos resultantes. La última etapa declarada es la que se despliega; las anteriores son descartables y no viajan en la imagen final.
32
+
33
+ Esto separa las herramientas de build de las dependencias de runtime: el compilador vive en una etapa intermedia y el resultado (por ejemplo, un bundle o un paquete instalado) se copia a una imagen final mínima.
34
+
35
+ ### 2. Imagen base oficial y versionada (pineada)
36
+
37
+ La imagen base se elige entre las imágenes **oficiales y verificadas**, y se fija a una **versión específica** (tag concreto). No se usa `latest` ni tags móviles: el build debe producir el mismo resultado en cada ejecución. Una imagen base cargada con cosas innecesarias se descarta en favor de una sencilla y específica.
38
+
39
+ ### 3. Imagen base custom publicada por el pipeline
40
+
41
+ Cuando una imagen necesita herramientas que se instalan en cada build (por ejemplo, un entorno de tooling para desarrollo o CI), se construye una **imagen base custom** y se publica en el registro. Esa imagen se mantiene y publica en el registro, no se reinstala ad-hoc en cada Dockerfile. Cada instrucción de un Dockerfile se ejecuta en cada pipeline y consume recursos: lo que se repite entre apps se consolida en una imagen base publicada.
42
+
43
+ ### 4. Instrucciones estables primero, por caché de layers
44
+
45
+ Cada instrucción de un Dockerfile produce un layer; el build aprovecha la caché de layers y evita reconstruir lo que no cambió. Para maximizarla, las instrucciones que cambian poco van **primero** y las que cambian a menudo, **al final**: primero la imagen base y las dependencias de terceros, después el código de la aplicación.
46
+
47
+ En proyectos con gestor de dependencias y lockfile, la instalación de dependencias se hace en dos pasos: primero se instalan solo las dependencias de producción usando el lockfile (aprovechando la caché cuando no cambiaron) y después se copia el código y se instala la aplicación. Un solo paso anula la caché ante el mínimo cambio de código.
48
+
49
+ ### 5. Minimizar layers y limpiar en el mismo RUN
50
+
51
+ Se combinan instrucciones relacionadas en un solo `RUN` y se limpian cachés y archivos temporales en la misma instrucción. El objetivo es menos layers y una imagen final más chica.
52
+
53
+ ### 6. `.dockerignore` para no copiar archivos innecesarios
54
+
55
+ Cada contexto de build declara un `.dockerignore` que excluye lo que no aporta a la imagen final: directorios de control de versiones, archivos de entorno y secretos, dependencias instaladas localmente y artefactos de build. El patrón concreto depende del ecosistema, pero la regla es: **el contexto de build contiene solo lo que la imagen necesita**.
56
+
57
+ ### 7. Solo dependencias de producción
58
+
59
+ La imagen final instala únicamente lo que producción usa. Compiladores, librerías de desarrollo y herramientas de test no entran en la imagen final: si se necesitan para construir, viven en una etapa multistage (ver punto 1).
60
+
61
+ Una imagen publicada se verifica en el registro: si pesa de más, se revisa antes de darla por buena, porque ese exceso se paga en cada entorno que la descarga.
62
+
63
+ ### 8. Usuario no-root
64
+
65
+ Ningún contenedor corre como `root` en producción. El Dockerfile crea un usuario limitado y lo declara con la directiva correspondiente; la app se ejecuta con ese usuario.
66
+
67
+ ### 9. Nombres significativos y Dockerfile documentado
68
+
69
+ Cuando un contexto tiene varios Dockerfiles, cada uno lleva un nombre que describe su uso (por ejemplo, `Dockerfile.app`, `Dockerfile.tooling`). Cada Dockerfile se documenta con comentarios que explican **por qué** se hizo cada paso, no solo qué hace: las decisiones de diseño incrustadas en el build deben poder leerse tiempo después.
70
+
71
+ ## Consecuencias
72
+
73
+ ### Positivas
74
+
75
+ - **Builds más rápidos y baratos**: la caché de layers y el orden de las instrucciones reducen el trabajo repetido.
76
+ - **Imágenes más chicas**: multistage, limpieza y solo dependencias de producción reducen el tamaño final.
77
+ - **Reproducibilidad**: versiones pineadas y sin `latest` hacen el build determinista.
78
+ - **Seguridad**: el usuario no-root y la exclusión de secretos vía `.dockerignore` reducen la superficie de riesgo.
79
+ - **Mantenibilidad**: nombres significativos y Dockerfiles documentados facilitan el mantenimiento.
80
+
81
+ ### Negativas
82
+
83
+ - **Disciplina de mantenimiento**: elegir y pinear versiones, mantener `.dockerignore` y la imagen base custom requiere atención continua.
84
+ - **Curva de aprendizaje**: multistage y el orden por caché exigen entender cómo funcionan los layers de Docker.
85
+ - **Imagen base custom como dependencia**: publicarla por el pipeline introduce un artefacto intermedio que hay que versionar y actualizar.
@@ -0,0 +1,97 @@
1
+ # A-019: Convenciones de autoría de Helm charts
2
+
3
+ **Estado**: Aceptado
4
+
5
+ **Fecha**: 2026-08-13
6
+
7
+ **originally**: cortex-helm-chart
8
+
9
+ **Enriquecido desde**: Cortex doc "helm-chart" — convenciones de autoría de Helm charts
10
+
11
+ **tags**: despliegue
12
+
13
+ ## Contexto
14
+
15
+ [A-012](./A-012-helm-mecanismo-primario-despliegue.md) fija Helm como mecanismo primario de despliegue y define la forma externa del chart: un chart por app, valores por entorno e imagen preconstruida. Pero no define cómo se **escribe** un chart de calidad. Sin convenciones internas, cada chart:
16
+
17
+ - Usa labels y nombres de recursos distintos, dificultando observabilidad y agrupación.
18
+ - Omite probes de readiness, de modo que un pod recibe tráfico antes de estar listo.
19
+ - No valida los requisitos previos del clúster, y los fallos aparecen en runtime con mensajes opacos.
20
+ - No documenta su uso, sus valores ni sus pasos post-instalación.
21
+ - Empaqueta archivos innecesarios o sensibles por falta de `.helmignore`.
22
+
23
+ Estas prácticas existen de forma implícita; falta fijarlas como convención común de autoría.
24
+
25
+ ## Decisión
26
+
27
+ Adoptar convenciones canónicas para la **autoría** de Helm charts. Este ADR no vuelve a decidir el mecanismo de despliegue (eso es [A-012](./A-012-helm-mecanismo-primario-despliegue.md)): fija cómo se escribe el chart que ese mecanismo manda crear.
28
+
29
+ ### 1. Topología completa del chart
30
+
31
+ Cada chart describe la topología completa de la app: Deployment, Service, Ingress, ConfigMap y referencias a Secret (nunca los valores sensibles). La forma externa la fija [A-012 §1](./A-012-helm-mecanismo-primario-despliegue.md).
32
+
33
+ ### 2. Templates comunes de labels y nombre
34
+
35
+ El chart define templates reutilizables para:
36
+
37
+ - **Labels comunes** (`common.labels` o similar): los labels estándar de Kubernetes (`app.kubernetes.io/name`, `component`, `part-of`, `managed-by`) que todos los recursos del chart incluyen.
38
+ - **Nombre base** (`chart.name` o similar): genera el nombre base de los recursos aplicando reglas de truncado y limpieza, para respetar los límites de longitud de nombres de Kubernetes.
39
+
40
+ Los recursos no escriben labels ni nombres a mano: usan los templates.
41
+
42
+ ### 3. `values.schema.json` que valida los valores
43
+
44
+ El chart incluye un `values.schema.json` que valida los valores antes de instalar. Ese esquema se genera desde el contrato de settings ([A-014 §3](./A-014-generacion-artefactos-despliegue-desde-contrato-settings.md)); los valores que no provienen del contrato son la excepción acotada que [A-014 §4](./A-014-generacion-artefactos-despliegue-desde-contrato-settings.md) admite.
45
+
46
+ ### 4. Validación de requisitos del clúster con hooks
47
+
48
+ El chart declara explícitamente los requisitos del clúster que la app necesita para funcionar (pods de infraestructura, secretos y config maps preexistentes). Un Job con anotación de hook se ejecuta en `pre-install` y `pre-upgrade` para validar que esos requisitos existen, con política de limpieza del hook definida. La lista de requisitos se expresa como datos del chart, sin nombrar secretos ni credenciales concretas: son referencias a recursos que el entorno debe proveer.
49
+
50
+ ### 5. `readinessProbe` en todo contenedor
51
+
52
+ Todo contenedor de un Deployment declara un `readinessProbe` que refleja el estado "listo para servir" (un endpoint de salud). Sin probe, el pod recibe tráfico mientras aún se inicializa. El probe además hace útil la espera `--wait` del pipeline ([A-016](./A-016-cicd-gitlab-pipeline-raiz-pipelines-modulares.md)).
53
+
54
+ ### 6. Tests de despliegue como hooks de Helm
55
+
56
+ El chart incluye Jobs de test anotados con `helm.sh/hook: test`, que verifican el despliegue (por ejemplo, que la app responde en su URL interna) después de instalar o actualizar. Llevan política de limpieza para que los pods de test no queden colgados.
57
+
58
+ ### 7. Documentación del chart
59
+
60
+ Cada chart incluye:
61
+
62
+ - **`NOTES.txt`**: instrucciones post-instalación que se muestran al terminar el despliegue (cómo acceder a la app, comandos útiles), con templates para información dinámica.
63
+ - **`README.md`**: propósito del chart, requisitos previos, cómo instalarlo y la descripción de cada parámetro de `values`.
64
+
65
+ ### 8. Higiene del paquete con `.helmignore`
66
+
67
+ El chart declara un `.helmignore` que excluye del paquete final archivos y directorios que no pertenecen al chart: control de versiones, archivos de entorno y secretos, temporales y de respaldo. El paquete resultante es liviano y no arrastra datos sensibles.
68
+
69
+ ### 9. Exposición HTTP y DNS
70
+
71
+ Las apps accesibles externamente exponen su URL mediante un recurso **Ingress** (reglas de host/path y TLS configurables por `values`). Los registros DNS de esos hosts no se gestionan a mano: se sincronizan automáticamente con **external-dns** a partir de los Ingress.
72
+
73
+ ### 10. Mensajes de terminación claros en Jobs
74
+
75
+ Los Jobs de chequeo y testing escriben un mensaje de terminación específico (usando el path de termination-log del contenedor) para que el fallo sea legible en la descripción del pod. Un fallo de un hook de pre-upgrade no se queda en un mensaje genérico de timeout: el job declara la causa real.
76
+
77
+ ### 11. Templates condicionales para recursos opcionales
78
+
79
+ Los recursos que solo se despliegan bajo una condición (por ejemplo, un servicio auxiliar activado por un flag de `values`) se envuelven en condicionales del template. El recurso se renderiza solo cuando su condición se cumple.
80
+
81
+ ## Consecuencias
82
+
83
+ ### Positivas
84
+
85
+ - **Charts homogéneos**: labels, nombres, probes, hooks y documentación siguen el mismo patrón en todas las apps.
86
+ - **Fallos tempranos y legibles**: la validación de requisitos y los mensajes de terminación acotan los fallos a su causa real.
87
+ - **Validación de valores**: el `values.schema.json` generado rechaza configuraciones inválidas antes de instalar.
88
+ - **Despliegues saludables**: el `readinessProbe` evita que el tráfico llegue a pods no listos.
89
+ - **Paquetes livianos y sin secretos**: `.helmignore` mantiene el chart limpio.
90
+ - **URLs y DNS consistentes**: Ingress + external-dns automatizan la exposición.
91
+
92
+ ### Negativas
93
+
94
+ - **Boilerplate de chart**: cada chart lleva templates, probes, hooks y documentación; una app nueva debe copiar y adaptar el patrón.
95
+ - **Dependencia del contrato de settings**: el `values.schema.json` se genera desde el contrato y su vigencia depende del paso de exportación ([A-014](./A-014-generacion-artefactos-despliegue-desde-contrato-settings.md)).
96
+ - **Curva de aprendizaje**: dominar templates, hooks y el ciclo de vida de Helm no es trivial.
97
+ - **Infraestructura previa**: la validación de requisitos y external-dns asumen que el clúster ya provee esos componentes.
@@ -0,0 +1,58 @@
1
+ # A-020: Entorno de desarrollo estandarizado con DevContainer
2
+
3
+ **Estado**: Aceptado
4
+
5
+ **Fecha**: 2026-08-13
6
+
7
+ **originally**: cortex-ide-vscode
8
+
9
+ **Enriquecido desde**: Cortex doc "ide-vscode" — entorno de desarrollo con DevContainer
10
+
11
+ **tags**: tooling
12
+
13
+ ## Contexto
14
+
15
+ El entorno de desarrollo local determina la primera experiencia de un miembro nuevo y la reproducibilidad del trabajo diario. Sin un entorno estandarizado:
16
+
17
+ - "En mi máquina funciona": dependencias, versiones de herramientas y configuraciones de IDE difieren entre equipos.
18
+ - El onboarding exige instalar y configurar herramientas a mano, una por una.
19
+ - Las herramientas de orquestación y despliegue se instalan con versiones distintas o se omiten.
20
+ - El árbol de un monorepo grande abruma: no queda claro dónde están las apps, el dominio y las features.
21
+
22
+ ## Decisión
23
+
24
+ Estandarizar el entorno de desarrollo con un **DevContainer** canónico en cada repositorio, una **imagen base con tooling** y un **workspace DDD**.
25
+
26
+ ### 1. DevContainer en la raíz del repositorio
27
+
28
+ Cada repositorio define un `.devcontainer` en su raíz que describe el entorno de desarrollo: imagen base, extensiones recomendadas del IDE, configuraciones y scripts de post-creación. Un desarrollador abre el repositorio en el DevContainer y obtiene un entorno idéntico al del resto, sin instalar nada a mano.
29
+
30
+ ### 2. Imagen base con tooling preinstalado
31
+
32
+ El DevContainer referencia una **imagen base preconfigurada** que ya trae las herramientas comunes de desarrollo y despliegue (orquestación, CLI de clúster, gestores de paquetes). La instalación de esas herramientas no se repite en cada arranque del contenedor.
33
+
34
+ La imagen base del DevContainer es una imagen de contenedor más: se construye siguiendo las prácticas de [A-018](./A-018-practicas-build-imagenes-docker.md) y se publica por el pipeline ([A-016](./A-016-cicd-gitlab-pipeline-raiz-pipelines-modulares.md)). A-018 define **cómo** se construye una imagen; este ADR define **qué** imagen usa el entorno de desarrollo. La imagen se versiona y se actualiza periódicamente para incorporar herramientas nuevas o versiones estables.
35
+
36
+ ### 3. Workspace DDD
37
+
38
+ Cada repositorio define un archivo de workspace (`.code-workspace`) que mapea las carpetas principales en secciones lógicas: aplicaciones (`apps/`), dominio (`domain/`) y features (`features/`). El workspace refleja la estructura de Clean Architecture ([A-001](./A-001-clean-architecture-4-capas.md)): quien abre el repositorio navega por capas, no por un árbol plano de archivos.
39
+
40
+ ### 4. Configuración por entorno fuera del workspace
41
+
42
+ Los valores concretos de configuración por entorno no viven en el workspace ni en el DevContainer: se rigen por el contrato de settings ([A-013](./A-013-contrato-settings-unica-fuente-configuracion.md)) y sus artefactos derivados ([A-014](./A-014-generacion-artefactos-despliegue-desde-contrato-settings.md)). El entorno de desarrollo estandariza las herramientas y la vista del código, no la configuración de negocio.
43
+
44
+ ## Consecuencias
45
+
46
+ ### Positivas
47
+
48
+ - **Entorno idéntico**: se elimina el "en mi máquina funciona" y las diferencias de versiones entre desarrolladores.
49
+ - **Onboarding rápido**: un miembro nuevo abre el DevContainer y trabaja, sin instalar herramientas a mano.
50
+ - **Herramientas consistentes**: la imagen base fija las versiones de orquestación y despliegue.
51
+ - **Navegación por capas**: el workspace DDD hace visible la estructura de Clean Architecture.
52
+
53
+ ### Negativas
54
+
55
+ - **Mantenimiento de la imagen base**: hay que versionarla y actualizarla cuando cambian las herramientas.
56
+ - **Dependencia del DevContainer**: quien trabaje fuera del contenedor puede encontrar diferencias de entorno.
57
+ - **Curva de aprendizaje**: configurar y mantener un DevContainer y su imagen base requiere familiaridad con Docker.
58
+ - **Costo de reconstrucción**: cambiar la imagen base obliga a reconstruir el entorno.
@@ -0,0 +1,55 @@
1
+ # A-022: Formato de reporte de tests (CTRF)
2
+
3
+ **Estado**: Aceptado
4
+
5
+ **Fecha**: 2026-08-13
6
+
7
+ **originally**: cortex-testing
8
+
9
+ **Enriquecido desde**: Cortex doc "testing" — sección sobre exportación de resultados de tests en CTRF
10
+
11
+ **tags**: testing, tooling
12
+
13
+ ## Contexto
14
+
15
+ Los tests de un proyecto (unitarios, de integración y end-to-end) se ejecutan con runners distintos y producen reportes en formatos propios de cada herramienta. Sin un formato común:
16
+
17
+ - El pipeline de CI/CD no puede consumir los resultados de forma uniforme para decidir si una etapa pasa o falla.
18
+ - No hay una fuente única y legible por máquina del estado de los tests para paneles, métricas de calidad y documentación generada.
19
+ - Cada runner exige un parser ad hoc, y sumar un lenguaje o un framework nuevo obliga a escribir integración nueva.
20
+
21
+ CTRF (Common Test Report Format) resuelve esto: es un estándar abierto, agnóstico al lenguaje y a la herramienta, que describe la ejecución de tests en un esquema JSON estable.
22
+
23
+ ## Decisión
24
+
25
+ Adoptar **CTRF** como el formato canónico de reporte de tests. Todo runner que ejecute tests en un proyecto de Imolko exporta sus resultados en CTRF; el pipeline de CI/CD y las herramientas que consumen reportes leen CTRF, no los formatos propietarios de cada runner.
26
+
27
+ ### 1. Exportación desde todos los runners
28
+
29
+ Cada runner se configura con su exportador CTRF correspondiente, sea un plugin oficial, un reporter de la comunidad o un adaptador propio. La lista de runners y sus mecanismos de exportación vive en cada proyecto (su configuración de tests), no se duplica en este ADR.
30
+
31
+ ### 2. Reporte accesible para el pipeline
32
+
33
+ El reporte CTRF se produce como artefacto del job de test y queda disponible para el pipeline de CI/CD ([A-016](./A-016-cicd-gitlab-pipeline-raiz-pipelines-modulares.md)). El pipeline consume el reporte para:
34
+
35
+ - Decidir el resultado del job: un reporte con tests fallidos o errores de ejecución implica job fallido.
36
+ - Publicar el reporte como artefacto descargable.
37
+ - Alimentar la documentación generada y los paneles de calidad con el estado real de los tests.
38
+
39
+ ### 3. Relación con la metodología de tests
40
+
41
+ CTRF no define qué testear ni cómo nombrarlo: la metodología la fija [A-003](./A-003-tdd.md) (TDD, pirámide de tests, nomenclatura por capa). CTRF define únicamente el formato de salida. Un test bien diseñado sigue A-003; su reporte sale en CTRF.
42
+
43
+ ## Consecuencias
44
+
45
+ ### Positivas
46
+
47
+ - **Interoperabilidad**: un solo consumidor (pipeline, panel, documentación) lee todos los reportes, sin importar el runner.
48
+ - **Menos integraciones ad hoc**: sumar un runner nuevo solo requiere su exportador CTRF, no un parser nuevo.
49
+ - **Observabilidad uniforme**: el estado de los tests es comparable entre proyectos y lenguajes.
50
+
51
+ ### Negativas
52
+
53
+ - **Dependencia del exportador**: cada runner debe tener (o permitir implementar) un exportador CTRF; si no existe, hay que adaptarlo.
54
+ - **Configuración adicional**: cada proyecto debe configurar el exportador y publicar el artefacto en el pipeline.
55
+ - **Esquema en evolución**: hay que mantenerse al día con las versiones del esquema CTRF para no quedar desalineados con los consumidores.
@@ -0,0 +1,65 @@
1
+ # A-023: Estándar de composición de bloques de prompt para agentes
2
+
3
+ **Estado**: Aceptado
4
+
5
+ **Fecha**: 2026-08-13
6
+
7
+ **originally**: cortex-prompt-composition-guide
8
+
9
+ **Enriquecido desde**: Cortex doc "prompt-composition-guide" — estructura estándar de bloques de prompt para agentes
10
+
11
+ **tags**: agentes, tooling
12
+
13
+ ## Contexto
14
+
15
+ Los agentes que colaboran en los proyectos de Imolko (revisores de artefactos, generadores, orquestadores de casos de uso) se comportan según prompts. Sin un estándar de composición:
16
+
17
+ - Cada agente arma sus instrucciones a su manera, con bloques que mezclan responsabilidades.
18
+ - Las instrucciones procedimentales ("paso 1, paso 2, ...") se degradan en ejecuciones largas o con entradas grandes.
19
+ - La ambigüedad produce salidas inconsistentes y difíciles de validar por máquina.
20
+ - Los bloques de contexto se vuelcan sin prioridad, y el agente extrae lo irrelevante o alucina.
21
+
22
+ ## Decisión
23
+
24
+ Adoptar una **estructura estándar de bloques de prompt** para todo agente de Imolko. Los prompts se componen de bloques con una responsabilidad única, reglas declarativas y un contrato de salida estricto.
25
+
26
+ ### Principios
27
+
28
+ 1. **Separación de responsabilidades**: cada bloque cumple un único propósito (personalidad, tarea, restricciones, datos) y no se mezclan.
29
+ 2. **Declarativo sobre procedimental**: se expresan reglas, restricciones y definiciones, no secuencias de pasos ni pseudo-algoritmos.
30
+ 3. **Diseño guiado por restricciones**: se limitan los grados de libertad y se hacen explícitas las expectativas para minimizar la ambigüedad.
31
+ 4. **Contexto de solo lectura**: el contexto no se modifica ni se reinterpreta; se usa selectivamente.
32
+ 5. **Continuidad sobre creatividad**: en pipelines de varias etapas, la salida previa es fuente de verdad y la nueva salida extiende, no redefine.
33
+
34
+ ### Bloques estándar
35
+
36
+ 1. **Personalidad**: quién es el agente; ancla tono y perspectiva y acota el tipo de razonamiento. Declara qué posee y qué no hace.
37
+ 2. **Capacidades**: en qué es bueno el agente; limita el dominio de expertise y evita la deriva hacia áreas ajenas.
38
+ 3. **Tarea**: qué debe producir en esta ejecución; fija la intención de salida y acota el alcance, sin verbos ambiguos.
39
+ 4. **Definición de especialidad** (crítico): qué representa esta sección, qué no debe hacer y cómo se relaciona con las anteriores; evita solapamientos.
40
+ 5. **Reglas de decisión** (metodología): cómo se toman decisiones, con restricciones declarativas, no pasos.
41
+ 6. **Restricciones de ejecución**: bloque unificado de restricciones de comportamiento, alineación y redacción.
42
+ 7. **Prioridad de contexto** (recomendado): cómo se usa el contexto, con un orden estándar para guiar la extracción relevante.
43
+ 8. **Bloques de contexto**: datos estructurados provistos al agente (marca, audiencia, producto).
44
+ 9. **Trabajo en curso** (estado): salidas previas del pipeline.
45
+ 10. **Reglas de continuidad**: cómo interactuar con el trabajo previo; no reafirmar contenido previo.
46
+ 11. **Contrato**: cumplimiento de la salida; adherencia estricta al esquema, sin texto extra y siempre una estructura válida.
47
+
48
+ ### Relación con la arquitectura
49
+
50
+ Este estándar define la composición interna del prompt. La integración arquitectónica del agente — un puerto de dominio como cualquier otra dependencia — sigue la anatomía de [A-025](./A-025-anatomia-mecanica-feature-puertos-adaptadores-drivers.md). Los bloques declarados aquí se renderizan desde plantillas. El texto de prompt vive en plantillas, nunca en código.
51
+
52
+ ## Consecuencias
53
+
54
+ ### Positivas
55
+
56
+ - **Consistencia entre agentes**: todos comparten la misma arquitectura de bloques.
57
+ - **Determinismo y confiabilidad**: restricciones declarativas y contrato estricto reducen salidas inválidas.
58
+ - **Arquitectura escalable**: la estructura escala a múltiples agentes y especialidades sin degenerar en prompts artesanales.
59
+ - **Mantenibilidad**: cada bloque tiene una responsabilidad única y puede revisarse aislado.
60
+
61
+ ### Negativas
62
+
63
+ - **Disciplina de autoría**: componer un prompt exige respetar la separación de responsabilidades y no caer en instrucciones procedimentales.
64
+ - **Más estructura**: un prompt bien formado es más largo y deliberado que una instrucción libre.
65
+ - **Curva de aprendizaje**: el equipo debe internalizar los principios para aplicarlos de forma consistente.
@@ -0,0 +1,41 @@
1
+ # A-024: Features como slices verticales portables
2
+
3
+ **Estado**: Aceptado
4
+
5
+ **Fecha**: 2026-08-13
6
+
7
+ **originally**: A-001
8
+
9
+ **Enriquecido desde**: Cortex ADR 004 — Clean Architecture feature structure with ports & drivers
10
+
11
+ **tags**: arquitectura
12
+
13
+ ## Contexto
14
+
15
+ Un workspace de Imolko crece con muchos casos de uso y múltiples destinos de despliegue (servidor HTTP, worker, simulador, nodo local). Para que una feature sea reutilizable entre esos destinos, debe implementar un caso de uso y ser independiente de la tecnología y de la app que la despliega. Esta decisión define dónde viven las features y cómo se componen en las apps.
16
+
17
+ ## Decisión
18
+
19
+ Las features viven bajo un directorio `features/` a nivel de workspace, **nunca** dentro de apps individuales. Cada feature es una unidad autocontenida de lógica de negocio que puede desarrollarse, probarse y evolucionar independientemente de cualquier destino de despliegue.
20
+
21
+ Las apps componen features en su capa de wiring importando e instanciando las features que necesitan. Esta es la ventaja arquitectónica principal: las features son **portables entre apps y modos de despliegue** — una feature desarrollada para una app puede reusarse en otra sin modificación.
22
+
23
+ Las features no se acoplan entre sí: una feature nunca importa ningún elemento de otra feature; el acoplamiento ocurre solo en el wiring.
24
+
25
+ El empaquetado por feature es opcional: una feature puede ser un paquete propio o convivir con otras en un paquete compartido. El cierre tecnológico es una propiedad **del paquete**, no del archivo: una feature es tech-free solo si su paquete lo es. Como el `package.json` es compartido, una dependencia tecnológica de cualquier miembro del paquete entra en el closure de todo el que lo importa; por eso una feature tech-free **no puede compartir paquete** con features que dependen de tecnología, y se le da paquete propio cuando su portabilidad o evolución independiente lo exige. El núcleo de una feature (caso de uso, adaptadores y driver ports) no importa tecnologías ([A-025](./A-025-anatomia-mecanica-feature-puertos-adaptadores-drivers.md)); el cierre tecnológico lo gobierna el empaquetado por tecnología ([A-026](./A-026-infraestructura-compartida.md)).
26
+
27
+ La anatomía interna de una feature (puertos de dominio, adaptadores, driver ports) y su mecánica (cadena Puerto→Adaptador→Driver port, ISP) están en [A-025](./A-025-anatomia-mecanica-feature-puertos-adaptadores-drivers.md). La composición en wiring se detalla en [A-027](./A-027-patron-wiring-entry-point.md).
28
+
29
+ ## Consecuencias
30
+
31
+ ### Positivas
32
+
33
+ - **Portabilidad de features**: una feature desarrollada para una app se reusa en otra sin modificación — su paquete no declara dependencias tecnológicas.
34
+ - **Desarrollo paralelo**: múltiples features pueden desarrollarse simultáneamente sin conflictos.
35
+ - **Despliegues independientes**: cada app selecciona y empaqueta solo las features que necesita y el driver de la tecnología que usa.
36
+ - **Onboarding**: un desarrollador que aprende una feature entiende inmediatamente todas las demás.
37
+
38
+ ### Negativas
39
+
40
+ - **Disciplina de composición**: mantener el acoplamiento entre features solo en el wiring requiere revisión constante.
41
+ - **Sin librería compartida de ports**: la reutilización real se consigue componiendo features, no compartiendo su interior.
@@ -0,0 +1,104 @@
1
+ # A-025: Anatomía y mecánica de una feature: puertos, adaptadores y drivers
2
+
3
+ **Estado**: Aceptado
4
+
5
+ **Fecha**: 2026-08-13
6
+
7
+ **originally**: A-001
8
+
9
+ **Enriquecido desde**: Cortex ADR 004 — Clean Architecture feature structure with ports & drivers
10
+
11
+ **tags**: arquitectura
12
+
13
+ ## Contexto
14
+
15
+ Cada feature de caso de uso necesita una estructura interna consistente y una cadena clara que conecte su lógica de negocio con la infraestructura sin acoplarla. Sin un estándar de anatomía y de mecánica, cada feature improvisa su disposición de archivos y sus límites de responsabilidad, lo que degrada la navegación y la intercambiabilidad de implementaciones.
16
+
17
+ El dominio y la tecnología hablan vocabularios distintos: el caso de uso opera con agregados y value objects; la infraestructura, con tablas, filas, mensajes y protocolos. La traducción entre ambos debe ser explícita, tipada y localizada, para que cambiar de tecnología no toque el caso de uso y para que ningún agregado de dominio ni tipo tecnológico (`any`, `unknown`) cruce la costura sin control.
18
+
19
+ La versión anterior de esta decisión alojaba `drivers/<tech>/` y el transporte dentro de la feature. Eso forzaba a la feature a importar tecnología (mongoose, decoradores de framework) y rompía el objetivo de [A-024](./A-024-features-slices-verticales-portables.md): un paquete de feature que no declara dependencias tecnológicas. Esta decisión corrige la anatomía: la feature conserva solo las piezas tech-free; la implementación tecnológica y el transporte viven fuera.
20
+
21
+ ## Decisión
22
+
23
+ ### Anatomía canónica
24
+
25
+ ```
26
+ features/<caso-de-uso>/
27
+ ├── use-case/
28
+ │ ├── use-case.ts # lógica de negocio — retorna el modelo de salida (DTO o stream)
29
+ │ ├── input.dto.ts # contrato de entrada del caso de uso
30
+ │ ├── output.dto.ts # modelo de salida en términos del dominio (VOs, tech-free)
31
+ │ ├── <dependencia>.port.ts # puertos de dominio: repository, query, event-bus…
32
+ │ └── errors/
33
+ ├── adapters/
34
+ │ ├── controller/
35
+ │ │ ├── adapter.ts # valida/traduce el input, invoca el caso de uso y traduce la salida de dominio
36
+ │ │ └── input.dto.ts # esquema de validación del input externo
37
+ │ ├── <dependencia>/
38
+ │ │ ├── adapter.ts # implementa el puerto de dominio; traduce dominio ↔ raw data
39
+ │ │ └── driver.port.ts # la costura: contrato raw-data que shared-infra implementa
40
+ │ └── …
41
+ └── index.ts # API pública (exporta puertos, adaptadores, caso de uso, DTOs y errores)
42
+ ```
43
+
44
+ - **`use-case/`** — la lógica de negocio, su modelo de salida y los puertos de dominio que consume. Los puertos de dominio (`*.port.ts`) viven junto al caso de uso.
45
+ - **`adapters/`** — una carpeta por puerto de dominio, con `adapter.ts` y `driver.port.ts`. `controller/` valida y traduce el input, invoca el caso de uso y traduce la salida de dominio a su forma plana.
46
+ - **`index.ts`** — la API pública que las apps importan; exporta puertos de dominio, driver ports, adaptadores, caso de uso, DTOs y errores. No exporta drivers: estos viven en `shared-infra/` ([A-026](./A-026-infraestructura-compartida.md)).
47
+ - **La feature no tiene `drivers/`, ni `presenter/`, ni `service.port.ts`**: el driver tecnológico vive en `shared-infra/<tech>/` ([A-026](./A-026-infraestructura-compartida.md)); el presentador es propiedad de la app, no de la feature — el caso de uso retorna su modelo de salida y la app lo presenta ([A-027](./A-027-patron-wiring-entry-point.md)); el service port se disuelve porque driver y tecnología cohabitan en el mismo paquete.
48
+
49
+ ### Dos puertos, un adaptador
50
+
51
+ | Rol | Archivo | Vocabulario |
52
+ |-----|---------|-------------|
53
+ | **Puerto de dominio** | `use-case/*.port.ts` (`XxxPort`) | dominio — tipos y lenguaje del negocio |
54
+ | **Driver port** | `adapters/*/driver.port.ts` (`XxxDriverPort`) | raw data — mementos/snapshots, CloudEvents (cuando la costura es event-shaped) y DTOs planos; sin errores de dominio (`null` señala ausencia); sin tipos tecnológicos; nunca `any`/`unknown` |
55
+ | **Adaptador** | `adapters/*/adapter.ts` (`XxxAdapter`) | la traducción — implementa el puerto de dominio, delega en el driver port, restaura agregados y mapea errores |
56
+ | **Driver** | `shared-infra/<tech>/` (`Driver`) | la implementación tecnológica — implementa el driver port completo ([A-026](./A-026-infraestructura-compartida.md)) |
57
+
58
+ - El **puerto de dominio** declara lo que el caso de uso necesita.
59
+ - El **driver port** declara lo que un driver debe satisfacer. Es la costura tipada: usa **raw data** — mementos y DTOs planos — y nunca tipos tecnológicos (mongoose, decoradores de framework) ni `any`/`unknown`. La ausencia de datos se expresa con `null`; los errores de dominio se deciden en el adaptador, no en el driver.
60
+ - El **adaptador** implementa el puerto de dominio y delega en el driver port; es el único lugar donde ocurre la traducción tipada dominio ↔ raw data, donde `null` se convierte en error de dominio y un memento en agregado restaurado.
61
+ - El **driver** implementa el driver port en `shared-infra/<tech>/`: la consulta, el mapeo modelo↔memento, el manejo de errores tecnológicos y las transacciones viven enteros allí, junto a la tecnología ([A-026](./A-026-infraestructura-compartida.md)).
62
+ - El **controlador** es el espejo de entrada del presentador: valida el input externo, lo traduce al `InputDto` del caso de uso e invoca el caso de uso ya ensamblado. No ensambla adaptadores ni caso de uso: la app es la única raíz de composición y le inyecta el caso de uso construido ([A-027](./A-027-patron-wiring-entry-point.md)).
63
+
64
+ ### La costura y el flujo
65
+
66
+ La cadena completa es: `caso de uso → puerto de dominio → adaptador → driver port`. El driver port es la costura: el caso de uso y los adaptadores no importan nada más allá de ella ([A-001](./A-001-clean-architecture-4-capas.md)). El driver que la implementa vive en `shared-infra/<tech>/`; la app lo instancia y lo inyecta en el wiring ([A-027](./A-027-patron-wiring-entry-point.md)).
67
+
68
+ El caso de uso **retorna** su modelo de salida (`output.dto.ts`): un `OutputDto` en respuestas one-shot (en términos del dominio — value objects, no primitivos; [A-050](./A-050-el-caso-de-uso-pide-al-dominio-no-lee-su-representacion.md)), o un **stream nativo** (`AsyncIterable<OutputDto>`, sin RxJS ni otra dependencia tecnológica) cuando emite más de un valor. No hay presentador en la feature: el presentador es un objeto de frontera **propiedad de la app**, el espejo del controlador — el controlador recibe el input desde afuera y lo entrega al caso de uso; el presentador recibe la salida del caso de uso (una vez, o iterando el stream) y la entrega al exterior, mapeándola al formato de la interface con `representations/<protocol>/` cuando la wire es schema'd (`@ObjectType` de GraphQL, protobuf, OpenAPI/Avro) y con transformaciones cosméticas propias de la app en caso contrario ([A-026](./A-026-infraestructura-compartida.md), [A-027](./A-027-patron-wiring-entry-point.md)). La dependencia apunta hacia adentro: la feature retorna un `OutputDto` en términos del dominio (value objects; o su stream) y no nombra ni el transporte, ni la representación, ni el presentador. Cuando la app no levanta un presentador dedicado (respuestas one-shot), el **controlador** traduce la salida de dominio a su forma plana; el presentador sigue siendo la pieza correcta cuando hay stream o una wire schema'd.
69
+
70
+ ### Reglas
71
+
72
+ - **Un caso de uso por feature.**
73
+ - **ISP en los puertos de dominio**: agrupan métodos por coherencia de negocio, no por CRUD; un protocolo estrecho por dependencia del caso de uso.
74
+ - **Naming por rol: la carpeta del adaptador espeja el puerto de dominio que implementa.** `adapters/<dependencia>/` se nombra igual que `use-case/<dependencia>.port.ts`; `<dependencia>` se nombra por el **rol** que la dependencia juega para el caso de uso — acceso a un agregado (`claim-repository`, `stock-movement-repository`), publicación de eventos (`event-bus`), generación de identificadores (`id-generator`) — y no por el agregado que expone. La carpeta y el puerto cambian juntos: el nombre del puerto es la autoridad y la carpeta lo espeja. Un `stock-movement/` (concepto) hermanado con `claim-repository/` (rol) es el olor que esta regla elimina.
75
+ - **El puerto de dominio no expone discriminadores del vocabulario raw del driver.** Cuando el caso de uso necesita resolver un objeto por un concepto de negocio ("el movimiento de esta factura"), el puerto declara un método con verbo de intención (`findByFactura(facturaId)`, [A-029](./A-029-verbos-intencion-metodos-lado-negocio.md)); el literal que traduce ese concepto al vocabulario del driver (`'FACTURA'`) vive en el adaptador. Un método CRUD-genérico que obliga al caso de uso a elegir el literal (`findByDocumentRef(type, reference)` con `'FACTURA'`) es el olor que esta regla elimina.
76
+ - **El puerto de dominio nunca retorna `null`**: la ausencia la traduce el adaptador. En una consulta, a error de dominio (`ReferenceNotFoundError`); en una verificación, al veredicto `valid`/`invalid`. El `null` es vocabulario del driver port (raw data), no del caso de uso.
77
+ - **La restauración en el adaptador compone y encamina los errores por el canal**: cuando el adaptador restaura un agregado desde su memento, compone `Memento.of` y `Aggregate.restore` con los operadores del tier (`seedV(...).pipe(V.chain(...))`) y ensancha los errores a la unión `Errors` de la feature **por anotación de tipo**, no con casts ([A-005](./A-005-either-return-custom-methods.md)). Nunca lanza ni desenvuelve con `getValue()`: los fallos de reconstrucción del memento y de `restore` viajan por el canal de error del `TaskValidation`/`Validation` y llegan a la app ([A-007](./A-007-data-contract-fail-fast.md)). La costura async se expresa con `TV.fromAsync` y un helper privado de firma `(param) => TaskValidation`. La unión `Errors` de la feature incluye los errores de cada agregado que restaura (`ClaimErrors`, `StockMovementErrors`), `MementoTamperedError` y los errores v8n de forma de la costura (`InvalidTypeError`, `TooSmallError`, `TooBigError`).
78
+ - **Puerto de verificación**: cuando el caso de uso necesita comprobar un invariante (no obtener el objeto), el puerto declara un método con verbo de intención (`verify*`, [A-029](./A-029-verbos-intencion-metodos-lado-negocio.md)) que retorna `TaskValidation<Errors, void>` — `valid` si pasa, `invalid` con el error de dominio si no. El adaptador implementa el check (interpreta `null` e invoca predicados del dominio); el caso de uso delega y conserva el contexto.
79
+ - **Los puertos de dominio usan conceptos de dominio; el driver port usa raw data.** DTOs custom solo cuando el dominio carece del concepto. Nunca `any` ni `unknown`. Cuando la costura es event-shaped, el raw data de escritura son eventos en formato estándar (p. ej. CloudEvents) y el de lectura el snapshot del agregado; el evento de dominio sin confirmar — que porta tipos de dominio — no cruza la costura tal cual: el adaptador produce la envolvente.
80
+ - **El tipo de retorno de un método del puerto de dominio refleja su naturaleza**: lecturas y verificaciones declaran `TaskValidation`/`Validation`; mutaciones declaran `TaskExecution`/`Execution`. El tipo fija la fase en el pipe del caso de uso y el compilador la hace cumplir.
81
+ - **El caso de uso retorna su modelo de salida** — un `OutputDto` o un `AsyncIterable<OutputDto>` nativo; no hay presentador ni `service.port.ts` en la feature (el presentador es propiedad de la app, [A-027](./A-027-patron-wiring-entry-point.md)).
82
+ - **La feature es tech-free**: solo importa el stack de tipado y las utilidades genéricas que ya usa el dominio (result types, validación) y builtins de plataforma (`crypto`); nunca tecnología de framework, transporte o persistencia (nestjs, mongoose, pg, graphql, rxjs…).
83
+ - **Las features nunca comparten puertos, adaptadores ni driver ports** — el acoplamiento ocurre solo en el wiring ([A-024](./A-024-features-slices-verticales-portables.md), [A-027](./A-027-patron-wiring-entry-point.md)).
84
+ - **Las features nunca importan `shared-infra/` ni `representations/`** ([A-026](./A-026-infraestructura-compartida.md)); el driver se inyecta a través del driver port.
85
+ - **Un driver port, una implementación por tecnología** en `shared-infra/<tech>/`. La tecnología nombra la implementación: `in-memory` y `local-disk` son tecnologías de aplazamiento, igual que `postgres` o `mongo`; permiten construir el caso de uso y la app sin elegir tecnología real y comparten estado dentro de su paquete ([A-026](./A-026-infraestructura-compartida.md)).
86
+ - **El transporte vive en la app** ([A-027](./A-027-patron-wiring-entry-point.md)): el resolver HTTP/GraphQL o el comando CLI valida el input, inyecta los drivers, ejecuta el controlador y mapea el modelo de salida a la wire.
87
+
88
+ ## Consecuencias
89
+
90
+ ### Positivas
91
+
92
+ - **Feature sin dependencias tecnológicas**: el paquete de la feature no declara tecnologías, lo que cumple [A-024](./A-024-features-slices-verticales-portables.md).
93
+ - **Costura tipada y explícita**: el adaptador concentra la traducción dominio ↔ raw data, y el driver port tipado impide que `any`/`unknown` o tipos tecnológicos se filtren hacia la feature.
94
+ - **Intercambiabilidad**: cambiar de tecnología toca solo `shared-infra/<tech>/`; el puerto de dominio, el adaptador, el driver port y el caso de uso no cambian.
95
+ - **Aplazamiento de la tecnología**: `in-memory` y `local-disk` permiten construir y ejecutar el caso de uso y la app entera antes de elegir tecnología real.
96
+ - **Menos archivos**: la feature ya no arrastra `drivers/`, `presenter/` ni `service.port.ts`.
97
+ - **Consistencia entre features**: la anatomía canónica hace predecible la navegación.
98
+
99
+ ### Negativas
100
+
101
+ - **Dos paquetes por feature**: la feature (puertos + adaptadores + caso de uso) y su driver en `shared-infra/<tech>/`; una feature nueva toca ambos.
102
+ - **Raw data en la costura**: tipar el driver port exige modelar el memento/snapshot; para consultas complejas (filtros, paginación, transacciones) el puerto se expresa en términos de dominio y raw data, y la implementación entera vive en `shared-infra/<tech>/`.
103
+ - **Disciplina de traducción**: mantener el driver port sin tipos tecnológicos ni errores de dominio requiere revisión constante.
104
+ - **La regla de una verificación se replica en el doble de test**: el check vive en el adaptador; el doble de prueba del puerto repite esa lógica. Es el precio de un caso de uso que delega en lugar de decidir.
@@ -0,0 +1,44 @@
1
+ # A-026: Infraestructura compartida (shared-infra)
2
+
3
+ **Estado**: Aceptado
4
+
5
+ **Fecha**: 2026-08-13
6
+
7
+ **originally**: A-001
8
+
9
+ **Enriquecido desde**: Cortex ADR 004 — Clean Architecture feature structure with ports & drivers
10
+
11
+ **tags**: arquitectura
12
+
13
+ ## Contexto
14
+
15
+ Varias features y apps necesitan definiciones y lógica que ninguna de ellas posee exclusivamente: definiciones tecnológicas compartidas (esquemas de persistencia, formas de wire) y operaciones tecnológicas reutilizables. Sin una categorización de esta infraestructura compartida, el código común se mezcla con el de las features o se duplica, erosionando la dirección de dependencias.
16
+
17
+ Si además esta infraestructura se empaqueta como un único paquete, importar cualquier parte arrastra las dependencias de todas las tecnologías: una app que solo usa MongoDB cargaría también las dependencias de Postgres. La granularidad del paquete y la dirección de la dependencia resuelven juntas ese problema.
18
+
19
+ ## Decisión
20
+
21
+ El código tecnológico compartido se empaqueta por tecnología — un paquete por tech (`shared-infra/mongo/`, `shared-infra/postgres/`, `shared-infra/in-memory/`, …) — para que cada app importe solo las tecnologías que usa. Dos clases de paquete, con direcciones opuestas:
22
+
23
+ **`shared-infra/<tech>/`** — los drivers que implementan los driver ports que las features declaran ([A-025](./A-025-anatomia-mecanica-feature-puertos-adaptadores-drivers.md)), junto con las operaciones tecnológicas que necesitan (pools de conexiones, query builders, file I/O) y sus esquemas de persistencia y modelos tecnológicos (mongo `@Schema` y sus modelos, entidades postgres, layouts de EventStore/snapshots). El driver implementa el driver port completo — consulta, mapeo modelo↔memento, errores y transacciones — y vive aquí, nunca en la feature. Preferentemente agnósticos del dominio: acoplan conceptos de dominio solo cuando la operación lo exige (tipos de eventos, lógica de replay). Importan los driver ports y el dominio; las features nunca las importan. El esquema de persistencia es privado del driver: vive junto a él porque cambian juntos, y no se comparte fuera de este paquete. `in-memory` y `local-disk` son tecnologías de aplazamiento más: permiten construir el caso de uso y la app sin elegir tecnología real, y comparten estado dentro del paquete — varios drivers de features distintas inyectan el mismo store.
24
+
25
+ **`representations/<protocol>/`** — las formas de wire (tipos `@ObjectType`/`@InputType`, mensajes protobuf, esquemas OpenAPI/Avro) que representan conceptos del dominio para el transporte. Las importa la app para mapear el modelo de salida a la wire; nunca las features, ni sus driver ports, ni los drivers de `shared-infra/`. Importan solo el dominio. Son **domain-aware y app-agnostic**: solo formas schema'd de protocolo, reutilizables por varias apps; los modelos de persistencia, los helpers de conversión (modelo↔memento) y cualquier comportamiento viven en `shared-infra/<tech>/`, nunca aquí.
26
+
27
+ Son canónicas por protocolo: toda forma de wire que representa un concepto del dominio vive aquí, tenga uno o varios consumidores — una representación del dominio nunca se define ad-hoc en una app. Las formas de wire que son el contrato de una operación puntual (input, result o filter de un entrypoint) no representan un concepto del dominio: viven con su entrypoint, no aquí.
28
+
29
+ La dirección de dependencias apunta hacia adentro: `representations/<protocol>/` importa solo el dominio; `shared-infra/<tech>/` importa el dominio y los driver ports que las features declaran (sus esquemas de persistencia viven dentro del mismo paquete); las features nunca importan `shared-infra/` ni `representations/` ([A-001](./A-001-clean-architecture-4-capas.md)).
30
+
31
+ ## Consecuencias
32
+
33
+ ### Positivas
34
+
35
+ - **Duplicación reducida**: cada forma de wire vive una sola vez por protocolo, y cada operación y esquema de persistencia una sola vez por tecnología.
36
+ - **Cierre tecnológico ligero**: la app importa solo los paquetes de la tecnología que usa (`shared-infra/<tech>/`) y las formas de wire de los protocolos que expone (`representations/<protocol>/`); las dependencias de las demás tecnologías no entran en su closure.
37
+ - **Separación clara**: cada paquete tiene un vocabulario y una dirección de importación definidos — `shared-infra/<tech>/` (drivers y esquemas de persistencia) y `representations/<protocol>/` (formas de wire).
38
+ - **Dominio protegido**: ports y dominio nunca importan `shared-infra/` ni `representations/`.
39
+
40
+ ### Negativas
41
+
42
+ - **Acoplamiento a features**: cada paquete `shared-infra/<tech>/` importa los driver ports de las features que implementa; no es una librería reutilizable entre sistemas ([A-017](./A-017-taxonomia-repositorios-librerias-sistemas.md)).
43
+ - **Tres hogares por tipo de forma**: el equipo distingue forma de persistencia (privada del driver, en `shared-infra/<tech>/`) de representación del dominio (canónica por protocolo, en `representations/<protocol>/`) y de payload de operación (input/result/filter, propiedad del entrypoint que lo expone); confundirlas — una representación del dominio definida ad-hoc en una app, un payload de operación promovido a `representations/`, o un esquema de persistencia en `representations/` — reintroduce la duplicación o acopla `representations/` a una app.
44
+ - **Acoplamiento innecesario al dominio**: un driver que acopla conceptos de dominio sin que la operación lo exija erosiona la reutilización y acerca la infraestructura al dominio.