From 4ff48deb2462215cd94a7fbb541efb1af48e515e Mon Sep 17 00:00:00 2001 From: jesquivels Date: Mon, 26 Oct 2026 08:49:57 -0600 Subject: [PATCH] first commit --- .dockerignore | 9 + .editorconfig | 15 + .env.example | 11 + .gitattributes | 7 + .github/workflows/ci.yaml | 27 + .gitignore | 6 + .mvn/wrapper/maven-wrapper.properties | 3 + AGENTS.md | 15 + Dockerfile | 22 + README.md | 40 ++ Specs_Orquestador_SpringBoot_PostgreSQL.md | 524 ++++++++++++++++++ compose.yaml | 19 + contracts/README.md | 5 + examples/flows/README.md | 5 + examples/http/health.http | 8 + mvnw | 295 ++++++++++ mvnw.cmd | 189 +++++++ pom.xml | 54 ++ schemas/README.md | 5 + specs/ARCHITECTURE.md | 31 ++ specs/DECISIONS.md | 43 ++ specs/IMPLEMENTATION_PLAN.md | 231 ++++++++ specs/PROGRESS.md | 53 ++ specs/RUNBOOK.md | 73 +++ .../orchestrator/OrchestratorApplication.java | 11 + .../orchestrator/action/package-info.java | 2 + .../orchestrator/audit/package-info.java | 2 + .../orchestrator/decision/package-info.java | 2 + .../orchestrator/document/package-info.java | 2 + .../orchestrator/execution/package-info.java | 2 + .../orchestrator/flow/package-info.java | 2 + .../integration/package-info.java | 2 + .../orchestrator/request/package-info.java | 2 + .../security/SecurityConfiguration.java | 23 + .../orchestrator/shared/package-info.java | 2 + src/main/resources/application-local.yaml | 5 + src/main/resources/application.yaml | 48 ++ src/main/resources/db/migration/README.md | 6 + .../com/organizacion/orchestrator/README.md | 6 + 39 files changed, 1807 insertions(+) create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .env.example create mode 100644 .gitattributes create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 AGENTS.md create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 Specs_Orquestador_SpringBoot_PostgreSQL.md create mode 100644 compose.yaml create mode 100644 contracts/README.md create mode 100644 examples/flows/README.md create mode 100644 examples/http/health.http create mode 100644 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml create mode 100644 schemas/README.md create mode 100644 specs/ARCHITECTURE.md create mode 100644 specs/DECISIONS.md create mode 100644 specs/IMPLEMENTATION_PLAN.md create mode 100644 specs/PROGRESS.md create mode 100644 specs/RUNBOOK.md create mode 100644 src/main/java/com/organizacion/orchestrator/OrchestratorApplication.java create mode 100644 src/main/java/com/organizacion/orchestrator/action/package-info.java create mode 100644 src/main/java/com/organizacion/orchestrator/audit/package-info.java create mode 100644 src/main/java/com/organizacion/orchestrator/decision/package-info.java create mode 100644 src/main/java/com/organizacion/orchestrator/document/package-info.java create mode 100644 src/main/java/com/organizacion/orchestrator/execution/package-info.java create mode 100644 src/main/java/com/organizacion/orchestrator/flow/package-info.java create mode 100644 src/main/java/com/organizacion/orchestrator/integration/package-info.java create mode 100644 src/main/java/com/organizacion/orchestrator/request/package-info.java create mode 100644 src/main/java/com/organizacion/orchestrator/shared/infrastructure/security/SecurityConfiguration.java create mode 100644 src/main/java/com/organizacion/orchestrator/shared/package-info.java create mode 100644 src/main/resources/application-local.yaml create mode 100644 src/main/resources/application.yaml create mode 100644 src/main/resources/db/migration/README.md create mode 100644 src/test/java/com/organizacion/orchestrator/README.md diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1619d57 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +** +!.mvn/ +!.mvn/** +!mvnw +!pom.xml +!src/ +!src/** +!Dockerfile +!.dockerignore diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..19a189b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.{yaml,yml,xml,json}] +indent_size = 2 + +[mvnw.cmd] +end_of_line = crlf diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..06a10a0 --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# Copiar a .env y completar antes de ejecutar docker compose up --build -d. +# PostgreSQL externo: sustituir por su DNS/IP. No usar localhost dentro del contenedor. +# Para una base en el equipo anfitrión: jdbc:postgresql://host.docker.internal:5432/orquestador +DB_URL=jdbc:postgresql://postgres.example.com:5432/orquestador +DB_USERNAME=orchestrator +DB_PASSWORD= +JWT_ISSUER_URI= +JWT_JWK_SET_URI= +JWT_AUDIENCE=orquestador-nmp +APP_BIND_ADDRESS=127.0.0.1 +APP_PORT=8080 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..86d816b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +* text=auto +*.java text eol=lf +*.md text eol=lf +*.yaml text eol=lf +*.xml text eol=lf +mvnw text eol=lf +mvnw.cmd text eol=crlf diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..50536eb --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,27 @@ +name: verify +on: + push: + pull_request: +permissions: + contents: read +jobs: + verify: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '21' + cache: maven + - name: Verify + run: sh ./mvnw --batch-mode --no-transfer-progress clean verify + - name: Test reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-reports + path: | + target/surefire-reports/ + target/failsafe-reports/ + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89784a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/target/ +/.idea/ +*.iml +/.env +*.log +/.tools/ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..8dea6c2 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5037b14 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,15 @@ +# Instrucciones del repositorio + +Fuente: Specs_Orquestador_SpringBoot_PostgreSQL.md, sección 15. +Leer docs/PROGRESS.md y docs/IMPLEMENTATION_PLAN.md antes de continuar. + +1. Leer esta especificación completa, instrucciones existentes y estado del repositorio. No reemplazar trabajo del usuario ni introducir otro stack. +2. Crear docs/IMPLEMENTATION_PLAN.md con T01–T17 y subtareas verificables. Crear docs/PROGRESS.md con columnas ID, estado, dependencias, archivos, comandos/checks, resultado, bloqueo y siguiente paso. Estados: PENDING, IN_PROGRESS, BLOCKED, DONE. +3. Elegir la primera tarea pendiente cuyas dependencias estén DONE. Mantener una única tarea principal IN_PROGRESS. No iniciar interfaz administrativa, Kafka o Drools para resolver requisitos del MVP. +4. Antes de codificar, identificar criterios de cierre y prueba pertinente. Implementar una porción completa; actualizar migraciones, contrato y pruebas cuando el comportamiento lo requiera. +5. Ejecutar verificaciones de la tarea. Al fallar, corregir dentro de esa tarea; no marcar DONE por compilación si requiere concurrencia o integración. Documentar diferencias entre stub y proveedor real. +6. Registrar evidencia concreta: comando, resultado, fecha y archivos. Los bloqueos externos no equivalen a prueba aprobada. Cuando no existan credenciales/contrato de un proveedor, completar adapter y pruebas con mocks, marcar integración real pendiente y no inventar respuestas. +7. Marcar DONE solo al cumplir criterios y no dejar regresiones conocidas. Actualizar siguiente tarea. Continuar autónomamente con tareas autorizadas; preguntar solo por decisiones de negocio que no puedan inferirse, no por cada fase. +8. Al retomar una sesión, leer PROGRESS y revisar cambios actuales. Reanudar tarea IN_PROGRESS/BLOCKED; evitar regenerar todo el proyecto o repetir verificaciones ya vigentes sin motivo. +9. Si hay una desviación necesaria, registrar ADR con motivo, efecto y alternativas; no cambiar la semántica de idempotencia, versiones o seguridad silenciosamente. +10. No hacer despliegue productivo, conectar un buró real ni enviar documentos personales para la demo sin autorización y configuración correspondiente. La entrega solicitada es implementación local verificable. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..112fb91 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM eclipse-temurin:21.0.12.1_1-jdk-jammy AS build +WORKDIR /workspace +COPY .mvn/ .mvn/ +COPY mvnw pom.xml ./ +RUN sed -i 's/\r$//' mvnw +COPY src/ src/ +# package ejecuta pruebas unitarias; la suite de integración se ejecuta en CI con verify. +RUN sh ./mvnw --batch-mode --no-transfer-progress package + +FROM eclipse-temurin:21.0.12.1_1-jre-jammy AS runtime +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl \ + && rm -rf /var/lib/apt/lists/* \ + && groupadd --gid 10001 orchestrator \ + && useradd --uid 10001 --gid orchestrator --no-create-home --shell /usr/sbin/nologin orchestrator +WORKDIR /app +COPY --from=build --chown=10001:10001 /workspace/target/*.jar /app/app.jar +USER 10001:10001 +EXPOSE 8080 +HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \ + CMD curl --fail --silent --max-time 4 http://localhost:8080/actuator/health/readiness > /dev/null || exit 1 +ENTRYPOINT ["java", "-jar", "/app/app.jar"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..50abd8d --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# Orquestador NMP + +Arquetipo de un microservicio Spring Boot para ejecutar flujos configurables y versionados sobre PostgreSQL. La fuente funcional es [la especificación](Specs_Orquestador_SpringBoot_PostgreSQL.md). + +## Estado + +Esta entrega crea la estructura Maven, aplicación, configuración JWT, salud, configuración de persistencia y documentación de ejecución y seguimiento. El motor, endpoints de negocio, migraciones y flujos todavía están pendientes. Consultar [PROGRESS](specs/PROGRESS.md) para evidencia y bloqueos; no interpretar la estructura como implementación completa. + +## Requisitos y ejecución + +- Docker con contenedores Linux y Compose v2. La imagen compila con JDK 21 y Maven Wrapper; no requiere Java/Maven en el anfitrión. +- PostgreSQL 17 externo con base y usuario existentes, accesible desde Docker. +- Un issuer JWT con JWKS y audiencia; se conserva la autenticación. + +```powershell +Copy-Item .env.example .env +# Completar DB_URL, DB_USERNAME, DB_PASSWORD y parámetros JWT. +docker compose config --quiet +docker compose up --build -d +docker compose ps +docker compose logs -f orquestador +``` + +Compose levanta únicamente `orquestador`. Para PostgreSQL en otra máquina usar `jdbc:postgresql://HOST:5432/BASE`; si está en el anfitrión de Docker, usar `host.docker.internal` en lugar de `localhost`. Ver [RUNBOOK](specs/RUNBOOK.md). + +Salud: `http://localhost:8080/actuator/health/readiness`. El puerto publicado se configura con APP_PORT. El healthcheck incluye PostgreSQL; liveness no depende de proveedores. Las demás rutas requieren JWT. No hay endpoints de negocio implementados todavía. +## Organización y seguimiento + +- [Arquitectura](specs/ARCHITECTURE.md): paquetes y fronteras. +- [Decisiones](specs/DECISIONS.md): versiones y motivos. +- [Plan T01–T17](specs/IMPLEMENTATION_PLAN.md): dependencias, subtareas y criterios de cierre. +- [Seguimiento](specs/PROGRESS.md): estado real, verificaciones y siguiente paso. + +Surefire ejecuta `*Test` en `test`; Failsafe ejecuta `*IT` en `verify`. La estructura inicial aún no contiene pruebas de negocio; T01 debe completar su smoke test y T03 incorporar Testcontainers. CI ejecuta el mismo `clean verify`. + +Los contratos, schemas, migraciones y ejemplos de flujos tienen directorios reservados. El Dockerfile compila con JDK 21 y ejecuta con JRE 21 como usuario no root. Compose no administra PostgreSQL ni sus volúmenes. Mocks y roles de mínimo privilegio siguen pendientes de T11 y T03/T07. + +## Límites funcionales previstos + +Nuevos umbrales, destinos y pasos con capacidades existentes se publicarán como configuración. Una nueva capacidad de validación puede requerir un adapter; un nuevo tipo de tarea puede requerir soporte del front. No incluye UI administrativa, Kafka, Drools ni migración automática de instancias. diff --git a/Specs_Orquestador_SpringBoot_PostgreSQL.md b/Specs_Orquestador_SpringBoot_PostgreSQL.md new file mode 100644 index 0000000..706b526 --- /dev/null +++ b/Specs_Orquestador_SpringBoot_PostgreSQL.md @@ -0,0 +1,524 @@ +# Especificación de implementación: orquestador configurable de solicitudes + +Versión del documento: 1.0 · Fecha: 25 de octubre de 2026 +Estado: especificación propuesta para construcción; no constituye una implementación. + +## 1. Objetivo y alcance + +Construir un microservicio Spring Boot que ejecute múltiples máquinas de estados de autorización. El tipo de solicitud determina el flujo y la versión asignados al crear una instancia. Estados, eventos, tareas, condiciones, mapeos y transiciones se almacenan en PostgreSQL y se modifican publicando configuraciones, sin recompilar cuando las capacidades necesarias ya existan. + +Caso obligatorio: solicitud creada → asignar cliente consultando una API → capturar INE → capturar estado de cuenta → consultar buró → decidir ruta por scoring. Una nueva versión incorpora captura y validación de licencia antes del buró, sin modificar Java ni crear columnas para licencia. + +El motor no debe contener condiciones específicas de crédito, INE o licencia en su lógica central. Sí debe implementar capacidades genéricas: validar entrada, registrar documento, invocar una integración, evaluar decisiones y persistir resultados. + +Incluye backend, worker durable basado en PostgreSQL, contratos para front configurable, administración REST de flujos, auditoría y ejemplos. Excluye interfaz gráfica administrativa, OCR propio, conexión real al buró sin contrato y credenciales, almacenamiento binario de documentos, BPMN, tareas paralelas, compensaciones automáticas y migración automática de instancias. No incluir Drools inicialmente; ofrecer un puerto de decisiones para integrarlo posteriormente. + +## 2. Decisiones tecnológicas + +- Java 21; Spring Boot en una versión estable mantenida y compatible con Java 21. En T01 fijar versión exacta y BOM, verificar compatibilidad de dependencias y documentarla. No usar versiones dinámicas, snapshots ni inventar el parche más reciente. +- Maven Wrapper. Spring MVC, Bean Validation, Security OAuth2 Resource Server JWT, Actuator y cliente HTTP de Spring compatible con la versión seleccionada. +- PostgreSQL 17 como versión mayor objetivo; fijar parche/imagen reproducible al implementar. Flyway para esquema; Hibernate ddl-auto=validate. +- Spring Data JPA para agregados y JDBC para reclamación de trabajos con SKIP LOCKED. No mantener entidades JPA abiertas durante llamadas HTTP. +- JSONB para definiciones y datos extensibles; columnas relacionales para identidad, versiones, estado, permisos de acceso e índices operativos. BigDecimal para comparaciones numéricas. +- JUnit 5, Testcontainers PostgreSQL, WireMock o equivalente compatible, y pruebas de arquitectura si aportan valor. +- OpenAPI 3.1 en archivo contractual; validar herramienta de documentación contra la versión de Boot. +- No requiere Kafka ni Redis para el MVP. La tabla execution es una cola durable; una futura publicación a Kafka exige outbox transaccional. + +## 3. Arquitectura y límites + +Paquete raíz sugerido: com.organizacion.orchestrator. Un módulo Maven, organizado por capacidades y capas internas. + +| Paquete | Responsabilidad | +|---|---| +| flow | Borradores, validación, publicación, activación y caché | +| request | Instancias, autorización por recurso y consulta de tareas | +| execution | Recepción de comandos, idempotencia, worker y recuperación | +| decision | AST tipado, operadores y tablas de decisión | +| action | ActionHandler, registro de handlers y mapeos | +| integration | Clientes HTTP, catálogo permitido, normalización de resultados | +| document | Referencias y verificación con servicio documental | +| audit | Historial inmutable y consultas paginadas | +| shared | Errores, identidad, reloj, identificadores y observabilidad | + +Puertos mínimos: + +```java +interface DecisionEvaluator { + DecisionResult evaluate(DecisionDefinition definition, EvaluationContext context); +} +interface ActionHandler { + String type(); + ActionResult execute(ActionDefinition definition, ActionContext context); +} +interface FlowDefinitionValidator { + ValidationReport validate(FlowDefinition definition); +} +``` + +Usar records/objetos tipados para contratos, JSON Schema para entrada configurable y AST de condiciones. No ejecutar Java, SQL, JavaScript, SpEL irrestricto ni scripts suministrados en definiciones. Un campo de configuración no puede seleccionar clases Java. + +## 4. Vocabulario y semántica + +- Estado de negocio: avance de la solicitud, por ejemplo INE_PENDIENTE. +- Evento: intención recibida, por ejemplo REGISTRAR_INE. El backend obtiene estado e identidad, nunca los acepta como autoridad del front. +- Transición: estado origen + evento + guardia previa + acciones ordenadas + selección de resultado + estado destino. +- Guardia: condición previa sin efectos externos. Si no cumple, rechazar el evento antes de aceptarlo. +- Acción: capacidad implementada con entrada/salida tipada y política de reintentos. +- Decisión: evaluación pura; no invoca servicios ni modifica persistencia. +- Tarea: metadatos para que el front muestre formulario/captura. No es otro motor independiente. +- Resultado de negocio: CLIENTE_NO_ENCONTRADO, DOCUMENTO_INVALIDO, etc. Puede mantener el mismo estado con respuesta explícita. +- Fallo técnico: timeout, autenticación con proveedor, respuesta inválida. No equivale a cliente inexistente ni a scoring bajo. +- Estado de ejecución: separado del negocio; PENDING, RUNNING, RETRY_WAIT, SUCCEEDED, FAILED, RECONCILIATION_REQUIRED. + +MVP: una transición por evento aceptado; una sola ejecución activa por solicitud. Un evento puede encadenar varias acciones en orden, pero no genera transiciones automáticas recursivas. SUCCEEDED significa procesado técnicamente; puede incluir resultado de negocio no favorable. + +## 5. Configuración de flujos + +Definición JSON completa e inmutable al publicar. Claves de estados y eventos son strings; nunca enums Java por flujo. Cada versión contiene estados, eventos, transiciones, decisiones y tareas. schemaVersion versiona el formato del DSL independientemente de la versión del proceso. + +Ejemplo coherente del núcleo de un flujo mínimo de scoring (válido funcionalmente; schemas referenciados se incluyen en el paquete de configuración): + +```json +{ + "schemaVersion": 1, + "flowKey": "CREDITO", + "version": 1, + "initialState": "BURO_PENDIENTE", + "states": [ + {"key": "BURO_PENDIENTE", "terminal": false}, + {"key": "PASO_N", "terminal": true}, + {"key": "PASO_M", "terminal": true}, + {"key": "REVISION_MANUAL", "terminal": true} + ], + "events": [{"key": "VALIDAR_BURO", "inputSchema": {"type": "object", "additionalProperties": false}}], + "transitions": [{ + "id": "consultar-buro", + "from": "BURO_PENDIENTE", + "event": "VALIDAR_BURO", + "allowedRoles": ["ANALISTA"], + "actions": [{ + "id": "buro", + "type": "INVOKE_INTEGRATION", + "integrationKey": "BURO_CONSULTA", + "inputs": {"clienteId": {"path": "/request/data/clienteId"}}, + "resultKey": "buro" + }], + "decision": "RUTA_SCORING", + "destinations": {"N": "PASO_N", "M": "PASO_M", "MANUAL": "REVISION_MANUAL"}, + "updates": [{"target": "/validaciones/buro", "value": {"path": "/results/buro"}}] + }], + "decisions": [{ + "id": "RUTA_SCORING", + "hitPolicy": "UNIQUE", + "rules": [ + {"id": "alto", "when": {"op": "GT", "left": {"path": "/results/buro/score"}, "right": {"literal": 70}}, "output": "N"}, + {"id": "medio", "when": {"op": "AND", "args": [ + {"op": "GT", "left": {"path": "/results/buro/score"}, "right": {"literal": 60}}, + {"op": "LTE", "left": {"path": "/results/buro/score"}, "right": {"literal": 70}} + ]}, "output": "M"} + ], + "defaultOutput": "MANUAL" + }], + "tasks": [] +} +``` + +El esquema de salida de BURO_CONSULTA exige score numérico válido; score ausente no llega al default como si fuera bajo. Sus límites válidos deben proceder del contrato del proveedor, no asumir universalmente 0–100. Los umbrales 60/70 son ejemplos del negocio. + +Tarea documental de la versión 2, como fragmento para insertar en el flujo completo: + +```json +{ + "state": "LICENCIA_PENDIENTE", + "event": "REGISTRAR_LICENCIA", + "ui": { + "component": "DOCUMENT_UPLOAD", + "title": "Sube tu licencia de conducir", + "documentType": "LICENCIA_CONDUCIR", + "acceptedMimeTypes": ["application/pdf", "image/jpeg", "image/png"], + "maxBytes": 10485760 + }, + "inputSchema": { + "type": "object", + "required": ["documentId"], + "additionalProperties": false, + "properties": {"documentId": {"type": "string", "format": "uuid"}} + } +} +``` + +El backend debe aplicar las mismas restricciones; ui es presentación. El evento referencia documentId, no URL ni resultado de validación. Una transición usa REGISTER_DOCUMENT, después INVOKE_INTEGRATION con perfil LICENCIA_CONDUCIR y normaliza VALID/INVALID. INVALID permanece en LICENCIA_PENDIENTE con motivo; VALID pasa a BURO_PENDIENTE. Error técnico no produce INVALID. + +## 6. Lenguaje de condiciones y decisiones + +Operadores MVP: EQ, NEQ, GT, GTE, LT, LTE, IN, EXISTS, AND, OR, NOT. Referencias mediante JSON Pointer, sin evaluación de expresiones libres. Contexto de solo lectura: request.data, event.data, results y actor con atributos confiables. + +- No convertir implícitamente strings a números ni fechas. Comparaciones numéricas con BigDecimal. +- EXISTS retorna false ante ruta ausente; otros operadores sobre ausente o tipos incompatibles producen EVALUATION_ERROR. Distinguir null de ausente; null solo comparable explícitamente con EQ/NEQ. +- AND/OR cortocircuitan en orden; NOT tiene un operando. IN exige lista homogénea. +- UNIQUE: cero coincidencias usa default obligatorio; más de una es AMBIGUOUS_DECISION y no cambia negocio. +- FIRST: solo habilitable explícitamente; orden persistido define precedencia y queda auditado. +- Validación estática detecta solapamientos de intervalos simples. No prometer resolver satisfacibilidad arbitraria; conservar verificación de ambigüedad en runtime. +- Límites: definición 1 MiB, 200 estados, 500 transiciones, 50 acciones por transición, profundidad AST 20, 1.000 nodos por decisión, datos de solicitud 256 KiB. Configurables con máximos operativos. +- Updates solo apuntan dentro de request.data; rechazar claves duplicadas, destinos solapados y cambios a identidad, versión, estado o ACL. Validar esquema de datos resultante antes del commit. + +## 7. Persistencia PostgreSQL + +UUID generado por aplicación; timestamps timestamptz en UTC; IDs ajenos con tipos explícitos. El SQL siguiente establece el núcleo. Implementar como migraciones Flyway e incluir CHECKs, FKs e índices detallados; no usarlo como sustituto de las validaciones de publicación. + +```sql +CREATE SCHEMA orchestrator; + +CREATE TABLE orchestrator.flow_definition ( + id uuid PRIMARY KEY, + flow_key varchar(100) NOT NULL, + version integer NOT NULL CHECK (version > 0), + status varchar(20) NOT NULL CHECK (status IN ('DRAFT','PUBLISHED','RETIRED')), + schema_version integer NOT NULL, + definition jsonb NOT NULL CHECK (jsonb_typeof(definition) = 'object'), + checksum varchar(64), + revision bigint NOT NULL DEFAULT 0, + created_at timestamptz NOT NULL DEFAULT now(), + created_by varchar(200) NOT NULL, + published_at timestamptz, + UNIQUE (flow_key, version) +); + +CREATE TABLE orchestrator.request_type ( + type_key varchar(100) PRIMARY KEY, + active_flow_id uuid REFERENCES orchestrator.flow_definition(id), + enabled boolean NOT NULL DEFAULT true, + revision bigint NOT NULL DEFAULT 0 +); + +CREATE TABLE orchestrator.request_instance ( + id uuid PRIMARY KEY, + type_key varchar(100) NOT NULL REFERENCES orchestrator.request_type(type_key), + flow_id uuid NOT NULL REFERENCES orchestrator.flow_definition(id), + current_state varchar(100) NOT NULL, + data jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(data) = 'object'), + owner_subject varchar(200) NOT NULL, + business_scope varchar(100) NOT NULL, + revision bigint NOT NULL DEFAULT 0, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now() +); + +CREATE TABLE orchestrator.execution ( + id uuid PRIMARY KEY, + request_id uuid NOT NULL REFERENCES orchestrator.request_instance(id), + idempotency_key varchar(200) NOT NULL, + payload_hash varchar(64) NOT NULL, + actor_subject varchar(200) NOT NULL, + actor_snapshot jsonb NOT NULL, + event_key varchar(100) NOT NULL, + event_data jsonb NOT NULL, + transition_id varchar(100) NOT NULL, + from_state varchar(100) NOT NULL, + accepted_revision bigint NOT NULL, + status varchar(40) NOT NULL CHECK (status IN + ('PENDING','RUNNING','RETRY_WAIT','SUCCEEDED','FAILED','RECONCILIATION_REQUIRED')), + attempts integer NOT NULL DEFAULT 0, + next_attempt_at timestamptz NOT NULL DEFAULT now(), + lease_owner varchar(200), + lease_until timestamptz, + fencing_token bigint NOT NULL DEFAULT 0, + result jsonb, + error_code varchar(100), + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + UNIQUE (request_id, idempotency_key) +); +CREATE UNIQUE INDEX uq_request_active_execution + ON orchestrator.execution(request_id) + WHERE status IN ('PENDING','RUNNING','RETRY_WAIT','RECONCILIATION_REQUIRED'); +CREATE INDEX ix_execution_due + ON orchestrator.execution(next_attempt_at, created_at) + WHERE status IN ('PENDING','RETRY_WAIT'); +CREATE INDEX ix_execution_lease ON orchestrator.execution(lease_until) + WHERE status = 'RUNNING'; +CREATE INDEX ix_request_scope_state + ON orchestrator.request_instance(business_scope, current_state, created_at, id); + +CREATE TABLE orchestrator.action_execution ( + id uuid PRIMARY KEY, + execution_id uuid NOT NULL REFERENCES orchestrator.execution(id), + action_id varchar(100) NOT NULL, + status varchar(30) NOT NULL CHECK (status IN + ('PENDING','RUNNING','SUCCEEDED','FAILED','UNKNOWN')), + attempt_count integer NOT NULL DEFAULT 0, + external_idempotency_key varchar(200) NOT NULL, + input_hash varchar(64) NOT NULL, + result jsonb, + provider_reference varchar(200), + error_code varchar(100), + started_at timestamptz, + finished_at timestamptz, + UNIQUE (execution_id, action_id) +); + +CREATE TABLE orchestrator.document_reference ( + id uuid PRIMARY KEY, + request_id uuid NOT NULL REFERENCES orchestrator.request_instance(id), + document_type varchar(100) NOT NULL, + storage_document_id varchar(200) NOT NULL, + mime_type varchar(100) NOT NULL, + size_bytes bigint NOT NULL CHECK (size_bytes >= 0), + checksum varchar(128) NOT NULL, + created_by varchar(200) NOT NULL, + created_at timestamptz NOT NULL DEFAULT now(), + UNIQUE (request_id, document_type, storage_document_id) +); + +CREATE TABLE orchestrator.audit_entry ( + id uuid PRIMARY KEY, + request_id uuid NOT NULL REFERENCES orchestrator.request_instance(id), + execution_id uuid REFERENCES orchestrator.execution(id), + kind varchar(100) NOT NULL, + actor_subject varchar(200) NOT NULL, + flow_id uuid NOT NULL REFERENCES orchestrator.flow_definition(id), + from_state varchar(100), + to_state varchar(100), + detail jsonb NOT NULL, + created_at timestamptz NOT NULL DEFAULT now() +); +CREATE INDEX ix_audit_request + ON orchestrator.audit_entry(request_id, created_at, id); +``` + +Añadir tablas admin_audit para publicación/activación (flow_id, acción, actor, checksums, instante, correlationId) y reconciliation_resolution para resoluciones autorizadas e idempotentes. La creación de solicitudes requiere registro durable de idempotencia propio con clave única por actor/scope y hash; crear instancia y registro en la misma transacción. + +No duplicar la versión de flujo en la instancia: flow_id referencia la fila inmutable. No almacenar archivos, tokens, JWT completos ni secretos en JSONB. Integration registry es configuración de despliegue con secretos por referencia; los flujos solo usan integrationKey y perfil permitido. + +Un trigger debe impedir UPDATE de definition, flow_key, version, schema_version y checksum después de publicar; permitir únicamente transición controlada PUBLISHED→RETIRED. Prohibir DELETE de versiones publicadas y escritura ordinaria al historial; migraciones usan un rol separado. RETIRED impide nuevas asignaciones y permite ejecutar instancias existentes. Sin borrado físico de versiones referenciadas. + +## 8. Protocolo de ejecución durable + +### 8.1 Aceptación del evento: transacción corta + +1. Autenticar, verificar acceso a la solicitud y validar tamaño/formato. +2. Bloquear la fila de solicitud con SELECT FOR UPDATE. +3. Buscar Idempotency-Key: mismo hash y actor autorizado retorna ejecución existente, aun si cambió revision; mismo key con diferente comando retorna 409 IDEMPOTENCY_CONFLICT. El hash incluye evento, datos y revisión esperada con serialización canónica. La clave se conserva durante la retención de la solicitud. +4. Comprobar If-Match contra revision, ausencia de ejecución activa, estado/evento, rol y guardias con el snapshot actual. Nunca aceptar scoring ni validaciones de proveedor desde el front. +5. Insertar execution PENDING con transición, identidad mínima confiable y versión fijada por instancia; incrementar revision de solicitud y registrar EVENT_ACCEPTED en la misma transacción. +6. Confirmar y retornar 202 con Location a ejecución. No llamar APIs dentro de esta transacción. + +### 8.2 Worker y acciones + +1. Reclamar un lote pequeño con SELECT FOR UPDATE SKIP LOCKED, orden next_attempt_at/created_at, para PENDING o RETRY_WAIT vencidos; actualizar RUNNING, lease, contador y fencing_token. Confirmar antes de ejecutar HTTP. +2. Lease inicial 60 s, renovación cada 15 s; timeouts HTTP inferiores a lease y presupuesto total por acción definido. Usar tiempo de PostgreSQL para vencimientos. Valores configurables. +3. Ejecutar acciones secuenciales con checkpoint por acción. Una acción SUCCEEDED se reutiliza, no se repite. Guardar y verificar input_hash. +4. Clave externa estable derivada de executionId + actionId, idéntica en reintentos. Los intentos no modifican esa clave. +5. Toda escritura del worker verifica lease_owner y fencing_token vigente. Un worker obsoleto no puede finalizar; fencing local no evita duplicados externos, de ahí la idempotencia del proveedor. +6. Evaluar decisión sobre snapshot + resultados normalizados, producir actualizaciones candidatas y validar schema. +7. Transacción final: bloquear solicitud y comprobar ejecución activa, lease/token y estado/revisión esperados; actualizar datos y estado, incrementar revision, marcar SUCCEEDED y registrar acciones/decisión/transición de forma atómica. + +Mientras hay ejecución activa, rechazar otros eventos con 409 REQUEST_BUSY. No permitir PATCH libre de datos. Un evento nuevo tras un resultado inválido utiliza nueva clave; repetición de la misma clave consulta el mismo resultado. + +### 8.3 Reintentos y recuperación + +- Reintentar solo fallos transitorios según contrato: timeout de lectura segura, conexión, 429 y ciertos 5xx. Máximo inicial 3 intentos totales por acción, backoff 2 s y 4 s con jitter y Retry-After acotado. Validar que la configuración no permita ciclos infinitos. +- No reintentar ciegamente operaciones con efectos sin idempotencia/consulta de estado. Si hubo timeout o caída después del envío y antes de guardar respuesta, resultado UNKNOWN → RECONCILIATION_REQUIRED. +- Un recuperador detecta leases vencidos: retoma consultas/idempotentes; bloquea para conciliación operaciones no seguras en curso. Conserva checkpoints previos. +- FAILED solo libera la solicitud cuando se conoce que no hay efectos pendientes; conservar estado/datos de negocio previos e historial del fallo. Si existen efectos exitosos no compensados que impiden repetir el comando, usar RECONCILIATION_REQUIRED. +- Endpoint de conciliación reservado: adjuntar evidencia de resultado verificado y reanudar, o cerrar sin transición únicamente cuando la ausencia/compensación de efectos esté verificada. No admitir cambiar manualmente a cualquier estado. +- No prometer exactly-once entre base de datos y APIs. Semántica: entrega al menos una vez con idempotencia y conciliación de incertidumbre. + +## 9. Catálogo de acciones e integraciones + +Handlers MVP: REGISTER_DOCUMENT, INVOKE_INTEGRATION y NO_OP (solo simulación). Cada tipo declara schema de configuración/entrada/salida y clasificación READ_ONLY, IDEMPOTENT_WRITE o NON_IDEMPOTENT_WRITE. La clasificación procede del catálogo confiable, nunca de un flujo editable. + +INVOKE_INTEGRATION resuelve integrationKey a endpoint, método, autenticación, timeouts y contrato permitidos. La publicación fija contractVersion/profileVersion; no cambiar semántica de una integración bajo una versión publicada. Rotación de secretos o DNS operativo puede ocurrir sin alterar el contrato. + +No aceptar URL, headers arbitrarios o credenciales del evento. Permitir mappings explícitos de campos, no plantillas ejecutables; bloquear cambios de host y redirecciones inesperadas. Registrar solo metadatos no sensibles. + +Adapters de ejemplo con mocks: + +| Integración | Entrada | Salida normalizada | +|---|---|---| +| CLIENTES_CONSULTA | clienteId | found boolean; customerId cuando found=true | +| DOCUMENTO_VALIDA | documentId, perfil | status VALID/INVALID, reasonCodes; datos mínimos derivados | +| BURO_CONSULTA | clienteId y referencia de consentimiento | score numérico y consultationId | + +404 significa no encontrado únicamente si así lo define el contrato. Respuesta 200 sin campos obligatorios es fallo de contrato. Buró requiere consentimiento/requisitos del proveedor; esta especificación no define criterios regulatorios. + +REGISTER_DOCUMENT consulta el servicio documental y comprueba pertenencia a la solicitud/scope, tipo, MIME real, tamaño, checksum y estado de análisis antimalware. El front no puede vincular un documento ajeno. Sin servicio real, implementar stub explícito solo en perfil local/test. No ofrecer un éxito simulado en producción. + +## 10. Contratos REST + +Base /api/v1, JSON, JWT salvo salud mínima. Fechas ISO-8601 UTC. Errores application/problem+json con code y correlationId. Paginación por cursor, máximo 100. + +| Método y ruta | Función / respuesta | +|---|---| +| POST /requests | Crear, resolver flujo activo y fijar versión; 201, Location y ETag | +| GET /requests/{id} | Estado, versión de flujo, datos autorizados, revision, ejecución activa; 200 y ETag | +| GET /requests/{id}/available-events | Eventos permitidos, inputSchema, ui y causas de bloqueo no sensibles | +| POST /requests/{id}/events | Aceptar comando; 202 y Location de ejecución | +| GET /executions/{id} | Estado técnico, resultado de negocio y error permitido | +| GET /requests/{id}/history | Auditoría paginada y filtrada | +| POST /admin/flows | Crear borrador con clave y versión únicas | +| PUT /admin/flows/{id} | Editar borrador con If-Match | +| POST /admin/flows/{id}/validate | Validación y errores por JSON Pointer | +| POST /admin/flows/{id}/simulate | Evaluación pura con resultados de acciones suministrados por admin; sin llamadas reales | +| POST /admin/flows/{id}/publish | Publicar si valida; checksum y auditoría | +| PUT /admin/request-types/{key}/active-flow | Activación atómica con If-Match del tipo | +| POST /admin/flows/{id}/retire | Impedir nuevas asignaciones; conservar existentes | +| POST /admin/executions/{id}/resolve | Conciliación verificada e idempotente | + +POST /requests usa Idempotency-Key obligatorio, typeKey y datos iniciales validados; owner/scope se derivan o se autorizan explícitamente desde JWT, no se confían sin verificación. + +Ejemplo de comando: + +```http +POST /api/v1/requests/00000000-0000-0000-0000-000000000123/events +Authorization: Bearer +Idempotency-Key: asignar-cliente-001 +If-Match: "7" +Content-Type: application/json + +{"event":"ASIGNAR_CLIENTE","data":{"clienteId":"CLI-456"}} +``` + +```json +{ + "executionId": "00000000-0000-0000-0000-000000000789", + "status": "PENDING", + "requestId": "00000000-0000-0000-0000-000000000123" +} +``` + +El front consulta ejecución con backoff; al finalizar vuelve a obtener solicitud y ETag. available-events es orientativo: el POST revalida. Si hay ejecución activa devolver lista vacía con blockedByExecutionId. El contrato de tareas permite al front renderizar DOCUMENT_UPLOAD y FORM; no incluye implementar dicho front. + +Códigos: 400 sintaxis; 401 sin autenticación; 403 acceso denegado; 404 recurso no visible/inexistente según política; 409 REQUEST_BUSY/INVALID_EVENT/IDEMPOTENCY_CONFLICT; 412 revisión incorrecta; 413 tamaño; 422 schema/guardia no satisfecha; 428 falta If-Match; 503 fallo al aceptar por infraestructura. Un fallo asíncrono se consulta en ejecución, no se transforma en éxito HTTP de negocio. + +## 11. Publicación y versionado + +Validar: schemaVersion soportada, IDs únicos, estado inicial existente, destinos válidos, terminales sin eventos salientes, alcanzabilidad, estados sin salida no terminales, handlers/integraciones conocidos, mappings y schemas, roles válidos, todas las salidas de decisión cubiertas y límites. Cada pareja estado/evento identifica una transición; condiciones de selección van en decisión/guardia. Rechazar asignación de campos protegidos y perfiles inexistentes. + +Las capacidades de integración y sus schemas se resuelven en publicación; ausencia temporal de proveedor no invalida una definición previamente publicada, pero impide ejecutar correctamente la acción. + +DRAFT→PUBLISHED→RETIRED. Edición de borrador con locking optimista. Publicación serializada e inmutable, checksum SHA-256 del JSON canónico. Activación y retiro bloquean las filas pertinentes para impedir una nueva asignación a un flujo retirado. Creación bloquea/lee consistentemente request_type y flujo activo. Caché solo por flow_id/checksum; resolución de activo siempre coherente con PostgreSQL. + +No migrar solicitudes automáticamente. Para MVP: nuevas solicitudes usan v2, existentes conservan v1. Futuro mecanismo de migración requerirá mapa de estados, validación de datos, ausencia de ejecución activa, simulación, autorización y auditoría. + +## 12. Seguridad y operación + +Roles de ejemplo: FLOW_EDITOR, FLOW_PUBLISHER, FLOW_ADMIN, ANALISTA y EXECUTION_OPERATOR; permisos técnicos distintos de roles configurables de negocio. JWT validado por issuer/audience/firma/vencimiento. Verificar scope/propiedad en cada recurso, incluyendo ejecución, documentos e historial. No derivar roles del JSON del evento. La autorización se registra al aceptar el comando; reintentos técnicos no necesitan reutilizar el JWT vencido. Política de revocación posterior queda explícita como mejora futura. + +Registrar correlationId, executionId, requestId, actionId, flujo/versión, tiempos, reintentos y resultado sin documentos, números de identificación ni score crudo en logs. Auditoría guarda IDs de reglas aplicadas y razones con acceso restringido; retención configurable y eliminación conforme a políticas de datos de la organización. + +Métricas: comandos aceptados/rechazados, antigüedad del pendiente más viejo, ejecuciones por estado, latencia por integrationKey, leases vencidos y conciliaciones. Evitar IDs de solicitud como etiquetas de métricas. Readiness depende de PostgreSQL; liveness no consulta proveedores. Apagado ordenado deja de reclamar trabajos y termina/renueva los vigentes dentro del presupuesto. + +Defaults operativos propuestos, a medir: 10 workers por instancia, lotes 10, polling 500 ms, timeout conexión 2 s y respuesta 10 s; no son SLO garantizados. Límite de body y datos aplicable antes de deserializar grandes cargas. TLS, secretos externos y roles DB de mínimo privilegio. Desarrollo incluye Docker Compose PostgreSQL y mocks; producción sin credenciales predeterminadas ni endpoints admin abiertos. + +## 13. Casos de aceptación obligatorios + +| ID | Escenario | Evidencia esperada | +|---|---|---| +| A01 | Cliente encontrado | Acción consulta; persiste cliente y cambia CREADA→INE_PENDIENTE | +| A02 | Cliente no encontrado | SUCCEEDED con outcome CLIENTE_NO_ENCONTRADO; conserva CREADA | +| A03 | API cliente falla | Reintentos limitados; nunca se interpreta como inexistente | +| A04 | Scoring 71/70/61/60 | N/M/M/REVISION_MANUAL respectivamente | +| A05 | Score ausente/string | Fallo de contrato; no ruta por defecto de score bajo | +| A06 | Dos reglas UNIQUE coinciden | AMBIGUOUS_DECISION; no transición | +| A07 | Mismo evento/clave repetido | Mismo executionId y un único efecto lógico | +| A08 | Misma clave, otros datos | 409 IDEMPOTENCY_CONFLICT | +| A09 | Dos eventos simultáneos | Solo uno aceptado; otro 409 o 412 según orden | +| A10 | Publicar v2 con licencia | Nuevas instancias exigen licencia; v1 conserva recorrido | +| A11 | Documento inválido/ajeno | Inválido mantiene tarea con motivo; ajeno se rechaza sin vincular | +| A12 | Reinicio durante ejecución | Recuperación desde checkpoint sin perder evento | +| A13 | Caída tras efecto externo | Idempotencia evita duplicado o exige conciliación | +| A14 | Dos workers / lease vencido | Worker obsoleto no confirma; un solo cambio final | +| A15 | Rol/scope ajeno | No obtiene ni modifica recursos ajenos | +| A16 | Flujo publicado editado | API y DB bloquean cambio de definición | +| A17 | Activación/retirada concurrentes | Ninguna nueva solicitud referencia flujo retirado | +| A18 | Agregar licencia | Solo nueva configuración y perfil soportado; sin cambio Java ni columnas | +| A19 | Persistencia final falla | No transición parcial; recuperación con checkpoint y auditoría consistente | +| A20 | Payload intenta escribir score | Rechazado por schema; resultados de proveedor protegidos | + +Pruebas reales PostgreSQL, no H2 para concurrencia/JSONB. Usar fault injection y barreras deterministas, no sleeps frágiles. Probar rollback antes/después de cada frontera crítica. No afirmar que pasó una prueba no ejecutada. + +## 14. Lista ordenada de implementación + +Cada tarea comienza pendiente. Las dependencias forman el orden obligatorio de referencia; se permite subdividir, no omitir entregables. + +| ID | Tarea | Depende de | Criterio de cierre | +|---|---|---|---| +| T01 | Bootstrap y ADR de versiones | — | Maven Wrapper, Java 21, versiones fijadas, compile y health local | +| T02 | Contratos OpenAPI y JSON Schema del DSL | T01 | Estados/eventos/decisiones/tareas definidos; ejemplos validados automáticamente | +| T03 | Modelo PostgreSQL y Flyway | T02 | Migraciones desde cero; FKs, índices e inmutabilidad probados con Testcontainers | +| T04 | Modelo de dominio y puertos | T02,T03 | Dominio sin clientes HTTP/JPA; DTOs separados y errores estables | +| T05 | Evaluador de decisiones | T04 | Tipos, null/ausente, límites, UNIQUE/FIRST; A04–A06 | +| T06 | Validador/publicación/activación | T03–T05 | Borradores con revisión, grafo validado, checksum, A16–A17 | +| T07 | Seguridad y autorización por recurso | T04 | JWT real validado en pruebas; roles/scope aplicados a rutas admin y negocio | +| T08 | Crear/consultar solicitudes | T06,T07 | Fija versión, idempotencia de creación, ETag, esquema de datos y acceso | +| T09 | Aceptar eventos idempotentes | T08 | 202 durable, guardias, locking, A07–A09 sin invocar HTTP todavía | +| T10 | Worker PostgreSQL y leases | T09 | Reclamación multiinstancia, fencing, checkpoints y recuperación inicial | +| T11 | Handlers e integraciones HTTP | T10 | Catálogo permitido, contratos, timeouts y mocks; A01–A03 | +| T12 | Reintentos y conciliación | T11 | Fallos inciertos tratados; endpoint protegido; A12–A14,A19 | +| T13 | Documentos y tareas para front | T11,T12 | Referencias verificadas, eventos disponibles, A11,A20 | +| T14 | Flujos demostrativos v1/v2 | T05,T06,T13 | Cliente/INE/estado cuenta/buró; v2 licencia; A10,A18 | +| T15 | Auditoría y observabilidad | T09–T14 | Historial atómico, métricas y logs sin PII, checks operativos | +| T16 | Verificación integral | T15 | A01–A20 automatizados; pruebas de reinicio/concurrencia reproducibles | +| T17 | Empaquetado y documentación | T16 | Dockerfile, Compose, README, ejemplos HTTP y runbook ejecutables | + +Auditoría mínima debe implementarse junto a cada escritura desde T06; T15 completa consultas/operación. Seguridad debe preceder exposición de endpoints; no dejar protección para el final. T10 no se considera terminado si solo usa @Async o memoria. + +### Entregables del repositorio + +- pom.xml, mvnw, mvnw.cmd y .mvn con versiones fijas. +- src/main/java, src/test/java y src/main/resources/db/migration. +- contracts/openapi.yaml y schemas/flow-definition.schema.json. +- examples/flows/credito-v1.json, credito-v2-licencia.json y compra-v1.json completos, válidos y publicables. +- examples/http con creación/publicación/activación/eventos/consulta y scoring de frontera. +- docs/ARCHITECTURE.md, docs/DECISIONS.md, docs/IMPLEMENTATION_PLAN.md, docs/PROGRESS.md y docs/RUNBOOK.md. +- Dockerfile, compose.yaml, .env.example sin secretos y CI. + +No es necesario crear microservicios independientes para cada componente; son módulos internos del orquestador. + +## 15. Instrucciones para llevar la secuencia + +Estas instrucciones deben copiarse a AGENTS.md o al archivo de instrucciones del agente de programación del repositorio. Son parte normativa del encargo de implementación. + +1. Leer esta especificación completa, instrucciones existentes y estado del repositorio. No reemplazar trabajo del usuario ni introducir otro stack. +2. Crear docs/IMPLEMENTATION_PLAN.md con T01–T17 y subtareas verificables. Crear docs/PROGRESS.md con columnas ID, estado, dependencias, archivos, comandos/checks, resultado, bloqueo y siguiente paso. Estados: PENDING, IN_PROGRESS, BLOCKED, DONE. +3. Elegir la primera tarea pendiente cuyas dependencias estén DONE. Mantener una única tarea principal IN_PROGRESS. No iniciar interfaz administrativa, Kafka o Drools para resolver requisitos del MVP. +4. Antes de codificar, identificar criterios de cierre y prueba pertinente. Implementar una porción completa; actualizar migraciones, contrato y pruebas cuando el comportamiento lo requiera. +5. Ejecutar verificaciones de la tarea. Al fallar, corregir dentro de esa tarea; no marcar DONE por compilación si requiere concurrencia o integración. Documentar diferencias entre stub y proveedor real. +6. Registrar evidencia concreta: comando, resultado, fecha y archivos. Los bloqueos externos no equivalen a prueba aprobada. Cuando no existan credenciales/contrato de un proveedor, completar adapter y pruebas con mocks, marcar integración real pendiente y no inventar respuestas. +7. Marcar DONE solo al cumplir criterios y no dejar regresiones conocidas. Actualizar siguiente tarea. Continuar autónomamente con tareas autorizadas; preguntar solo por decisiones de negocio que no puedan inferirse, no por cada fase. +8. Al retomar una sesión, leer PROGRESS y revisar cambios actuales. Reanudar tarea IN_PROGRESS/BLOCKED; evitar regenerar todo el proyecto o repetir verificaciones ya vigentes sin motivo. +9. Si hay una desviación necesaria, registrar ADR con motivo, efecto y alternativas; no cambiar la semántica de idempotencia, versiones o seguridad silenciosamente. +10. No hacer despliegue productivo, conectar un buró real ni enviar documentos personales para la demo sin autorización y configuración correspondiente. La entrega solicitada es implementación local verificable. + +Plantilla inicial de progreso: + +```markdown +| ID | Estado | Evidencia | Bloqueo | Siguiente acción | +|---|---|---|---|---| +| T01 | PENDING | — | — | Verificar versiones y crear proyecto | +``` + +Comandos de referencia, que la implementación debe hacer funcionar y documentar: + +```bash +./mvnw clean verify +docker compose up -d postgres mocks +./mvnw spring-boot:run -Dspring-boot.run.profiles=local +``` + +La suite de integración puede iniciar PostgreSQL/mocks mediante Testcontainers; Docker es requisito de esas pruebas. Definir si verify ejecuta integración con Failsafe y mantener ese comportamiento en CI. Usar issuer JWT de prueba realista en local, sin introducir un bypass de seguridad disponible en producción. + +## 16. Definición final de terminado + +Entrega completa cuando un checkout limpio permite levantar el entorno, aplicar migraciones, ejecutar la suite y reproducir: publicación/activación de dos tipos de solicitud; cliente encontrado/no encontrado; captura INE y estado de cuenta; scoring por fronteras; publicación v2 con licencia; convivencia v1/v2; repetición idempotente; dos eventos concurrentes; recuperación tras reinicio y conciliación de un efecto incierto. + +README debe indicar límites reales: nuevo tipo de tarea requiere soporte del front; nueva capacidad de validación puede requerir adapter; cambios de umbrales/destinos y nuevos pasos con capacidades existentes son configuración. No afirmar que todo cambio de negocio se hace sin código. + +## 17. Fuentes técnicas y decisiones propuestas + +Fuentes oficiales consultadas como referencia; las políticas del motor en este documento son decisiones de diseño propuestas, no garantías de las bibliotecas. + +- Spring Boot, requisitos de sistema: https://docs.spring.io/spring-boot/system-requirements.html — verificar y fijar release concreta en T01. +- PostgreSQL 17, locking explícito: https://www.postgresql.org/docs/17/explicit-locking.html — fundamento para bloqueos de fila; el protocolo de leases/fencing debe implementarse. +- PostgreSQL 17, JSON: https://www.postgresql.org/docs/17/datatype-json.html — almacenamiento JSONB; schemas y semántica del DSL los valida la aplicación. diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..a1e742a --- /dev/null +++ b/compose.yaml @@ -0,0 +1,19 @@ +services: + orquestador: + build: + context: . + dockerfile: Dockerfile + image: orquestador-nmp:local + environment: + DB_URL: ${DB_URL:?Configura DB_URL en .env} + DB_USERNAME: ${DB_USERNAME:?Configura DB_USERNAME en .env} + DB_PASSWORD: ${DB_PASSWORD:?Configura DB_PASSWORD en .env} + JWT_ISSUER_URI: ${JWT_ISSUER_URI:?Configura JWT_ISSUER_URI en .env} + JWT_JWK_SET_URI: ${JWT_JWK_SET_URI:?Configura JWT_JWK_SET_URI en .env} + JWT_AUDIENCE: ${JWT_AUDIENCE:?Configura JWT_AUDIENCE en .env} + SERVER_PORT: "8080" + ports: + - "${APP_BIND_ADDRESS:-127.0.0.1}:${APP_PORT:-8080}:8080" + extra_hosts: + - "host.docker.internal:host-gateway" + stop_grace_period: 40s diff --git a/contracts/README.md b/contracts/README.md new file mode 100644 index 0000000..35149d5 --- /dev/null +++ b/contracts/README.md @@ -0,0 +1,5 @@ +# Contratos pendientes de T02 + +Crear openapi.yaml (OpenAPI 3.1) con las rutas de la sección 10 de la especificación, +JWT, ETag, If-Match, Idempotency-Key, errores problem+json y cursores. +Validar automáticamente contrato y ejemplos antes de cerrar T02. diff --git a/examples/flows/README.md b/examples/flows/README.md new file mode 100644 index 0000000..cb32445 --- /dev/null +++ b/examples/flows/README.md @@ -0,0 +1,5 @@ +# Flujos pendientes de T14 + +Entregar credito-v1.json, credito-v2-licencia.json y compra-v1.json completos y publicables. +La versión 2 incorpora licencia antes del buró utilizando capacidades existentes. +No presentar fragmentos como flujos funcionales. diff --git a/examples/http/health.http b/examples/http/health.http new file mode 100644 index 0000000..f6846c4 --- /dev/null +++ b/examples/http/health.http @@ -0,0 +1,8 @@ +### Salud mínima; requiere la aplicación iniciada y PostgreSQL disponible. +GET http://localhost:8080/actuator/health + +### Readiness comprueba PostgreSQL. +GET http://localhost:8080/actuator/health/readiness + +### Liveness no consulta proveedores. +GET http://localhost:8080/actuator/health/liveness diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..bd8896b --- /dev/null +++ b/mvnw @@ -0,0 +1,295 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..92450f9 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4612928 --- /dev/null +++ b/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 4.1.2 + + + com.organizacion + orquestador-nmp + 0.1.0-SNAPSHOT + Orquestador NMP + Orquestador configurable de solicitudes con ejecución durable en PostgreSQL + + 21 + UTF-8 + UTF-8 + 5.13.4 + + + org.springframework.bootspring-boot-starter-webmvc + org.springframework.bootspring-boot-starter-validation + org.springframework.bootspring-boot-starter-data-jpa + org.springframework.bootspring-boot-starter-jdbc + org.springframework.bootspring-boot-starter-security-oauth2-resource-server + org.springframework.bootspring-boot-starter-actuator + org.springframework.bootspring-boot-starter-flyway + org.flywaydbflyway-database-postgresql + org.postgresqlpostgresqlruntime + org.junit.jupiterjunit-jupitertest + + + + org.springframework.bootspring-boot-maven-plugin + + org.apache.maven.pluginsmaven-enforcer-plugin3.6.2 + build-requirementsenforce + + [21,22)Configura JAVA_HOME con JDK 21. + [3.9.12,4) + + + + + org.apache.maven.pluginsmaven-surefire-plugin3.5.4 + + + org.apache.maven.pluginsmaven-failsafe-plugin3.5.4 + integration-testverify + + + + diff --git a/schemas/README.md b/schemas/README.md new file mode 100644 index 0000000..e2154f6 --- /dev/null +++ b/schemas/README.md @@ -0,0 +1,5 @@ +# Schemas pendientes de T02 + +Crear flow-definition.schema.json y schemas de entrada/salida, datos y tareas. +Separar schemaVersion de la versión del flujo. Validar AST tipado y límites. +Una configuración válida estructuralmente aún requiere la validación semántica T06. diff --git a/specs/ARCHITECTURE.md b/specs/ARCHITECTURE.md new file mode 100644 index 0000000..30460cf --- /dev/null +++ b/specs/ARCHITECTURE.md @@ -0,0 +1,31 @@ +# Arquitectura + +Un único módulo Maven, paquete raíz `com.organizacion.orchestrator`. La implementación sigue la especificación; esta entrega establece sus fronteras, no implementa todavía el motor. + +| Capacidad | Responsabilidad | +|---|---| +| flow | Borradores, validación, publicación, activación y caché inmutable | +| request | Instancias, acceso por recurso y eventos disponibles | +| execution | Comandos idempotentes, cola PostgreSQL, leases, fencing y recuperación | +| decision | AST tipado y decisiones puras con BigDecimal | +| action | Registro de handlers y mappings permitidos | +| integration | Catálogo confiable, HTTP y normalización de contratos | +| document | Referencias verificadas; sin almacenamiento binario | +| audit | Historial inmutable y auditoría administrativa | +| shared | Errores, identidad, reloj y configuración transversal | + +Cada capacidad crecerá en `domain`, `application`, `infrastructure` y `api` cuando la tarea lo requiera. Dominio no depende de HTTP/JPA. API usa DTOs separados. Infraestructura implementa puertos. No crear entidades o interfaces ficticias para aparentar completar T04. + +## Fronteras transaccionales + +1. Aceptar un evento en transacción corta: acceso, bloqueo de solicitud, idempotencia, revisión, guardia, ejecución PENDING y auditoría. +2. Worker reclama con SKIP LOCKED y confirma antes de HTTP. Cada acción guarda checkpoint y clave externa estable. +3. Finalizar en transacción: validar lease/token/revisión, aplicar datos/estado y guardar resultado y auditoría atómicamente. + +Una sola ejecución activa por solicitud. La incertidumbre de un efecto externo exige conciliación. No prometer exactly-once. Estados de ejecución y negocio son independientes. + +## Configuración y acceso + +Publicación inmutable; instancias fijan flow_id. Activo se resuelve coherentemente en PostgreSQL. JSONB extiende datos; no permite scripts, clases, URLs o credenciales arbitrarios. Cambios de licencia con capacidades existentes no requieren columnas ni condiciones Java específicas. + +JWT se valida por firma, expiración, issuer y audiencia. Actualmente sólo se expone salud mínima. Roles, scope y propiedad se implementarán antes de los endpoints de negocio. Sin entidades abiertas durante llamadas HTTP (`open-in-view=false`). diff --git a/specs/DECISIONS.md b/specs/DECISIONS.md new file mode 100644 index 0000000..21b7b0f --- /dev/null +++ b/specs/DECISIONS.md @@ -0,0 +1,43 @@ +# Decisiones técnicas + +## ADR-001 — Base reproducible (2026-10-26) + +Estado: seleccionada; verificación de runtime JDK 21 y PostgreSQL pendiente en T01. + +| Componente | Selección | Motivo | +|---|---|---| +| Java | 21, `release=21`, Enforcer `[21,22)` | Versión requerida por la especificación | +| Spring Boot / BOM | 4.1.2 / spring-boot-dependencies 4.1.2 | Release estable verificada en documentación oficial y Maven Central; compatible con Java 21 | +| Maven | 3.9.12 | Wrapper oficial con distribución fija | +| Maven Wrapper | 3.3.4, only-script | Scripts oficiales, sin JAR de wrapper en el repositorio | +| PostgreSQL | 17.11-bookworm | Major 17 requerido y parche fijo; imagen pendiente de pull/verificación con Docker | +| JUnit | 5.13.4 | Mantener JUnit 5 requerido; sobrescribir junit-jupiter.version del BOM | +| Surefire / Failsafe | 3.5.4 | Separar pruebas unitarias de integración; verify ejecuta ambas | +| Maven Enforcer | 3.6.2 | Fallar explícitamente si el JDK no es 21 | + +MVC, Security, Hibernate, Flyway, JDBC PostgreSQL y demás dependencias se resuelven desde el BOM fijado. No declarar versiones dinámicas ni snapshots de dependencias. `0.1.0-SNAPSHOT` es la versión de desarrollo de este proyecto, no una dependencia externa. + +Spring Boot 4 usa starters modulares: `webmvc`, `security-oauth2-resource-server` y `flyway`. No se añade `springdoc` hasta validar compatibilidad en T02. OpenAPI será contractual. Testcontainers y servidor de mocks se fijarán y verificarán al incorporarse en T03/T11. No adoptar automáticamente dependencias de test de Boot que cambien JUnit 5; comprobar integración con Spring Test al introducirla. + +Fuentes consultadas el 2026-10-26: + +- [Requisitos oficiales de Spring Boot](https://docs.spring.io/spring-boot/system-requirements.html). +- [Dependencias gestionadas](https://docs.spring.io/spring-boot/appendix/dependency-versions/coordinates.html). +- [POM publicado de Boot 4.1.2](https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-starter-parent/4.1.2/spring-boot-starter-parent-4.1.2.pom). +- [PostgreSQL: descargas y release 17.11](https://www.postgresql.org/download/). + +Alternativa: Boot 3.5, compatible con Java 21, pero se prefiere la línea estable actual para un proyecto nuevo. No cambia la semántica del motor. La compatibilidad de bibliotecas futuras se verificará antes de incorporarlas. + +## ADR-002 — Alcance de esta entrega + +El pedido actual comprende arquetipo, plan y seguimiento. Se prepara T01 y se documenta T02–T17; no se implementan anticipadamente el dominio, contratos incompletos ni endpoints con respuestas simuladas. Las tareas se cierran con sus verificaciones, no por existencia de carpetas. + +La configuración local conserva JWT y exige parámetros externos. No se incluye issuer falso ni contraseña predeterminada. El Compose actual es exclusivamente de desarrollo; no es un despliegue productivo. + +## ADR-003 — Docker del servicio con PostgreSQL externo (2026-10-26) + +Por instrucción expresa del usuario se adelanta el empaquetado del arquetipo: Compose contiene sólo orquestador y sustituye el anterior servicio PostgreSQL. No administra base ni volúmenes externos. T17 no se considera terminado: la demo integral sigue pendiente de sus dependencias. + +Dockerfile multietapa con Temurin 21.0.12.1_1 (JDK para construir, JRE para ejecutar), tags verificados en el [catálogo oficial](https://github.com/docker-library/official-images/blob/master/library/eclipse-temurin). Runtime no root y healthcheck de readiness. Maven Wrapper construye con package y ejecuta pruebas unitarias; verify con integración permanece en CI. El contexto permite sólo fuentes, POM y Wrapper; excluye .env, target y archivos del IDE. + +Compose exige DB_URL/usuario/contraseña y parámetros JWT, conserva el puerto interno 8080 y publica localhost:8080 por defecto. Se incluye host-gateway para PostgreSQL en el anfitrión. La construcción y conexión real están pendientes de un entorno Docker y valores externos. Referencias: [build multietapa](https://docs.docker.com/build/building/multi-stage/) e [interpolación de Compose](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/). diff --git a/specs/IMPLEMENTATION_PLAN.md b/specs/IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000..9e70d94 --- /dev/null +++ b/specs/IMPLEMENTATION_PLAN.md @@ -0,0 +1,231 @@ +# Plan de implementación + +Fecha: 2026-10-26. Fuente normativa: [especificación](../Specs_Orquestador_SpringBoot_PostgreSQL.md), secciones 13–16. + +El alcance actual es crear el arquetipo y dejar preparada la ejecución del plan. La implementación funcional continúa por tareas autorizadas. Una sola tarea principal IN_PROGRESS; elegir la primera pendiente con dependencias DONE. Un bloqueo externo se documenta y no equivale a una prueba aprobada. + +## Secuencia y criterios + +| ID | Tarea | Dependencias | Criterio de cierre | +|---|---|---|---| +| T01 | Bootstrap y ADR de versiones | — | Wrapper reproducible, compilación y health local comprobados | +| T02 | Contratos OpenAPI y JSON Schema del DSL | T01 | OpenAPI 3.1 y schemas completos; ejemplos validados | +| T03 | Modelo PostgreSQL y Flyway | T02 | Restricciones e inmutabilidad verificadas en PostgreSQL real | +| T04 | Modelo de dominio y puertos | T02,T03 | Dominio sin HTTP/JPA y contratos tipados | +| T05 | Evaluador de decisiones | T04 | A04–A06 y errores de evaluación comprobados | +| T06 | Validador, publicación y activación | T03,T04,T05 | A16–A17; catálogo resoluble y publicación consistente | +| T07 | Seguridad y autorización por recurso | T04 | A15; rutas protegidas antes de exposición | +| T08 | Crear y consultar solicitudes | T06,T07 | Creación atómica e idempotente, versión fijada | +| T09 | Aceptar eventos idempotentes | T08 | A07–A09; aceptación durable sin HTTP | +| T10 | Worker PostgreSQL y leases | T09 | Cola durable multiinstancia; worker obsoleto no confirma | +| T11 | Handlers e integraciones HTTP | T10 | A01–A03; fallos técnicos separados del resultado de negocio | +| T12 | Reintentos y conciliación | T11 | A12–A14,A19; efectos inciertos no liberan solicitud | +| T13 | Documentos y tareas para front | T11,T12 | A11,A20; INVALID conserva tarea, error técnico no equivale a INVALID | +| T14 | Flujos demostrativos v1/v2 | T05,T06,T13 | A10,A18; dos tipos de solicitud publicables | +| T15 | Auditoría y observabilidad | T09,T10,T11,T12,T13,T14 | Auditoría atómica desde T06 y operación observable | +| T16 | Verificación integral | T15 | Todos los casos obligatorios pasan en entorno reproducible | +| T17 | Empaquetado y documentación | T16 | Checkout limpio reproduce definición final de terminado | + +## T01 — Bootstrap y ADR de versiones + +Dependencias: —. Entregables: pom.xml, mvnw*, .mvn, configuración, ADR-001. + +- [ ] Fijar versiones y BOM. +- [ ] Generar Wrapper oficial. +- [ ] Preparar paquetes y configuración. +- [ ] Ejecutar compilación y smoke test de salud con JDK 21/PostgreSQL. + +Verificación: JDK 21; ./mvnw clean verify; arranque local y HTTP health. Cierre: Wrapper reproducible, compilación y health local comprobados. + +## T02 — Contratos OpenAPI y JSON Schema del DSL + +Dependencias: T01. Entregables: contracts/openapi.yaml, schemas/, ejemplos contractuales. + +- [ ] Definir rutas, DTOs y errores. +- [ ] Definir schema del flujo/AST/tareas. +- [ ] Validar ejemplos y casos inválidos automáticamente. + +Verificación: Validador OpenAPI/JSON Schema dentro de verify. Cierre: OpenAPI 3.1 y schemas completos; ejemplos validados. + +## T03 — Modelo PostgreSQL y Flyway + +Dependencias: T02. Entregables: db/migration/, pruebas *IT. + +- [ ] Crear tablas, FKs e índices. +- [ ] Añadir idempotencia de creación/admin_audit/reconciliation_resolution. +- [ ] Proteger inmutabilidad e historial con roles y triggers. +- [ ] Probar migraciones desde cero. + +Verificación: Testcontainers PostgreSQL, rollback y migraciones. Cierre: Restricciones e inmutabilidad verificadas en PostgreSQL real. + +## T04 — Modelo de dominio y puertos + +Dependencias: T02,T03. Entregables: flow/, request/, execution/, decision/, action/. + +- [ ] Definir records y errores estables. +- [ ] Separar DTOs y entidades. +- [ ] Implementar puertos DecisionEvaluator, ActionHandler y FlowDefinitionValidator. + +Verificación: Pruebas de dominio y dependencias entre capas. Cierre: Dominio sin HTTP/JPA y contratos tipados. + +## T05 — Evaluador de decisiones + +Dependencias: T04. Entregables: decision/, pruebas de AST. + +- [ ] Implementar operadores/JSON Pointer/BigDecimal. +- [ ] Distinguir null y ausente. +- [ ] Aplicar límites y cortocircuito. +- [ ] Resolver UNIQUE/FIRST y ambigüedad. + +Verificación: JUnit parametrizado: 71/70/61/60, tipos y solapamientos. Cierre: A04–A06 y errores de evaluación comprobados. + +## T06 — Validador, publicación y activación + +Dependencias: T03,T04,T05. Entregables: flow/, admin_audit, pruebas *IT. + +- [ ] Validar grafo, mappings, roles y capacidades. +- [ ] Editar borradores con revision. +- [ ] Publicar checksum canónico e inmutabilidad. +- [ ] Activar/retirar con bloqueos y auditoría. + +Verificación: Pruebas PostgreSQL de carreras publicación/activación/retiro. Cierre: A16–A17; catálogo resoluble y publicación consistente. + +## T07 — Seguridad y autorización por recurso + +Dependencias: T04. Entregables: shared/infrastructure/security/, autorización por capacidad. + +- [ ] Validar JWT real (firma/issuer/audience/exp). +- [ ] Mapear permisos técnicos y roles. +- [ ] Comprobar propiedad/scope en cada recurso. +- [ ] Separar rol DB de ejecución. + +Verificación: Tokens firmados válidos/expirados, otro issuer/audience, scope/rol ajenos. Cierre: A15; rutas protegidas antes de exposición. + +## T08 — Crear y consultar solicitudes + +Dependencias: T06,T07. Entregables: request/, repositorios y API. + +- [ ] Resolver y fijar flujo activo. +- [ ] Validar datos iniciales. +- [ ] Idempotencia durable de creación. +- [ ] Exponer consulta y ETag con acceso por recurso. + +Verificación: IT creación repetida/conflictiva y activación concurrente. Cierre: Creación atómica e idempotente, versión fijada. + +## T09 — Aceptar eventos idempotentes + +Dependencias: T08. Entregables: execution/api, aceptación, auditoría. + +- [ ] Bloquear solicitud. +- [ ] Verificar hash antes de revisión. +- [ ] Validar If-Match/rol/guardia. +- [ ] Insertar PENDING, revision y auditoría atómicamente. +- [ ] Responder 202/Location. + +Verificación: IT duplicados, conflictos, concurrencia y rollback. Cierre: A07–A09; aceptación durable sin HTTP. + +## T10 — Worker PostgreSQL y leases + +Dependencias: T09. Entregables: execution/infrastructure, worker y repositorios. + +- [ ] Reclamar con SKIP LOCKED. +- [ ] Renovar lease con tiempo DB. +- [ ] Exigir owner/token en escrituras. +- [ ] Checkpoints por acción y recuperación inicial. + +Verificación: IT dos workers, lease vencido y checkpoint; no @Async en memoria. Cierre: Cola durable multiinstancia; worker obsoleto no confirma. + +## T11 — Handlers e integraciones HTTP + +Dependencias: T10. Entregables: action/, integration/, mocks, compose.yaml. + +- [ ] Implementar catálogo confiable y REGISTER_DOCUMENT/INVOKE_INTEGRATION/NO_OP sólo simulación. +- [ ] Fijar contratos/perfiles. +- [ ] Mapear entradas/salidas y timeouts. +- [ ] Añadir mocks. + +Verificación: Mocks de cliente encontrado/no encontrado, timeout y salida inválida. Cierre: A01–A03; fallos técnicos separados del resultado de negocio. + +## T12 — Reintentos y conciliación + +Dependencias: T11. Entregables: execution/, endpoint resolve, pruebas *IT. + +- [ ] Limitar intentos/backoff/jitter. +- [ ] Recuperar leases según clasificación de efecto. +- [ ] Tratar UNKNOWN. +- [ ] Conciliar con evidencia y autorización/idempotencia. + +Verificación: Fault injection antes/después de envío, checkpoint y commit. Cierre: A12–A14,A19; efectos inciertos no liberan solicitud. + +## T13 — Documentos y tareas para front + +Dependencias: T11,T12. Entregables: document/, request/available-events. + +- [ ] Verificar pertenencia/tipo/MIME/tamaño/checksum/antimalware. +- [ ] Guardar referencias. +- [ ] Exponer schemas/UI y bloqueos. +- [ ] Impedir escritura de resultados del proveedor. + +Verificación: Documento ajeno/inválido, ejecución activa y payload con score. Cierre: A11,A20; INVALID conserva tarea, error técnico no equivale a INVALID. + +## T14 — Flujos demostrativos v1/v2 + +Dependencias: T05,T06,T13. Entregables: examples/flows/, examples/http/. + +- [ ] Crear crédito v1, crédito v2 con licencia y compra v1 completos. +- [ ] Publicar/activar y recorrer. +- [ ] Demostrar coexistencia de versiones sin cambios Java/columnas. + +Verificación: Recorridos completos de v1/v2, licencia válida/inválida y fronteras. Cierre: A10,A18; dos tipos de solicitud publicables. + +## T15 — Auditoría y observabilidad + +Dependencias: T09,T10,T11,T12,T13,T14. Entregables: audit/, métricas, logs, health. + +- [ ] Completar consultas paginadas/filtradas. +- [ ] Métricas sin cardinalidad por solicitud. +- [ ] Logs sin PII. +- [ ] Readiness/liveness y apagado ordenado. + +Verificación: Rollback/auditoría, acceso a historial, métricas y apagado. Cierre: Auditoría atómica desde T06 y operación observable. + +## T16 — Verificación integral + +Dependencias: T15. Entregables: src/test/, CI, matriz A01–A20. + +- [ ] Automatizar A01–A20. +- [ ] Reinicios y concurrencia deterministas. +- [ ] Verificar fronteras de rollback y seguridad. +- [ ] Conservar reportes. + +Verificación: ./mvnw clean verify con Docker, sin skips de integración. Cierre: Todos los casos obligatorios pasan en entorno reproducible. + +## T17 — Empaquetado y documentación + +Dependencias: T16. Entregables: Dockerfile, compose.yaml, README, RUNBOOK, examples/http, CI. + +- [ ] Construir imagen y Compose completos. +- [ ] Incluir mocks e issuer local documentado. +- [ ] Ejecutar demo desde checkout limpio. +- [ ] Documentar límites, recuperación y contratos reales pendientes. + +Verificación: Build de imagen, compose, verify y demo completa de sección 16. Cierre: Checkout limpio reproduce definición final de terminado. + +## Reglas de ejecución + +Antes de codificar, revisar criterios y prueba pertinente. Al cerrar, registrar fecha, archivos, comando exacto y resultado en PROGRESS. Mantener actualizados contrato, migraciones y pruebas cuando cambie el comportamiento. Auditoría mínima acompaña cada escritura desde T06; T15 completa consultas y operación. T06 puede implementar servicios internos, pero sus endpoints no se exponen antes de T07. No modificar semántica de seguridad/idempotencia/versionado sin ADR. + +## Matriz de aceptación + +| Casos | Tarea responsable | Evidencia final | +|---|---|---| +| A01–A03 | T11 | Cliente encontrado/no encontrado/fallo técnico | +| A04–A06 | T05 | Fronteras, contratos inválidos y UNIQUE ambiguo | +| A07–A09 | T09 | Idempotencia y concurrencia de eventos | +| A10,A18 | T14 | Coexistencia v1/v2 y licencia configurable | +| A11,A20 | T13 | Documentos y protección de resultados | +| A12–A14,A19 | T12 | Recuperación, efectos inciertos y rollback | +| A15 | T07 | Autorización por rol/scope/propiedad | +| A16–A17 | T06 | Inmutabilidad y activación/retiro concurrentes | + +T16 vuelve a ejecutar la matriz completa; pruebas unitarias no sustituyen PostgreSQL real. La integración real con proveedores permanece pendiente si faltan contrato/credenciales, aunque los mocks pasen. diff --git a/specs/PROGRESS.md b/specs/PROGRESS.md new file mode 100644 index 0000000..7465d8c --- /dev/null +++ b/specs/PROGRESS.md @@ -0,0 +1,53 @@ +# Seguimiento de implementación + +Actualizado: 2026-10-26. Entrega actual: arquetipo y planificación. El motor no está implementado. + +Estados permitidos: PENDING, IN_PROGRESS, BLOCKED, DONE. No más de una tarea principal IN_PROGRESS. Consultar IMPLEMENTATION_PLAN para subtareas y criterios. + +| ID | Estado | Dependencias | Archivos | Comandos/checks | Resultado | Bloqueo | Siguiente paso | +|---|---|---|---|---|---|---|---| +| T01 | BLOCKED | — | pom.xml, mvnw*, .mvn, configuración, ADR-001 | Ver evidencia E01–E04 | Arquetipo creado; package con JDK 24 y release 21 aprobado; cierre pendiente | JDK 21 y Docker/PostgreSQL no disponibles para validación local completa | Validar con JDK 21 y PostgreSQL; añadir smoke test de salud | +| T02 | PENDING | T01 | contracts/openapi.yaml, schemas/, ejemplos contractuales | Pendiente: Validador OpenAPI/JSON Schema dentro de verify | No ejecutado | Dependencias sin completar | Definir rutas, DTOs y errores | +| T03 | PENDING | T02 | db/migration/, pruebas *IT | Pendiente: Testcontainers PostgreSQL, rollback y migraciones | No ejecutado | Dependencias sin completar | Crear tablas, FKs e índices | +| T04 | PENDING | T02,T03 | flow/, request/, execution/, decision/, action/ | Pendiente: Pruebas de dominio y dependencias entre capas | No ejecutado | Dependencias sin completar | Definir records y errores estables | +| T05 | PENDING | T04 | decision/, pruebas de AST | Pendiente: JUnit parametrizado: 71/70/61/60, tipos y solapamientos | No ejecutado | Dependencias sin completar | Implementar operadores/JSON Pointer/BigDecimal | +| T06 | PENDING | T03,T04,T05 | flow/, admin_audit, pruebas *IT | Pendiente: Pruebas PostgreSQL de carreras publicación/activación/retiro | No ejecutado | Dependencias sin completar | Validar grafo, mappings, roles y capacidades | +| T07 | PENDING | T04 | shared/infrastructure/security/, autorización por capacidad | Pendiente: Tokens firmados válidos/expirados, otro issuer/audience, scope/rol ajenos | No ejecutado | Dependencias sin completar | Validar JWT real (firma/issuer/audience/exp) | +| T08 | PENDING | T06,T07 | request/, repositorios y API | Pendiente: IT creación repetida/conflictiva y activación concurrente | No ejecutado | Dependencias sin completar | Resolver y fijar flujo activo | +| T09 | PENDING | T08 | execution/api, aceptación, auditoría | Pendiente: IT duplicados, conflictos, concurrencia y rollback | No ejecutado | Dependencias sin completar | Bloquear solicitud | +| T10 | PENDING | T09 | execution/infrastructure, worker y repositorios | Pendiente: IT dos workers, lease vencido y checkpoint; no @Async en memoria | No ejecutado | Dependencias sin completar | Reclamar con SKIP LOCKED | +| T11 | PENDING | T10 | action/, integration/, mocks, compose.yaml | Pendiente: Mocks de cliente encontrado/no encontrado, timeout y salida inválida | No ejecutado | Dependencias sin completar | Implementar catálogo confiable y REGISTER_DOCUMENT/INVOKE_INTEGRATION/NO_OP sólo simulación | +| T12 | PENDING | T11 | execution/, endpoint resolve, pruebas *IT | Pendiente: Fault injection antes/después de envío, checkpoint y commit | No ejecutado | Dependencias sin completar | Limitar intentos/backoff/jitter | +| T13 | PENDING | T11,T12 | document/, request/available-events | Pendiente: Documento ajeno/inválido, ejecución activa y payload con score | No ejecutado | Dependencias sin completar | Verificar pertenencia/tipo/MIME/tamaño/checksum/antimalware | +| T14 | PENDING | T05,T06,T13 | examples/flows/, examples/http/ | Pendiente: Recorridos completos de v1/v2, licencia válida/inválida y fronteras | No ejecutado | Dependencias sin completar | Crear crédito v1, crédito v2 con licencia y compra v1 completos | +| T15 | PENDING | T09,T10,T11,T12,T13,T14 | audit/, métricas, logs, health | Pendiente: Rollback/auditoría, acceso a historial, métricas y apagado | No ejecutado | Dependencias sin completar | Completar consultas paginadas/filtradas | +| T16 | PENDING | T15 | src/test/, CI, matriz A01–A20 | Pendiente: ./mvnw clean verify con Docker, sin skips de integración | No ejecutado | Dependencias sin completar | Automatizar A01–A20 | +| T17 | PENDING | T16 | Dockerfile, compose.yaml, README, RUNBOOK, examples/http, CI | Pendiente: Build de imagen, compose, verify y demo completa de sección 16 | No ejecutado | Dependencias sin completar | Construir imagen y Compose completos | + +## Evidencias — 2026-10-26 + +- E01: `java -version` y `mvn -version`: Java 17.0.12 y Maven 3.9.12. Se encontró también Corretto 24; no JDK 21 en las ubicaciones revisadas. `Get-Command docker` no encontró Docker en PATH. +- E02: `mvn -B -N org.apache.maven.plugins:maven-wrapper-plugin:3.3.4:wrapper '-Dmaven=3.9.12' '-Dtype=only-script'`: BUILD SUCCESS. Wrapper oficial creado con distribución 3.9.12. Se corrigió una primera invocación cuya propiedad sin comillas fue fragmentada por PowerShell. +- E03: con JAVA_HOME apuntando temporalmente a Corretto 24, `.\mvnw.cmd -B '-Denforcer.skip=true' package`: BUILD SUCCESS; JAR ejecutable generado con compilación release 21. Fue una comprobación limitada; no equivale a build/runtime con JDK 21. No modificar Enforcer ni CI para omitir el requisito. No había pruebas ejecutables en esta comprobación. +- E04: POM/BOM Boot 4.1.2 y dependencias resueltos desde Maven Central. Arranque, health, Compose, Testcontainers y A01–A20 NO ejecutados. La imagen PostgreSQL fijada está pendiente de pull. +- E05: `.\mvnw.cmd -B --no-transfer-progress validate` con Java 17: rechazo esperado de Enforcer, mensaje `Configura JAVA_HOME con JDK 21.` La regla impide compilar accidentalmente con el runtime incorrecto. +- E06: `.\mvnw.cmd -B --no-transfer-progress dependency:tree '-Dincludes=org.junit.jupiter:*,org.junit.platform:*'`: BUILD SUCCESS; Jupiter 5.13.4 y Platform 1.13.4 confirmados. +- E07: lectura XML del POM y comprobación de IDs: T01–T17 presentes en plan/seguimiento. Cabecera binaria de OrchestratorApplication.class: major 65 (Java 21). La inspección inicial con javap quedó impedida por acceso del sandbox a java.security; la lectura directa de la cabecera confirmó el bytecode. + +## Próxima sesión + +### Preparación Docker solicitada — 2026-10-26 + +- Archivos: Dockerfile, .dockerignore, compose.yaml, .env.example, README.md, RUNBOOK.md y ADR-003. +- Entregado: imagen multietapa Java 21, usuario no root, healthcheck y un único servicio conectado a PostgreSQL externo. No se incluyó PostgreSQL en Compose. No se modificó ningún .env del usuario. +- Verificación: análisis con PyYAML 6.0.3 y comprobaciones estáticas aprobaron YAML, servicio único, ausencia de volúmenes/dependencias locales, seis variables obligatorias, puerto interno y mapeo al anfitrión. Herramienta temporal en .tools, fuera de Git y del contexto Docker. +- Bloqueo: Docker no disponible en PATH ni en la ubicación habitual de Docker Desktop; no se ejecutaron docker build, compose config ni arranque/conexión a PostgreSQL externo. Esta comprobación estática no sustituye esas pruebas. +- Siguiente paso: completar .env, ejecutar docker compose config --quiet y docker compose up --build -d; comprobar readiness y logs. T01 continúa BLOCKED y T17 PENDING hasta satisfacer sus criterios completos. + +1. Leer este registro, especificación y cambios del repositorio. +2. Resolver T01: configurar JDK 21, PostgreSQL y valores de entorno JWT; incorporar y ejecutar smoke test, `clean verify` y health local. +3. Registrar evidencia y marcar T01 DONE sólo cuando cumpla todos sus criterios; entonces iniciar T02. + +## Plantilla de evidencia adicional + +`Fecha | ID | Archivos | Comando/check exacto | Resultado y reporte | Bloqueo | Próximo paso` diff --git a/specs/RUNBOOK.md b/specs/RUNBOOK.md new file mode 100644 index 0000000..28677a7 --- /dev/null +++ b/specs/RUNBOOK.md @@ -0,0 +1,73 @@ +# Docker con PostgreSQL externo + +## Preparación + +Requiere Docker con contenedores Linux y Compose v2. La imagen compila con JDK 21 y ejecuta con JRE 21; no requiere Java/Maven en el anfitrión. PostgreSQL y el proveedor JWT son externos. + +1. Crear base y usuario en PostgreSQL 17. Permitir conexión desde Docker según firewall, listen_addresses y pg_hba.conf del servidor. +2. Copiar `.env.example` a `.env`, sin sobrescribir uno ya configurado. +3. Completar DB_URL, DB_USERNAME, DB_PASSWORD, JWT_ISSUER_URI, JWT_JWK_SET_URI y JWT_AUDIENCE. + +| Variable | Uso | +|---|---| +| DB_URL | JDBC de la base externa: jdbc:postgresql://db.interna:5432/orquestador | +| DB_USERNAME / DB_PASSWORD | Credenciales del usuario de la base existente | +| JWT_ISSUER_URI | Issuer exacto de los tokens | +| JWT_JWK_SET_URI | Endpoint JWKS correspondiente, accesible desde Docker | +| JWT_AUDIENCE | Audiencia esperada | +| APP_PORT | Puerto publicado; predeterminado 8080 | +| APP_BIND_ADDRESS | Interfaz publicada; predeterminado 127.0.0.1; usar 0.0.0.0 para acceso remoto | + +Si PostgreSQL corre en el anfitrión, usar `jdbc:postgresql://host.docker.internal:5432/orquestador`. Compose incluye host-gateway para Linux. `localhost` dentro del contenedor apunta al propio contenedor. La misma consideración aplica al endpoint JWKS; conservar el issuer exacto de los tokens. + +Si el proveedor requiere TLS, añadir los parámetros JDBC requeridos, por ejemplo `?sslmode=verify-full`; montar el certificado raíz y configurar `sslrootcert` cuando corresponda. Contraseñas con `$` pueden escribirse entre comillas simples en `.env` para conservar su valor literal. + +## Levantar y comprobar + +```powershell +Copy-Item .env.example .env +# Editar .env con valores del entorno externo. +docker compose config --quiet +docker compose up --build -d +docker compose ps +docker compose logs -f orquestador +curl.exe --fail http://localhost:8080/actuator/health/readiness +curl.exe --fail http://localhost:8080/actuator/health/liveness +``` + +En Linux/macOS usar `cp` y `curl`. Compose carga `.env` y pasa explícitamente las variables al contenedor. Las variables exportadas en la sesión tienen precedencia. `.env` está excluido de la imagen. + +Salud debe devolver HTTP 200 y status UP. Readiness comprueba PostgreSQL; liveness no depende de proveedores. El healthcheck interno usa 8080 aunque APP_PORT cambie. Otra ruta sin token debe devolver 401. No hay endpoints de negocio implementados todavía. + +El build ejecuta `package`, incluyendo pruebas unitarias. La suite de integración se ejecuta fuera del build con `clean verify` y Docker disponible. No se monta el socket Docker dentro del build. Se requiere acceso a Maven Central, Docker Hub y repositorios Ubuntu durante la construcción. + +## Operación + +```powershell +docker compose up --build -d # Reconstruir tras cambios de código +docker compose up -d # Aplicar cambios en .env +docker compose stop # Detener el servicio +docker compose down # Eliminar contenedor y red de Compose +``` + +Estos comandos no administran la base externa. Flyway permanece habilitado y aplicará las migraciones incluidas al arrancar; actualmente no existen migraciones SQL de negocio. La base debe existir antes del arranque. La separación de roles de migración/ejecución queda pendiente de T03/T07. + +## Ejecución desde el IDE + +Configurar JDK 21 y las seis variables DB/JWT en la sesión o Run Configuration. Spring Boot no importa `.env` automáticamente fuera de Compose. + +```powershell +.\mvnw.cmd clean verify +.\mvnw.cmd spring-boot:run '-Dspring-boot.run.profiles=local' +``` + +## Diagnóstico + +- Variable faltante: completar el valor requerido en `.env`. +- Conexión rechazada/timeout: revisar DNS, puerto, firewall y acceso desde Docker; no usar localhost para el anfitrión. +- Error de autenticación PostgreSQL: comprobar usuario, contraseña y permisos. +- Readiness DOWN/unhealthy: consultar logs y conectividad PostgreSQL. Un healthcheck fallido marca el estado, no reinicia automáticamente. +- JWT inválido: revisar issuer/audiencia y acceso a JWKS. +- Fallo de build: comprobar acceso a los repositorios de dependencias. + +Registrar evidencia en PROGRESS. No considerar verificado el arranque si no se ejecutó contra Docker y PostgreSQL externo. diff --git a/src/main/java/com/organizacion/orchestrator/OrchestratorApplication.java b/src/main/java/com/organizacion/orchestrator/OrchestratorApplication.java new file mode 100644 index 0000000..e2a1a77 --- /dev/null +++ b/src/main/java/com/organizacion/orchestrator/OrchestratorApplication.java @@ -0,0 +1,11 @@ +package com.organizacion.orchestrator; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class OrchestratorApplication { + public static void main(String[] args) { + SpringApplication.run(OrchestratorApplication.class, args); + } +} diff --git a/src/main/java/com/organizacion/orchestrator/action/package-info.java b/src/main/java/com/organizacion/orchestrator/action/package-info.java new file mode 100644 index 0000000..bbbcfe2 --- /dev/null +++ b/src/main/java/com/organizacion/orchestrator/action/package-info.java @@ -0,0 +1,2 @@ +/** Capacidades genéricas y registro de handlers. */ +package com.organizacion.orchestrator.action; diff --git a/src/main/java/com/organizacion/orchestrator/audit/package-info.java b/src/main/java/com/organizacion/orchestrator/audit/package-info.java new file mode 100644 index 0000000..21f0eb8 --- /dev/null +++ b/src/main/java/com/organizacion/orchestrator/audit/package-info.java @@ -0,0 +1,2 @@ +/** Historial inmutable y auditoría. */ +package com.organizacion.orchestrator.audit; diff --git a/src/main/java/com/organizacion/orchestrator/decision/package-info.java b/src/main/java/com/organizacion/orchestrator/decision/package-info.java new file mode 100644 index 0000000..6f11c9a --- /dev/null +++ b/src/main/java/com/organizacion/orchestrator/decision/package-info.java @@ -0,0 +1,2 @@ +/** Decisiones puras sobre un AST tipado. */ +package com.organizacion.orchestrator.decision; diff --git a/src/main/java/com/organizacion/orchestrator/document/package-info.java b/src/main/java/com/organizacion/orchestrator/document/package-info.java new file mode 100644 index 0000000..067af7f --- /dev/null +++ b/src/main/java/com/organizacion/orchestrator/document/package-info.java @@ -0,0 +1,2 @@ +/** Referencias documentales verificadas. */ +package com.organizacion.orchestrator.document; diff --git a/src/main/java/com/organizacion/orchestrator/execution/package-info.java b/src/main/java/com/organizacion/orchestrator/execution/package-info.java new file mode 100644 index 0000000..29bb86f --- /dev/null +++ b/src/main/java/com/organizacion/orchestrator/execution/package-info.java @@ -0,0 +1,2 @@ +/** Comandos y ejecución durable con PostgreSQL. */ +package com.organizacion.orchestrator.execution; diff --git a/src/main/java/com/organizacion/orchestrator/flow/package-info.java b/src/main/java/com/organizacion/orchestrator/flow/package-info.java new file mode 100644 index 0000000..e6b9ca3 --- /dev/null +++ b/src/main/java/com/organizacion/orchestrator/flow/package-info.java @@ -0,0 +1,2 @@ +/** Configuración, publicación y activación de flujos. */ +package com.organizacion.orchestrator.flow; diff --git a/src/main/java/com/organizacion/orchestrator/integration/package-info.java b/src/main/java/com/organizacion/orchestrator/integration/package-info.java new file mode 100644 index 0000000..e70a4ad --- /dev/null +++ b/src/main/java/com/organizacion/orchestrator/integration/package-info.java @@ -0,0 +1,2 @@ +/** Catálogo confiable e integraciones HTTP. */ +package com.organizacion.orchestrator.integration; diff --git a/src/main/java/com/organizacion/orchestrator/request/package-info.java b/src/main/java/com/organizacion/orchestrator/request/package-info.java new file mode 100644 index 0000000..0a0be8d --- /dev/null +++ b/src/main/java/com/organizacion/orchestrator/request/package-info.java @@ -0,0 +1,2 @@ +/** Instancias y autorización por recurso. */ +package com.organizacion.orchestrator.request; diff --git a/src/main/java/com/organizacion/orchestrator/shared/infrastructure/security/SecurityConfiguration.java b/src/main/java/com/organizacion/orchestrator/shared/infrastructure/security/SecurityConfiguration.java new file mode 100644 index 0000000..a3597a9 --- /dev/null +++ b/src/main/java/com/organizacion/orchestrator/shared/infrastructure/security/SecurityConfiguration.java @@ -0,0 +1,23 @@ +package com.organizacion.orchestrator.shared.infrastructure.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration(proxyBeanMethods = false) +public class SecurityConfiguration { + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http + .csrf(csrf -> csrf.disable()) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/actuator/health", "/actuator/health/liveness", "/actuator/health/readiness").permitAll() + .anyRequest().authenticated()) + .oauth2ResourceServer(resourceServer -> resourceServer.jwt(Customizer.withDefaults())) + .build(); + } +} diff --git a/src/main/java/com/organizacion/orchestrator/shared/package-info.java b/src/main/java/com/organizacion/orchestrator/shared/package-info.java new file mode 100644 index 0000000..71d7e03 --- /dev/null +++ b/src/main/java/com/organizacion/orchestrator/shared/package-info.java @@ -0,0 +1,2 @@ +/** Contratos y capacidades transversales. */ +package com.organizacion.orchestrator.shared; diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml new file mode 100644 index 0000000..d30d9e9 --- /dev/null +++ b/src/main/resources/application-local.yaml @@ -0,0 +1,5 @@ +# Local conserva la validación JWT; proporciona un issuer de desarrollo real. +spring: + datasource: + url: ${DB_URL:jdbc:postgresql://localhost:5432/orquestador} + username: ${DB_USERNAME:orchestrator} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..3ecc92b --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,48 @@ +spring: + application: + name: orquestador-nmp + datasource: + url: ${DB_URL} + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + jpa: + open-in-view: false + hibernate: + ddl-auto: validate + properties: + hibernate: + jdbc: + time_zone: UTC + flyway: + enabled: true + locations: classpath:db/migration + clean-disabled: true + security: + oauth2: + resourceserver: + jwt: + issuer-uri: ${JWT_ISSUER_URI} + jwk-set-uri: ${JWT_JWK_SET_URI} + audiences: ${JWT_AUDIENCE} + lifecycle: + timeout-per-shutdown-phase: 30s + +server: + port: ${SERVER_PORT:8080} + shutdown: graceful + +management: + endpoints: + web: + exposure: + include: health + endpoint: + health: + show-details: never + probes: + enabled: true + group: + readiness: + include: readinessState,db + liveness: + include: livenessState diff --git a/src/main/resources/db/migration/README.md b/src/main/resources/db/migration/README.md new file mode 100644 index 0000000..9f5373f --- /dev/null +++ b/src/main/resources/db/migration/README.md @@ -0,0 +1,6 @@ +# Migraciones + +Las migraciones del modelo se implementan en T03, después del contrato T02. +Usar V1__descripcion.sql y versiones consecutivas; nunca editar una migración aplicada. +Separar el rol de migraciones del rol de ejecución al implementar privilegios. +No se incluye un esquema parcial que pueda confundirse con el modelo aprobado. diff --git a/src/test/java/com/organizacion/orchestrator/README.md b/src/test/java/com/organizacion/orchestrator/README.md new file mode 100644 index 0000000..1f7f690 --- /dev/null +++ b/src/test/java/com/organizacion/orchestrator/README.md @@ -0,0 +1,6 @@ +# Pruebas + +JUnit 5: clases *Test con Surefire durante test; clases *IT con Failsafe durante verify. +T01 incorpora el smoke test de arranque/health al disponer de Java 21 y PostgreSQL. +T03 incorpora Testcontainers PostgreSQL; nunca sustituir concurrencia o JSONB por H2. +No omitir integraciones silenciosamente cuando Docker no esté disponible.