docs(v5): Complete system documentation
Comprehensive documentation for TZZR system v5 including: - 00_VISION: Glossary and foundational philosophy - 01_ARQUITECTURA: System overview and server specs - 02_MODELO_DATOS: Entity definitions and data planes (T0, MST, BCK) - 03_COMPONENTES: Agent docs (CLARA, MARGARET, FELDMAN, GRACE) - 04_SEGURIDAD: Threat model and secrets management - 05_OPERACIONES: Infrastructure and backup/recovery - 06_INTEGRACIONES: GPU services (RunPod status: blocked) - 99_ANEXOS: Repository inventory (24 repos) Key findings documented: - CRITICAL: UFW inactive on CORP/HST - CRITICAL: PostgreSQL 5432 exposed - CRITICAL: .env files with 644 permissions - RunPod workers not starting (code ready in R2) - Infisical designated as single source of secrets (D-001) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
311
03_COMPONENTES/agentes/clara-margaret.md
Normal file
311
03_COMPONENTES/agentes/clara-margaret.md
Normal file
@@ -0,0 +1,311 @@
|
||||
# CLARA y MARGARET - Agentes de Ingesta
|
||||
|
||||
**Versión:** 5.0
|
||||
**Fecha:** 2024-12-24
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Aspecto | CLARA | MARGARET |
|
||||
|---------|-------|----------|
|
||||
| Servidor | DECK (72.62.1.113) | CORP (92.112.181.188) |
|
||||
| Puerto | 5051 | 5051 |
|
||||
| Dominio | Personal | Empresarial |
|
||||
| Base de datos | tzzr | corp |
|
||||
| Tabla log | clara_log | margaret_log |
|
||||
| Bucket R2 | deck | corp |
|
||||
| Estado | Operativo | Operativo |
|
||||
|
||||
---
|
||||
|
||||
## Arquitectura
|
||||
|
||||
```
|
||||
PACKET (App móvil)
|
||||
│
|
||||
│ POST /ingest
|
||||
│ Content-Type: multipart/form-data
|
||||
│ X-Auth-Key: {h_instancia}
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ CLARA (Personal) / MARGARET (Corp) │
|
||||
│ │
|
||||
│ 1. Validar h_instancia │
|
||||
│ 2. Calcular h_entrada (SHA-256) │
|
||||
│ 3. Subir archivos a R2 │
|
||||
│ 4. Insertar en log (inmutable) │
|
||||
│ 5. Estado: 'recibido' │
|
||||
│ 6. Responder con h_entrada │
|
||||
└─────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
ALFRED / JARED (siguiente paso)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Endpoint de Ingesta
|
||||
|
||||
### Request
|
||||
|
||||
```http
|
||||
POST /ingest HTTP/1.1
|
||||
Host: deck.example.com:5051
|
||||
Content-Type: multipart/form-data
|
||||
X-Auth-Key: {h_instancia}
|
||||
|
||||
--boundary
|
||||
Content-Disposition: form-data; name="tipo"
|
||||
|
||||
documento
|
||||
--boundary
|
||||
Content-Disposition: form-data; name="archivo"; filename="doc.pdf"
|
||||
Content-Type: application/pdf
|
||||
|
||||
{binary data}
|
||||
--boundary--
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"h_entrada": "a1b2c3d4e5f6...",
|
||||
"estado": "recibido",
|
||||
"timestamp": "2024-12-24T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cálculo de h_entrada
|
||||
|
||||
```python
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
def calcular_h_entrada(h_instancia, tipo, contenido_bytes, timestamp):
|
||||
"""
|
||||
Calcula el hash único de entrada.
|
||||
|
||||
h_entrada = SHA-256(h_instancia:tipo:sha256(contenido):timestamp)
|
||||
"""
|
||||
hash_contenido = hashlib.sha256(contenido_bytes).hexdigest()
|
||||
|
||||
data = f"{h_instancia}:{tipo}:{hash_contenido}:{timestamp}"
|
||||
|
||||
return hashlib.sha256(data.encode()).hexdigest()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schema: clara_log / margaret_log
|
||||
|
||||
```sql
|
||||
CREATE TABLE clara_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
h_entrada VARCHAR(64) UNIQUE NOT NULL,
|
||||
h_instancia VARCHAR(64) NOT NULL,
|
||||
tipo VARCHAR(50) NOT NULL,
|
||||
contenido_url TEXT, -- locker://deck/{h_entrada}/archivo
|
||||
contenido_hash VARCHAR(64), -- SHA-256 del contenido
|
||||
metadata JSONB,
|
||||
estado VARCHAR(20) DEFAULT 'recibido',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
processed_at TIMESTAMPTZ,
|
||||
processed_by VARCHAR(50) -- 'alfred' o 'manual'
|
||||
);
|
||||
|
||||
-- margaret_log tiene schema idéntico
|
||||
CREATE TABLE margaret_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
h_entrada VARCHAR(64) UNIQUE NOT NULL,
|
||||
h_instancia VARCHAR(64) NOT NULL,
|
||||
tipo VARCHAR(50) NOT NULL,
|
||||
contenido_url TEXT,
|
||||
contenido_hash VARCHAR(64),
|
||||
metadata JSONB,
|
||||
estado VARCHAR(20) DEFAULT 'recibido',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
processed_at TIMESTAMPTZ,
|
||||
processed_by VARCHAR(50)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Estados del Log
|
||||
|
||||
| Estado | Descripción |
|
||||
|--------|-------------|
|
||||
| recibido | Ingesta completada, pendiente procesamiento |
|
||||
| procesando | ALFRED/JARED está procesando |
|
||||
| enviado_mason | Enviado a MASON para enriquecimiento |
|
||||
| consolidado | Procesado por FELDMAN |
|
||||
| error | Error en procesamiento |
|
||||
|
||||
---
|
||||
|
||||
## Almacenamiento en R2
|
||||
|
||||
### Estructura de URLs
|
||||
|
||||
```
|
||||
locker://deck/{h_entrada}/archivo.ext
|
||||
locker://corp/{h_entrada}/archivo.ext
|
||||
```
|
||||
|
||||
### Implementación
|
||||
|
||||
```python
|
||||
import boto3
|
||||
|
||||
def subir_a_r2(bucket, h_entrada, archivo_bytes, filename):
|
||||
"""Sube archivo a Cloudflare R2."""
|
||||
|
||||
s3 = boto3.client(
|
||||
's3',
|
||||
endpoint_url='https://7dedae6030f5554d99d37e98a5232996.r2.cloudflarestorage.com',
|
||||
aws_access_key_id=R2_ACCESS_KEY,
|
||||
aws_secret_access_key=R2_SECRET_KEY
|
||||
)
|
||||
|
||||
key = f"{h_entrada}/{filename}"
|
||||
|
||||
s3.put_object(
|
||||
Bucket=bucket,
|
||||
Key=key,
|
||||
Body=archivo_bytes
|
||||
)
|
||||
|
||||
return f"locker://{bucket}/{key}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Validación de h_instancia
|
||||
|
||||
```python
|
||||
def validar_instancia(h_instancia):
|
||||
"""
|
||||
Valida que h_instancia exista y esté activa.
|
||||
|
||||
TODO: Implementar tabla de instancias activas.
|
||||
Actualmente: Validación básica de formato.
|
||||
"""
|
||||
if not h_instancia:
|
||||
return False
|
||||
|
||||
if len(h_instancia) != 64:
|
||||
return False
|
||||
|
||||
# TODO: Consultar tabla de instancias
|
||||
return True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tipos de Ingesta Soportados
|
||||
|
||||
| Tipo | Descripción | Extensiones |
|
||||
|------|-------------|-------------|
|
||||
| documento | Documentos | pdf, doc, docx |
|
||||
| imagen | Imágenes | jpg, png, gif |
|
||||
| audio | Audio | mp3, wav, m4a |
|
||||
| video | Video | mp4, mov |
|
||||
| texto | Texto plano | txt, md |
|
||||
| json | Datos estructurados | json |
|
||||
|
||||
---
|
||||
|
||||
## Configuración
|
||||
|
||||
### Variables de Entorno
|
||||
|
||||
```bash
|
||||
# CLARA (.env en /opt/clara/)
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_NAME=tzzr
|
||||
DB_USER=clara
|
||||
DB_PASSWORD=****
|
||||
|
||||
R2_ENDPOINT=https://7dedae6030f5554d99d37e98a5232996.r2.cloudflarestorage.com
|
||||
R2_ACCESS_KEY=****
|
||||
R2_SECRET_KEY=****
|
||||
R2_BUCKET=deck
|
||||
|
||||
# MARGARET (.env en /opt/margaret/)
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_NAME=corp
|
||||
DB_USER=margaret
|
||||
DB_PASSWORD=****
|
||||
|
||||
R2_BUCKET=corp
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
|
||||
```yaml
|
||||
# CLARA
|
||||
version: '3.8'
|
||||
services:
|
||||
clara:
|
||||
container_name: clara-clara
|
||||
image: tzzr/clara:latest
|
||||
ports:
|
||||
- "5051:5051"
|
||||
env_file:
|
||||
- .env
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Diferencias CLARA vs MARGARET
|
||||
|
||||
| Aspecto | CLARA | MARGARET |
|
||||
|---------|-------|----------|
|
||||
| Uso | Personal | Empresarial |
|
||||
| Volumen | Bajo | Alto |
|
||||
| Privacidad | Máxima | Compartida (empresa) |
|
||||
| Integración | ALFRED | JARED → MASON → FELDMAN |
|
||||
| Backup | deck bucket | corp bucket |
|
||||
|
||||
---
|
||||
|
||||
## Seguridad
|
||||
|
||||
### Actual
|
||||
- Autenticación por h_instancia
|
||||
- Comunicación HTTP (sin TLS interno)
|
||||
- .env con permisos 644 (**CRÍTICO: cambiar a 600**)
|
||||
|
||||
### Recomendado
|
||||
- TLS interno con certificados
|
||||
- Rate limiting
|
||||
- Validación de tipos MIME
|
||||
- Escaneo antivirus (ClamAV)
|
||||
|
||||
---
|
||||
|
||||
## Monitoreo
|
||||
|
||||
### Logs
|
||||
```bash
|
||||
# CLARA
|
||||
docker logs clara-clara -f
|
||||
|
||||
# MARGARET
|
||||
docker logs margaret-margaret -f
|
||||
```
|
||||
|
||||
### Métricas Sugeridas
|
||||
- Ingestas por minuto
|
||||
- Tamaño promedio de archivos
|
||||
- Tiempo de procesamiento
|
||||
- Errores por tipo
|
||||
390
03_COMPONENTES/agentes/feldman.md
Normal file
390
03_COMPONENTES/agentes/feldman.md
Normal file
@@ -0,0 +1,390 @@
|
||||
# FELDMAN - Agente de Consolidación
|
||||
|
||||
**Versión:** 5.0
|
||||
**Fecha:** 2024-12-24
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Aspecto | Valor |
|
||||
|---------|-------|
|
||||
| Servidor | CORP (92.112.181.188) |
|
||||
| Puerto | 5054 |
|
||||
| Base de datos | corp |
|
||||
| Tablas | feldman_cola, feldman_bloques, feldman_validaciones |
|
||||
| Estado | Operativo |
|
||||
|
||||
---
|
||||
|
||||
## Rol en el Sistema
|
||||
|
||||
FELDMAN es el "notario" del sistema: valida y consolida datos en bloques inmutables.
|
||||
|
||||
```
|
||||
MASON (enriquecido)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ FELDMAN │
|
||||
│ │
|
||||
│ 1. Recibir en feldman_cola │
|
||||
│ 2. Validar reglas (M-001, M-002, M-003)│
|
||||
│ 3. Calcular hash_contenido │
|
||||
│ 4. Encadenar (hash_previo) │
|
||||
│ 5. Crear milestone o bloque │
|
||||
│ 6. Marcar como consolidado │
|
||||
└─────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ milestones / bloques (inmutables) │
|
||||
└─────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
SENTINEL (auditoría futura)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reglas de Validación
|
||||
|
||||
| Regla | Nombre | Descripción | Acción si falla |
|
||||
|-------|--------|-------------|-----------------|
|
||||
| M-001 | Hash único | h_bloque/h_milestone no existe previamente | Rechazar |
|
||||
| M-002 | Encadenamiento | hash_previo apunta a bloque existente | Rechazar |
|
||||
| M-003 | Integridad | hash_contenido = SHA-256(contenido) | Rechazar |
|
||||
|
||||
### Implementación
|
||||
|
||||
```python
|
||||
def validar_m001(h_bloque):
|
||||
"""M-001: Hash debe ser único."""
|
||||
existe = db.execute(
|
||||
"SELECT 1 FROM bloques WHERE h_bloque = %s",
|
||||
[h_bloque]
|
||||
).fetchone()
|
||||
return existe is None
|
||||
|
||||
def validar_m002(hash_previo, h_instancia):
|
||||
"""M-002: hash_previo debe existir (excepto génesis)."""
|
||||
if hash_previo is None:
|
||||
# Primer bloque de la instancia (génesis)
|
||||
existe = db.execute(
|
||||
"SELECT 1 FROM bloques WHERE h_instancia = %s",
|
||||
[h_instancia]
|
||||
).fetchone()
|
||||
return existe is None # OK si no hay bloques previos
|
||||
|
||||
existe = db.execute(
|
||||
"SELECT 1 FROM bloques WHERE h_bloque = %s",
|
||||
[hash_previo]
|
||||
).fetchone()
|
||||
return existe is not None
|
||||
|
||||
def validar_m003(hash_contenido, contenido):
|
||||
"""M-003: Hash debe coincidir con contenido."""
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
contenido_serializado = json.dumps(contenido, sort_keys=True)
|
||||
hash_calculado = hashlib.sha256(contenido_serializado.encode()).hexdigest()
|
||||
|
||||
return hash_contenido == hash_calculado
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schema de Tablas
|
||||
|
||||
### feldman_cola
|
||||
|
||||
```sql
|
||||
CREATE TABLE feldman_cola (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tipo VARCHAR(50) NOT NULL, -- 'milestone' o 'bloque'
|
||||
h_entrada VARCHAR(64) NOT NULL, -- Referencia a origen
|
||||
h_instancia VARCHAR(64) NOT NULL,
|
||||
contenido JSONB NOT NULL,
|
||||
prioridad INTEGER DEFAULT 0,
|
||||
estado VARCHAR(20) DEFAULT 'pendiente',
|
||||
intentos INTEGER DEFAULT 0,
|
||||
ultimo_error TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
processed_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX idx_feldman_cola_estado ON feldman_cola(estado);
|
||||
CREATE INDEX idx_feldman_cola_prioridad ON feldman_cola(prioridad DESC);
|
||||
```
|
||||
|
||||
### feldman_bloques (alias de bloques)
|
||||
|
||||
```sql
|
||||
-- Ver 02_MODELO_DATOS/planos.md para schema completo
|
||||
CREATE TABLE bloques (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
h_bloque VARCHAR(64) UNIQUE NOT NULL,
|
||||
h_instancia VARCHAR(64) NOT NULL,
|
||||
secuencia BIGINT NOT NULL,
|
||||
hash_previo VARCHAR(64),
|
||||
hash_contenido VARCHAR(64) NOT NULL,
|
||||
tipo_bloque VARCHAR(50) NOT NULL,
|
||||
contenido JSONB NOT NULL,
|
||||
-- ... campos adicionales
|
||||
UNIQUE (h_instancia, secuencia)
|
||||
);
|
||||
```
|
||||
|
||||
### feldman_validaciones
|
||||
|
||||
```sql
|
||||
CREATE TABLE feldman_validaciones (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
h_entrada VARCHAR(64) NOT NULL,
|
||||
tipo_destino VARCHAR(50) NOT NULL, -- 'milestone' o 'bloque'
|
||||
h_destino VARCHAR(64), -- h_milestone o h_bloque
|
||||
regla VARCHAR(50) NOT NULL, -- M-001, M-002, M-003
|
||||
resultado BOOLEAN NOT NULL,
|
||||
mensaje TEXT,
|
||||
validated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_feldman_val_entrada ON feldman_validaciones(h_entrada);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Flujo de Consolidación
|
||||
|
||||
```
|
||||
1. Entrada en feldman_cola
|
||||
│
|
||||
▼
|
||||
2. FELDMAN procesa (poll cada N segundos)
|
||||
│
|
||||
├─► Validar M-001 (hash único)
|
||||
│ ├─ PASS → continuar
|
||||
│ └─ FAIL → registrar error, marcar 'rechazado'
|
||||
│
|
||||
├─► Validar M-002 (encadenamiento)
|
||||
│ ├─ PASS → continuar
|
||||
│ └─ FAIL → registrar error, marcar 'rechazado'
|
||||
│
|
||||
├─► Validar M-003 (integridad)
|
||||
│ ├─ PASS → continuar
|
||||
│ └─ FAIL → registrar error, marcar 'rechazado'
|
||||
│
|
||||
▼
|
||||
3. Todas las validaciones PASS
|
||||
│
|
||||
▼
|
||||
4. Obtener última secuencia de h_instancia
|
||||
│
|
||||
▼
|
||||
5. Calcular h_bloque/h_milestone
|
||||
│
|
||||
▼
|
||||
6. INSERT en bloques/milestones
|
||||
│
|
||||
▼
|
||||
7. Actualizar feldman_cola → 'consolidado'
|
||||
│
|
||||
▼
|
||||
8. Registrar en feldman_validaciones
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cálculo de Hashes
|
||||
|
||||
### h_bloque
|
||||
|
||||
```python
|
||||
def calcular_h_bloque(h_instancia, secuencia, hash_previo, hash_contenido):
|
||||
"""
|
||||
Calcula el hash del bloque.
|
||||
|
||||
h_bloque = SHA-256(h_instancia:secuencia:hash_previo:hash_contenido)
|
||||
"""
|
||||
import hashlib
|
||||
|
||||
# hash_previo puede ser None para bloque génesis
|
||||
previo = hash_previo or "genesis"
|
||||
|
||||
data = f"{h_instancia}:{secuencia}:{previo}:{hash_contenido}"
|
||||
|
||||
return hashlib.sha256(data.encode()).hexdigest()
|
||||
```
|
||||
|
||||
### hash_contenido
|
||||
|
||||
```python
|
||||
def calcular_hash_contenido(contenido):
|
||||
"""
|
||||
Calcula el hash del contenido.
|
||||
|
||||
hash_contenido = SHA-256(contenido_serializado)
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
# Serialización determinista
|
||||
contenido_str = json.dumps(contenido, sort_keys=True, ensure_ascii=False)
|
||||
|
||||
return hashlib.sha256(contenido_str.encode()).hexdigest()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### POST /consolidar
|
||||
|
||||
```http
|
||||
POST /consolidar HTTP/1.1
|
||||
Host: corp.example.com:5054
|
||||
Content-Type: application/json
|
||||
|
||||
```
|
||||
|
||||
### Response (éxito)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"h_bloque": "ghi789...",
|
||||
"secuencia": 42,
|
||||
"hash_contenido": "jkl012...",
|
||||
"validaciones": {
|
||||
"M-001": true,
|
||||
"M-002": true,
|
||||
"M-003": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response (error)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Validación fallida",
|
||||
"validaciones": {
|
||||
"M-001": true,
|
||||
"M-002": false,
|
||||
"M-003": true
|
||||
},
|
||||
"mensaje": "M-002: hash_previo no existe en cadena"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuración
|
||||
|
||||
### Variables de Entorno
|
||||
|
||||
```bash
|
||||
# /opt/feldman/.env
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_NAME=corp
|
||||
DB_USER=feldman
|
||||
DB_PASSWORD=****
|
||||
|
||||
POLL_INTERVAL=5 # Segundos entre polls
|
||||
MAX_BATCH=10 # Elementos por lote
|
||||
MAX_RETRIES=3 # Reintentos antes de rechazar
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Estados de la Cola
|
||||
|
||||
| Estado | Descripción |
|
||||
|--------|-------------|
|
||||
| pendiente | En espera de procesamiento |
|
||||
| procesando | FELDMAN está validando |
|
||||
| consolidado | Validado y creado bloque/milestone |
|
||||
| rechazado | Falló validación, no se reintentará |
|
||||
| error | Error técnico, puede reintentarse |
|
||||
|
||||
---
|
||||
|
||||
## Auditoría
|
||||
|
||||
Cada validación se registra en `feldman_validaciones`:
|
||||
|
||||
```sql
|
||||
-- Consultar historial de validaciones
|
||||
SELECT
|
||||
h_entrada,
|
||||
regla,
|
||||
resultado,
|
||||
mensaje,
|
||||
validated_at
|
||||
FROM feldman_validaciones
|
||||
WHERE h_entrada = 'abc123...'
|
||||
ORDER BY validated_at;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Futuro: Blockchain
|
||||
|
||||
FELDMAN está diseñado para evolucionar hacia blockchain:
|
||||
|
||||
1. **Actual:** Encadenamiento por hash_previo en PostgreSQL
|
||||
2. **Fase 2:** Merkle tree para verificación eficiente
|
||||
3. **Fase 3:** Smart contract en blockchain (Ethereum/Polygon)
|
||||
|
||||
```
|
||||
Merkle Root
|
||||
│
|
||||
┌───────────┴───────────┐
|
||||
│ │
|
||||
Hash(AB) Hash(CD)
|
||||
│ │
|
||||
┌─────┴─────┐ ┌─────┴─────┐
|
||||
│ │ │ │
|
||||
Hash(A) Hash(B) Hash(C) Hash(D)
|
||||
│ │ │ │
|
||||
Bloque A Bloque B Bloque C Bloque D
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoreo
|
||||
|
||||
### Logs
|
||||
```bash
|
||||
docker logs feldman-feldman -f
|
||||
```
|
||||
|
||||
### Consultas de Estado
|
||||
|
||||
```sql
|
||||
-- Elementos pendientes
|
||||
SELECT COUNT(*) FROM feldman_cola WHERE estado = 'pendiente';
|
||||
|
||||
-- Últimas validaciones fallidas
|
||||
SELECT * FROM feldman_validaciones
|
||||
WHERE resultado = false
|
||||
ORDER BY validated_at DESC
|
||||
LIMIT 10;
|
||||
|
||||
-- Bloques creados hoy
|
||||
SELECT COUNT(*) FROM bloques
|
||||
WHERE created_at >= CURRENT_DATE;
|
||||
```
|
||||
-- Últimas validaciones fallidas
|
||||
SELECT * FROM feldman_validaciones
|
||||
WHERE resultado = false
|
||||
ORDER BY validated_at DESC
|
||||
LIMIT 10;
|
||||
|
||||
-- Bloques creados hoy
|
||||
SELECT COUNT(*) FROM bloques
|
||||
WHERE created_at >= CURRENT_DATE;
|
||||
```
|
||||
403
03_COMPONENTES/servicios/grace.md
Normal file
403
03_COMPONENTES/servicios/grace.md
Normal file
@@ -0,0 +1,403 @@
|
||||
# GRACE - Servicio de IA Cognitiva
|
||||
|
||||
**Versión:** 5.0
|
||||
**Fecha:** 2024-12-24
|
||||
|
||||
---
|
||||
|
||||
## Estado Actual
|
||||
|
||||
**BLOQUEADO:** RunPod no inicia workers.
|
||||
|
||||
| Aspecto | Valor |
|
||||
|---------|-------|
|
||||
| Plataforma | RunPod Serverless |
|
||||
| Endpoint ID | r00x4g3rrwkbyh |
|
||||
| Balance | ~$72 USD |
|
||||
| Workers activos | 0 |
|
||||
| Estado | Inoperativo |
|
||||
|
||||
---
|
||||
|
||||
## Descripción
|
||||
|
||||
GRACE es el servicio de extracción de información mediante IA. Procesa contenido multimedia para extraer datos estructurados.
|
||||
|
||||
```
|
||||
Contenido Raw
|
||||
(audio, imagen, video, documento)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ GRACE (GPU) │
|
||||
│ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ ASR │ │ OCR │ │ TTS │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ Face │ │Embeddings│ │ Avatar │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ │
|
||||
│ + 12 módulos pendientes │
|
||||
└─────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
Datos Estructurados
|
||||
(JSON, vectores, metadatos)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Módulos Implementados (6 de 18)
|
||||
|
||||
### ASR - Automatic Speech Recognition
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| Modelo | Whisper Large v3 |
|
||||
| Input | Audio (mp3, wav, m4a) |
|
||||
| Output | Texto + timestamps |
|
||||
| GPU | A10G recomendado |
|
||||
|
||||
```python
|
||||
@grace.module("asr")
|
||||
def process_asr(audio_bytes: bytes) -> dict:
|
||||
"""Transcribe audio a texto."""
|
||||
model = whisper.load_model("large-v3")
|
||||
result = model.transcribe(audio_bytes)
|
||||
return {
|
||||
"text": result["text"],
|
||||
"segments": result["segments"],
|
||||
"language": result["language"]
|
||||
}
|
||||
```
|
||||
|
||||
### OCR - Optical Character Recognition
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| Modelo | EasyOCR + Tesseract |
|
||||
| Input | Imagen (jpg, png) |
|
||||
| Output | Texto + bounding boxes |
|
||||
| GPU | A10G recomendado |
|
||||
|
||||
```python
|
||||
@grace.module("ocr")
|
||||
def process_ocr(image_bytes: bytes) -> dict:
|
||||
"""Extrae texto de imagen."""
|
||||
reader = easyocr.Reader(['es', 'en'])
|
||||
result = reader.readtext(image_bytes)
|
||||
return {
|
||||
"text": " ".join([r[1] for r in result]),
|
||||
"boxes": [{"text": r[1], "bbox": r[0], "confidence": r[2]} for r in result]
|
||||
}
|
||||
```
|
||||
|
||||
### TTS - Text to Speech
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| Modelo | Coqui TTS |
|
||||
| Input | Texto |
|
||||
| Output | Audio (wav) |
|
||||
| GPU | A10G recomendado |
|
||||
|
||||
### Face - Reconocimiento Facial
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| Modelo | InsightFace |
|
||||
| Input | Imagen |
|
||||
| Output | Embeddings faciales |
|
||||
| GPU | A10G recomendado |
|
||||
|
||||
### Embeddings - Vectores Semánticos
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| Modelo | Sentence Transformers |
|
||||
| Input | Texto |
|
||||
| Output | Vector 384/768 dims |
|
||||
| GPU | A10G recomendado |
|
||||
|
||||
### Avatar - Generación de Avatares
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| Modelo | StyleGAN |
|
||||
| Input | Imagen facial |
|
||||
| Output | Avatar estilizado |
|
||||
| GPU | A10G recomendado |
|
||||
|
||||
---
|
||||
|
||||
## Módulos Pendientes (12)
|
||||
|
||||
| Módulo | Descripción | Prioridad |
|
||||
|--------|-------------|-----------|
|
||||
| Document parsing | Extracción de PDFs | Alta |
|
||||
| Image classification | Clasificación de imágenes | Media |
|
||||
| Object detection | Detección de objetos | Media |
|
||||
| Sentiment analysis | Análisis de sentimiento | Media |
|
||||
| Named entity recognition | Extracción de entidades | Alta |
|
||||
| Translation | Traducción de texto | Media |
|
||||
| Summarization | Resumen de texto | Media |
|
||||
| Question answering | Respuestas a preguntas | Baja |
|
||||
| Code generation | Generación de código | Baja |
|
||||
| Audio classification | Clasificación de audio | Baja |
|
||||
| Video analysis | Análisis de video | Baja |
|
||||
| Multimodal fusion | Fusión multimodal | Baja |
|
||||
|
||||
---
|
||||
|
||||
## Código en R2
|
||||
|
||||
El código está listo y disponible:
|
||||
|
||||
```
|
||||
s3://architect/gpu-services/
|
||||
├── base/
|
||||
│ └── bootstrap.sh # Script de inicialización
|
||||
├── grace/
|
||||
│ └── code/
|
||||
│ └── handler.py # Handler principal
|
||||
├── penny/
|
||||
│ └── code/
|
||||
│ └── handler.py
|
||||
└── factory/
|
||||
└── code/
|
||||
└── handler.py
|
||||
```
|
||||
|
||||
### Descargar Código
|
||||
|
||||
```bash
|
||||
source /home/orchestrator/orchestrator/.env
|
||||
export AWS_ACCESS_KEY_ID="$R2_ACCESS_KEY"
|
||||
export AWS_SECRET_ACCESS_KEY="$R2_SECRET_KEY"
|
||||
|
||||
aws s3 sync s3://architect/gpu-services/grace/ ./grace/ \
|
||||
--endpoint-url https://7dedae6030f5554d99d37e98a5232996.r2.cloudflarestorage.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Handler RunPod
|
||||
|
||||
```python
|
||||
import runpod
|
||||
|
||||
def handler(event):
|
||||
"""
|
||||
Handler principal de GRACE para RunPod Serverless.
|
||||
|
||||
Input:
|
||||
{
|
||||
"input": {
|
||||
"module": "asr|ocr|tts|face|embeddings|avatar",
|
||||
"data": "base64 encoded content",
|
||||
"options": {}
|
||||
}
|
||||
}
|
||||
|
||||
Output:
|
||||
{
|
||||
"output": { ... resultado del módulo ... },
|
||||
"error": null
|
||||
}
|
||||
"""
|
||||
try:
|
||||
module = event["input"]["module"]
|
||||
data = base64.b64decode(event["input"]["data"])
|
||||
options = event["input"].get("options", {})
|
||||
|
||||
if module == "asr":
|
||||
result = process_asr(data, **options)
|
||||
elif module == "ocr":
|
||||
result = process_ocr(data, **options)
|
||||
elif module == "tts":
|
||||
result = process_tts(data, **options)
|
||||
elif module == "face":
|
||||
result = process_face(data, **options)
|
||||
elif module == "embeddings":
|
||||
result = process_embeddings(data, **options)
|
||||
elif module == "avatar":
|
||||
result = process_avatar(data, **options)
|
||||
else:
|
||||
return {"error": f"Módulo desconocido: {module}"}
|
||||
|
||||
return {"output": result, "error": None}
|
||||
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
runpod.serverless.start({"handler": handler})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Uso desde MASON
|
||||
|
||||
```python
|
||||
import runpod
|
||||
import base64
|
||||
|
||||
runpod.api_key = "..." # Desde Infisical
|
||||
|
||||
def call_grace(module: str, content: bytes, options: dict = None) -> dict:
|
||||
"""Llama a GRACE para procesar contenido."""
|
||||
|
||||
job = runpod.run(
|
||||
endpoint_id="r00x4g3rrwkbyh",
|
||||
input={
|
||||
"module": module,
|
||||
"data": base64.b64encode(content).decode(),
|
||||
"options": options or {}
|
||||
}
|
||||
)
|
||||
|
||||
# Polling hasta completar
|
||||
while True:
|
||||
status = runpod.status(job["id"])
|
||||
if status["status"] == "COMPLETED":
|
||||
return status["output"]
|
||||
elif status["status"] == "FAILED":
|
||||
raise Exception(status.get("error", "Job failed"))
|
||||
time.sleep(1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Problema Actual: Workers No Inician
|
||||
|
||||
### Incidente 2024-12-24
|
||||
|
||||
- **Síntoma:** 0 workers activos a pesar de jobs en cola
|
||||
- **Balance:** $72+ (suficiente)
|
||||
- **Configuración:** Correcta
|
||||
- **Causa probable:** Problema de capacidad RunPod
|
||||
|
||||
### Intentos de Diagnóstico
|
||||
|
||||
1. Verificado endpoint activo en dashboard
|
||||
2. Verificado balance suficiente
|
||||
3. Verificado configuración de scaling
|
||||
4. Jobs quedan en estado "IN_QUEUE" indefinidamente
|
||||
|
||||
### Log de Errores
|
||||
|
||||
```
|
||||
No hay logs disponibles - workers nunca inician
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Alternativas Evaluadas
|
||||
|
||||
### 1. Modal (Recomendado)
|
||||
|
||||
```python
|
||||
import modal
|
||||
|
||||
stub = modal.Stub("grace")
|
||||
image = modal.Image.debian_slim().pip_install("whisper", "easyocr")
|
||||
|
||||
@stub.function(gpu="A10G", image=image)
|
||||
def process_asr(audio_bytes: bytes) -> dict:
|
||||
import whisper
|
||||
model = whisper.load_model("large-v3")
|
||||
result = model.transcribe(audio_bytes)
|
||||
return {"text": result["text"]}
|
||||
```
|
||||
|
||||
**Pros:**
|
||||
- Serverless real
|
||||
- Buen DX (developer experience)
|
||||
- Python-native
|
||||
- Cold start rápido
|
||||
|
||||
**Contras:**
|
||||
- Menos GPUs disponibles que RunPod
|
||||
- Pricing puede ser mayor
|
||||
|
||||
### 2. Replicate
|
||||
|
||||
```python
|
||||
import replicate
|
||||
|
||||
output = replicate.run(
|
||||
"openai/whisper:large-v3",
|
||||
input={"audio": audio_url}
|
||||
)
|
||||
```
|
||||
|
||||
**Pros:**
|
||||
- Modelos pre-entrenados
|
||||
- API simple
|
||||
- Sin gestión de infraestructura
|
||||
|
||||
**Contras:**
|
||||
- Menos control
|
||||
- Más caro a escala
|
||||
- Dependencia de modelos de terceros
|
||||
|
||||
### 3. Lambda Labs
|
||||
|
||||
**Pros:**
|
||||
- Hardware dedicado
|
||||
- GPUs disponibles
|
||||
|
||||
**Contras:**
|
||||
- Menos flexible
|
||||
- Reserva manual
|
||||
- No serverless
|
||||
|
||||
### 4. Self-Hosted
|
||||
|
||||
**Pros:**
|
||||
- Control total
|
||||
- Sin dependencias externas
|
||||
- Costo fijo a largo plazo
|
||||
|
||||
**Contras:**
|
||||
- CapEx alto
|
||||
- Mantenimiento
|
||||
- Requiere expertise
|
||||
|
||||
---
|
||||
|
||||
## Plan de Migración
|
||||
|
||||
### Fase 1: Evaluación
|
||||
|
||||
- [ ] Crear cuenta Modal
|
||||
- [ ] Portar módulo ASR a Modal
|
||||
- [ ] Comparar latencia con RunPod (cuando funcione)
|
||||
- [ ] Comparar costos
|
||||
|
||||
### Fase 2: Migración
|
||||
|
||||
- [ ] Portar 6 handlers a Modal
|
||||
- [ ] Actualizar endpoints en MASON
|
||||
- [ ] Actualizar documentación
|
||||
- [ ] Testing end-to-end
|
||||
|
||||
### Fase 3: Producción
|
||||
|
||||
- [ ] Desplegar en Modal
|
||||
- [ ] Monitorear costos y performance
|
||||
- [ ] Deprecar RunPod endpoints
|
||||
- [ ] Cancelar cuenta RunPod
|
||||
|
||||
---
|
||||
|
||||
## Principio Fundamental
|
||||
|
||||
**GRACE nunca modifica datos.**
|
||||
|
||||
GRACE es read-only: extrae información pero no puede modificar el contenido original ni los datos del sistema. Las extracciones se almacenan como metadatos asociados al contenido original.
|
||||
|
||||
```
|
||||
Contenido Original ────► GRACE ────► Metadatos Extraídos
|
||||
(inmutable) (nuevos datos)
|
||||
```
|
||||
Reference in New Issue
Block a user