Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
> **Navegación bilingüe:** [Read English version](./0113-nodejs-lighthouse-evidence-adapter.md)

# ADR-0113: Plataforma Node.js — Lighthouse (Apache-2.0) como adaptador de evidencia de referencia

> **Firma del Agente:** Agente Arquitecto (Winston)

## Estado
Propuesto (2026-07-13 — Comité de Arquitectura)

## Fecha
2026-07-13

## Contexto y Problema

La [ADR-0111](./0111-quality-signal-provider-port.es.md) estableció la costura de
Proveedores de Señales de Calidad: las herramientas externas de calidad/evidencia
alimentan a Evolith Core a través de un único puerto guiado `IQualitySignalProvider`
y un modelo `Evidence` canónico con procedencia obligatoria, y el Core nunca
ejecuta un proveedor. Esa ADR dejó deliberadamente las implementaciones concretas
de adaptadores y sus elecciones de proveedor/runtime a una **ADR de Plataforma
acompañante** — esta.

Para probar la costura de extremo a extremo (GT-534) necesitamos un primer
proveedor concreto. Esa elección es una decisión de plataforma: fija un runtime
(Chrome headless), un lenguaje/sistema de módulos (un módulo Node.js) y una
herramienta de terceros con su propia licencia. Esas consecuencias merecen su
propia decisión registrada en vez de quedar enterradas en un archivo de adaptador.

El problema: **¿qué herramienta y runtime concretos adoptamos para el primer
adaptador de evidencia, de modo que valide el puerto sin volverse dependencia dura
de la suite, sin riesgo de licencia y produciendo salida determinista y
normalizable?**

## Objetivo y Alcance

Registrar la elección concreta de proveedor/runtime para el primer adaptador
detrás de `IQualitySignalProvider`. En alcance: la herramienta (Lighthouse), su
licencia (Apache-2.0), el runtime que implica (Node.js + Chrome headless), el
límite de módulo que la mantiene opcional y el contrato de normalización que debe
respetar. Fuera de alcance: el diseño del puerto/registro (propiedad de la
ADR-0111) y los adaptadores futuros (TestSprite, rúbrica de revisión estructural,
scorecards GEO — cada uno con su propio registro si introduce un nuevo compromiso
de plataforma).

## Opciones Consideradas

### Opción A: Lighthouse como módulo Node embebido (elegida)

Ejecutar Google Lighthouse vía su API programática de Node contra una URL
desplegada, consumir su resultado JSON (LHR) y mapearlo a `Evidence` canónica.
Elegida: Lighthouse es **Apache-2.0** (permisiva, sin copyleft, sin puerta
comercial), un auditor maduro y ampliamente confiable, con un módulo Node
embebible de salida JSON pura, y es **determinista** — el mejor encaje con la
clase `determinism: 'deterministic'` de la costura y la forma de menor riesgo de
probar el puerto.

### Opción B: Ejecutar Lighthouse vía su CLI / la API alojada PageSpeed Insights

Invocar el CLI `lighthouse`, o llamar a la API alojada de PageSpeed Insights.
Rechazada: el CLI añade gestión de procesos y parseo sin ganancia frente al módulo
Node; la API alojada introduce dependencia de red, cuotas/llaves y egreso de datos
a un tercero — justo el acoplamiento que la ADR-0111 existe para evitar.

### Opción C: Otro auditor (WebPageTest, Sitespeed.io, un SaaS)

Rechazada para el adaptador *de referencia*: mayor superficie de runtime/licencia
o una nube propietaria. Siguen siendo válidos como adaptadores *adicionales*
detrás del mismo puerto más adelante — el punto de la ADR-0111 es que la elección
es descartable.

## Decisión y Justificación

1. **Herramienta y licencia.** Adoptar **Lighthouse (Apache-2.0)** como proveedor
de evidencia de referencia. La licencia permisiva no impone copyleft ni riesgo
de re-licenciamiento comercial (contrasta con la situación de MassTransit v9 en
la ADR-0110); Lighthouse es ilustrativo, no estructural (prueba de fuego de la
ADR-0111).

