=== GOVP Public Evidence ===
Contributors: gemacode
Tags: provenance, evidence, ed25519, verification, govp
Requires at least: 5.8
Requires PHP: 7.4
Stable tag: 2.38.2
License: MIT

Emite y verifica evidencias públicas GOVP-1 directamente en WordPress, sin sidecar.

== Description ==

GOVP Public Evidence convierte un sitio WordPress en emisor y verificador de
evidencias **GOVP-1**: registros firmados (Ed25519) que prueban que un activo
digital concreto (un documento, dataset, modelo o agente) existía con un
contenido exacto y atribuible a una identidad de dominio, verificable por
cualquiera y sin conexión. La verificación no necesita que el emisor siga vivo,
pero sí que **sobreviva una copia del registro** (en el propio dominio, en un
mirror o en un bundle exportado): la durabilidad depende de la disponibilidad de
esa copia, no es mágica.

Capa de fuentes y verificación (v2.21+):
GOVP ya no es solo "declaración firmada → testigos → confianza". Cualquier fuente
heterogénea (in-toto, sensores, robots, calibraciones, firmware, PLC, ERP,
aprobaciones humanas) se normaliza a un modelo común (GOVP_Sources) y se somete a
un VERIFICADOR ESPECÍFICO de su tipo (interface GOVP_Source_Verifier; SVL) antes de
contar: comprueba la firma, que el origen esté AUTORIZADO en un registro de anclas
que la fuente no controla (ninguna fuente se auto-valida), y las reglas del tipo
(rango, vigencia, revocación, provenance...). El estado es verificable
(verified/failed/expired/revoked/unavailable/declared_only), nunca un simple
true/false. Solo las fuentes en estado `verified` alimentan la malla de
observadores y el cálculo de confianza, que pondera por número, independencia
efectiva (anti-Sybil) y calidad de verificación (T = V × I × Q). Las fuentes
verificadas se encadenan en un DAG (GOVP_Chain) con un nodo CONCLUSIÓN recomputable;
una conclusión solo se marca "verificada de extremo a extremo" cuando todas sus
entradas superaron verificación real.

Qué significa "verificado" (y qué NO): que la señal superó las comprobaciones
independientes de su tipo (autenticidad, autorización, vigencia), NO que el valor
sea físicamente cierto. De la verdad del contenido responde el emisor. GOVP
verifica la CADENA DE CONFIANZA de cada señal y la pondera por independencia y
calidad; no sustituye a un evaluador con garantías (réplica determinista, cotas),
que es el papel de QEL. La frase defendible es: "GOVP verifica cada fuente según
las reglas de su tipo antes de incluirla, y la confianza se deriva del número, la
calidad y la independencia de las fuentes verificadas con éxito" — no "se prueba
que toda señal sea verdadera".

Novedades de la v2 (ruptura arquitectónica respecto a 1.x):

* **Sin sidecar.** Todo el motor crítico es PHP (libsodium) en servidor y
  JavaScript en cliente. Funciona en hosting compartido.
* **Verificación offline en el navegador** con WebCrypto Ed25519; si el runtime
  no lo soporta, cae al endpoint REST del propio sitio (libsodium) — nunca un
  "no se pudo verificar" por culpa del navegador.
* **Núcleo verbatim de la suite de conformidad.** El plugin trae los vectores de
  oro y un **auto-test** que prueba, dentro de tu instalación, que verifica byte
  a byte igual que la referencia Python y los vectores publicados.
* **Publicación en .well-known**: identidad del dominio (`govp.txt`), discovery
  (`govp/index.json`) y cada record (`govp/<id>.govp`).
* **Canonicalización de página GOVP-WP-CANON-1** en servidor; marca tu contenido
  legal con `data-govp-content` para hashes estables.

== Qué prueba y qué no ==

En una frase: GOVP produce **evidencia firmada de una afirmación publicada por un
dominio** — no una "certificación fuerte" de la verdad de su contenido.

Prueba la **integridad de la afirmación**: que el contenido firmado no se ha
alterado y es atribuible a la clave del sitio. **No** prueba que el contenido sea
cierto, legal, ni la inclonabilidad física de un objeto. La frontera es honesta y
deliberada.

Límites importantes (por diseño, documentados a propósito):

* **Fecha.** Con perfil GOVP-BASIC, `generated-at` la declara el emisor y no está
  anclada a un tiempo externo. Para fecha verificable usa un sello RFC 3161
  (GOVP-RFC3161) o un QTSP eIDAS (GOVP-QTSP).
* **Transparencia.** El registro de transparencia local (translog) vive en la base
  de datos del propio sitio. Es útil como bitácora, pero **NO equivale a
  transparencia pública**: un administrador o una BD comprometida pueden reescribir
  su historia. La transparencia fuerte exige anclaje externo (sello temporal,
  testigos independientes o un log público de terceros).
* **Disponibilidad.** Emisión, almacén, publicación y verificación recaen en el
  mismo WordPress. Si ese dominio cae o se compromete, la fuerza probatoria local
  se resiente. Mitígalo exportando bundles y manteniendo un mirror externo.
* **Superficie pública.** Los endpoints de lectura (`/records`, `/badge`,
  `.well-known/*`) responden con `Access-Control-Allow-Origin: *` de forma
  intencionada: la evidencia es pública y debe poder verificarse desde cualquier
  origen. No publican secretos.

== Uso ==

1. En **GOVP** → genera la clave de firma del sitio. Comprueba que el auto-test
   sale CONFORME.
2. En el editor de una entrada/página, panel **GOVP** → "Certificar".
3. Muestra el verificador donde quieras: `[govp_verify id="GOVP-DOC-xxxxxxxxxxxx"]`
   o automáticamente al pie de las entradas certificadas.

== Requisitos ==

PHP 7.4+ con ext/sodium y ext/dom. La normalización de dominios IDN de myGOVP
usa un punycode propio (RFC 3492): ext/intl ya NO es necesaria.

== Producción y seguridad ==

* **Clave privada fuera del raíz web (obligatorio en producción).** Por defecto la
  clave se guarda en una subcarpeta aleatoria de `uploads/` protegida con `.htaccess`.
  En un entorno de **producción** (`wp_get_environment_type() === 'production'`) con la
  clave bajo el raíz web, **la emisión se bloquea con un error claro**; la verificación
  sigue funcionando. Resuélvelo definiendo una ruta segura en `wp-config.php`:
  `define('GOVP_KEY_DIR', '/var/lib/govp-keys');`
  Si aceptas el riesgo y quieres mantenerla en `uploads`, define explícitamente
  `define('GOVP_ALLOW_WEBROOT_KEY', true);`.
* **Firma externa (KMS/HSM/Vault/endpoint).** Para que la clave no resida junto a la
  app, declara la pública y engancha el firmado:
  `define('GOVP_EXTERNAL_PUBKEY', '<base64 de la Ed25519 pública>');`
  y un filtro que devuelva la firma (64 bytes) producida por tu KMS/HSM:
  `add_filter('govp_external_sign', fn($n,$msg,$pub)=> mi_kms_sign_ed25519($msg), 10, 3);`
  El core **verifica** que la firma devuelta cuadre con la pública declarada antes de
  usarla. Mientras haya firmador externo, no se usa ni se requiere clave local.
* **Toda llamada HTTP saliente** (verificación, discovery myGOVP, testigos, TSA) pasa
  por `GOVP_Http`: solo IPs públicas, `redirection = 0`, IP fijada (anti-rebinding),
  esquemas/puertos en lista blanca.
* **Identificador de activo estable.** El `asset-id` por defecto es estable
  (`post-<ID>`): renombrar la URL NO cambia la identidad. Para usar el slug:
  `update_option('govp_asset_id_mode', 'slug');` o el filtro `govp_asset_id`.
