525 lines
37 KiB
Markdown
525 lines
37 KiB
Markdown
# 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 <token>
|
||
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.
|