2. **Runtime.** El adaptador corre sobre **Node.js** y requiere **Chrome
headless** en tiempo de ejecución. Es un compromiso de *runtime* (el paso de
recolección en la capa de orquestación), nunca una dependencia de diseño ni del
Core.

3. **Límite de módulo — opcional, importado perezosamente.** El adaptador vive en
`@beyondnet/evolith-infra-providers` e importa SOLO las formas canónicas de
`Evidence` desde `core-domain`. `lighthouse` y `chrome-launcher` **no** son
dependencias declaradas del paquete; el runner por defecto las importa
*dinámicamente* para que el paquete compile e instale sin ninguna presente
(ADR-0111 §5 — ninguna herramienta externa es jamás dependencia dura).

4. **Costura de testeabilidad.** La corrida real de Chrome headless queda detrás
de un puerto `LighthouseRunner` inyectado. Los tests unitarios inyectan un LHR
simulado, así la suite corre sin Chrome y sin red. Una corrida real necesita
Chrome + una URL desplegada y es un asunto de runtime.

5. **Contrato de normalización.** El adaptador mapea cada categoría de Lighthouse
(`performance` → `performance`, `accessibility` → `a11y`, `best-practices`,
`seo`) a una métrica `0..100` Y a un `EvidenceFinding` cuya severidad se deriva
deterministamente del puntaje de la categoría. Emite
`determinism: 'deterministic'` y **procedencia completa y obligatoria**
(`collectedBy: 'lighthouse'`, `adapterVersion`, un `artifactHash` SHA-256 del
LHR y un `timestamp` tomado del `fetchTime` del LHR), vía `normalizeEvidence`.

6. **Sin inversión de dependencia.** El puerto `IQualitySignalProvider` es
propiedad de la capa de orquestación (agent-runtime). Importarlo en un paquete
de borde de infraestructura invertiría la dirección de dependencia (infra →
orquestación), por lo que el adaptador conforma al puerto **estructuralmente**
(una interfaz espejada en el paquete) y el runtime registra la instancia. La
conformidad estructural con el puerto real se verifica en tiempo de build.

## Evidencia y Criterios de Evaluación

- **Chequeo de licencia**: Lighthouse es Apache-2.0 — permisiva, sublicenciable,
sin puerta comercial (verificar contra el `LICENSE` upstream).
- **Determinismo**: el adaptador emite `determinism: 'deterministic'`; un LHR fijo
produce `Evidence` idéntica (mismas métricas, findings y `artifactHash`).
- **Procedencia**: toda `Evidence` emitida lleva una `Provenance` completa
(obligatoria por ADR-0111 §6), impuesta por `normalizeEvidence`.
- **Pureza del borde**: `grep` no muestra `lighthouse`/`chrome-launcher` en las
dependencias declaradas del paquete; solo se importan dinámicamente.
- **Statelessness preservado**: el Core importa solo `Evidence`; ningún import de
adaptador o proveedor alcanza `core-domain` (criterio de límite ADR-0101 /
ADR-0111).

## Consecuencias, Riesgos y Compromisos

Positivo: prueba la costura de la ADR-0111 de extremo a extremo con una dimensión
de evidencia real y determinista; la licencia permisiva elimina el riesgo legal;
la costura del runner inyectado mantiene herméticos los tests unitarios; el
proveedor queda descartable y seleccionable por tenant.

Negativo / compromisos: una corrida real necesita una imagen de runtime con Chrome
headless y una URL desplegada (un costo operativo, no de diseño); los puntajes de
Lighthouse pueden variar de corrida a corrida en un objetivo vivo por varianza de
red/CPU aunque la clase de herramienta sea determinista — los consumidores deben
tratar una corrida única como una muestra puntual y pueden promediar entre
corridas. Riesgo: deriva de versión de Chrome/Lighthouse cambiando ids de
categoría o semántica de puntaje (mitigado por el adaptador versionado y el
`artifactHash` de procedencia).