* **Ficheros .well-known.** Si la detección automática del raíz no es fiable
  (multisitio, symlinks, `DOCUMENT_ROOT` atípico), fíjala explícitamente:
  `define('GOVP_WELL_KNOWN_DIR', '/var/www/html');` (o la ruta `.well-known` directa).
  En **GOVP → Ajustes** ves el estado de escritura y un botón para regenerar.
* **Rate limiting** por IP en los endpoints públicos, configurable y desactivable.
* **API de lectura paginada:** `GET /wp-json/govp/v1/records?page=1&per_page=50`
  (máx. 200), con cabeceras `X-WP-Total` y `X-WP-TotalPages`.

== Tests ==

Suite de conformidad cross-language reproducible con un solo comando:
`make test` (lint PHP + `pytest conformance/` en un virtualenv aislado).
Requiere PHP-cli con ext/sodium, python3 y make. Ver `requirements.txt` y
`composer.json`.

== Changelog ==

= 2.38.2 =
* El escenario reproducible de malla levanta tres nodos WordPress reales por HTTP:
  valida acuerdo honesto, detecta y atribuye un split-view, y comprueba continuidad
  cuando un nodo deja de responder.
* La integración continua ejecuta la malla junto a PHP 7.4, 8.1 y 8.3, lint y
  conformidad PHP↔Python.
* La custodia de pruebas mantiene cada clave fuera del webroot y la comparte de
  forma explícita entre WP-CLI y el proceso HTTP del mismo nodo.

= 2.38.1 =
* El registro del bloque `govp/verify` es idempotente; evita avisos y fallos en
  ciclos de carga repetidos.
* La imagen de pruebas incluye Python y puede ejecutar de forma autocontenida la
  conformidad PHP↔Python.
* La documentación enumera únicamente las implementaciones entregables y elimina
  la referencia prematura a Kotlin.

= 2.9.7 =
Endurecimiento para producción (responde a la auditoría de "valoración máxima"):
* **Toda la salida HTTP centralizada en `GOVP_Http`.** Se eliminan los
  `wp_safe_remote_get/post` residuales en testigos, discovery myGOVP y TSA: ahora
  `redirection = 0`, validación de IP pública y pinning anti-rebinding en todos.
  Nuevo `GOVP_Http::safe_post()` endurecido para la TSA RFC 3161.
* **Clave segura por obligación.** En producción, si la clave está bajo el raíz web
  la **emisión se bloquea** con un error accionable (la verificación sigue intacta).
  Overrides: `GOVP_KEY_DIR` (recomendado) o `GOVP_ALLOW_WEBROOT_KEY`. El health check
  lo marca como fallo duro.
* **Firma externa (KMS/HSM/Vault/endpoint).** Hook `govp_external_sign` +
  `GOVP_EXTERNAL_PUBKEY`/opción `govp_external_pubkey`. El core verifica que la firma
  devuelta cuadre con la pública declarada. Sin clave local cuando hay firmador externo.
* **REST `/records` paginado de verdad:** `?page=&per_page=` (máx. 200) con `total`,
  `pages` y cabeceras `X-WP-Total` / `X-WP-TotalPages`.
* **Escritura `.well-known` robusta:** override explícito `GOVP_WELL_KNOWN_DIR` y
  detección de raíz coherente con `ABSPATH` (evita escribir en una raíz equivocada
  cuando el SAPI reporta mal `DOCUMENT_ROOT`).
* **Lenguaje comercial corregido** en la ficha pública: "declaración verificable
  publicada por un dominio… prueba integridad, autoría técnica y no-modificación
  posterior — no la veracidad del contenido; la alteración es detectable mientras se
  conserve la evidencia".
* Harness de integración ampliado a 32 comprobaciones (firma externa, paginación,
  secure_status), deterministas.

= 2.38.0 =
* El badge del verificador («Verificado» / «Válido con límites» / etc.) ahora es un ENLACE a la página de evidencia GOVP de ese registro (/govp/ID). Aplica al shortcode [govp_verify], al bloque de verificación y a la insignia automática de los posts. La URL respeta la base pública configurada y subdirectorios; en webs externas usa la URL absoluta del emisor incluida en el marcador.

= 2.37.0 =
* Corregido: el sello de pie ahora se inyecta vía wp_footer en lugar de al final del contenido, por lo que se ve en CUALQUIER tema (incluidos los de bloques/FSE como Twenty Twenty-Two/Three/Four/Five y page builders), donde el añadido a the_content no aparecía. Se sigue eligiendo por página en la columna «Sello». El shortcode [govp_footer_seal] sigue para colocación manual dentro del contenido.

= 2.36.0 =
* El sello de pie de página ahora se decide POR PÁGINA. Se elimina el interruptor global: cada afirmación protegida tiene en su fila (Protección -> Afirmaciones protegidas) una columna «Sello» con un botón Sí/No para mostrarlo u ocultarlo individualmente.
* Por defecto, ninguna página muestra el sello hasta que lo marcas una a una. El shortcode [govp_footer_seal] sigue disponible para colocación manual en el tema.

