--- type: TP asset_id: TP-EL-IntelliBanks-ImplementacionMultibancos-Fase01-v01 version: v01 tipo: TP — Transfer Pack (arranque de room de implementación) status: 🟢 listo para arrancar room · Opus + código montado · alcance Fase 0 + 1 owner: Alex sherpa: SherpaX Alex ratificador: Victor Heredia (L3 arquitectura) implementa: Alex (backend) intellibank: IB-XX-Maestro fecha_creacion: 2026-07-07 proposito: > Arrancar el room de IMPLEMENTACIÓN de multibancos (multi-tenant) en IntelliBanks. Consolida todo el contexto trabajado el 7-jul-2026 (descripción de producto, análisis multi-empresa, ARQ v02 con identidad reconciliada, wargame, decisión de auth) y baja la Fase 0 + Fase 1 del ARQ a pasos ejecutables con archivos, SQL y endpoints exactos. Se corre con Opus y la carpeta genniuxgit montada. insumos_canonicos: - ARQ-EL-IntelliBanks-MultiEmpresa-ZeroTrust-v01 (v02 · el diseño · IB-XX-Maestro) - DC-EL-IntelliBanks-DescripcionProducto-v01 (estado real del sistema) - DC-EL-IntelliBanks-AnalisisMultiEmpresa-v01 (base multi-tenant) - RFI-XX-IntelliBanks-BlindajeGobernanzaSync-v01 (Capas 0–5) - PROPUESTA-Integracion-Auth.md (genniux_board · origen=tenant · en repo) - SP-EL-IntelliBanks-PlanAccionFable5-20260707-v01 (contexto verificado en código) gate_g0: PASS · deriva del ARQ v02 ratificable; código real inspeccionado 7-jul tags: [TP, intellibanks, multibancos, multi-tenant, implementacion, fase0, fase1, zero-trust, opus] --- # TP · Implementación Multibancos IntelliBanks — Fase 0 + 1 ## Arranque de room de implementación · Opus + código · el diseño ya está, esto lo baja a código > **Qué es esto.** El paquete para arrancar el room donde **se implementa** el multi-tenant en IntelliBanks. El diseño ya está cerrado y ratificable en `ARQ-EL-IntelliBanks-MultiEmpresa-ZeroTrust-v01` (v02). Este TP NO re-diseña: baja la **Fase 0 (estructura invisible) + Fase 1 (identidad + enforcement server-side)** a pasos con archivos, SQL y endpoints exactos, verificados contra el código real el 7-jul-2026. --- ## 0. Cómo arrancar el room (4 pasos) 1. Room nuevo con **Opus**, carpeta **`genniuxgit`** montada (contiene `backend-patch/` con los PHP de producción y `src/` con Angular). 2. Corre `/arrancaroom` y pega este TP. 3. Adjunta los insumos canónicos (§1). El `ARQ-` v02 es la fuente de verdad del diseño. 4. Ejecuta §4 en orden: Fase 0 → 1a (shadow) → 1b (keystone). **No saltes a 1b sin el dataset de sombra.** --- ## 1. Contexto en 30 segundos (qué se decidió y por qué) - **Problema:** hoy el backend confía en el cliente. `usuario` viaja en claro (verificado en `auth.service.ts`), cualquiera puede suplantar a otro y escribir donde sea. Con 6 usuarios ya ensució el vault; con 100 es caos. Multi-empresa sin blindar = fuga cross-tenant garantizada. - **Principio:** Zero-Trust del cliente — el cliente propone, el servidor dispone. El **tenant es la primera regla de gobernanza**; se valida junto con ruta/naming en un solo pipeline por escritura. - **Identidad (decisión cerrada v02):** NO se crea padrón nuevo de usuarios. `signup.php` **ya** autentica contra `colaborador` de `genniux_board`; `pap_eje` **ya es** el catálogo de tenants. Por tanto: - `company_id` **=** `colaborador.origen` (FK a `pap_eje`). **No hay tabla `companies`.** - Skills solo posee lo suyo: tabla nueva **`user_tokens`** (tokens de dispositivo revocables) + sesión. Nada de `skills_users` como segundo padrón de credenciales. - **Dedup (bug raíz):** `upload.php` crea skill nuevo en vez de actualizar para archivos que cambian seguido → los 2,154 duplicados ya limpiados regresan solos. Se mata **por esquema** con `UNIQUE(company_id, folder_id, name)` + upsert. - **Alcance de este room:** Fase 0 + 1. Cliente (carpeta `/`, conmutador) es Fase 2 — otro room. --- ## 2. Estado real del código (verificado 7-jul · dónde tocar) **Repo:** `genniuxgit/` · backend PHP 5.6 en `backend-patch/` (espejo de producción `genniux.net/skills-api/api/`), Angular en `src/`. - **`db.php`** — include común de todos los endpoints (`require_once 'db.php'`). **Aquí se engancha `gate.php`** (identidad + versión + governance). Ya tiene el fix de `HTTP_ORIGIN` con isset() y whitelist. - **`upload.php`** — L18: `$usuario` de `$_POST['usuario']` o header `HTTP_X_USER_ID` o `'sistema'`. L123-126: el `SELECT ... WHERE name=? AND folder_id=?` cuya lógica insert-vs-update es el bug de dedup. **Punto de cirugía del upsert.** - **`folders.php`** — L41-50: ya rechaza carpeta sin autor (HTTP 400). Patrón a replicar para tenant. - **`get_sync_map.php`** — L125/131: `SELECT ... FROM skill_folders` y `skills` sin filtro. **Agregar `WHERE company_id IN (, :cid)`.** Caché `true_map.json` → pasa a `true_map_.json`. - **`list_skills.php`, `search.php`, `get_skill_content.php`, `admin_api.php`, `delete_skill.php`, `update_skill.php`, `restaurar.php`** — todos incluyen `db.php`; todos reciben el filtro/validación de tenant. - **Restricciones:** PHP 5.6 (nada de `??`, arrow fns; usar ternarios `isset()`), respaldar antes de tocar (patrón `.bak-YYYYMMDD` ya en uso), rama `alex`, `cleanFileName` = única fuente de verdad de nombres. GETs cacheados por CDN → verificar escrituras con parámetro anti-caché. --- ## 3. Modelo de datos objetivo (Fase 0) ```sql -- NO se crea tabla companies: el catálogo de tenants es pap_eje (genniux_board). -- company_id = colaborador.origen. -- 1) Tokens de dispositivo (lo único nuevo de identidad, skills-owned) CREATE TABLE user_tokens ( id VARCHAR(64) PRIMARY KEY, wikiname VARCHAR(100) NOT NULL, -- = colaborador.wikiname (= 'usuario' actual) token_hash CHAR(64) NOT NULL, -- sha256(token) device_label VARCHAR(120) NULL, created_at DATETIME NOT NULL, last_used_at DATETIME NULL, revoked TINYINT(1) NOT NULL DEFAULT 0, INDEX (wikiname), INDEX (token_hash) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 2) Denormalización de tenant en las filas de skills (para WHERE rápido) ALTER TABLE skills ADD COLUMN company_id INT NULL; -- = origen ALTER TABLE skill_folders ADD COLUMN company_id INT NULL; -- (opcional auditoría) ALTER TABLE user_sync_history ADD COLUMN company_id INT NULL; -- 3) Backfill: resolver origen del owner vía colaborador y sellar EmpowerLabs -- (id de origen EmpowerLabs a confirmar en pap_eje: ) UPDATE skills s JOIN colaborador c ON c.wikiname = s.created_by SET s.company_id = c.origen WHERE s.company_id IS NULL; UPDATE skill_folders f JOIN colaborador c ON c.wikiname = f.created_by SET f.company_id = c.origen WHERE f.company_id IS NULL; -- Filas sin match (created_by = 'sistema'/'desktop_daemon'): asignar explícito. UPDATE skills SET company_id = WHERE company_id IS NULL; UPDATE skill_folders SET company_id = WHERE company_id IS NULL; -- 4) Índices (después del backfill) CREATE INDEX ix_skills_company ON skills (company_id); CREATE INDEX ix_skills_company_folder ON skills (company_id, folder_id); CREATE INDEX ix_folders_company_parent ON skill_folders (company_id, parent_id); -- 5) Fila "global" en pap_eje para IB-XX-Maestro (decisión de Victor · S-2) -- p.ej. INSERT ... o reservar id 0 según convención de pap_eje. -- 6) Índice UNIQUE anti-duplicados — SOLO tras re-correr admin_cleanup (ver Fase 1b) -- CREATE UNIQUE INDEX ux_skills_tenant_path ON skills (company_id, folder_id, name); ``` > Todas las columnas entran **nullable** primero → backfill → luego NOT NULL/UNIQUE. Cero impacto de usuario en Fase 0: el sistema se comporta idéntico. --- ## 4. Plan ejecutable ### Fase 0 · Estructura invisible 1. **Respaldar** BD (usar `admin_cleanup.php?action=backup` — exporta skills/versions/folders con INSERTs) + `.bak` de cada PHP a tocar. 2. Confirmar en `pap_eje` el `origen` de EmpowerLabs (``) y definir la fila "global" (S-2, Victor). 3. Correr el DDL §3 pasos 1–5. Verificar backfill: `SELECT company_id, COUNT(*) FROM skills GROUP BY company_id` — 0 filas NULL al terminar. 4. Crear `user_tokens` (paso 1). Aún sin emitir tokens. 5. **Criterio de salida Fase 0:** app funciona idéntica, cero NULL en `company_id`, índices creados. Nada visible cambió. ### Fase 1a · Identidad + enforcement en SOMBRA (no rechaza) 1. **`signup.php`** — al autenticar contra `colaborador` (ya lo hace), emitir token: `bin2hex(openssl_random_pseudo_bytes(32))`, guardar `sha256(token)` en `user_tokens`, devolver `{token, origen, origen_slug, puesto, min_client_version, governance_version}`. (Confirmar paridad de salt Node↔PHP `*_3Mp0W3rL485_*` si el login cambia de codebase.) 2. **`gate.php`** (nuevo, incluido tras `db.php`): `require_identity()` (token→wikiname→origen; fallback legacy a `usuario` **solo loguea**, no confía), `require_version()` (lee header `X-IB-Client-Version`), `governance()` (carga `governance.json` cacheado). **En 1a todo esto LOGUEA veredicto, no rechaza.** 3. **`validate_write()`** desplegado en shadow: recorre los 10 pasos del pipeline (§d del ARQ) y escribe el veredicto (`accept/quarantine/reject` + `rule_id` + `company_id`) en `skill_activity_logs`, pero **deja pasar** todo (salvo vacíos, que ya se rechazan hoy). 4. Cliente manda token en `X-IB-Token` (fallback param `token`). El daemon (`intellibanks-sync.js`) lee token de `.user_session.json`. 5. **Criterio de salida 1a:** correr ≥48h de operación real → el log de sombra muestra qué se rechazaría/cuarentenaría. Calibrar `governance.json` con datos reales (evitar falsos positivos que cuarentenen trabajo legítimo). Naming en `warn`. ### Fase 1b · Keystone ON (rechaza de verdad) 1. Re-correr **`admin_cleanup.php`**: `analyze` → `backup` → `cleanup` (confirm=SI, dry_run=0) para eliminar duplicados residuales. 2. Crear el índice `UNIQUE(company_id, folder_id, name)` (paso 6 del §3). 3. **`upload.php`** — reescribir la decisión insert-vs-update (L123+): `SELECT id FROM skills WHERE company_id=:cid AND folder_id<=>:fid AND name=:name` → existe ⇒ ruta de update (nueva versión del mismo `skill_id`); no existe ⇒ insert con `company_id`. El UNIQUE es la red final ante carreras. 4. **`get_sync_map.php` + lecturas** — agregar `WHERE company_id IN (, :cid)`; caché por tenant (`true_map_.json`); validación de pertenencia por `skill_id` en `get_skill_content`/`render`/`restaurar` (ID de otro tenant ⇒ 404, no contenido). 5. **`validate_write()`** pasa de sombra a real: tenant/ruta/extensión ⇒ `reject`/`quarantine`; naming sigue `warn`. Cuarentena en `_Quarantine//` dentro del espacio del tenant, `status='quarantined'`. 6. **`require_version()`** ON: cliente < `min_client_version` ⇒ 426. (Distribuir el cliente compatible ANTES de encender esto.) 7. **Rollback:** todo el enforcement detrás de un flag de config (no deploy) — apagar revierte a shadow. --- ## 5. Criterios de aceptación (probar a escala, no con 6) Simular ≥20 clientes concurrentes, 1 "malo" (versión vieja + carpeta respaldada + SherpaX mal configurado) y 1 con token de empresa A tocando rutas/IDs de B: - [ ] Request con token de A hacia ruta/ID de B ⇒ 403/404, **0 fugas cross-tenant** (escritura y serving). - [ ] `usuario` falsificado sin token válido ⇒ 401, no "adivina" identidad. - [ ] 100 updates rápidos del mismo path ⇒ **exactamente 1 fila** por (company, folder, name). - [ ] Escritura fuera de `IB-*` / dotfolder / mirror `archivo.*` ⇒ cuarentena per-tenant con usuario y regla, **0 al canónico**. - [ ] Cliente < versión mínima ⇒ 0 escrituras (426), atribuido. - [ ] Backfill: 0 filas con `company_id` NULL; cada archivo del vault de Victor mapea a ``. - [ ] Verificado con parámetro anti-caché (los GET del CDN no dan falso "no persistió"). --- ## 6. Tripleta + NEXTs - **Owner:** Alex · **Sherpa:** SherpaX Alex · **Ratifica arquitectura:** Victor (L3) · **Implementa:** Alex **NEXTs** - [ ] **Victor** — ratificar el `ARQ-` v02 y las 2 decisiones de negocio: fila "global" en `pap_eje` (S-2), default no-compartir cross-empresa. - [ ] **Alex** — confirmar `` en `pap_eje` y paridad de salt Node↔PHP (pendiente menor del ARQ). - [ ] **Alex** — ejecutar Fase 0 → 1a (≥48h sombra) → 1b, en rama `alex`, con respaldo previo a cada endpoint. - [ ] **Jay/equipo** — si aún no se corrió: wargame adversarial con Opus sobre el `ARQ-` v02 (foco riesgo CDN cross-tenant) ANTES de encender 1b en producción. - [ ] **Equipo WORX** — escribir `CP-XX-IntelliBanks-Governance-v01.json` (Capa 0, contrato §c del ARQ) — lo consume `gate.php` y el SherpaX. - [ ] Fase 2 (cliente: carpeta `/` + conmutador + migración local con candado) queda para un room posterior. --- ## CHANGELOG | Versión | Fecha | Cambio | |---|---|---| | v01 | 2026-07-07 | Creación. TP de arranque del room de implementación de multibancos (Fase 0+1, Opus + genniugit montado). Consolida el contexto del 7-jul (ARQ v02 con identidad genniux_board reconciliada, decisión de auth: sin segundo padrón, company_id=origen, sin tabla companies). Baja Fase 0 (DDL con backfill vía colaborador, user_tokens) y Fase 1 (signup emite token, gate.php + validate_write en sombra→keystone, upsert anti-dedup, filtros de tenant) a pasos ejecutables con archivos/SQL/endpoints reales verificados en código. Criterios de aceptación adversariales y NEXTs. |