## Referencias

- Lighthouse — motor de auditoría de runtime (Apache-2.0), API programática de
Node con salida JSON (LHR): <https://github.com/GoogleChrome/lighthouse>
- Licencia de Lighthouse (Apache-2.0):
<https://github.com/GoogleChrome/lighthouse/blob/main/LICENSE>
- `chrome-launcher` — lanzador de Chrome headless usado por el runner por defecto:
<https://github.com/GoogleChrome/chrome-launcher>

## Decisiones y Estándares Relacionados

- [ADR-0111](./0111-quality-signal-provider-port.es.md) — el puerto de Proveedores
de Señales de Calidad + la `Evidence` canónica que este adaptador implementa
(decisión padre).
- [ADR-0101](./0101-core-stateless-evaluation-engine.es.md) — Core stateless; el
adaptador corre en orquestación, nunca dentro del evaluador.
- [ADR-0110](./0110-masstransit-v8-apache-license-pin.es.md) — precedente de
tratar la licencia de una dependencia estructural como asunto arquitectónico
(aquí lo opuesto: una dependencia *opcional* con licencia permisiva).
- [ADR-0104](./0104-topology-driven-advisory-design-governance.es.md) — deriva los
criterios que esta evidencia de runtime confirma o refuta (el lazo de
conformidad).
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
> **Bilingual Navigation:** [Ver versión en Español](./0113-nodejs-lighthouse-evidence-adapter.es.md)

# ADR-0113: Node.js Platform — Lighthouse (Apache-2.0) as the Reference Evidence Adapter

> **Agent Signature:** Architect Agent (Winston)

## Status
Proposed (2026-07-13 — Architecture Board)

## Date
2026-07-13

## Context and Problem

[ADR-0111](./0111-quality-signal-provider-port.md) established the Quality Signal
Provider seam: external quality/evidence tools feed Evolith Core through a single
driven port `IQualitySignalProvider` and a canonical, provenance-stamped
`Evidence` model, and the Core never executes a provider. That ADR deliberately
left the concrete adapter implementations and their vendor/runtime choices to a
**companion Platform ADR** — this one.

To prove the seam end-to-end (GT-534) we need a first concrete provider. The
choice of that provider is a platform decision: it fixes a runtime (headless
Chrome), a language/module system (a Node.js module), and a third-party tool with
its own license. Those consequences deserve their own recorded decision rather
than being buried in an adapter file.

The problem: **which concrete tool and runtime do we adopt for the first evidence
adapter, such that it validates the port without becoming a hard dependency of the
suite, carries no license risk, and produces deterministic, normalizable output?**

## Objective and Scope

Record the concrete vendor/runtime choice for the first adapter behind
`IQualitySignalProvider`. In scope: the tool (Lighthouse), its license
(Apache-2.0), the runtime it implies (Node.js + headless Chrome), the module
boundary that keeps it optional, and the normalization contract it must honor.
Out of scope: the port/registry design (owned by ADR-0111) and future adapters
(TestSprite, structural-review rubric, GEO scorecards — each gets its own record
if it introduces a new platform commitment).

## Options Considered

### Option A: Lighthouse as an embedded Node module (chosen)

Run Google Lighthouse via its programmatic Node API against a deployed URL,
consume its JSON result (LHR), and map it to canonical `Evidence`. Chosen:
Lighthouse is **Apache-2.0** (permissive, no copyleft, no commercial gate), a
mature and widely trusted auditor, ships an embeddable Node module with pure JSON
output, and is **deterministic** — the strongest fit for the seam's
`determinism: 'deterministic'` class and the lowest-risk way to prove the port.

### Option B: Drive Lighthouse via its CLI / a hosted PageSpeed Insights API

Shell out to the `lighthouse` CLI, or call the hosted PageSpeed Insights API.
Rejected: the CLI adds process-management and parsing overhead for no gain over
the Node module; the hosted API introduces a network dependency, quota/keys, and
data egress to a third party — the exact coupling ADR-0111 exists to avoid.

### Option C: A different auditor (WebPageTest, Sitespeed.io, a SaaS)

Rejected for the *reference* adapter: either heavier runtime/licensing surface or
a proprietary cloud. They remain perfectly valid as *additional* adapters behind
the same port later — the point of ADR-0111 is that the choice is disposable.

## Decision and Rationale

1. **Tool & license.** Adopt **Lighthouse (Apache-2.0)** as the reference
evidence vendor. The permissive license means no copyleft obligation and no
commercial re-licensing risk (contrast ADR-0110's MassTransit v9 situation);
Lighthouse is illustrative, not load-bearing (ADR-0111 litmus test).

2. **Runtime.** The adapter runs on **Node.js** and requires a **headless
Chrome** at execution time. This is a *runtime* commitment (the collection
step in the orchestration layer), never a design-time or Core dependency.

3. **Module boundary — optional, lazily imported.** The adapter lives in
`@beyondnet/evolith-infra-providers` and imports ONLY the canonical `Evidence`
shapes from `core-domain`. `lighthouse` and `chrome-launcher` are **not**
declared dependencies of the package; the default runner imports them
*dynamically* so the package builds and installs with neither present
(ADR-0111 §5 — no external tool is ever a hard dependency).

4. **Testability seam.** The real headless-Chrome run sits behind an injected
`LighthouseRunner` port. Unit tests inject a stubbed LHR, so the suite runs
with no Chrome and no network. A live run needs Chrome + a deployed URL and is
a runtime concern.

5. **Normalization contract.** The adapter maps each Lighthouse category
(`performance` → `performance`, `accessibility` → `a11y`, `best-practices`,
`seo`) to a `0..100` metric AND an `EvidenceFinding` whose severity is derived
deterministically from the category score. It emits
`determinism: 'deterministic'` and **full, mandatory provenance**
(`collectedBy: 'lighthouse'`, `adapterVersion`, a SHA-256 `artifactHash` of the
LHR, and a `timestamp` taken from the LHR `fetchTime`), via
`normalizeEvidence`.

6. **No dependency inversion.** The `IQualitySignalProvider` port is owned by the
orchestration layer (agent-runtime). Importing it into an infra-edge package
would invert the dependency direction (infra → orchestration), so the adapter
conforms to the port **structurally** (a mirrored interface in-package) and the
runtime registers the instance. Structural conformance to the real port is
verified at build time.

## Evidence and Evaluation Criteria

- **License check**: Lighthouse is Apache-2.0 — permissive, sublicensable,
no commercial gate (verify against the upstream `LICENSE`).
- **Determinism**: the adapter emits `determinism: 'deterministic'`; a fixed LHR
yields identical `Evidence` (same metrics, findings and `artifactHash`).
- **Provenance**: every emitted `Evidence` carries a complete `Provenance`
(mandatory per ADR-0111 §6), enforced by `normalizeEvidence`.
- **Purity of the edge**: `grep` shows no `lighthouse`/`chrome-launcher` in the
package's declared dependencies; they are dynamically imported only.
- **Statelessness preserved**: the Core imports only `Evidence`; no adapter or
vendor import reaches `core-domain` (ADR-0101 / ADR-0111 boundary criterion).

## Consequences, Risks, and Trade-offs

Positive: proves the ADR-0111 seam end-to-end with a real, deterministic evidence
dimension; permissive licensing removes legal risk; the injected-runner seam keeps
unit tests hermetic; the vendor stays disposable and tenant-selectable.

Negative / trade-offs: a live run needs a headless-Chrome-capable runtime image
and a deployed URL (an operational cost, not a design one); Lighthouse scores can
vary run-to-run on a live target due to network/CPU variance even though the tool
class is deterministic — consumers should treat a single run as a point sample and
may average across runs. Risk: Chrome/Lighthouse version drift changing category
ids or score semantics (mitigated by the versioned adapter and provenance
`artifactHash`).

## References

- Lighthouse — runtime auditing engine (Apache-2.0), Node programmatic API with
JSON (LHR) output: <https://github.com/GoogleChrome/lighthouse>
- Lighthouse license (Apache-2.0):
<https://github.com/GoogleChrome/lighthouse/blob/main/LICENSE>
- `chrome-launcher` — headless Chrome launcher used by the default runner:
<https://github.com/GoogleChrome/chrome-launcher>

## Related Decisions and Standards

- [ADR-0111](./0111-quality-signal-provider-port.md) — the Quality Signal
Provider port + canonical `Evidence` this adapter implements (parent decision).
- [ADR-0101](./0101-core-stateless-evaluation-engine.md) — stateless Core; the
adapter runs in orchestration, never inside the evaluator.
- [ADR-0110](./0110-masstransit-v8-apache-license-pin.md) — precedent for treating
a load-bearing dependency's license as an architectural concern (here the
opposite: an *optional* dependency with a permissive license).
- [ADR-0104](./0104-topology-driven-advisory-design-governance.md) — derives the
criteria this runtime evidence confirms or refutes (the conformance loop).
1 change: 1 addition & 0 deletions reference/core/architecture/adrs/core/README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
* [0110-masstransit-v8-apache-license-pin](./0110-masstransit-v8-apache-license-pin.es.md) — **Permanecer en MassTransit v8 (Apache-2.0); v9 es comercial y no sublicenciable**
* [0111-quality-signal-provider-port](./0111-quality-signal-provider-port.es.md) — **Puerto de Proveedores de Señales de Calidad — la evidencia externa (Lighthouse, TestSprite, …) entra vía adaptadores y `Evidence` canónica; el Core nunca depende de ninguna herramienta**
* [0112-rag-embedding-and-vector-store-platform](./0112-rag-embedding-and-vector-store-platform.es.md) — **Plataforma de embeddings y vector store para RAG — Qwen3-Embedding (Apache-2.0) totalmente OSS/self-hosted sobre pgvector; realiza el contrato agnóstico al modelo del ADR-0090, cero egress del corpus**
* [0113-nodejs-lighthouse-evidence-adapter](./0113-nodejs-lighthouse-evidence-adapter.es.md) — **Plataforma Node.js — Lighthouse (Apache-2.0) como adaptador de evidencia de referencia detrás del puerto de Proveedores de Señales de Calidad; opcional, importado dinámicamente, determinista**

---
[Volver al Nivel Superior](../README.es.md)
Expand Down
1 change: 1 addition & 0 deletions reference/core/architecture/adrs/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
* [0110-masstransit-v8-apache-license-pin](./0110-masstransit-v8-apache-license-pin.md) — **Stay on MassTransit v8 (Apache-2.0); v9 is commercial and non-sublicensable**
* [0111-quality-signal-provider-port](./0111-quality-signal-provider-port.md) — **Quality Signal Provider port — external evidence (Lighthouse, TestSprite, …) enters via adapters and canonical `Evidence`; Core never depends on any tool**
* [0112-rag-embedding-and-vector-store-platform](./0112-rag-embedding-and-vector-store-platform.md) — **RAG embedding & vector-store platform — fully OSS/self-hosted Qwen3-Embedding (Apache-2.0) on pgvector; realizes ADR-0090's model-agnostic contract, zero corpus egress**
* [0113-nodejs-lighthouse-evidence-adapter](./0113-nodejs-lighthouse-evidence-adapter.md) — **Node.js Platform — Lighthouse (Apache-2.0) as the reference evidence adapter behind the Quality Signal Provider port; optional, dynamically imported, deterministic**

---
[Back to Upper Level](../README.md)
Expand Down
Loading
Loading