= 2.35.0 =
* El sello de pie de página ahora es una etiqueta simple: la palabra «GOVP» en blanco sobre un fondo indigo (#3B43B5), con esquinas redondeadas, enlazada a la verificación de la página. Sustituye al sello anterior de icono + palabra. Sigue siendo opt-in (Difusión) y disponible como shortcode [govp_footer_seal].

= 2.34.0 =
* NUEVO: sello de pie de página (opt-in). Una marca discreta —la palabra «GOVP» sobre un mini-icono de sello (cuadrado indigo con un check)— al final de cada página protegida, enlazada a su verificación con la evidencia precargada. Más sobrio que el sello tipo pill.
* Se activa/desactiva en GOVP -> Protección -> Difusión, y también está disponible como shortcode [govp_footer_seal] (admite id="..." y framed="0" para el sello sin el marco superior), para colocarlo en el pie del tema.
* Suite ampliada a 115 tests (el sello se muestra solo en páginas protegidas y enlaza al verificador; se añade en el contenido solo cuando está activado y dentro del loop principal).

= 2.33.0 =
* NUEVO: código QR de validación en móvil. La ficha pública de cada evidencia muestra un QR que, al escanearlo con la cámara o con la app GOVP, abre la página de verificación con el registro YA PRECARGADO y el contexto (título + GOVP-ID + tipo); la persona solo pulsa Verificar. Validación MANUAL, no automática.
* Nuevo parámetro ?govp=GOVP-ID en la página de verificación: rellena el campo, muestra qué se va a validar (título y GOVP-ID) y enfoca el botón, sin arrancar la verificación sola.
* El QR aparece también en el historial del Centro de Transparencia (última versión) y mediante el shortcode [govp_qr] (por id de página).
* El QR se genera en el navegador como SVG, SIN servicios externos ni CDN (librería MIT empaquetada en el plugin). Verificado escaneable por round-trip (codificación y decodificación independiente).
* Suite ampliada a 113 tests (URL de verificación precargada; el shortcode QR apunta al verificador con el GOVP-ID).

= 2.32.0 =
* NUEVO: constructor visual de definiciones de fuente (GOVP -> Fuentes), sin código. Cada fuente define su tipo (operator.approval, sensor, calibración, in-toto, ERP, firmware, file.hash, url.snapshot, custom.source), su namespace de ancla, sus campos dinámicos y sus reglas; con export/importar JSON.
* El Centro de Protección permite ADJUNTAR FUENTES al proteger con plantilla: se elige la definición, se rellenan sus campos y se pega la clave pública + la firma producidas por un tercero. Las fuentes pasan por la SVL real; si al menos una queda verificada (firma íntegra + clave autorizada como ancla externa), la afirmación alcanza el nivel «Verificada de forma independiente».
* Honestidad por diseño: GOVP NO firma estas fuentes, las verifica. Una firma válida cuya clave NO está autorizada como ancla NO concede independencia (queda en Sellada): no se puede falsear la verificación independiente desde el propio sitio. Las anclas se gestionan en Evidencia operacional -> Anclas (SVL).
* Suite ampliada a 111 tests (definiciones de fuente CRUD/export-import; camino completo a INDEPENDENTLY_VERIFIED con fuente anclada; fuente firmada pero sin ancla se queda en SEALED).

= 2.31.0 =
* NUEVO: el Centro de Protección protege CON PLANTILLA (cierra el círculo no-code). Botón "Con plantilla" en cada página (sugerida o ya protegida) que abre una pantalla donde se elige la plantilla y se rellenan sus campos mediante un formulario que se construye solo según la plantilla seleccionada; al enviar, GOVP sella la página, evalúa las reglas y calcula su nivel.
* Nueva columna "Nivel" en la tabla de afirmaciones protegidas: Declarada / Sellada / Verificada / Verif. independiente, con color. El flujo completo del usuario final ya es: crear plantilla (sin código) → aplicarla a una página → ver el nivel alcanzado y su evidencia.
* El nivel se expone también en el endpoint público del widget. Verificado por ejecución: el handler real (con nonce) aplica la plantilla y deja la página en VERIFIED; el panel renderiza la columna Nivel y los botones. Suite 108 tests.

= 2.30.0 =
* NUEVO (núcleo multipropósito de la PCPL): motor de plantillas configurable por el usuario, SIN escribir código. Ya no hay verificadores cerrados por caso (ISO, precios, legal…): el usuario crea sus propios modelos de protección.
* GOVP_Claim_Template: plantilla reutilizable {nombre, categoría (legal/commercial/scientific/ai/corporate/custom), riesgo, campos dinámicos, reglas, tipos de fuente}. Campos: texto, número, fecha, URL, archivo, booleano, lista; con obligatorio/opcional y validación (URL/fecha/regex).
* GOVP_Verification_Rule: 9 reglas reutilizables evaluadas SIN eval — field_exists, valid_url, file_exists, hash_required, valid_date, not_expired, signature_required, external_anchor_required y custom_expression (campo·operador·valor).
* GOVP_Source_Definition: definiciones de fuente (nativas o custom.source) configurables.
* Cuatro niveles de protección claros: DECLARED → SEALED → VERIFIED → INDEPENDENTLY_VERIFIED (este último cuando hay ancla/verificador externo vía SVL).
* Constructor visual en GOVP → Plantillas: lista, alta/edición con campos y reglas dinámicos, clonar, y EXPORTAR/IMPORTAR JSON para reutilizar la misma plantilla en varios sitios WordPress. Catálogo de fábrica de ejemplo (no editable; clonable).
* GOVP_Claims::apply_template(): valida los campos contra la plantilla, sella con GOVP (firma), evalúa las reglas y calcula el nivel; las fuentes opcionales pasan por la SVL real para decidir si hay ancla externa. El nivel se expone también en el endpoint público del widget.
* No rompe SVL/Mesh/Witness/Trust/Anti-Sybil: los orquesta. Suite ampliada a 108 tests (reglas sin eval, CRUD+builtins, export/import, progresión de niveles, validación de campos, apply_template extremo a extremo).

= 2.29.0 =
* NUEVO: descarte de sugerencias por página dentro del Centro de Protección. Cada página sugerida tiene un botón "Descartar" que la excluye de las sugerencias SIN protegerla (no todas las páginas que detecta el sistema interesan al usuario).
* Reversible: sección plegable "Descartadas" con un botón "Volver a sugerir" por página, para deshacer un descarte por error.
* El recuento del aviso de bienvenida se invalida al descartar o restaurar, de modo que el número refleja siempre las páginas realmente pendientes. El aviso superior (con su "descartar" global) se mantiene igual.
* Suite ampliada a 100 tests (descartar excluye de la detección e invalida el recuento; restaurar la devuelve a las sugeridas).

= 2.28.0 =
* NUEVO (expansión fuera de WordPress): widget incrustable. Cualquier web —WordPress o no— puede mostrar el sello "Protegido por GOVP" con estado EN VIVO pegando un <div class="govp-evidence" data-claim="ID"></div> y un <script src=".../wp-json/govp/v1/embed.js" async>. El sello muestra el nº de versiones y enlaza al historial público de esa afirmación.
* Endpoint público GET /wp-json/govp/v1/claim/{id} (CORS *), que devuelve SOLO datos ya públicos (título, tipo, estado, nº de versiones, enlace a evidencia y a historial); 404 si la afirmación no está protegida; sin exponer hashes ni internos.
* govp-embed.js autónomo y sin dependencias: deriva su origen del propio <script>, pinta el sello con DOM seguro (sin innerHTML de datos), soporta varios sellos por página y carga asíncrona.
* Sección "Difusión" del panel: nueva fila del widget y códigos de inserción listos para copiar por cada afirmación protegida.
* Suite ampliada a 98 tests. Verificado por ejecución: JSON 200/404 con CORS *, embed.js con sintaxis válida (node --check), e inyección real del sello en un DOM simulado enlazando al historial ("3 versiones · ver historial").

= 2.27.0 =
* NUEVO (diferencial): historial de versiones en la página pública de Transparencia. Cada afirmación tiene vista de detalle (?govp_claim=ID) que lista TODAS sus versiones —no solo la actual— con fecha (UTC), huella del contenido y enlace a la evidencia de cada versión, de la más reciente a la más antigua. "Esto es lo que afirmábamos en 2024, 2025, 2026", cada versión fechada, firmada y verificable de forma independiente.
* El recuento de versiones de la lista de Transparencia ahora enlaza directamente al historial de esa afirmación.
* Suite ampliada a 94 tests (el historial muestra todas las versiones con un enlace de verificación por versión; el recuento enlaza al detalle). Verificado por ejecución: 3 versiones de una tarifa con huellas distintas, cada govp_id verifica por separado.

= 2.26.0 =
* NUEVO (difusión / adopción): el Centro de Protección ahora ayuda a que el producto se expanda por sí solo.
* Onboarding al activar: un aviso de bienvenida detecta cuántas páginas críticas hay sin proteger (y cuántas de riesgo alto) y lleva al Centro de Protección con un clic. Se puede descartar; el recuento se cachea 1 h y se refresca al proteger.
* Página pública de Transparencia autocreada: botón "Crear página de Transparencia" que publica una página /transparencia con [govp_transparency] (idempotente: no duplica). URL limpia y compartible.
* Sello automático opt-in: interruptor que añade "Protegido por GOVP · Ver evidencia" al final de cada página protegida (solo en la query principal singular; nunca en feeds, extractos ni portada). Cada visita difunde la evidencia.
* Sección "Difusión" en el panel con ambos controles y recordatorio de shortcodes.
* Suite ampliada a 92 tests (sello fuera del loop no inyecta e inyecta dentro del loop principal, página idempotente, onboarding respeta el flag). Verificado por ejecución: aviso con CTA, inyección real del sello en el loop principal con enlace a la evidencia.

= 2.25.0 =
* NUEVO: Centro de Protección (Public Claims Protection Layer). Capa funcional encima de GOVP que reposiciona el producto: "proteja las afirmaciones públicas de su organización y demuestre qué publicó y cuándo". Submenú propio en el menú GOVP.
* Detección automática: escanea páginas/entradas publicadas, sugiere tipo (privacidad, condiciones, cookies, aviso legal, tarifas, certificación, clínico, científico, promoción, producto, especificación, IA, declaración corporativa...) y nivel de riesgo (alto/medio/bajo), con el riesgo alto primero. "Proteger" en un clic o "Proteger todo".
* Historial de versiones (el diferencial): cada afirmación conserva todas sus versiones con hash, fecha y evidencia GOVP; no solo el estado actual. Si una página protegida cambia, se marca automáticamente (save_post) y un clic crea una versión nueva.
* Centro de Transparencia público: shortcode [govp_transparency] (lista de afirmaciones protegidas con estado verificado e historial) y sello [govp_claim_badge] ("Protegido por GOVP · Ver evidencia"). Lenguaje orientado a negocio; la criptografía queda bajo el capó. Reutiliza la certificación de páginas existente (canonicalización + firma).
* Suite ampliada con 6 tests (detección/tipo/riesgo, protección+evidencia, sin versión duplicada si no cambia, cambio->marca+versión nueva, transparencia, badge).

= 2.24.2 =
* Corrección: la página de administración "Evidencia operacional" daba 404. Causa: su submenú se registraba con la prioridad por defecto de admin_menu y, al inicializarse antes que el menú padre 'govp', el page hook se calculaba mal (admin_page_govp-opev en vez de govp_page_govp-opev). Ahora se registra con prioridad 30 (igual que Ajustes), después del menú padre. La página carga correctamente.

= 2.24.1 =
* Corrección de documentación: la descripción del plugin (cabecera y readme) describía aún el producto "declaración -> firma -> testigos -> confianza" y no mencionaba la capa de fuentes/verificación (Sources/SVL/Chain) que ya estaba en el código desde la 2.21. Ahora la prosa refleja el pipeline real (fuente -> verificación por tipo contra anclas independientes -> fuente verificada -> malla -> confianza) con los límites honestos de qué significa "verificado".

= 2.24.0 =
* Acabado público: la página pública de una conclusión muestra un BADGE prominente que distingue "Verificada de extremo a extremo" (DAG íntegro + todas las fuentes verificadas por el SVL) de "estructura verificada; fuentes no verificadas de extremo a extremo" (alguna fuente solo declarada). La cadena se verifica una sola vez y alimenta tanto el badge como el informe detallado.
* Acabado de autoría: el panel incluye una ayuda de composición (registros recientes con sus GOVP-ID, fuentes y claves disponibles) para construir las condiciones de una conclusión sin pegar identificadores a ciegas.

= 2.23.0 =
* SVL <-> Chain: veredicto VERIFICADO DE EXTREMO A EXTREMO. verify_chain() ahora, además de la integridad estructural (firmas + integridad + enlaces + recompute de la conclusión), lee el resultado del SVL embebido en cada nodo y expone structural_ok, sources_verified y end_to_end_verified. Una conclusión solo es end-to-end si todos sus nodos de evidencia están VERIFICADOS por el SVL, no solo sellados.
* GOVP_SVL::seal_verified(): sella un nodo a partir de fuentes realmente verificadas por el SVL, embebiendo su resultado; el nodo es encadenable. GOVP_SVL::verify_list() expuesto.
* Panel de anclas de verificación (SVL): el operador registra/quita orígenes autorizados por espacio (device, issuer, builder, firmware_vendor, erp_system, operator) con validez opcional. El informe de cadena (panel y página pública) muestra la columna Fuentes (SVL) por nodo y la banda end-to-end.
* Suite ampliada con 2 tests de la conexión (cadena con entradas verificadas -> end-to-end; entrada sin ancla -> estructura válida pero NO end-to-end).

= 2.22.0 =
* NUEVO: Source Verification Layer (clase GOVP_SVL). «Cada señal se verifica, no se declara». Ninguna fuente puede auto-validarse: firmar el propio payload solo prueba autoría; la verificación exige contrastar la fuente contra ANCLAS independientes que la fuente no controla (registros de claves/emisores/builders autorizados, ventanas de validez, revocación) más las reglas de su tipo.
* Entidad GOVP_Verified_Source y resultado estándar GOVP_Verification_Result (status, method, reason, confidence, evidence). Estados: verified, failed, expired, revoked, unavailable, declared_only (nunca un simple true/false).
* Interfaz universal GOVP_Source_Verifier con verificadores específicos por tipo: in-toto, sensor, calibración, firmware, robot, ERP y operador humano. Pipeline idéntico para todos; in-toto sin privilegios.
* El estado lo fija el VERIFICADOR, nunca la fuente: una fuente firmada por sí misma pero sin ancla queda 'declared_only', no 'verified'. Verificación en dos pasadas (calibraciones primero -> contexto para sensores: una calibración caducada vuelve el sensor 'expired').
* Trust trabaja SOLO con fuentes verificadas. Fórmula T = V × I × Q (verificadas × independencia efectiva n_eff × calidad media de verificación), con banda high/medium/low. El resultado identifica qué fuentes respaldan la conclusión y por qué.
* Suite ampliada con 8 tests del SVL (sensor válido->verified, firma mala->failed, calibración caducada->expired, in-toto->verified, trust excluye fallidas, no auto-validación, verificador por tipo, revocación).

= 2.21.0 =
* NUEVO: Modelo universal de fuentes de evidencia (clase GOVP_Sources, "Source Adapter Layer"). Convierte cualquier fuente heterogénea (in-toto, sensor, robot, calibración, firmware, PLC, ERP, operario, fichero, URL, declaración) en una unidad común verificable GOVP_Evidence_Source. in-toto NO recibe trato especial: produce una fuente igual que las demás y pasa por el mismo pipeline.
* Taxonomía de source_type normalizada; adaptadores from_intoto/from_sensor/from_calibration/from_robot/from_generic; canonicalización determinista (mismo hash con distinto orden de claves); firma/verificación Ed25519 con binding identidad+tipo+contenido; modo estricto que rechaza campos desconocidos.
* Trust por INDEPENDENCIA (no recuento bruto): cada fuente alimenta a GOVP_Sybil2/GOVP_Trust vía independence_group (owner) y clave del firmante (key). 4 orígenes independientes -> n_eff=4 strong; 4 correlacionados (mismo grupo/clave/IP/ASN) -> n_eff=1 weak. Solo cuentan las fuentes verificadas.
* Estado agregado honesto: una fuente fallida da 'verified_with_failed_sources' (no 'verified'); declared_only/unavailable dan 'partial'.
* Manifiesto GOVP (claim + evidence_sources + result{status,trust_level,n_eff}) sellable como registro GOVP firmado, público y encadenable.
* Suite ampliada con 8 tests (equivalencia, trust por independencia, fallo->parcial, in-toto no privilegiado, canonicalización, firma/verificación, modo estricto).

= 2.20.0 =
* Cadena de evidencia, ahora con superficies de uso: AUTORÍA desde el panel (sella un nodo conclusión sobre registros existentes definiendo condiciones GOVP-ID | fuente | clave | op | args | etiqueta, veredicto y combinador) y LECTOR (verifica cualquier cadena por su GOVP-ID y muestra el DAG + recompute).
* AUDITORÍA PÚBLICA: la página pública de un registro que es conclusión o tiene dependencias muestra el DAG verificado (firma + integridad + enlaces + recompute), con enlace al informe JSON de la cadena para recomputación independiente.
* Renderizador de informe reutilizado en panel y página pública (GOVP_Chain::report_html).

= 2.19.0 =
* NUEVO: Cadena/DAG de evidencia industrial (clase GOVP_Chain). Cada nodo es un registro GOVP que DECLARA sus dependencias (depends_on) ancladas por el hash del nodo padre: manipular un nodo aguas arriba rompe el enlace. Modela el flujo Robot -> Sensor -> Calibración -> in-toto -> Firmware -> PLC -> ERP -> Conclusión.
* Nodo CONCLUSIÓN: referencia variables de sus entradas y declara un veredicto bajo una regla determinista (operadores eq/ne/gt/gte/lt/lte/between/in/exists, combinador and/or). El verificador RECOMPUTA la regla sobre las entradas ancladas por hash y confirma que el veredicto es reproducible.
* Verificador verify_chain(): recorre el DAG comprobando firma + integridad + enlaces + recompute de la conclusión, con detección de ciclos. Endpoint REST de solo lectura: GET /govp/v1/opev/chain/{GOVP-ID}.
* Tamper-evidencia demostrada: alterar la temperatura de un sensor aguas arriba rompe la integridad y vuelve la conclusión no reproducible; una conclusión que declara un veredicto que no se deriva de las entradas se detecta.
* Honestidad respecto a QEL: GOVP enlaza, sella y verifica la estructura y recomputa una regla declarada; el evaluador con garantías (réplica determinista, cotas) sigue siendo QEL.

= 2.18.0 =
* NUEVO: Ingesta firmada por fuente (máquina-a-máquina). Endpoint REST `POST /govp/v1/opev/contribute`: cada sistema fuente (PLC, sensor, ERP, gateway, in-toto, laboratorio) firma su contribución con su propia clave Ed25519 y la envía. GOVP verifica la firma y que la fuente esté en el allowlist de fuentes autorizadas, y acumula las contribuciones por asunto.
* El operador sella las contribuciones acumuladas en un registro GOVP donde CADA dato queda atribuido y firmado por su fuente (signed_by + signature por fuente, re-verificable de forma independiente). El sensor mismo avala su lectura, no el operador transcribiéndola.
* Rechazos: fuente no autorizada -> 403; firma que no casa con los datos (manipulación) -> 400; reenvíos deduplicados por firma.
* Panel: gestión de "Fuentes autorizadas" (registrar/quitar clave pública + etiqueta), "Ingesta pendiente de sellar" (por asunto, con botón Sellar) y nota para integradores con el endpoint y el esquema de firma. Rate-limit del segmento opev.

= 2.17.0 =
* NUEVO: el usuario puede DEFINIR las variables que quiere identificar. Perfiles de variables reutilizables (clave | fuente | tipo | etiqueta) que se gestionan y se cargan en el formulario de captura con un clic.
* Corroboración HONESTA agrupada por hash: el panel agrupa a los observadores por el hash observado, mide n_eff por grupo y lo compara con el asset-sha256 del registro. Si los testigos discrepan o el hash no coincide con el registro, el veredicto es DISCREPANCY — ya no se muestra "fuerte" cuando en realidad no concuerdan (corrige el conteo que sumaba observadores aunque no estuvieran de acuerdo).
* Reincorporadas las correcciones del flujo de certificación (2.14.1): handle_certify acepta GET+POST y exige edit_post; el endpoint REST /certify usa edit_post sobre el post_id; ajax_certify devuelve JSON 403 claro ante nonce inválido (no "-1"); el metabox muestra aviso de borrador y un enlace de respaldo seguro.

= 2.16.0 =
* Corroboración de la evidencia operacional surfaced en el panel: cada registro muestra su veredicto de observadores INDEPENDIENTES (ninguno / débil / fuerte + n_eff), recomputado con la capa witness+trust ya existente (GOVP_Trust::assess).
* Cierra el bucle honesto: nodos independientes descargan el registro publicado y firman un recibo (/witness/submit); el nodo estampa sus señales de red; la independencia se mide con anti-Sybil (mismo dueño/red colapsan). Corrobora publicación+integridad, NO la veracidad de los valores (eso es QEL).
* Validado por ejecución: 3 observatorios independientes → strong (n_eff=3); 3 testigos del mismo dueño → colapsan a n_eff=1.

= 2.15.0 =
* NUEVO: Evidencia operacional multi-fuente (GOVP_OpEvidence). Un panel admin «Evidencia operacional» permite declarar un conjunto de variables agrupadas por FUENTES (robot, sensor, calibración, in-toto, PLC, ERP, firmware, operario, laboratorio…), tantas capas como se quiera.
* GOVP canoniza, hashea y FIRMA el conjunto como un registro GOVP-1 `dataset` verificable e íntegro, ligado al dominio. Es una DECLARACIÓN (no corroboración ni medida de veracidad).
* El payload canónico exacto se sirve crudo por REST (`GET /govp/v1/opev/{id}`), de modo que cualquiera —o QEL— puede descargarlo y recomputar el `asset-sha256` del registro firmado (byte-idéntico, garantizado vía base64 a salvo del filtrado de WP).
* Listo para QEL: el payload incluye `qel: { status: pending, profile }`; la validación por regla (rango de temperatura, calibración vigente, etc.) la realizará QEL en el futuro si se contrata. GOVP sella; QEL juzga.

= 2.14.0 =
* Nueva página pública **Transparencia** (`/govp/transparencia`), enlazada en el menú junto a Registro y Verificar.
* Muestra el estado de la cadena de transparencia: nº de entradas, cabeza (head), raíz Merkle e integridad según el servidor.
* Botón **«Verificar la cadena en mi navegador»**: descarga la cadena completa (`/translog/export`) y recalcula en el cliente cada enlace, cada hash y cada firma Ed25519, sin fiarse del servidor. Localiza cualquier manipulación en el `seq` exacto.
* Degradación honesta: si el navegador no soporta Ed25519 nativo, verifica encadenado + hashes (detecta alteración de contenido u orden) y avisa de que las firmas se comprueben con GOVP Studio.

= 2.13.3 =
* Bienvenida en Ajustes de GOVP: una frase que explica qué hace el plugin y una
  lista de 3 pasos (generar la clave, guardarla fuera de la web, certificar el
  primer documento) que se marca sola segun el estado real de la instalacion. Sin
  cambios de logica.

= 2.13.2 =
* Páginas públicas (/govp, ficha de cada registro y /verify) explicadas para un
  visitante que llega sin contexto: qué es el registro, qué demuestra el sello
  «Verificado» (integridad, autoría y fecha declarada) y —con la misma claridad—
  qué NO demuestra (ni veracidad ni legalidad del contenido), más para qué sirve
  verificar y qué significa un resultado verde. Solo texto y estilo; sin cambios de lógica.

= 2.13.1 =
* Pantalla myGOVP reescrita para quien llega de cero: explica con lenguaje claro
  QUÉ es la capa de red, POR QUÉ conviene configurar la URL de myGOVP (avales entre
  dominios verificables, resiliencia si tu sitio cae, ser verificable por terceros,
  compromisos públicos con fecha) y que es OPCIONAL. Cada sección lleva una línea de
  ayuda que explica qué hace. Sin cambios de lógica.

= 2.13.0 =
* La insignia «Verificado» al final de los posts ahora es OPCIONAL y está
  DESACTIVADA por defecto. Se activa con una casilla nueva en Ajustes de GOVP >
  Páginas públicas > «Insignia en los posts». Antes se inyectaba en todos los posts
  certificados sin opción; ahora solo aparece si el administrador la marca.

= 2.12.1 =
* CORRECCIÓN IMPORTANTE: el botón «Certificar / actualizar» de la caja GOVP del
  editor no funcionaba en el editor de bloques (Gutenberg) porque su formulario
  quedaba anidado dentro del formulario del editor (HTML inválido) y el clic no
  llegaba a ejecutarse — por eso no se creaba ningún GOVP. Ahora el botón certifica
  por AJAX, sin formulario anidado, y muestra «¡Listo!» con el GOVP-ID y el enlace
  al registro en la propia caja, sin recargar. Funciona también sobre borradores.

= 2.12.0 =
Emisión más automática y con confirmación clara:
* En modo «Automático» (Ajustes de GOVP > Re-certificación), **publicar un post ya
  crea el GOVP por sí solo** (antes solo re-certificaba los ya certificados). Al
  editar un post certificado sigue emitiendo una versión nueva. Filtrable con
  `govp_autocertify_on_publish`.
* Mensaje de éxito claro: al certificar (manual o automático) se muestra «¡Listo!»
  con el GOVP-ID y un enlace «Ver registro público». Si algo falla, el aviso explica
  la causa en vez de quedarse en silencio.
* La caja «GOVP» del editor muestra el enlace al registro cuando está certificado y,
  si la emisión estuviera bloqueada por la clave en el webroot, lo avisa y enlaza a
  «Asegurar la clave automáticamente».
* Test PHPUnit añadido (alta automática al publicar).

= 2.11.0 =
Aseguramiento de la clave de firma con un clic (sin editar wp-config.php ni SSH):
* Nuevo botón «Asegurar la clave automáticamente» en Ajustes de GOVP: cuando la
  clave está dentro de la raíz web, el plugin crea una carpeta segura FUERA del
  webroot, mueve la clave allí (verificando byte a byte y preservando la identidad,
  la clave pública no cambia) y guarda la ruta. Reactiva la emisión sin tocar
  wp-config.php.
* Al generar una clave nueva, el plugin intenta ubicarla fuera del webroot de forma
  automática (best-effort); las instalaciones nuevas nacen seguras cuando el
  alojamiento lo permite.
* Si el alojamiento no ofrece una carpeta externa escribible, se refuerza la carpeta
  actual (.htaccess + web.config + index.php) como segundo mejor y se recomienda
  GOVP_KEY_DIR. La ubicación de destino es filtrable con `govp_secure_key_dir`.
* Test PHPUnit añadido (la reubicación mueve la clave fuera del webroot preservando
  identidad y capacidad de firma).

= 2.10.4 =
* tests/smoke-http.sh: la comprobación del identity ahora es insensible a
  mayúsculas (`public-key`), alineada con el formato real de .well-known/govp.txt;
  antes el smoke podía fallar en falso pese a un plugin correcto.

= 2.10.3 =
Endurecimiento de seguridad (SSRF, rate limiting y límites de entrada):
* **SSRF:** `GOVP_Http::ip_is_public()` reescrita con coincidencia CIDR robusta
  (IPv4 e IPv6) contra listas explícitas de rangos no enrutables: privados,
  loopback, link-local, CGNAT, multicast, reservados, benchmarking y documentación
  (incluye 224.0.0.0/4, 240.0.0.0/4, 198.18.0.0/15, 192.0.2.0/24, 198.51.100.0/24,
  203.0.113.0/24, ff00::/8, 2001:db8::/32, fc00::/7, fe80::/10, etc.). Las
  IPv4-mapeadas en IPv6 se evalúan como su IPv4 real. Solo pasan IPs públicas.
* **Rate limit myGOVP:** nuevo bucket `mygovp` (60 peticiones/hora) sobre
  /mygovp/resolve, /mygovp/seal y /mygovp/verify.
* **Límite de tamaño:** /mygovp/verify rechaza con HTTP 413 ({"error":"record too
  large"}) cualquier `record` mayor de 64 KiB, antes de parsear.
* Tests PHPUnit añadidos (SSRF multicast/benchmarking/documentación + 413). Makefile:
  `make phpunit`/`make check` fallan con mensaje claro si falta Composer.

= 2.10.2 =
Hallazgos al ejecutar la suite PHPUnit completa (53 tests, 726 asserts) y un smoke
de endpoints por el dispatcher REST interno:
* **Fix:** el shortcode `[govp_verify]` se registraba a nivel de fichero; ahora se
  registra en el hook `init`, simétrico con el bloque Gutenberg y robusto al orden de
  carga (resolvía un fallo de paridad shortcode/bloque bajo ciertos arranques de test).
* Verificado por ejecución real (PHPUnit 9.6, el que fija composer): 53/53, 0 fallos,
  1 skip (TSA en vivo, que pasa con GOVP_TSA_LIVE=1). Smoke por rest_do_request:
  /records (+paginación), /records/{id} (+trust), /verify, /translog/checkpoint,
  /trust/{id}, /mesh/head (firmado), /mesh/reconcile (honesto y split-view), CORS.

= 2.10.1 =
Limpieza de superficie HTTP y endurecimiento de configuración (RC → producción):
* **Sin fallbacks HTTP residuales:** `GOVP_Verify::fetch()` y `GOVP_TSA::request()`
  pasan TODO el tráfico externo por `GOVP_Http` (anti-SSRF, sin redirecciones, IP
  fijada). Si el componente HTTP no estuviera, fallan CERRADO en vez de degradar a
  `wp_safe_remote_*`. Cero llamadas `wp_*remote*` fuera de `GOVP_Http`.
* **`GOVP_KEY_DIR` falla duro:** si la constante está definida pero apunta a un
  directorio no usable, en producción se lanza una excepción clara en lugar de caer
  en silencio a `uploads` (salvo override `GOVP_ALLOW_WEBROOT_KEY`).
* **Makefile:** nuevos objetivos `phpunit`, `smoke`, `mesh` y `check`
  (lint + conformidad + PHPUnit) para la verificación completa.

= 2.10.0 =
Red mínima confiable (anti-Sybil por independencia, no por recuento):
* **`GOVP_Trust`**: el nivel de un hecho ("strong"/"weak"/"none") depende del número
  de observadores **INDEPENDIENTES**, calculado con el módulo anti-Sybil congelado
  (`GOVP_Sybil2::collapse`, que funde observadores correlacionados por
  clave/owner/IP/net24/ASN/registrar). 10 recibos desde la misma caja/ASN colapsan a
  n_eff=1 → no es "fuerte"; 3 genuinamente independientes → "fuerte". Umbral via filtro
  `govp_min_independent` (por defecto 3).
* **Estampado de señales de red** (`ip`/`net24`/`net64`) en el momento de recibir el
  recibo de testigo — no autodeclaradas; el cuerpo firmado no se toca. ASN/registrar
  enriquecibles con el filtro `govp_observer_signals`.
* **REST `/trust/{id}`** con el veredicto; el veredicto se incluye también en
  `/records/{id}`. Rate limit extendido al bucket `trust`.
* Tests: `test-trust.php` (independientes→fuerte, sybils→débil, e2e con recibos
  firmados) + batería en el harness.

= 2.9.9 =
Malla en HTTP real (gossip de cabezas + detección de split-view):
* **Transporte gossip por REST**: `GET /mesh/head` anuncia la cabeza firmada del
  nodo (HeadAnnouncement, dominio `GOVP::mesh.head.v1`); `POST /mesh/reconcile`
  coteja cabezas de pares contra la local SIN fetch de servidor (anti-SSRF) y
  levanta **alarma de equivocación** si un par anuncia otro manifest_hash en el
  mismo epoch. Antes la lógica de malla no tenía transporte HTTP.
* **Escenario Docker de 3 nodos** (`docker-compose.mesh.yml` + `tests/mesh-scenario.sh`
  + `tests/mesh/`): valida sobre HTTP real acuerdo honesto, split-view (con el
  culpable señalado) y caída de nodo.
* **Tests** (`test-mesh.php` + harness): simulación de 3 nodos sobre el código real —
  acuerdo honesto, split-view, firma inválida y dominio fuera de registro rechazados,
  nodo caído sin crash, y prueba de equivocación no repudiable.
* Rate limit extendido al bucket `mesh`.

= 2.9.8 =
Evidencia exportable y auditable externamente (transparencia + revocación):
* **Checkpoint Merkle firmado del log**: compromiso compacto (count + cabeza +
  raíz Merkle) firmado Ed25519, exportable y servible en `/translog/checkpoint`.
  Con `verify_against_log()` **detecta truncado/rollback** del diario.
* **Pruebas de inclusión Merkle** (`merkle_proof`/`verify_inclusion`): cada entrada
  se prueba de forma independiente contra la raíz.
* **Anclaje externo vía TSA (RFC 3161)**: sella la raíz Merkle del checkpoint con una
  TSA (p. ej. FreeTSA). Tras el anclaje, un tercero atestigua el estado del log en el
  tiempo; truncarlo es detectable. Botón "Anclar checkpoint ahora" en Ajustes.
* **Revocación firmada publicada**: la meta-evidencia `revoked.govp` ahora se publica
  en `.well-known` y va al export/mirror (antes solo vivía en la base de datos).
* El mirror incluye `transparency/checkpoint.json`, `checkpoint-anchor.json` (si hay)
  y `revoked.govp`. Tests añadidos: `test-transparency.php` + batería en el harness.

= 2.9.7 =
RFC 3161 (sellos de tiempo) endurecido y probado contra tokens REALES:
* **Verificación contra un token real de FreeTSA**, congelado como fixture y
  contrastado con `openssl ts -verify` como verdad de referencia. Batería de
  negativos: imprint distinto, token manipulado, CA equivocada/vacía, DER ilegible.
* **`time_in_validity`**: ahora se exige que `genTime` caiga dentro de la validez
  del certificado firmante (rechaza el caso "caducado"). Antes no se comprobaba.
* **Selección del firmante por verificación de firma** entre los certificados
  embebidos (RFC 5652 SignerInfo.sid), robusto al orden y a cadenas completas.
* **Corrección de seguridad (parser ASN.1):** `GOVP_ASN1::parse` se desbordaba con
  campos de longitud fuera de rango (desbordamiento de entero → bucle/consumo de
  memoria). Como `request()` parsea la respuesta remota de la TSA, era un vector
  DoS. Ahora rechaza longitud indefinida, nº de bytes de longitud absurdo y
  cualquier longitud que exceda el buffer. Sellado+verificación en vivo contra
  FreeTSA validado end-to-end.
* Tests añadidos: `test-tsa.php` (token real + negativos + anti-runaway) y sellado
  en vivo opcional (`GOVP_TSA_LIVE=1`).

= 2.9.6 =
Blindaje contra regresiones (no cambia el comportamiento del plugin):
* **Suite PHPUnit + WordPress** (`tests/phpunit/`, `phpunit.xml.dist`,
  `bin/install-wp-tests.sh`) que cubre emisión, ficheros físicos, asset-id estable,
  no-truncado del índice, SSRF, rate limit, consistencia de clave/identidad,
  export-mirror sellado y el bloque.
* **Harness de integración ligero** (`tests/integration-harness.php`): arranca un
  WordPress real y asevera todas las regresiones corregidas; corre contra cualquier
  instalación (incl. SQLite) sin test-lib ni MySQL. 24 comprobaciones, deterministas.
* **CI** (`.github/workflows/ci.yml`): `php -l` + conformidad (pytest) + PHPUnit en
  matriz PHP 7.4/8.1/8.3 contra MariaDB, en cada push y PR.
* **Docker** (`docker-compose.yml`, `docker/Dockerfile.tests`) con WordPress vivo y
  **smoke test HTTP** (`tests/smoke-http.sh`) que verifica `.well-known` y el 429 por
  HTTP real — cierra el hueco que un entorno sin sockets de escucha no puede validar.
* `make test` (conformidad), `make integration` (WP_ROOT=…), `make phpunit`.
* `.distignore` para separar el build de distribución de los ficheros de desarrollo.

= 2.9.5 =
* **Rate limiting** por IP en los endpoints REST públicos (`/verify`, `/records`,
  `/badge`, `/witness*`, `/translog*`, `/tsa/verify`): ventana fija configurable,
  respuesta 429 con `Retry-After`, usa object cache si existe (transients si no).
  Los administradores autenticados no se limitan. Desactivable con la opción
  `govp_rate_limit` y ajustable con el filtro `govp_rate_limits`.
* **Export / mirror público completo** (`GOVP → Ajustes`): descarga un `.zip`
  autocontenido con identidad, TODOS los registros, revocaciones, transparencia,
  testigos y sellos de tiempo, más un `manifest.json` con sha256 de cada fichero y
  un `manifest.sig` (Ed25519 del dominio). Descomprimido en cualquier servidor, su
  subárbol `.well-known/` sirve la misma evidencia con independencia del sitio de
  origen. Es la copia que respalda la verificabilidad aunque el dominio desaparezca.
* **Corrección importante (clave):** la clave pública se deriva ahora del secreto
  ACTIVO en disco, no de los metadatos almacenados. Evita que firma, verificación,
  identidad (`govp.txt`) y el sello de bundles/mirror se desincronicen si cambia la
  ubicación de la clave (`GOVP_KEY_DIR` vs `uploads`). La identidad publicada ancla
  siempre la clave que realmente firma.

= 2.9.4 =
Endurecimiento de seguridad y diagnóstico (lote tras auditoría):
* **Anti-SSRF reforzado** en las descargas server-side de URLs controladas por el
  registro o el usuario (verificación cross-domain de `canonical` y emisión rápida
  por URL). Nuevo `GOVP_Http`: allowlist de esquema (https) y puerto (80/443);
  resolución DNS previa y rechazo de TODAS las IPs privadas, loopback, link-local,
  ULA, CGNAT y reservadas (IPv4, IPv6 e IPv4-mapped); **sin redirecciones**
  (elimina el salto a destino interno); e **IP-pinning** en cURL (CURLOPT_RESOLVE
  a la IP validada) que cierra el DNS rebinding. La emisión por URL baja de 8 MiB
  a 2 MiB y reporta el motivo exacto del rechazo.
* **Diagnóstico de instalación** en GOVP → Ajustes: comprueba ext/sodium, ext/dom,
  ext/zip, presencia y ubicación de la clave (avisa si está bajo el raíz web y
  recomienda `GOVP_KEY_DIR`), escritura de `.well-known`, rutas REST registradas,
  WP-Cron y enlaces permanentes. Convierte fallos silenciosos en visibles.
* Escape de desarrollo `GOVP_DEV_ALLOW_PRIVATE` documentado para entornos locales.

= 2.9.3 =
Lote de robustez tras auditoría externa:
* **El índice publicado ya no se trunca a 500.** `GOVP_Publisher` y el índice
  dinámico recorren TODOS los registros por lotes (`GOVP_Store::each()`); antes,
  con más de 500 evidencias, `index.json` y los ficheros se publicaban incompletos
  en silencio. El store gana `count()` y `all($limit,$offset)`.
* **Registro público paginado** (50 por página, con navegación) en lugar de un
  tope duro; el REST de lectura ya soportaba paginación.
* **`asset-id` estable por defecto** (`post-<ID>`): renombrar el slug ya no cambia
  el GOVP-ID del mismo contenido. Opción `govp_asset_id_mode` y filtro
  `govp_asset_id` para volver al slug o personalizar.
* **Clave privada fuera del raíz web**: define `GOVP_KEY_DIR` en `wp-config.php`
  para guardarla fuera de `uploads/` (donde el `.htaccess` no protege en Nginx);
  `uploads` queda como respaldo.
* **Reproducibilidad de tests**: nuevos `requirements.txt`, `composer.json` y
  `Makefile`. `make test` ejecuta lint PHP + la suite de conformidad con `pytest`
  en un virtualenv aislado. La suite myGOVP es ahora compatible con pytest.
* **Documentación honesta**: se retira el "para siempre" (la durabilidad depende de
  que sobreviva una copia); se reencuadra el producto como *evidencia firmada de una
  afirmación publicada por un dominio*; se documenta que el translog local NO es
  transparencia pública y que el CORS abierto de lectura es intencionado.

= 2.9.2 =
* **CRÍTICO — Se vuelven a crear los ficheros físicos `.well-known`.** El componente
  que materializa la evidencia en disco (`GOVP_Publisher`) existía pero **no se
  cargaba**: todas sus llamadas estaban tras `class_exists('GOVP_Publisher')`, que
  era siempre falso, así que la escritura de `govp.txt`, `index.json`, `revoked.json`
  y de cada `&lt;GOVP-ID&gt;.govp` se saltaba en silencio. El verificador dinámico
  enmascaraba el fallo. Ahora la clase se incluye y los ficheros se crean en la
  activación, en cada emisión, al revocar y al regenerar la clave.
* **Activación** materializa todo el árbol `.well-known` (antes solo se habría
  creado tras la siguiente emisión).
* **Borrado** de un registro elimina además su `.govp` físico y refresca el index
  (antes quedaba huérfano en disco).
* **Ajustes → Ficheros públicos `.well-known`**: nueva sección con estado
  (directorio, nº de ficheros, errores de permisos), interruptor de escritura y
  botón «Regenerar ficheros ahora» para diagnosticar y forzar la publicación.
* **La certificación desde el metabox ya no falla en silencio**: si la emisión da
  error (p. ej. sin clave), se muestra el motivo en un aviso; si va bien, lo confirma.
* Auditoría estática completa del plugin: 0 llamadas a métodos inexistentes y 0
  callbacks de hooks rotos; `GOVP_Publisher` era el único componente sin cablear.

= 2.9.1 =
* **Bloque del editor «GOVP · Verificación»** (`govp/verify`). Inserta la insignia
  de verificación en vivo desde el editor de bloques, sin escribir el shortcode:
  elige verificar por **GOVP-ID de este sitio** o por **URL de un `.govp` externo**,
  con dominio esperado opcional. Es un bloque **dinámico**: el servidor emite el
  mismo marcador `.govp-verify` que el shortcode, así que la verificación sigue
  ocurriendo en el navegador del visitante contra el núcleo de conformidad — el
  bloque no reimplementa criptografía. Sin paso de build (JS plano), funciona tal
  cual en cualquier WordPress 5.8+.
* **Refactor**: el shortcode `[govp_verify]` y el bloque comparten un único
  renderizador (`GOVP_Block::widget_html`), eliminando la duplicación del marcador.

= 2.9.0 =
* **Superficie pública de cara al usuario, reintroducida (aditiva).** La v2 había
  priorizado la infraestructura (malla, observatorio, sellos, transparencia) y dejó
  fuera el escaparate que sí tenía la 1.x. Esta versión lo recupera **sin tocar el
  núcleo de verificación ni la capa de malla**:
* **Páginas públicas autónomas** (estilo Signal, independientes del tema):
  `/govp` (registro consultable), `/govp/{GOVP-ID}` (ficha-"certificado" con
  verificación en vivo, descarga `.govp` y fragmentos para incrustar) y `/verify`
  (verificador autónomo en cliente: pega un record, un ID o una URL, y comprueba
  además que un archivo coincide con el hash certificado calculándolo en el navegador).
* **API de lectura**: `GET /wp-json/govp/v1/records` (lista) y
  `GET /wp-json/govp/v1/records/{id}` (record + veredicto), ambos con CORS abierto.
* **Insignia incrustable** "Verificado por GOVP": SVG vivo en
  `GET /wp-json/govp/v1/badge/{id}` (verde/ámbar/rojo/gris según estado local),
  con `govp-badge.js` para cualquier web y una variante `<img>` sin JavaScript.
* **Pantalla "Ajustes"**: activar/desactivar páginas públicas, regenerar enlaces
  permanentes, **re-certificación automática (auto-lock)** en tres modos
  (desactivado / avisar / automático) y **emisión rápida** de evidencia desde
  archivo, texto o URL interna.
* **Robustez de enrutado**: las páginas funcionan también con WordPress instalado
  en subdirectorio (se resta el prefijo de `home`).
* **Corrección**: la emisión por archivo/texto ahora fija siempre un `evidence`
  no vacío (campo obligatorio del core); antes, un `evidence` vacío producía un
  record con `format_ok=false` pese a tener firma válida.

= 2.8.0 =
* **Capa myGOVP (hospitalidad soberana), OPCIONAL y aditiva.** Marco: *hospitalidad,
  no voto* (no es un "me gusta" ni suma en ningún recuento global). Este dominio es
  un ANFITRIÓN con TRES huecos reservados en su propia identidad y aloja en ellos a
  los INVITADOS (causas) que elige. La señal es la ANTIGÜEDAD ("invitado de honor
  desde hace N"), no un número.
  - Un hospedaje ES un GOVP-1 `document` válido que además porta campos `mygovp-*`
    firmados: lo verifican SIN cambios todos los verificadores del ecosistema
    (Studio, JS, KMP, Python). El núcleo GOVP base permanece intacto (dependencia
    sólo hacia abajo: los registros ordinarios nunca llevan `mygovp-*`).
  - Dos granularidades: `guest-full` (el invitado publica compromiso en
    `/.well-known/mygovp` → hospedaje pleno) y `guest-entity` (se aloja a la entidad
    en grueso, ETIQUETADO como más débil). El RECHAZO no existe como registro (evita
    Art. 9 RGPD). Reasignar un hueco reinicia la antigüedad y deja fuera al invitado
    anterior (marcharse es legible).
  - **Dos caras del mismo hecho firmado:** el anfitrión luce a quién aloja
    (`[mygovp_seal]`, `[mygovp_seal domain="otra.org"]`) y el invitado luce dónde es
    alojado (`[mygovp_guest_badge id="…"]` → "invitado de honor en <anfitrión> desde
    hace N"), SIEMPRE por-espacio, nunca sumado. Verificación en cliente (offline si
    hay Ed25519 nativo; fallback REST) con auto-relleno de favicon/título.
  - Manifiesto público en `/.well-known/mygovp` (perfil MYGOVP-1) — sólo hospedajes
    mantenidos con antigüedad; nunca completitud ni headcount (anti-Sybil).
  - Endpoints `govp/v1/mygovp/*` (resolve/seal/verify públicos; node/host/vacate/
    commitment protegidos) y submenú de administración con los 3 huecos.
* **Normalización de dominio autocontenida (eTLD+1).** Punycode RFC 3492 puro en
  PHP: ya NO se requiere ext/intl para IDN; la normalización es determinista entre
  hosts y byte-idéntica al codec `idna` de Python (verificado en la suite).
* **Suite de conformidad myGOVP** PHP↔Python (28 comprobaciones, 0 fallos) con
  vectores de oro congelados (`conformance/mygovp_vectors.json`).

= 2.2.0 =
* Verificacion CROSS-DOMAIN servidor-a-servidor: /wp-json/govp/v1/verify acepta
  resolve=true (o domain=...) y resuelve por red el govp.txt + revoked.json del
  dominio EMISOR (otro WordPress), validando por capas contra ESA identidad.
  Guardas anti-SSRF (wp_safe_remote_get; solo https en produccion). Un WordPress
  puede validar la evidencia de otro WordPress.

= 2.1.1 =
* CORS abierto en los recursos .well-known/govp* (los consume el verificador de campo desde otro origen).

= 2.1.0 =
* Verificación POR CAPAS (perfil RFC-GOVP-VERIFY-PROFILE): la API /verify devuelve
  evidence_authentic (perpetuo) y currently_trusted (vigente) además de core_valid.
* Identidad multi-clave en govp.txt (clave activa + en retirada con ventanas) — la
  rotación ya NO deja huérfana la evidencia histórica.
* Revocación de dominio: /.well-known/govp/revoked.json + meta-evidencia firmada
  revoked.govp; endpoint POST /wp-json/govp/v1/revoke y botón "Revocar" en el panel.
* Perfil de atestación (attestation) para avales de credibilidad entre dominios.

= 2.0.0 =
* Reescritura sin sidecar: motor PHP (libsodium) + verificación en cliente.
