test(ocr): cover restart recovery boundary
This commit is contained in:
parent
07e6ed23fb
commit
4e4ac7132f
4 changed files with 77 additions and 27 deletions
|
|
@ -2,14 +2,23 @@
|
||||||
|
|
||||||
**Proyecto:** Workspace de tools IA para empresas
|
**Proyecto:** Workspace de tools IA para empresas
|
||||||
**Modulo:** RAG
|
**Modulo:** RAG
|
||||||
**Ultima actualizacion:** 2026-09-16
|
**Ultima actualizacion:** 2026-09-17
|
||||||
**Ultima modificacion por:** Subagent Transactional Review Decisions
|
**Ultima modificacion por:** Agente RAG 2
|
||||||
**Estado:** Activo
|
**Estado:** Activo
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Registro de sesion
|
## Registro de sesion
|
||||||
|
|
||||||
|
### 2026-09-17 - Agente RAG 2 - Verificacion de despliegue OCR
|
||||||
|
**Agent:** Agente RAG 2 · **Model:** openai/gpt-5.6-terra · **Session:** `ses_29bdbd003ffeLrLjUlFgnp08Y7`
|
||||||
|
**Work:** Verificado el despliegue de `07e6ed2` tras los deploys manuales de OCR y RAG: `/health` publico devuelve RAG, PostgreSQL, Qdrant y reconciliador sanos; OCR interno devuelve `live=ok`, `ready=true`, cola vacia y version `0.1.0`. La migracion `003_ocr_recovery_audit.sql` y su tabla estan presentes. Con token administrativo, revision y recuperacion de una candidata inexistente devuelven el contrato seguro `404 OCR_CANDIDATE_NOT_FOUND` y no modifican datos. Se agrego la regresion local que representa dos instancias consecutivas del reconciliador y demuestra que trabajo OCR completado sin candidata durable permanece intacto tras reiniciar RAG. Se actualizo la operativa, que aun reflejaba el estado anterior en `false`.
|
||||||
|
**Validation:** `npm test` 101/101, `npm run check`, `npm run build`, y `ocr-service/.venv/bin/pytest -q` 16/16.
|
||||||
|
**Learned:** El incidente previo no fue un hardcode: EasyPanel habia persistido variables duplicadas y desfasadas respecto a su UI. Si hay discrepancia, verificar el registro persistido del servicio y el entorno del contenedor, no solo la pantalla del panel. Las etiquetas OCI de ambas imagenes siguen en `revision=unknown` porque EasyPanel no recibe `BUILD_REVISION`; los digests son la identidad exacta vigente y la etiqueta legible queda como mejora operativa no bloqueante.
|
||||||
|
**Files:** `tests/ocr/dispatcher.test.ts`, `docs/PENDIENTES_RAG.md`, `docs/OPERATIVA.md`, `docs/HISTORIAL_SESIONES.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### 2026-09-16 - Orchestrator Inline Unit 17f (OpenAI executor exhausted)
|
### 2026-09-16 - Orchestrator Inline Unit 17f (OpenAI executor exhausted)
|
||||||
**Agent:** gentle-orchestrator (GLM inline, parent `ses_29bdbd003ffeLrLjUlFgnp08Y7`)
|
**Agent:** gentle-orchestrator (GLM inline, parent `ses_29bdbd003ffeLrLjUlFgnp08Y7`)
|
||||||
**Work:** Unit 17f: production approve→index wiring — `PostgresOcrIndexingStore`+`OcrReadyIndexingService` constructed with PostgreSQL, approve route indexes after durable approval and returns `ready`; 503 `OCR_INDEXING_UNAVAILABLE` when unconfigured. RED 2/2 → GREEN 2/2; E2E updated to approve→index→ready; canonical 99/99; check/build/whitespace green. No production/migration/commit; task 7.4 pending.
|
**Work:** Unit 17f: production approve→index wiring — `PostgresOcrIndexingStore`+`OcrReadyIndexingService` constructed with PostgreSQL, approve route indexes after durable approval and returns `ready`; 503 `OCR_INDEXING_UNAVAILABLE` when unconfigured. RED 2/2 → GREEN 2/2; E2E updated to approve→index→ready; canonical 99/99; check/build/whitespace green. No production/migration/commit; task 7.4 pending.
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,36 @@
|
||||||
# Operativa del servicio RAG
|
# Operativa del servicio RAG
|
||||||
|
|
||||||
**Modulo:** RAG
|
**Modulo:** RAG
|
||||||
**Ultima actualizacion:** 2026-09-16
|
**Ultima actualizacion:** 2026-09-17
|
||||||
**Version:** 1.1
|
**Version:** 1.2
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Este documento registra los hechos operativos del servicio RAG: la configuracion vigente en EasyPanel y la salida por etapas del OCR. No contiene secretos: las credenciales reales viven unicamente en EasyPanel.
|
Este documento registra los hechos operativos del servicio RAG: la configuracion vigente en EasyPanel y la salida por etapas del OCR. No contiene secretos: las credenciales reales viven unicamente en EasyPanel.
|
||||||
|
|
||||||
## Estado verificado (2026-09-16)
|
## Estado verificado (2026-09-17)
|
||||||
|
|
||||||
- `KNOWLEDGE_LIFECYCLE_ENFORCED=true`: el ciclo de vida del conocimiento esta activo; no queda pendiente ninguna activacion.
|
- `KNOWLEDGE_LIFECYCLE_ENFORCED=true`: el ciclo de vida del conocimiento esta activo; no queda pendiente ninguna activacion.
|
||||||
- `OCR_INGEST_ENABLED=false`: la ingesta sigue el flujo nativo, sin OCR.
|
- `OCR_INGEST_ENABLED=true`: la ingesta OCR esta habilitada en el RAG desplegado.
|
||||||
- `OCR_SERVICE_URL`: configurada hacia el servicio privado `ocr-service` de la red interna.
|
- `OCR_SERVICE_URL`: configurada hacia el servicio privado `ocr-service` de la red interna.
|
||||||
- `OCR_INTERNAL_TOKEN`: secreto compartido configurado en EasyPanel (RAG y servicio OCR); su valor nunca se documenta.
|
- `OCR_INTERNAL_TOKEN`: secreto compartido configurado en EasyPanel (RAG y servicio OCR); su valor nunca se documenta.
|
||||||
- Volumen duradero del RAG en `/data/ingestions`: conserva originales y artefactos de revision.
|
- Volumen duradero del RAG en `/data/ingestions`: conserva originales y artefactos de revision.
|
||||||
- Volumen transitorio del OCR en `/data/jobs`: cola SQLite y trabajos en curso.
|
- Volumen transitorio del OCR en `/data/jobs`: cola SQLite y trabajos en curso.
|
||||||
- Verificadas exactamente 29 variables de entorno del RAG en EasyPanel, todas unicas y sin nombres duplicados.
|
- La respuesta publica sin token de una ruta OCR protegida es `401`, lo que confirma que el proceso desplegado tiene OCR habilitado. Con OCR deshabilitado, la misma ruta responderia `404` antes de autenticar.
|
||||||
|
- El estado mostrado en EasyPanel debe contrastarse con su entorno persistido y con el entorno del contenedor en ejecucion; anteriormente la UI mostro valores distintos por variables duplicadas y desfasadas.
|
||||||
|
- `GET /health` del RAG confirma PostgreSQL, Qdrant y reconciliador sanos. OCR responde internamente `live=ok` y `ready=true`, con cola vacia y version `0.1.0`.
|
||||||
|
- La migracion `003_ocr_recovery_audit.sql` esta aplicada y su tabla de auditoria existe.
|
||||||
|
- Las rutas autenticadas de revision y recuperacion devuelven errores estructurados y seguros para una candidata inexistente: `404`, `OCR_CANDIDATE_NOT_FOUND` y accion `verify_version_id`.
|
||||||
|
- Las imagenes en ejecucion tienen digest, pero ambas etiquetas OCI `org.opencontainers.image.revision` valen `unknown`: EasyPanel no esta pasando `BUILD_REVISION` durante el build. La identidad de revision verificable sigue pendiente.
|
||||||
|
|
||||||
## Lista rapida en EasyPanel
|
## Lista rapida en EasyPanel
|
||||||
|
|
||||||
1. Rotar las credenciales expuestas (ver seccion siguiente); la rotacion sigue pendiente.
|
1. Rotar las credenciales expuestas (ver seccion siguiente); la rotacion sigue pendiente.
|
||||||
2. Comprobar que las variables coinciden con la tabla de configuracion actual.
|
2. Comprobar que las variables coinciden con la tabla de configuracion actual.
|
||||||
3. Mantener `OCR_INGEST_ENABLED=false` hasta completar el orden seguro de salida del OCR.
|
3. Mantener `OCR_INGEST_ENABLED=true` para el flujo OCR ya habilitado.
|
||||||
4. Pulsar `Deploy` en EasyPanel despues de cada cambio principal.
|
4. Pulsar `Deploy` en EasyPanel despues de cada cambio principal.
|
||||||
5. Verificar `GET /health` tras cada deploy.
|
5. Verificar `GET /health` del RAG y las rutas internas de salud del OCR tras cada deploy.
|
||||||
|
6. Registrar el digest de cada imagen como identidad exacta de la version desplegada. Configurar `RAG_VERSION`, `OCR_VERSION` y `BUILD_REVISION` en un ciclo posterior si se necesita una revision legible desde health o etiquetas OCI.
|
||||||
|
|
||||||
## Accion de seguridad urgente: rotar credenciales
|
## Accion de seguridad urgente: rotar credenciales
|
||||||
|
|
||||||
|
|
@ -48,7 +54,7 @@ Como hacerlo:
|
||||||
|
|
||||||
## Configuracion actual (valores no secretos)
|
## Configuracion actual (valores no secretos)
|
||||||
|
|
||||||
Valores vigentes confirmados en EasyPanel (2026-09-16):
|
Valores vigentes confirmados en produccion (2026-09-17):
|
||||||
|
|
||||||
| Variable | Valor actual |
|
| Variable | Valor actual |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
|
@ -66,14 +72,14 @@ Valores vigentes confirmados en EasyPanel (2026-09-16):
|
||||||
| `POSTGRES_SSL` | `false` |
|
| `POSTGRES_SSL` | `false` |
|
||||||
| `KNOWLEDGE_LIFECYCLE_ENFORCED` | `true` |
|
| `KNOWLEDGE_LIFECYCLE_ENFORCED` | `true` |
|
||||||
| `INGEST_WRITES_ENABLED` | `true` |
|
| `INGEST_WRITES_ENABLED` | `true` |
|
||||||
| `OCR_INGEST_ENABLED` | `false` |
|
| `OCR_INGEST_ENABLED` | `true` |
|
||||||
| `OCR_SERVICE_URL` | configurada hacia el servicio privado `ocr-service` |
|
| `OCR_SERVICE_URL` | configurada hacia el servicio privado `ocr-service` |
|
||||||
|
|
||||||
Notas de comportamiento (derivadas del codigo, sin secretos):
|
Notas de comportamiento (derivadas del codigo, sin secretos):
|
||||||
|
|
||||||
- Si `ANSWER_API_KEY` no se define, el codigo reutiliza `EMBEDDING_API_KEY`.
|
- Si `ANSWER_API_KEY` no se define, el codigo reutiliza `EMBEDDING_API_KEY`.
|
||||||
- Si `POSTGRES_URL` no se define, el codigo prueba `DATABASE_URL`.
|
- Si `POSTGRES_URL` no se define, el codigo prueba `DATABASE_URL`.
|
||||||
- Si `OCR_INGEST_ENABLED` no existe, el codigo tambien la toma como `false`.
|
- Si `OCR_INGEST_ENABLED` no existe, el codigo tambien la toma como `false`; por eso debe existir explicitamente con valor `true` en el entorno persistido de EasyPanel.
|
||||||
- Un cambio en `KNOWLEDGE_LIFECYCLE_ENFORCED`, `INGEST_WRITES_ENABLED` o `OCR_INGEST_ENABLED` se hace siempre en EasyPanel y se registra en este documento.
|
- Un cambio en `KNOWLEDGE_LIFECYCLE_ENFORCED`, `INGEST_WRITES_ENABLED` o `OCR_INGEST_ENABLED` se hace siempre en EasyPanel y se registra en este documento.
|
||||||
|
|
||||||
## Variables con secretos
|
## Variables con secretos
|
||||||
|
|
@ -90,7 +96,7 @@ Estas variables contienen credenciales reales. Aqui solo se registran sus nombre
|
||||||
|
|
||||||
## Salida por etapas del OCR
|
## Salida por etapas del OCR
|
||||||
|
|
||||||
El OCR es un servicio privado e independiente. El RAG solo lo llama si `OCR_INGEST_ENABLED` esta activo; con el flag apagado, la ingesta sigue el flujo nativo actual, sin cambios. Hoy el flag esta en `false` en EasyPanel.
|
El OCR es un servicio privado e independiente. El RAG solo lo llama si `OCR_INGEST_ENABLED` esta activo; con el flag apagado, la ingesta sigue el flujo nativo actual, sin cambios. Hoy el flag esta en `true` en el RAG desplegado.
|
||||||
|
|
||||||
### Servicio OCR privado (referencia de configuracion)
|
### Servicio OCR privado (referencia de configuracion)
|
||||||
|
|
||||||
|
|
@ -115,15 +121,17 @@ El OCR es un servicio privado e independiente. El RAG solo lo llama si `OCR_INGE
|
||||||
- Si esa transferencia no llega, la limpieza automatica expira los trabajos a las 24 horas.
|
- Si esa transferencia no llega, la limpieza automatica expira los trabajos a las 24 horas.
|
||||||
- La limpieza transitoria nunca toca los artefactos duraderos del RAG ni el corpus activo.
|
- La limpieza transitoria nunca toca los artefactos duraderos del RAG ni el corpus activo.
|
||||||
|
|
||||||
### Orden seguro de salida (cuando cambian ambos servicios)
|
### Verificacion posterior al despliegue (cuando cambian ambos servicios)
|
||||||
|
|
||||||
1. Publicar el codigo en Git `main` (autorizacion ya concedida de forma separada).
|
1. Publicar el codigo en Git `main` y desplegar OCR y RAG desde EasyPanel.
|
||||||
2. Desplegar primero el servicio OCR.
|
2. Verificar el OCR internamente: `GET /health/live` y `GET /health/ready` deben responder con el servicio preparado.
|
||||||
3. Verificar el OCR: `GET /health/live` y `GET /health/ready` responden; un trabajo real autenticado llega a `succeeded`, devuelve un resultado ligado por integridad y admite limpieza transitoria.
|
3. Verificar `GET /health` del RAG: PostgreSQL, Qdrant y reconciliador deben estar correctos.
|
||||||
4. Desplegar el RAG en segundo lugar manteniendo `OCR_INGEST_ENABLED=false`.
|
4. Comprobar una ruta OCR protegida sin token. Un `401 Lifecycle admin token is required` confirma que OCR esta habilitado y que la autenticacion administrativa permanece protegida; no es un error que requiera correccion.
|
||||||
5. Verificar la ruta nativa del RAG: `/health`, ingesta textual, retrieval y corpus activo protegido, sin regresiones.
|
5. Contrastar `OCR_INGEST_ENABLED=true` en el entorno persistido de EasyPanel y en el proceso del contenedor si la UI no coincide con el comportamiento efectivo.
|
||||||
6. Solo entonces activar el OCR: `OCR_INGEST_ENABLED=true` y volver a pulsar `Deploy` en el RAG.
|
6. Consultar con token administrativo una version inexistente en revision y recuperacion: ambas deben devolver un error estructurado `404 OCR_CANDIDATE_NOT_FOUND` sin modificar datos.
|
||||||
7. Presentar el PDF de FacturaTech como candidato de revision no activado, con el `sourceRef` logico `Errores Junio 2026 - OCR verificado.md`. La aprobacion humana sigue siendo obligatoria antes de cualquier activacion.
|
7. Confirmar que `rag_schema_migrations` contiene `003_ocr_recovery_audit.sql` y que existe `rag_ocr_recovery_audit`.
|
||||||
|
8. Revisar los digests y etiquetas OCI de ambas imagenes. El digest es la identidad exacta actual; una revision OCI legible es una mejora operativa posterior si sigue en `unknown`.
|
||||||
|
9. No crear, recuperar, aprobar, indexar ni activar candidatas hasta la fase aprobada para v4 y FacturaTech.
|
||||||
|
|
||||||
### Rollback de emergencia
|
### Rollback de emergencia
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,14 @@ Esta secuencia tiene prioridad sobre la aceptacion productiva pendiente de Factu
|
||||||
|
|
||||||
### Fase 1. Corregir y verificar el codigo
|
### Fase 1. Corregir y verificar el codigo
|
||||||
|
|
||||||
1. Sustituir errores OCR previsibles por respuestas estructuradas, seguras y accionables.
|
**Estado:** Completada funcionalmente y verificada en produccion el 2026-09-17.
|
||||||
2. Incorporar una recuperacion administrativa controlada para candidatas con evidencia que no cumple el contrato.
|
|
||||||
3. Exponer versiones de producto y metadatos de build verificables en RAG y OCR.
|
|
||||||
4. Cubrir errores, recuperacion, reinicios y versiones con pruebas; desplegar y verificar en produccion.
|
|
||||||
|
|
||||||
**Salida:** el comportamiento nuevo esta validado y v4 permanece intacta.
|
1. Completado: errores OCR estructurados, seguros y accionables.
|
||||||
|
2. Completado: recuperacion administrativa autenticada y auditada para candidatas con evidencia invalida.
|
||||||
|
3. Completado: RAG y OCR exponen versiones; los digests identifican las imagenes desplegadas.
|
||||||
|
4. Completado: errores, recuperacion, reinicios y versiones cubiertos localmente y verificados en produccion sin modificar v4.
|
||||||
|
|
||||||
|
**Salida:** el comportamiento nuevo esta validado y v4 permanece intacta. La etiqueta OCI de revision legible sigue como mejora operativa no bloqueante: actualmente el digest es la identidad verificable de la imagen.
|
||||||
|
|
||||||
### Fase 2. Resolver la candidata v4
|
### Fase 2. Resolver la candidata v4
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -373,6 +373,37 @@ test("reconciler makes expired OCR leases dispatchable while leaving live leases
|
||||||
assert.deepEqual(calls, ["ocr-recovery", "ocr-dispatch"]);
|
assert.deepEqual(calls, ["ocr-recovery", "ocr-dispatch"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("reconciler restart leaves completed OCR work without a durable candidate untouched", async () => {
|
||||||
|
const candidate = { state: "indexing", durableArtifact: false };
|
||||||
|
const calls: string[] = [];
|
||||||
|
const catalog = {
|
||||||
|
async withGlobalTryLock(_name: string, handler: () => Promise<unknown>) { return handler(); },
|
||||||
|
async resolveActiveVersions() { return []; },
|
||||||
|
async validateActiveInvariant() { return []; },
|
||||||
|
async listOrphanedIndexingCandidates() { return []; }
|
||||||
|
};
|
||||||
|
const dispatcher = {
|
||||||
|
async recoverExpiredLeases() { calls.push("ocr-recovery"); return 0; },
|
||||||
|
async dispatchAvailable() { calls.push("ocr-dispatch"); return 0; },
|
||||||
|
async recoverCompletedCandidates() {
|
||||||
|
candidate.state = "review_required";
|
||||||
|
candidate.durableArtifact = true;
|
||||||
|
calls.push("candidate-recovery");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const reconciler of [
|
||||||
|
new KnowledgeLifecycleReconciler(catalog as never, vectorStore(), dispatcher as never),
|
||||||
|
new KnowledgeLifecycleReconciler(catalog as never, vectorStore(), dispatcher as never)
|
||||||
|
]) {
|
||||||
|
assert.equal((await reconciler.runOnce()).ok, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepEqual(candidate, { state: "indexing", durableArtifact: false });
|
||||||
|
assert.deepEqual(calls, ["ocr-recovery", "ocr-dispatch", "ocr-recovery", "ocr-dispatch"]);
|
||||||
|
});
|
||||||
|
|
||||||
test("runtime HTTP routing returns native 201, OCR 202/status, and catalog-down 503", async () => {
|
test("runtime HTTP routing returns native 201, OCR 202/status, and catalog-down 503", async () => {
|
||||||
const previous = { knowledgeLifecycleEnforced: env.knowledgeLifecycleEnforced, lifecycleAdminToken: env.lifecycleAdminToken, ocrIngestEnabled: env.ocrIngestEnabled };
|
const previous = { knowledgeLifecycleEnforced: env.knowledgeLifecycleEnforced, lifecycleAdminToken: env.lifecycleAdminToken, ocrIngestEnabled: env.ocrIngestEnabled };
|
||||||
setEnvFlag("knowledgeLifecycleEnforced", true);
|
setEnvFlag("knowledgeLifecycleEnforced", true);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue