# Testigo — From Intent to Proof (draft v0.3, 2026-07-15) > Nombre elegido: **Testigo** (colisión verificada: limpio). El working name anterior era "ProofFlow", que colisiona ×4. Capa de confianza que conecta la intención humana original con la ejecución por agentes y la evidencia verificable del resultado. No es task manager, ni CI/CD, ni agente, ni pagos. --- ## 1. Veredicto de la investigación (2026-07) **Espacio: parcialmente ocupado, calentándose rápido, sin estándar adoptado. Ventana: ~6-12 meses.** | Quién | Qué cubre de la cadena | Qué NO cubre | Tipo / madurez | |---|---|---|---| | in-toto / SLSA / sigstore | Provenance de build, atestaciones firmadas (DSSE) | Intención humana, aprobación, acciones de agentes | Estándar abierto maduro (CNCF) | | W3C PROV / PROV-O | Modelo genérico quién-hizo-qué post-hoc | Nada nativo de agentes IA (PROV-AGENT es research 2025) | Estándar W3C, adopción académica | | OTel GenAI / LangSmith / Langfuse | Telemetría de ejecución del agente | Evidencia firmable, aprobación, intención upstream | Maduro en observabilidad | | Identidad/delegación 2025-26 (AIP, Agent Passport, HDP, KYA, APS) | Delegación humano→agente verificable, receipts | Cadena completa intención→evidencia→resultado | Papers arXiv, NADA adoptado | | AER (Agent Execution Record) | Razonamiento del agente durante ejecución | Intención del cliente, reglas de negocio, aprobación | Paper 2026 + SDK preliminar | | EU AI Act art. 12/14, ISO 42001 | **Demanda** esta trazabilidad (reconstrucción por decisión + registro de supervisión humana), high-risk desde ago 2026 | Es ley, no implementación | Viento de cola regulatorio | | Claude Enterprise (Audit Logs + Compliance API) | 35 tipos de evento, retención 6 años, OTel | ⚠️ **NO captura sesiones locales** (gap documentado); solo su engine | Producto, enterprise-only | | GitHub agentic audit log / Copilot | Eventos de agentes **hosteados en GitHub** | Agentes locales, otros engines, intención/aprobación | Producto | | ProvenanceOne, aybruhm/provenance, Cursor Origin | Middleware de governance / git forge para agentes | Capa local del developer; POCs tempranos | Productos/POCs 2026 | | Vanta/Drata, DOORS/ALM | Controls de compliance; requisito→test clásico | Acciones de agentes, evidencia por acción | Productos maduros, otro nivel | | **Hyperion-GPU/ProofFlow** ⚠️ | Work contract → proof packet, MCP para Claude Code/Codex | Es tool standalone (hay que trabajar DENTRO de él), no protocolo; 48★ v0.1.8 | Competidor directo (y dueño del ex working name) | **El hueco estructural** (la tesis de éxito): las plataformas auditan *su* lado — Anthropic audita su API/enterprise pero no la sesión local; GitHub audita sus agentes hosteados; los observability venden telemetría sin firma ni aprobación. **Nadie está parado en la máquina del humano, en el momento de la aprobación, con visión multi-engine.** Ahí está agent-console, y ya captura ~80% de los insumos. ## 2. Estrategia de éxito ### Posicionamiento - **Categoría** (lo que evangelizamos): *intent-to-proof* — "toda acción importante de un agente puede demostrar por qué existió, quién la aprobó y qué evidencia produjo". - **Producto** (la cuña): el **proof packet** — un artefacto portable, firmado y verificable *sin agent-console*, que se adjunta a un PR, un release, una factura o un deploy. - **Protocolo** (el foso): el formato del packet + la coreografía de captura, publicado como predicado in-toto (interoperar > inventar). ### Loop de adopción (el packet ES el marketing) 1. El packet se comparte hacia afuera (PR, cliente, auditor) con un verificador de un click. 2. Quien lo recibe ve la cadena intención→aprobación→evidencia y pregunta "¿cómo genero yo esto?". 3. Respuesta: agent-console. El verificador standalone es la puerta de entrada gratuita. ### Dogfooding ×2 (credibilidad antes que spec) - **agent-console self-hosted**: cada PR del propio repo lleva su proof packet + commit trailer. El badge en el README y los PRs verificables SON la insignia. - **Fixy como caso de estudio**: cada deploy de Fixy lleva packet (encaja con FIXY_AGENTIC_COMPANY: una empresa operada por agentes que puede *demostrar* lo que sus agentes hicieron). Es la historia de venta: "así opera una empresa agéntica auditable". ### Interoperar, no inventar - Packet = in-toto Statement + DSSE envelope, predicado propio `…/attestation/v0.1` → verificable con tooling sigstore existente, historia EU AI Act art. 12 gratis. - Commits de agentes con trailers `Proof-Case:` / `Agent-Session:` — alineado con la práctica emergente de git-auditing (trailers sobreviven rebases). - Vocabulario del spec mapeado a art. 12/14 (reconstrucción por decisión, registro de supervisión humana) → cada feature es un checkbox regulatorio. ### Métricas de éxito (en orden) 1. Primer packet real: un deploy de Fixy o un PR de agent-console con packet verificable. 2. Un tercero verifica un packet **sin instalar agent-console** (verificador standalone funciona). 3. Primer usuario externo genera packets con su propio trabajo. 4. El spec público recibe su primer issue/PR de alguien que no somos nosotros. ### Riesgos y mitigaciones | Riesgo | Mitigación | |---|---| | Plataformas subsumen (Anthropic/GitHub shippean session records firmados) | Vivir en el gap documentado (local + multi-engine + aprobación humana); velocidad: packet demoable en semanas, no spec en meses | | Evidencia floja (intención sin resultado = ledger débil) | PostToolUse hook + diff pre/post turno son F1-F2, no opcionales | | Sobre-prometer integridad | Honestidad en el spec: ledger local es **tamper-evident** (hash-chain), no tamper-proof; anclaje del head hash en git refs/notes como refuerzo barato | | Privacidad (prompts contienen secretos/datos de cliente) | Pipeline de redacción + revisión humana ANTES de exportar (reusar la UX de approvals); niveles de detalle del packet | | Spec-first trap (enamorarse del protocolo) | Regla: nada entra al spec que no esté implementado y usado por nosotros | ### Nombre: Testigo (decidido 2026-07-15) El protocolo es el testigo presencial de la acción; "testigo firma, testigo declara" da el vocabulario del producto. Check de colisión limpio (no existe software/protocolo "Testigo"; certigo/Testmo no confunden). Descartados por tomados: ProofFlow (×4: Hyperion-GPU + PyPI `proofflow-mcp`, proofflow.online, proofflow.app, Huawei), ProofTrail (×2), IntentProof, Prova. Pendiente para F5 (publicación): dominio (testigo.dev o similar) y handle de GitHub. "Intent-to-proof" queda como nombre de la *categoría* en todo el copy. ## 3. Arquitectura v2 (endurecida) ### Unidad de correlación: el TURNO, agrupado en CASOS - **Turn** = prompt → stop (los hooks ya marcan ambos bordes). Todo evento PreToolUse/PostToolUse se ata al turno abierto de su `termId`. Correlación honesta: por termId + ventana temporal — el spec lo declara (binding heurístico, no criptográfico, dentro de la sesión). - **Case** (= `chain_id`) = hilo de intención: agrupa turnos de una o más sesiones. Nace de un ticket Jira (requisito), una nota, o el primer prompt; el usuario puede unir/partir casos. ### ProofEvent (ledger) ``` ProofEvent { seq, ts, case_id, turn_id, term_id, session_id, kind: prompt | approval_request | approval_decision | tool_use | tool_result | snapshot | turn_end | job_run | case_link | export, actor: { type: human|agent|scheduler, engine?, model? }, payload, // específico del kind prev_hash, hash // hash-chain sobre JSON canónico (JCS) } ``` - Persistencia: JSONL append-only por proyecto en `~/.local/share/agent-console/proof/`, calcado de `activity_service.rs` (atomic, crash-safe) pero **sin trim** (retención = valor). - Anclaje barato: head hash publicado como `refs/agent-console/proof-head` y como trailer en commits del agente. ### Cambios concretos sobre lo existente 1. `hooks_service.rs::respond()` — dejar de borrar req/res de approvals; emitir `approval_request` + `approval_decision` (con reason) al ledger. 2. Nuevo `posttooluse-hook.cjs` (Claude Code y Codex soportan el mismo schema de hooks) — captura tool, exit/resultado truncado → `tool_result`. 3. `stop-hook.cjs` enriquecido — al cerrar turno: snapshot post-turno + lista de archivos cambiados vs snapshot pre-turno (git diff --name-status entre refs) → `turn_end`. 4. Jira: persistir `case_id ↔ ticket` al sembrar sesión desde un issue. 5. Scheduler: `RunRecord` a disco como `job_run` (hoy vive en memoria). ### Proof packet (export) - Selección: un case (o rango de turnos) → **pipeline de redacción** (scan de secretos + revisión humana con la UX de approvals) → in-toto Statement con predicado propio → firma DSSE con clave ed25519 en keychain (misma infra que el token de Jira) → archivo `.proofpack` (zip: statement + evidencias referenciadas por hash). - **Verificador standalone**: CLI mínimo + página HTML single-file que valida hash-chain + firma + integridad de evidencias. Sin agent-console. Es la pieza de confianza y de adquisición. ### Qué NO hace (guardrails de scope) - No orquesta trabajo (no es workflow engine), no bloquea ejecución (captura, no gatekeeping — el gatekeeping ya existe: son los approvals), no sube nada a ningún servidor (local-first; compartir = exportar packet explícitamente). ## 4. Plan por fases (reordenado para momentum) - **F1 — Ledger + turnos** ✅ (2026-07-15): `testigo_service.rs` hash-encadenado (torn-tail self-healing, verify, input acotado); approvals persistidas (request en watcher, decision en `approval_respond`); turnos prompt→stop por termId; case_id con rebind desde el ledger; vínculo Jira en `startSessionForIssue` (`testigo_link_case`). Comandos: `testigo_list`, `testigo_verify`, `testigo_link_case`. *Demo: timeline crudo de un case en JSON.* - **F2 — Resultado** ✅ (2026-07-15): `posttooluse-hook.cjs` (observer auto-instalado, excerpt acotado 1KB) → eventos `tool_result` atados al turno; turn_end enriquecido con snapshot post-turno + `git diff --name-status` pre→post (cap 500 archivos, `filesTruncated` explícito); runs del scheduler al ledger como `job_run` bajo case `job:`. stop-hook ahora pasa cwd (fallback para worktrees si se pierde el estado del turno). *Demo: un turno cuenta qué cambió.* - **F3 — Packet + verificador** ✅ (2026-07-15, la insignia): `testigo_export.rs` — in-toto Statement v1 (predicado `https://github.com/cyl-castillo/testigo/attestation/v0.1`) firmado como DSSE ed25519 (clave en keychain, keyid = sha256 del pubkey); case export con poda tipo Merkle (eventos fuera del case = stubs seq/hashes → linkage verifica entero); redacción conservadora de secretos (token-shaped patterns; evento redactado conserva hashes = linkage sí, contenido no, declarado); rechaza exportar cadena rota. `testigo-verifier.html` standalone (WebCrypto, cero deps, cero red) escrito junto a cada packet. Panel "Proof" mínimo (verify badge + cases + export). ⚠️ Commit trailers movidos a F4 (requieren noción de "case activo" por sesión que el tab de F4 introduce). Incluye hardening de approvals descubierto en la prueba GUI: la cola del modal ahora re-sincroniza desde disco (`approvals_pending`, attach + window focus) — un evento perdido ya no es un approval invisible + stall de 90s, los `.req.json` son la verdad. - **F4 — UI "Proof" timeline + trailers** ✅ (2026-07-15): timeline por case en ProofPanel (click en case → turnos cronológicos: prompt, aprobaciones con razón, tool calls, archivos cambiados; export del case desde la vista); commit trailers `Testigo-Case:` en `git_commit` — atribución **por evidencia del ledger** (el turn_end más reciente ≤24h cuyo filesChanged intersecta los staged), nunca por sesión activa; best-effort, no bloquea el commit. - **F5 — Spec público** ✅ (2026-07-15): **https://github.com/cyl-castillo/testigo** — SPEC.md v0.1 (ledger, hash chain, turnos/cases, packet DSSE, algoritmo de verificación, security considerations honestas, mapping EU AI Act art. 12/14), JSON Schema del predicado, verificador standalone, ejemplo sintético auto-verificado (con redacted + stub), MIT. Pendientes de F5: ⏳ **dominio testigo.dev** (el predicado ya usa esa URI — no necesita resolver para ser identificador válido, pero conviene poseerlo antes de difundir) y ⏳ **caso de estudio Fixy** (primer deploy de Fixy trabajado vía console → packet real como ejemplo canónico). - **F6 — Revisión de redacción pre-firma** ✅ (2026-07-15): el export pasa a dos fases — `testigo_export_preview` muestra TODO lo que el packet contendría (post redacción automática) y el humano marca eventos para redacción manual ANTES de firmar; la redacción manual reemplaza el payload conservando seq/prevHash/hash (linkage verificable según el protocolo). Nació del caso real: el packet del deploy de Fixy traía la IP de prod y la ruta de la clave SSH — cosas que la redacción por patrones correctamente no toca y que solo un humano puede juzgar. ## 5. Roadmap v0.2 del protocolo - **V2-A — Anclaje git del ledger** ✅ (2026-07-16): tras cada cierre de turno (y job_run), el head del ledger se ancla como blob en `refs/agent-console/testigo-head` del checkout (`anchor_head`, best-effort, no-op fuera de git); los commits del console llevan `Testigo-Head: :` junto al `Testigo-Case:`. Reescribir el ledger pasa a exigir reescribir refs y commits pusheados. Spec §1.6 (aditivo, sin bump de predicado — a0b1eb0). - **V2-A.1 — Policy por proyecto** ✅ (2026-07-16): `TestigoSettings { witness, repo_marks }` persistido en testigo-settings.json (atómico + .bak), toggles en el tab Proof. **Defaults: witness ON (el ledger es local, fuera del repo) y repo_marks OFF** — trailers y anchor ref son opt-in por proyecto: en repos compartidos la marca es decisión del dueño. El gate de witness vive en `record()` (un solo punto cubre todos los kinds); las lecturas de evidencia pasada no se tocan. Spec actualizado (anchoring = MAY + opt-in, 1956214). - **V2-B — Timestamps RFC 3161** ✅ (2026-07-16): el export opcionalmente pide un timestamp token a una TSA sobre sha256 de la firma DSSE (construcción signature-time-stamp de CAdES) → prueba de existencia en el tiempo. **Corrección al plan original**: el token va a nivel packet (campo `timestamp` junto a `publicKey`), NO en el predicado — el predicado se firma antes de que exista la firma, así que un token sobre la firma no puede vivir dentro; y al ser aditivo fuera del envelope, no hay bump de predicado (v0.1 sigue; quitarlo no rompe la firma, solo pierde la prueba temporal). Spec §2.5. Implementación: `testigo_timestamp.rs` (TimeStampReq DER a mano — golden test vs openssl —, POST a la TSA en thread dedicado, chequeo estructural mínimo del grant: status 0/1 + digest ecoado), opt-in por proyecto (`timestampTsa` en TestigoSettings, default freetsa.org en el toggle del tab Proof), TSA caída = export falla (nunca packet silenciosamente sin lo pedido). El verificador declara el token (imprint ✓, genTime escaneado) y entrega el .tsr + comandos `openssl ts -verify` — sin fingir verificación CMS en el browser. E2E real contra freetsa en test `#[ignore]`. - **V2-E — Process context (spec v0.2, additive)** ✅ HECHO 2026-09-15 (working tree, sin commitear): `on_prompt` graba en el evento `prompt` los digests de los archivos de instrucciones presentes en el cwd al momento del prompt (`payload.context`, spec §1.7); el export deriva `provider` (harness agent-console + `languageModels` desde eventos `session_start`/`model_switch` de las sesiones involucradas, aunque queden fuera del rango), `contextArtifacts` (solo desde los prompts EMPAQUETADOS — un prompt redactado no aporta nada), `startTimestamp`/`endTimestamp` (ts de la primera/última entrada no-stub, formato `Date#toISOString` vía `rfc3339_ms`, verificable). Sin bump de URI ni de formato. Formas tomadas de FINOS Agentic Process Evidence (finos/ai-governance-framework#384) — mapeo en `testigo/docs/ape-mapping.md`. **Fix de paso:** `redactionCount` contaba hits de patrones; el spec (§2.3, normativo desde 2026-07-21) cuenta entradas — el packet publicado de Fixy (0.48.1) declara 12 sobre 8 y hoy FALLA en todos los verificadores: hay que re-exportarlo con este build (mismas redacciones manuales: seqs 2,4,5,7,8,10) y actualizar digests del README de testigo. Verificador embebido (`resources/testigo-verifier.html`) re-sincronizado con el del repo testigo (estaba atrasado desde jul-2026). - **V2-C — sigstore keyless**: modo de firma alternativo vía Fulcio/Rekor (identidad OIDC + log de transparencia público). Pesado y con implicancias de privacidad (el log es público): diseño en doc antes de código. - **V2-D — Requirements más allá de Jira + cases cross-project**: prefijos de case para otros trackers (github:, linear:) y agrupación de cases entre proyectos. - **Fix §1.5 — recómputo byte-exacto del hash** (2026-09-18, en paralelo con testigo#7 de Fredy): `verify` recomputaba re-serializando la struct, así que un miembro agregado después de `hash` (p. ej. un segundo `payload`) desaparecía del recómputo mientras cambiaba lo que la línea parsea — hash y linkage seguían "verificando". Ahora `recompute_line_hash` exige `hash` como miembro final (64 hex minúsculas), vacía solo ese valor y hashea todos los demás bytes de la línea cruda; el test de export usa la misma función y el verificador embebido lleva el hunk equivalente de testigo#7 (re-sincronizar cuando #7/#9/#17 mergeen). - **Re-sync del verificador embebido** (2026-10-05): `resources/testigo-verifier.html` vuelve a ser byte-idéntico al de testigo `main` tras mergear los 8 PRs de Fredy. Trae: validación de perfil y segmento (#17: `checkStructure`, códigos `firstFailure` nuevos, RFC 3161 inválido pasa de fail a warn), render por `textContent` (#9), recómputo byte-exacto (#7, ya estaba), resumen de checks agrupado (#24) y eventos de outcome (#26). Semántica nueva: la página se detiene en la primera falla de firma, keyid, perfil o contexto y no pinta la tabla. Los packets del productor (ej. Fixy 0.79.0) pasan el perfil sin cambios. Cadencia estándar: plan → fase → commit → release por fase (`/phased-feature-build`). ## 6. Monorepo: protocolo puro + plugins por engine (plan 2026-09-23) **Decisión de Carlos (2026-09-23):** el repo `testigo` queda como **protocolo** (spec, schema, conformance, verificador) más una librería de referencia neutra; la captura por engine se vuelve **plugins aparte** que implementan el protocolo — Claude Code y Codex primero, después cualquiera (LangChain #3, etc.). **Monorepo** (un `marketplace.json` lista N plugins) y el plugin de Claude **conserva el nombre `testigo`** para no romper a quien ya tiene `testigo@testigo` instalado. agent-console sigue siendo la implementación de referencia completa (Rust) y no cambia código en este arco. ### Estado de partida (lo que hay que desenredar) - `SPEC.md`, `schema/`, `predicate/`, `conformance/`, `verifier/`, `docs/` ya son neutros de engine (Claude aparece solo como ejemplo; `session_start.engine` y `external_evidence.source` son strings abiertos). - `cli/` mezcla dos cosas: el **núcleo neutro** (`ledger.mjs`, `export.mjs`, `verify.mjs`, `rfc3161.mjs`, `evidence.mjs`) y el **adaptador de Claude Code** (`hook.mjs` con `engine: "claude-code"` hardcodeado y `transcript_path` como evidencia externa, `hooks/hooks.json`, `commands/*.md`, `bin/testigo.mjs` con `init` que escribe `.claude/settings.json`). `package.json` se autodescribe como "captura desde hooks de Claude". - Ledger compartido por diseño: `~/.local/share/testigo/ledgers/.jsonl`, estado de sesiones keyed por `session_id`. Dos engines sobre el mismo repo escriben el mismo ledger y el case sale multi-engine solo — es la tesis del protocolo, no un problema. - El repo **no tiene CI** (`.github/workflows` no existe); los tests corren a mano (`cli/test.mjs`, `cli/test-concurrency.mjs`, `conformance/verify.mjs`). ### Restricción descubierta que fija el diseño Claude Code instala **solo el subdirectorio `source`** del plugin, copiado a `~/.claude/plugins/cache////` (verificado 2026-09-23 con `aimem-plugins`: 3 plugins de un repo, cada caché contiene únicamente su carpeta). Por lo tanto un plugin en `plugins/claude/` **no puede importar `../../packages/core`** en runtime. Solución: cada plugin lleva el núcleo **vendoreado** (`plugins//vendor/core/`, generado por `scripts/sync-vendor.mjs`, **commiteado**, y `scripts/check-vendor.mjs` en CI falla si difiere de `packages/core`). Se mantiene cero dependencias y no se depende de npm (la máquina no tiene login npm; `testigo` y `@testigo/core` están libres — E404 — reservarlos cuando haya token, no antes). ### Layout objetivo ``` testigo/ SPEC.md schema/ predicate/ conformance/ verifier/ docs/ ← protocolo, no se mueve packages/core/ ledger.mjs export.mjs verify.mjs rfc3161.mjs evidence.mjs + test.mjs packages/hooks-adapter/ hook.mjs neutro (schema de hooks Anthropic, que Codex copió): createHookHandler({ engine, events, evidence }) + test con fixtures plugins/claude/ .claude-plugin/plugin.json hooks/hooks.json commands/*.md bin/testigo.mjs README.md vendor/{core,hooks-adapter}/ plugins/codex/ bin/testigo-codex.mjs (init → ~/.codex/hooks.json) README.md vendor/… conformance/producer/ fixtures hook-input → ledger esperado + verify.mjs (M5) scripts/sync-vendor.mjs scripts/check-vendor.mjs .claude-plugin/marketplace.json testigo → ./plugins/claude · testigo-codex → ./plugins/codex .github/workflows/ci.yml core + adapter + plugins + conformance + check-vendor ``` ### Fases (una fase = un PR en `testigo`; commits de `git mv` separados de los de contenido) - **M0 — Despejar la pista (antes de mover nada).** Hay 6 PRs de Fredy abiertos que tocan `cli/lib/*`: #7 y #9 mergeables; #14, #17, #19 y #23 ya en conflicto entre sí (#19 hasta agrega `cli/lib/command.mjs` e `init.mjs`). Mover archivos debajo de ellos multiplica los conflictos. Orden: mergear #7 y #9; que #14 → #23 → #19 → #17 se rebaseen en secuencia y se mergeen; abrir un issue "Monorepo: protocol + per-engine plugins" con este plan, enlazando #2 (Codex adapter) y #3 (LangChain), para que nadie abra PRs nuevos contra `cli/`. *Gate:* ningún PR abierto que toque `cli/`. **Avance 2026-09-24:** #9 y #7 MERGEADOS (tests verdes por separado y en árbol combinado); la interacción esperada entre ambos (test 2 de `hash.test.mjs` simulaba el DOM en `vm` y el render por DOM de #9 lo rompía) resuelta en **#24 MERGEADO** (el chequeo byte-exacto pasa al harness de Playwright, 14/14 con Chrome; `hash.test.mjs` 2/2; 27+7 vectores). Issue del plan: **cyl-castillo/testigo#25**. Pedidos de rebase secuencial posteados en #14 → #23 → #19 → #17. ⏳ Falta: que Fredy rebasee y se mergeen los 4. Pendiente en agent-console: re-sincronizar `resources/testigo-verifier.html` con el del repo (ya tiene el hunk de #7; falta el de #9). - **M1 — Extraer el núcleo sin cambiar comportamiento.** `git mv` de los 5 módulos a `packages/core/`; `cli/` importa temporalmente de `../packages/core`; las secciones 2-4 de `cli/test.mjs` (link/chain/heal, export/redact, conformance como oráculo) pasan a `packages/core/test.mjs` sin tocar asserts. Agregar `ci.yml` acá (hoy no existe). *Gate:* `cli/test.mjs` + `test-concurrency.mjs` + conformance 20+7 verdes; un export con la misma seed de test produce el mismo packet que antes salvo `exportedAtMs`. **M1 MERGEADO 2026-10-07 (testigo PR #29, dos commits: `git mv` puro + recableado).** Corrección al plan: el vendoring NO puede esperar a M3 — con el núcleo fuera de `cli/`, toda instalación del plugin desde el marketplace en main HEAD se rompería (Claude Code copia solo el subdir). Así que ya en M1: `packages/core` es la fuente de verdad, `cli/vendor/core/` es la copia byte-idéntica que viaja con el plugin (`scripts/vendor.mjs sync|check`, gate en `cli npm test` y en CI); lo que se testea es lo que se instala; conformance importa `packages/core` directo. `export.mjs` resolvía el verificador relativo a su propio archivo → `defaultVerifierCandidates()` (dos y tres niveles arriba) + opción `verifierCandidates`. **Primer CI del repo** (`.github/workflows/ci.yml`): ubuntu+windows × node 20/22 + job Playwright con Chrome real; acciones pinneadas por SHA. Hallazgo: `test-portability.mjs` de Fredy falla en los runners de GitHub también en main sin M1 (baseline verificado con un PR borrador, run 37621736545; sospecha: temp en forma 8.3 `RUNNER~1` + code page) → issue testigo#31; el job de Windows corre el resto de la suite. Suite al mergear: CLI 10+8 + e2e + review + concurrencia, 69+47 vectores, 68 negativos, browser-script 116, Playwright 15/15. - **M2 — Adaptador de hooks neutro.** `hook.mjs` → `packages/hooks-adapter/` con `createHookHandler({ engine, events, evidence })`: `engine` deja de ser constante; la evidencia externa es estrategia inyectada (Claude: `transcript_path`; Codex: el rollout `~/.codex/sessions/**/rollout-*-.jsonl` si se puede localizar por session id, y si no, nada — declarado); `hooksConfig(command, events)` parametrizado por los eventos que cada engine soporta. *Gate:* tests actuales + test unitario del handler con fixtures por engine. - **M3 — Plugin `testigo` (Claude) en `plugins/claude/`.** Mover `cli/bin`, `cli/hooks`, `cli/commands`, `cli/README.md`; `marketplace.json` → `source: "./plugins/claude"` con el **mismo** `name: "testigo"` (las instalaciones existentes actualizan sin reinstalar); `vendor/` generado + `check-vendor` en CI; `cli/` desaparece (queda un `cli/README.md` stub de redirección durante un release). Actualizar rutas en `README.md` y en `docs/demo-inversor-2026-09.md` (apunta a `cli/bin` con rutas absolutas del disco de Carlos). *Gate:* e2e verde **y** prueba humana: `/plugin marketplace add` desde el branch, sesión completa, `/testigo:status`, export, packet verificado con `conformance/verify.mjs` — el plugin corriendo desde la caché, no desde el clone. - **M4 — Plugin `testigo-codex` en `plugins/codex/`.** `init` escribe/mergea `~/.codex/hooks.json` con los 4 eventos que Codex tiene (UserPromptSubmit, PreToolUse, PostToolUse, Stop; sin SessionStart ni PostModelSwitch → `session_start` con `engine: "codex"` se emite en el primer prompt de cada sesión, modelo solo si llega). Investigar `SessionEnd` (Codex 0.145) e `Interrupt` (0.150) para cerrar turnos sin Stop (hoy en agent-console quedan abiertos: M3/M7 del CLI watch). README con la declaración honesta de alcance (approvals fuera; qué campos manda Codex de verdad). Codex **no tiene marketplace**: distribución = `node plugins/codex/bin/testigo-codex.mjs init` desde el clone o desde la copia del marketplace de Claude (`~/.claude/plugins/marketplaces/testigo/`), documentado. Cierra el issue #2. *Gate:* sesión real de Codex → ledger → export → verifica; Claude y Codex sobre el mismo repo producen un case multi-engine (esa es la demo). - **M5 — Conformance de productores.** Hoy los vectores solo prueban verificadores. Nuevo `conformance/producer/`: fixtures de secuencias de hook-input (Claude y Codex) → ledger esperado (kinds, actores, bindings turno/case, payload normalizado sin ts ni hashes) y un runner que alimenta cualquier adaptador por stdin y compara. Los dos plugins lo pasan; los tests de `testigo_service.rs` en agent-console consumen las mismas fixtures. SPEC §6 "Producer conformance" (aditivo, sin bump). Esto es lo que vuelve real "todos los que quieran después": un adaptador nuevo (#3 LangChain) trae su fixture y pasa el runner. - **M6 — Versionado y release.** Versiones por paquete: core 0.3.0, `testigo` (Claude) 0.3.0, `testigo-codex` 0.1.0; tags `core-v*` / `claude-v*` / `codex-v*`; CHANGELOG por paquete. README raíz: "reference implementation: agent-console; lightweight reference producers: plugins/". M1-M3 salen juntos como plugin 0.3.0 (un salto visible); M4 es su propio release. ### Riesgos | Riesgo | Mitigación | |---|---| | Rebases de Fredy contra archivos movidos | M0 primero; `git mv` en commits propios para que `--follow` y los rebases resuelvan | | Deriva entre `packages/core` y `vendor/` | `check-vendor` bloqueante en CI; `sync-vendor` idempotente | | Doble testimonio (plugin + `testigo init` viejo, o dos plugins) | Ya advertido en el README; entre engines no hay doble: session ids distintos, mismo ledger por diseño | | Codex cambia hooks (async 0.148, SessionEnd, Interrupt) | El adapter recibe la lista de eventos por engine; la declaración de alcance vive en el README del plugin, no en el spec | | Nombre `testigo-codex` o scopes npm tomados | npm libre hoy (E404 en `testigo` y `@testigo/core`); reservar solo cuando haya token, el plan no depende de npm | Regla de oro intacta: nada entra al spec (M5) sin estar implementado y usado por nosotros.