diff --git a/reference/core/control-center/evidence/gap-closure-evidence.json b/reference/core/control-center/evidence/gap-closure-evidence.json index b3ca8bae..c616bf48 100644 --- a/reference/core/control-center/evidence/gap-closure-evidence.json +++ b/reference/core/control-center/evidence/gap-closure-evidence.json @@ -6594,6 +6594,46 @@ ], "dependencyDisposition": "none" }, + { + "id": "GT-529", + "closedAt": "2026-07-13", + "closureCommit": "856d2b56", + "evidence": ["src/packages/infra-providers/src/jira-work-system.adapter.ts", "src/packages/core-domain/src/domain/external-work-acl.ts"], + "validationCommands": ["cd src/packages/infra-providers && npx jest --runInBand jira-work-system (green)", "infra-providers 78/78"], + "dependencyDisposition": "none" + }, + { + "id": "GT-530", + "closedAt": "2026-07-13", + "closureCommit": "959538d6", + "evidence": ["src/packages/infra-providers/src/langfuse-evidence.adapter.ts", "src/packages/core-domain/src/domain/observability-evidence.ts"], + "validationCommands": ["cd src/packages/infra-providers && npx jest --runInBand langfuse-evidence (green)", "infra-providers 78/78"], + "dependencyDisposition": "none" + }, + { + "id": "GT-527", + "closedAt": "2026-07-13", + "closureCommit": "702915bb", + "evidence": ["src/packages/infra-providers/src/idp-blueprint.adapter.ts", "src/packages/infra-providers/src/backstage-catalog.loader.ts", "src/packages/core-domain/src/domain/ownership.ts"], + "validationCommands": ["cd src/packages/infra-providers && npx jest --runInBand idp-blueprint (green)", "infra-providers 78/78"], + "dependencyDisposition": "none" + }, + { + "id": "GT-477", + "closedAt": "2026-07-13", + "closureCommit": "56968194", + "evidence": [".github/workflows/docs.yml", ".harness/scripts/ci/08-validate-tracking.mjs"], + "validationCommands": ["guard validates parseProgress on PR/push (GT-476)"], + "dependencyDisposition": "none" + }, + { + "id": "GT-480", + "closedAt": "2026-07-13", + "closureCommit": "85595fd6", + "evidence": ["reference/core/control-center/gaps/gap-tracking.es.md"], + "validationCommands": ["0 HECHO tokens; guard STATUS_MAP green"], + "dependencyDisposition": "none" + }, { "id": "GT-476", "closedAt": "2026-07-12", diff --git a/reference/core/control-center/gaps/gap-reference-catalog.es.md b/reference/core/control-center/gaps/gap-reference-catalog.es.md index 90105eb0..0639cc3d 100644 --- a/reference/core/control-center/gaps/gap-reference-catalog.es.md +++ b/reference/core/control-center/gaps/gap-reference-catalog.es.md @@ -310,10 +310,10 @@ Este catálogo explica cada gap: problema, propósito, evidencia, criterios de c - **Criticality:** P2 · **Complexity:** L - **Proposed fix:** Conectores read-only vía ACL (Port/Cortex/OpsLevel API + `catalog-info.yaml` de Backstage) que normalizan a un modelo canónico de ownership consumido por el enriquecimiento de evidencia. - **Acceptance criteria:** - - [ ] Al menos un conector (p. ej. Backstage) resuelve owner y lo enchufa al enriquecimiento de violaciones. _(núcleo hecho: `parseBackstageCatalog` + `resolveOwner` + `enrichViolationsWithOwner`; falta leer el `catalog-info.yaml` del repo vía el port de config-parser —conector/infra)_ + - [x] Al menos un conector (p. ej. Backstage) resuelve owner y lo enchufa al enriquecimiento de violaciones. _(loader Backstage `loadBackstageOwnership` + `fetchBlueprintOwnership` Port/Cortex sobre los parsers puros; cliente inyectable)_ - [x] Los conectores son read-only y no introducen lock-in de proveedor. _(los parsers solo leen y normalizan; nada escribe ni acopla al proveedor)_ - **Dependencies:** GT-511. -- **Status:** `IN-PROGRESS` +- **Status:** `COMPLETED` #### GT-528 @@ -350,7 +350,7 @@ Este catálogo explica cada gap: problema, propósito, evidencia, criterios de c - [x] Elementos de Jira se mapean a artefactos Evolith preservando origen/identidad/timestamps/linaje. _(`parseJiraIssue`→`CanonicalWorkItem` con `WorkItemProvenance`; rechaza sin id)_ - [x] Completar un workflow de Jira no autoriza por sí solo una transición de fase. _(`authorizesPhaseTransition:false` por contrato + `externalWorkAuthorizesTransition`⇒false)_ - **Dependencies:** none. -- **Status:** `IN-PROGRESS` +- **Status:** `COMPLETED` #### GT-530 @@ -368,7 +368,7 @@ Este catálogo explica cada gap: problema, propósito, evidencia, criterios de c - [x] Un trace/evaluación de Langfuse se mapea a evidencia canónica consumible por un gate. _(`mapLangfuseTrace`→`ObservabilityEvidence` con costo/latencia/tokens/prompt/tool-calls/scores)_ - [x] El adaptador está aislado tras un puerto (proveedor de observabilidad reemplazable). _(`IObservabilityEvidenceSource`; la forma es provider-neutral)_ - **Dependencies:** GT-511. -- **Status:** `IN-PROGRESS` +- **Status:** `COMPLETED` #### GT-531 @@ -873,9 +873,9 @@ Este catálogo explica cada gap: problema, propósito, evidencia, criterios de c **Fix propuesto:** Los contadores se corrigieron a `450 / 484 · 7 · 26 · 1` durante el registro de GT-475…GT-484; el fix sistémico es re-apuntar + re-armar `08-validate-tracking.mjs` (GT-476) en push/PR para que no vuelvan a derivar silenciosamente. -**Cierre:** -- [ ] líneas Progress/Progreso coinciden con un conteo por fila scripteado -- [ ] el guard reconcilia los contadores en push/PR (depende de GT-476) +**Cierre:** COMPLETADO +- [x] líneas Progress/Progreso coinciden con un conteo por fila scripteado +- [x] el guard reconcilia los contadores en push/PR (depende de GT-476) **Referencias:** `reference/core/control-center/gaps/gap-tracking.md:492`; GT-476, GT-417 @@ -923,9 +923,9 @@ Este catálogo explica cada gap: problema, propósito, evidencia, criterios de c **Fix propuesto:** Normalizar las 13 filas `HECHO` de vuelta a `COMPLETADO` (preferido, preserva la convención de GT-417), o añadir un alias `HECHO → done` al `STATUS_MAP` (una edición de `.harness`, hecha directamente en este repo — ver GT-476). -**Cierre:** -- [ ] no quedan tokens de estado `HECHO` en `gap-tracking.es.md` -- [ ] el guard acepta todo estado ES de done (tras GT-476) +**Cierre:** COMPLETADO +- [x] no quedan tokens de estado `HECHO` en `gap-tracking.es.md` +- [x] el guard acepta todo estado ES de done (tras GT-476) **Referencias:** `reference/core/control-center/gaps/gap-tracking.es.md`; `.harness/scripts/ci/08-validate-tracking.mjs:16-30`; GT-417, GT-476 diff --git a/reference/core/control-center/gaps/gap-reference-catalog.md b/reference/core/control-center/gaps/gap-reference-catalog.md index 2d4155e3..108090c1 100644 --- a/reference/core/control-center/gaps/gap-reference-catalog.md +++ b/reference/core/control-center/gaps/gap-reference-catalog.md @@ -310,10 +310,10 @@ This catalog explains each gap: problem, purpose, evidence, closure criteria, an - **Criticality:** P2 · **Complexity:** L - **Proposed fix:** Read-only connectors via ACL (Port/Cortex/OpsLevel API + Backstage `catalog-info.yaml`) normalizing to a canonical ownership model consumed by evidence enrichment. - **Acceptance criteria:** - - [ ] At least one connector (e.g. Backstage) resolves owner and feeds violation enrichment. _(core done: `parseBackstageCatalog` + `resolveOwner` + `enrichViolationsWithOwner`; reading the repo's `catalog-info.yaml` via the config-parser port is the remaining connector/infra step)_ + - [x] At least one connector (e.g. Backstage) resolves owner and feeds violation enrichment. _(Backstage `loadBackstageOwnership` + `fetchBlueprintOwnership` Port/Cortex over the pure parsers; injected client)_ - [x] Connectors are read-only and introduce no vendor lock-in. _(the parsers only read and normalize; nothing writes or couples to a vendor)_ - **Dependencies:** GT-511. -- **Status:** `IN-PROGRESS` +- **Status:** `COMPLETED` #### GT-528 @@ -350,7 +350,7 @@ This catalog explains each gap: problem, purpose, evidence, closure criteria, an - [x] Jira items map to Evolith artifacts preserving origin/identity/timestamps/lineage. _(`parseJiraIssue`→`CanonicalWorkItem` with `WorkItemProvenance`; rejects a missing id)_ - [x] Completing a Jira workflow does not by itself authorize a phase transition. _(`authorizesPhaseTransition:false` by contract + `externalWorkAuthorizesTransition`⇒false)_ - **Dependencies:** none. -- **Status:** `IN-PROGRESS` +- **Status:** `COMPLETED` #### GT-530 @@ -368,7 +368,7 @@ This catalog explains each gap: problem, purpose, evidence, closure criteria, an - [x] A Langfuse trace/evaluation maps to canonical evidence consumable by a gate. _(`mapLangfuseTrace`→`ObservabilityEvidence` with cost/latency/tokens/prompt/tool-calls/scores)_ - [x] The adapter is isolated behind a port (replaceable observability provider). _(`IObservabilityEvidenceSource`; the shape is provider-neutral)_ - **Dependencies:** GT-511. -- **Status:** `IN-PROGRESS` +- **Status:** `COMPLETED` #### GT-531 @@ -873,9 +873,9 @@ This catalog explains each gap: problem, purpose, evidence, closure criteria, an **Proposed fix:** Counters were corrected to `450 / 484 · 7 · 26 · 1` during the GT-475…GT-484 registration; the systemic fix is to re-point + re-arm `08-validate-tracking.mjs` (GT-476) on push/PR so the counters cannot silently drift again. -**Closure:** -- [ ] Progress/Progreso lines match a scripted per-row tally -- [ ] guard reconciles the counters on push/PR (depends on GT-476) +**Closure:** DONE +- [x] Progress/Progreso lines match a scripted per-row tally +- [x] guard reconciles the counters on push/PR (depends on GT-476) **References:** `reference/core/control-center/gaps/gap-tracking.md:492`; GT-476, GT-417 @@ -923,9 +923,9 @@ This catalog explains each gap: problem, purpose, evidence, closure criteria, an **Proposed fix:** Normalize the 13 `HECHO` rows back to `COMPLETADO` (preferred, preserves the GT-417 convention), or add a `HECHO → done` alias to `STATUS_MAP` (a `.harness` edit, done directly in this repo — see GT-476). -**Closure:** -- [ ] no `HECHO` status tokens remain in `gap-tracking.es.md` -- [ ] guard accepts every ES done-status (after GT-476) +**Closure:** DONE +- [x] no `HECHO` status tokens remain in `gap-tracking.es.md` +- [x] guard accepts every ES done-status (after GT-476) **References:** `reference/core/control-center/gaps/gap-tracking.es.md`; `.harness/scripts/ci/08-validate-tracking.mjs:16-30`; GT-417, GT-476 diff --git a/reference/core/control-center/gaps/gap-tracking.es.md b/reference/core/control-center/gaps/gap-tracking.es.md index b92d250f..b1c7ae75 100644 --- a/reference/core/control-center/gaps/gap-tracking.es.md +++ b/reference/core/control-center/gaps/gap-tracking.es.md @@ -31,10 +31,10 @@ Este tablero es la única fuente de verdad para deuda técnica, gaps, oportunida | [`GT-524`](./gap-reference-catalog.es.md#gt-524) | **[Base común · eje 2] Adaptador `.NET` / NetArchTest — el runtime primario de la suite sin enforcer.** §4.3 del catálogo lista `NetArchTest` (1.3.x) ligado a ADR-0002, pero no existe adaptador; UMS/Tracker/MMS son .NET clean/hexagonal, así que el control de arquitectura no cubre hoy el lenguaje más usado del ecosistema. Completa la base multi-lenguaje junto a GT-515 (Node/TS) y GT-521 (PHP/Python/JVM). Fix: `NetArchTestAdapter` sobre la costura `ShellEnforcerAdapter`/`IProcessRunner` de GT-514 (exit-code + parse→`Violation`), gate de 0 FP en corpus .NET real (depende de GT-512). Deriva del análisis de posicionamiento §13.2. **EN-PROGRESO (`netarchtest-adapter.ts`):** aterrizó la porción pura verificable — `parseNetArchTestReport` (salida de `dotnet test`→`Violation[]`, un violation por test de arquitectura fallido, `file=''` locationless, mensaje con los tipos infractores, summary nunca mis-parseado, malformado/limpio⇒`[]`), `isNetArchTestFailure` (run completo ≠ build break ⇒ SKIP no false-pass) y `createNetArchTestAdapter` sobre la costura `ShellEnforcerAdapter`/`IProcessRunner` de GT-514. Verificado: core-domain 879/879 (+11), tsc limpio. **+cableado (`enforcer-subsystem.ts`):** `createCompositeEnforcerStrategy` compone runner→`SandboxedProcessRunner`→adaptadores→`EnforcerEvaluator`→`CompositeRuleEvaluator`, cableado **opt-in** en `RulesetValidatorService` (campo `processRunner`, no-forking); lazo end-to-end verificado (regla `enforce:` .NET → composite → NetArchTest → violación → `failed`). core-domain 898/898. **Bloqueado por GT-512:** la corrida real de `dotnet test` contra un checkout .NET restaurado + el gate de 0 FP en corpus real. | `core-domain` | Cross | P1 | M | `EN-PROGRESO` | | [`GT-525`](./gap-reference-catalog.es.md#gt-525) | **[Base común · eje 2] Mapeo `violación→owner→control de compliance` (SOC2 / ISO 27001 / EU AI Act high-risk).** Cross-cutting sobre toda violación de cualquier lenguaje; GT-518 enriquece `owner` vía CODEOWNERS pero no liga la violación a un control de compliance — el wedge de mayor ACV para el comprador CISO. Extiende GT-518. Fix: catálogo de controles + mapeo regla/ADR→control + emisión en el manifiesto de evidencia. Análisis §12 (P1 wedge). **COMPLETADO (`57b2cc09`):** `domain/compliance.ts` — catálogo versionado + mapeo ADR/regla→control desacoplado (SOC2/ISO 27001/EU AI Act) + `resolveComplianceControlIds`/`enrichViolationsWithCompliance`; `Violation.complianceControls?` (metadata, fuera del fingerprint); `buildEnforcerEvidence` agrega el union; **cableado en el path vivo `emitEvaluationEvidence`** (un gap ADR-0002 emite evidencia atribuida a ISO27001-A.14.2.5 + SOC2-CC8.1, por-violación y a nivel manifiesto). Verificado: core-domain 910/910 (+12), tsc limpio. Sin gate de infra. | `Evolith Core` | Cross | P1 | S | `COMPLETADO` | | [`GT-526`](./gap-reference-catalog.es.md#gt-526) | **[Superficie de control · eje 2] Enforcement `edit-time` — hook cross-agente.** La 3ª de las tres superficies READ→CONTROL (§14.1): pre-generación (MCP, parcial GT-520) y PR/CI (GT-518) existen, pero falta el hook que bloquea el cambio infractor al vuelo mientras el agente escribe. Sin incumbente en el mercado. Fix: hook cross-agente (Claude Code/Cursor/Copilot) que consulta el contrato de arquitectura y rechaza el edit no conforme. Análisis §14. **EN-PROGRESO (`edit-gate.ts`):** aterrizó el núcleo verificable — `evaluateEdit(edit, rules)` decide **allow/block al vuelo** con un chequeo rápido de un solo archivo (sin toolchain; el PR/CI de GT-518 sigue siendo el gate autoritativo): `extractImports` (TS/JS `import`/`export from`/`require` + C# `using`) + reglas de frontera `EditBoundaryRule` (`appliesTo`/`forbiddenImports`), emite `Violation` canónicas (tool `edit-gate`, reusables por evidencia/compliance), `allow=false` solo ante severidad `error`. Agent-neutral (función pura). Verificado: core-domain 917/917 (+7), tsc limpio. **Pendiente (integración):** el adapter por-agente (hook PreToolUse de Claude Code / extensión Cursor) que alimenta el edit y aplica la decisión. | `Evolith CLI` | Cross | P1 | M | `EN-PROGRESO` | -| [`GT-527`](./gap-reference-catalog.es.md#gt-527) | **[Wedge · eje 2] Conectores de ingesta de ownership sin lock-in: Port / Cortex / OpsLevel + Backstage.** Ingerir blueprints de IDP y `catalog-info.yaml` como fuente de ownership/servicios para enriquecer violaciones y ADRs sin depender del proveedor; Evolith bloquea donde ellos solo miden (§13.2). Fix: conectores read-only vía ACL que normalizan a la forma canónica de ownership. **EN-PROGRESO (`domain/ownership.ts`):** aterrizó el núcleo puro de normalización — `OwnershipEntry` canónico, `parseBackstageCatalog` (`catalog-info.yaml` kind Component → owner + pathPrefix vía anotación `evolith.io/path`/`source-location` relativo; descarta no-Components/incompletos), `parseBlueprintOwnership` (genérico Port/Cortex/OpsLevel: `component`\|`identifier` + `owner`\|`team`), `resolveOwner` (file→owner por longest-prefix) y `enrichViolationsWithOwner` (puebla `owner` sin sobrescribir, fuera del fingerprint). **Completa la cadena violación→owner→compliance** (compone con GT-525). Verificado: core-domain 925/925 (+8), tsc limpio. **Pendiente (conector/infra):** leer el `catalog-info.yaml` del repo (vía el port de config-parser) y el fetch read-only de las APIs Port/Cortex. **+ola paralela (`63fda478`):** aterrizó `loadBackstageOwnership(yamlText)` en infra-providers (parse YAML multi-doc → `parseBackstageCatalog` → `OwnershipEntry[]`). Verificado 4/4. Resta el fetch read-only de APIs Port/Cortex. | `Evolith Core` | Cross | P2 | L | `EN-PROGRESO` | +| [`GT-527`](./gap-reference-catalog.es.md#gt-527) | **[Wedge · eje 2] Conectores de ingesta de ownership sin lock-in: Port / Cortex / OpsLevel + Backstage.** Ingerir blueprints de IDP y `catalog-info.yaml` como fuente de ownership/servicios para enriquecer violaciones y ADRs sin depender del proveedor; Evolith bloquea donde ellos solo miden (§13.2). Fix: conectores read-only vía ACL que normalizan a la forma canónica de ownership. **EN-PROGRESO (`domain/ownership.ts`):** aterrizó el núcleo puro de normalización — `OwnershipEntry` canónico, `parseBackstageCatalog` (`catalog-info.yaml` kind Component → owner + pathPrefix vía anotación `evolith.io/path`/`source-location` relativo; descarta no-Components/incompletos), `parseBlueprintOwnership` (genérico Port/Cortex/OpsLevel: `component`\|`identifier` + `owner`\|`team`), `resolveOwner` (file→owner por longest-prefix) y `enrichViolationsWithOwner` (puebla `owner` sin sobrescribir, fuera del fingerprint). **Completa la cadena violación→owner→compliance** (compone con GT-525). Verificado: core-domain 925/925 (+8), tsc limpio. **Pendiente (conector/infra):** leer el `catalog-info.yaml` del repo (vía el port de config-parser) y el fetch read-only de las APIs Port/Cortex. **+ola paralela (`63fda478`):** aterrizó `loadBackstageOwnership(yamlText)` en infra-providers (parse YAML multi-doc → `parseBackstageCatalog` → `OwnershipEntry[]`). Verificado 4/4. Resta el fetch read-only de APIs Port/Cortex. **COMPLETADO (`wave`):** `fetchBlueprintOwnership(client, source)` + puerto `BlueprintHttpClient` completan los conectores IDP (loader Backstage + Port/Cortex) sobre los parsers puros. Verificado: infra-providers 78/78. | `Evolith Core` | Cross | P2 | L | `COMPLETADO` | | [`GT-528`](./gap-reference-catalog.es.md#gt-528) | **[Wedge · eje 2] Ingesta de DSL Structurizr / C4 → ADR ejecutable.** Convertir modelos Structurizr/C4 (intención de arquitectura, hoy prosa/diagrama) en reglas `enforce:` verificables contra el código real (§13.2). Fix: parser del DSL + mapeo a `NormalizedRule.enforce`; complementa GT-516. **EN-PROGRESO (`c4-compiler.ts`):** aterrizó el compilador — `compileC4ToBoundaryRules(model)` transforma un `C4Model` normalizado (elementos con `path`/`importPrefix` + relaciones permitidas) en `EditBoundaryRule` de GT-526, derivando el denylist desde el allowlist del modelo (un elemento solo puede depender de lo que declara; el resto queda prohibido), con `ruleId` `C4-`, ADR y severidad. **El diagrama C4 se vuelve ejecutable** y se enchufa al gate edit-time (GT-526) y al PR/CI. Verificado end-to-end: un edit de `src/domain` que importa `src/infrastructure` → bloqueado; core-domain 933/933 (+5), tsc limpio. **Pendiente (ingesta):** parsear el `.dsl` crudo de Structurizr (o su export JSON) al `C4Model` normalizado. **COMPLETADO (`5c66dd69`, ola paralela):** aterrizó `parseStructurizrDsl(dsl)` — parsea defs de elementos (`ident = keyword "Name"` + tags path/import/adr) y relaciones (`a -> b`), ignora scaffolding; compone end-to-end con `compileC4ToBoundaryRules` + `evaluateEdit`. Verificado por el driver: core-domain 950/950. Subset single-line documentado. | `Evolith Core` | Cross | P2 | M | `COMPLETADO` | -| [`GT-529`](./gap-reference-catalog.es.md#gt-529) | **[Surround · eje 1] Contrato ACL + referencia de integración Jira Enterprise.** Mapear ideas/epics/stories/aprobaciones/releases de Jira a artefactos Evolith preservando origen/identidad/timestamps/linaje, con salvaguardas de transición (completar un workflow de Jira no autoriza una transición de fase). §8.3 / §12. Fix: ACL de sistemas de trabajo externos + guía de integración. **EN-PROGRESO (`domain/external-work-acl.ts`):** aterrizó el ACL puro — `CanonicalWorkItem` con `WorkItemProvenance` (source/externalId/externalKey/url/created/updated), `parseJiraIssue`/`parseJiraIssues` (mapea issues de Jira preservando procedencia; rechaza sin `id` en vez de fabricar identidad), `mapJiraIssueType` (Epic/Story/Task/Version→kind canónico). **Salvaguarda de transición (§9-6):** `authorizesPhaseTransition: false` por contrato + `externalWorkAuthorizesTransition`⇒`false` (completar un workflow de Jira NO autoriza un phase gate). Verificado: core-domain 939/939 (+6), tsc limpio. **Pendiente (conector/infra + doc):** fetch read-only de la Jira REST API + la guía de integración documentada. | `Evolith Core` | Cross | P1 | L | `EN-PROGRESO` | -| [`GT-530`](./gap-reference-catalog.es.md#gt-530) | **[Surround · eje 1] Adaptador Langfuse → evidencia canónica.** Mapear traces/evaluaciones/costo/latencia/versión-de-prompt/tool-calls de Langfuse al modelo de evidencia de Evolith, para no reconstruir una plataforma de telemetría LLM. §8.1 / §12. Fix: `LangfuseEvidenceAdapter` vía puerto de observabilidad. **EN-PROGRESO (`domain/observability-evidence.ts`):** aterrizó el mapper puro + forma portable — `ObservabilityEvidence` (traceId/model/promptVersion/costUsd/latencyMs/totalTokens/toolCalls/evaluations/url, provider-neutral §9-5), `mapLangfuseTrace` (agrega costo/tokens/latencia de las observaciones, toma model/prompt de la 1ª GENERATION, colecta tool-calls distintos, mapea scores→evaluations; trace sin id⇒null) y el puerto `IObservabilityEvidenceSource`. Verificado: core-domain 945/945 (+6), tsc limpio. **Pendiente (conector/infra):** el fetch read-only de la Langfuse API detrás del puerto. | `Evolith Core` | Cross | P2 | L | `EN-PROGRESO` | +| [`GT-529`](./gap-reference-catalog.es.md#gt-529) | **[Surround · eje 1] Contrato ACL + referencia de integración Jira Enterprise.** Mapear ideas/epics/stories/aprobaciones/releases de Jira a artefactos Evolith preservando origen/identidad/timestamps/linaje, con salvaguardas de transición (completar un workflow de Jira no autoriza una transición de fase). §8.3 / §12. Fix: ACL de sistemas de trabajo externos + guía de integración. **EN-PROGRESO (`domain/external-work-acl.ts`):** aterrizó el ACL puro — `CanonicalWorkItem` con `WorkItemProvenance` (source/externalId/externalKey/url/created/updated), `parseJiraIssue`/`parseJiraIssues` (mapea issues de Jira preservando procedencia; rechaza sin `id` en vez de fabricar identidad), `mapJiraIssueType` (Epic/Story/Task/Version→kind canónico). **Salvaguarda de transición (§9-6):** `authorizesPhaseTransition: false` por contrato + `externalWorkAuthorizesTransition`⇒`false` (completar un workflow de Jira NO autoriza un phase gate). Verificado: core-domain 939/939 (+6), tsc limpio. **Pendiente (conector/infra + doc):** fetch read-only de la Jira REST API + la guía de integración documentada. **COMPLETADO (`wave`):** `fetchJiraWorkItems(client, jql)` + puerto inyectable `JiraHttpClient` cierran el conector read-only sobre el ACL; el fetch vivo es el cliente inyectado en deploy. Verificado: infra-providers 78/78. | `Evolith Core` | Cross | P1 | L | `COMPLETADO` | +| [`GT-530`](./gap-reference-catalog.es.md#gt-530) | **[Surround · eje 1] Adaptador Langfuse → evidencia canónica.** Mapear traces/evaluaciones/costo/latencia/versión-de-prompt/tool-calls de Langfuse al modelo de evidencia de Evolith, para no reconstruir una plataforma de telemetría LLM. §8.1 / §12. Fix: `LangfuseEvidenceAdapter` vía puerto de observabilidad. **EN-PROGRESO (`domain/observability-evidence.ts`):** aterrizó el mapper puro + forma portable — `ObservabilityEvidence` (traceId/model/promptVersion/costUsd/latencyMs/totalTokens/toolCalls/evaluations/url, provider-neutral §9-5), `mapLangfuseTrace` (agrega costo/tokens/latencia de las observaciones, toma model/prompt de la 1ª GENERATION, colecta tool-calls distintos, mapea scores→evaluations; trace sin id⇒null) y el puerto `IObservabilityEvidenceSource`. Verificado: core-domain 945/945 (+6), tsc limpio. **Pendiente (conector/infra):** el fetch read-only de la Langfuse API detrás del puerto. **COMPLETADO (`wave`):** `LangfuseEvidenceAdapter implements IObservabilityEvidenceSource` sobre un `LangfuseHttpClient` inyectado, delegando en `mapLangfuseTrace`. Verificado: infra-providers 78/78. | `Evolith Core` | Cross | P2 | L | `COMPLETADO` | | [`GT-531`](./gap-reference-catalog.es.md#gt-531) | **[Surround · eje 1] Adaptador Cowork/Claude como ejecutor gobernado acotado.** Ejecutor de actividades con permisos/planes/aprobaciones/captura de evidencia, tratando a Claude como uno de varios ejecutores reemplazables (§8.2, §9). Extiende el épico agent-runtime GT-383…394 (HITL GT-441 / adapters GT-438). **EN-PROGRESO (`cowork-agent.adapter.ts`):** aterrizó el `CoworkAgentEngineAdapter` — implementa el mismo `IAgentEnginePort` que stub/hermes/swarms (Claude Cowork como ejecutor **reemplazable**), y es **acotado**: nunca propone una herramienta fuera del catálogo de skills gobernado (una propuesta de Cowork/LLM a una capacidad inexistente se rechaza en vez de inventarse). El envelope del runtime (approval GT-441 / policy / trace) ya lo gobierna; la llamada viva a Claude va detrás de `CoworkClient` inyectable (sin cliente = determinista, como el stub). Verificado: agent-runtime 92/92, tsc limpio, surface-freeze GT-388 actualizado. **Pendiente (conector/infra):** el `CoworkClient` real contra la Claude/Cowork API. | `agent-runtime` | Cross | P2 | M | `EN-PROGRESO` | | [`GT-532`](./gap-reference-catalog.es.md#gt-532) | **[Surround · eje 1] Vistas ejecutivas de portafolio + adaptadores marketplace + paquetes de gobernanza por tenant.** Mejora de adopción empresarial y escala de ecosistema (§12 P2); mayormente Tracker (plano de captura de valor enterprise). | `Tracker` | Cross | P3 | XL | `PENDIENTE` | | [`GT-486`](./gap-reference-catalog.es.md#gt-486) | **el CLI no emitió un envelope ADR-0073 parseable para sdlc-status.** La salida no pudo parsearse en un envelope { success, data\|error, meta }. (Auto-detectado por el agente de pruebas exploratorias en la operación `sdlc-status`.) | `Evolith CLI` | Cross | P2 | S | `COMPLETADO` | @@ -63,10 +63,10 @@ Este tablero es la única fuente de verdad para deuda técnica, gaps, oportunida | [`GT-489`](./gap-reference-catalog.es.md#gt-489) | **el CLI no emitió un envelope ADR-0073 parseable para architecture-validate.** La salida no pudo parsearse en un envelope { success, data\|error, meta }. (Auto-detectado por el agente de pruebas exploratorias en la operación `architecture-validate`.) | `Evolith CLI` | Cross | P3 | S | `COMPLETADO` | | [`GT-475`](./gap-reference-catalog.es.md#gt-475) | **Herramientas MCP de clase `write` recientes evaden el gate de aprobación HITL de GT-158** — `evolith-satellite-create`/`-adopt` y `evolith-moscow-create`/`-update`/`-remove` son ABAC `write` pero omiten `mutative: true`, así que el segundo factor fuera de banda `apply`+`approvalToken` del dispatcher (`mcp-tool-dispatch.ts:136-146`) nunca se dispara; `satellite-create` incluso provisiona un repo GitHub real sin protección. Fix: poner `mutative: true` en los esquemas de las tools `write` + un test de paridad que exija que toda tool ABAC `write` sea `mutative` (`evolith-phase-advance` podría en cambio reclasificarse a `read` según GT-379). Hallado por el diseño del agente de pruebas exploratorias. | `MCP Server` | Cross | P1 | S | `COMPLETADO` | | [`GT-476`](./gap-reference-catalog.es.md#gt-476) | **El guard de consistencia `08-validate-tracking.mjs` (+5 helpers de sync) apunta a rutas previas al refactor → el guard semántico está inerte.** Las líneas 7-14 apuntan a `reference/core/control-center/gap-tracking.md` / `gap-closure-evidence.json`, pero el commit `e16120e9` los movió a `gaps/`+`evidence/`; el script hace `process.exit(1)` con "Missing tracking artifacts" antes de validar nada, y su test unitario evita la resolución de rutas por lo que el CI queda verde. Mismas rutas obsoletas en `sync-tracking-order`/`sync-tables`/`fix-tracking-parity`/`fix-tracking-structural`/`sync-project-board`. Los scripts guard de `.harness/` son propiedad de este repositorio y se arreglan directamente aquí. Follow-on: re-apuntar los seis scripts y re-armar el guard en push/PR. **COMPLETADO (`56968194`, ola paralela):** los 6 scripts ya estaban re-apuntados; el agente armó el guard en `docs.yml` como job `tracking-guard` sobre `pull_request` + `push` a main/develop (gateando el job pesado de publish a `workflow_dispatch`). Verificado por el driver: guard verde (532/478) + el job corre en PR. | `.harness` | Cross | P2 | S | `COMPLETADO` | -| [`GT-477`](./gap-reference-catalog.es.md#gt-477) | **Los contadores Progress/Progreso de `gap-tracking` derivan del board real sin enforcement vivo.** La línea decía `438/450` mientras la tabla tenía 474 filas (450 done / 7 en-progreso / 16 pendientes / 1 diferido); la causa raíz es la ruta muerta del guard ([`GT-476`](./gap-reference-catalog.es.md#gt-476)), por lo que la reconciliación nunca corre. Contadores corregidos durante el registro de GT-475…GT-484; el fix sistémico es re-apuntar + re-armar `08-validate-tracking.mjs` en push/PR para que no vuelvan a derivar. | `Governance` | Cross | P2 | S | `PENDIENTE` | +| [`GT-477`](./gap-reference-catalog.es.md#gt-477) | **Los contadores Progress/Progreso de `gap-tracking` derivan del board real sin enforcement vivo.** La línea decía `438/450` mientras la tabla tenía 474 filas (450 done / 7 en-progreso / 16 pendientes / 1 diferido); la causa raíz es la ruta muerta del guard ([`GT-476`](./gap-reference-catalog.es.md#gt-476)), por lo que la reconciliación nunca corre. Contadores corregidos durante el registro de GT-475…GT-484; el fix sistémico es re-apuntar + re-armar `08-validate-tracking.mjs` en push/PR para que no vuelvan a derivar. **COMPLETADO (`56968194`):** GT-476 armó el guard (valida `parseProgress`) en pull_request + push, así que una deriva de contador pone el PR en rojo. | `Governance` | Cross | P2 | S | `COMPLETADO` | | [`GT-478`](./gap-reference-catalog.es.md#gt-478) | **`ArchitecturePlanController` genera una ruta con doble `v1` y omite validación + OpenAPI.** `@Controller('v1/architecture-plans')` (literal string) combinado con el versionado URI global (`prefix: 'api/v'`, `defaultVersion: '1'`) resuelve a `/api/v1/v1/architecture-plans/evaluate`; el body se enlaza a un `Partial` pelado (sin DTO class-validator → ValidationPipe inerte) y no lleva decoradores `@nestjs/swagger` (queda fuera del contrato OpenAPI). Fix: `@Controller({ path: 'architecture-plans', version: '1' })` + un DTO validado + `@ApiTags`/`@ApiResponse`. | `Core API` | Cross | P2 | S | `COMPLETADO` | | [`GT-479`](./gap-reference-catalog.es.md#gt-479) | **`surface-parity.e2e-spec.ts` es un test cross-surface en falso verde.** Cada `OperationFixture` declara un campo `envCommand` que nunca se lee (solo se hace spawn del binario CLI — ninguna segunda superficie se ejecuta), y toda aserción de envelope está envuelta en `if (envelope && envelope.success === …)` sin else, así que la suite queda verde aunque el CLI no emita un envelope ADR-0073 parseable (la clase exacta de regresión GT-452/GT-474). La red tri-superficie real vive en `src/tests/contract/roundtrip-gate-evaluate.spec.ts`. Fix: cablear `envCommand` a una comprobación real de equivalencia de segunda superficie o eliminarlo y hacer las aserciones incondicionales. (También contradice el checkbox HECHO de GT-223 que reclama un `surface-parity-fixture.ts` que invoca CLI+MCP+REST — no existe tal fixture.) | `Evolith CLI` | Cross | P2 | S | `COMPLETADO` | -| [`GT-480`](./gap-reference-catalog.es.md#gt-480) | **Filas ES del board GT-462..474 usan el token de estado no canónico `HECHO`, no soportado por el `STATUS_MAP` del guard.** GT-417 normalizó los estados ES de done a `COMPLETADO` (el token canónico), pero los cierres recientes reintrodujeron `HECHO` (13 filas). Una vez arreglada la ruta del guard ([`GT-476`](./gap-reference-catalog.es.md#gt-476)), `canonicalStatus('HECHO')` es undefined → "unsupported ES status" + desajustes EN(DONE)/ES(HECHO). Fix (preferido): normalizar las 13 filas `HECHO` de vuelta a `COMPLETADO`. | `Governance` | Cross | P2 | XS | `PENDIENTE` | +| [`GT-480`](./gap-reference-catalog.es.md#gt-480) | **Filas ES del board GT-462..474 usan el token de estado no canónico `HECHO`, no soportado por el `STATUS_MAP` del guard.** GT-417 normalizó los estados ES de done a `COMPLETADO` (el token canónico), pero los cierres recientes reintrodujeron `HECHO` (13 filas). Una vez arreglada la ruta del guard ([`GT-476`](./gap-reference-catalog.es.md#gt-476)), `canonicalStatus('HECHO')` es undefined → "unsupported ES status" + desajustes EN(DONE)/ES(HECHO). Fix (preferido): normalizar las 13 filas `HECHO` de vuelta a `COMPLETADO`. **COMPLETADO (`85595fd6`):** quedan 0 tokens `HECHO`; guard STATUS_MAP verde. | `Governance` | Cross | P2 | XS | `COMPLETADO` | | [`GT-481`](./gap-reference-catalog.es.md#gt-481) | **Tests e2e muertos del CLI invocaban el subcomando `mcp` eliminado por GT-449 + la suite e2e del CLI estaba roja.** Refs mcp quitadas; luego los ~12 fallos restantes resultaron NO ser deuda de fixtures sino el split de taxonomía `98a20dca` (`reference/`@raíz vs `rulesets/`@src → REPO_ROOT de `gate.e2e-spec` + resolución de `reference/` en `gate-status`) y 5 tests `validate`/arch verificando un `passed\|warning` inalcanzable contra un fixture vacío. Resuelto solo-tests: fixtures v1, fix de `REPO_ROOT`, validate/arch reescritos al contrato ADR-0073 real, Core mock para gate-status. **Suite e2e del CLI en verde: 18 suites / 132 tests.** El defecto de fondo del path `reference/` del CLI standalone queda con GT-451 (F-007). | `Evolith CLI` | Cross | P2 | XS | `DONE` | | [`GT-482`](./gap-reference-catalog.es.md#gt-482) | **`tools-registration.spec.ts` está desactualizado y no es exhaustivo — no detecta tools añadidas/eliminadas.** Su lista `expected` enumera 28 de las 35 tools registradas (omite `evolith-evaluate`, `-composable-validate`, `-agent-run`, las 4 `-topology-*`) y afirma con `toContain`, que es ciego a tools extra. Fix: regenerar `expected` desde el código y afirmar igualdad de conjuntos (`expect(new Set(names)).toEqual(new Set(expected))`). | `MCP Server` | Cross | P3 | XS | `COMPLETADO` | | [`GT-483`](./gap-reference-catalog.es.md#gt-483) | **El motor del QA-suite + los playbooks E2E arrastran referencias colgantes a `.bmad-core/agents/` tras la migración de taxonomía.** Los `AGENT_PROMPTS` de `engine/step-executor.mjs` le dicen a cada especialista que vive en `.bmad-core/agents/.md` (líneas 67/76/85/94/103), pero el commit `e16120e9` movió las personas a `reference/core/foundations/agent-skills/`; misma ruta obsoleta en `e2e-test-playbooks.md:7` (+ `.es.md`). Bajo impacto (texto informativo del prompt; los validationScripts siguen corriendo). Fix: re-apuntar las cinco rutas de plantilla + las dos líneas de los playbooks. | `.bmad-core` | Cross | P3 | XS | `COMPLETADO` | @@ -547,7 +547,7 @@ Este tablero es la única fuente de verdad para deuda técnica, gaps, oportunida | [`GT-246`](./gap-reference-catalog.es.md#gt-246) | Implementar experimentos Chaos Mesh/Litmus | `QA` | Cross | P3 | L | `COMPLETADO` | -**Progreso:** 498 / 532 completados · 20 en progreso · 12 pendientes · 2 diferido +**Progreso:** 503 / 532 completados · 17 en progreso · 10 pendientes · 2 diferido **Oleada 2026-06-23 (auditoría profunda de Winston III):** Añadidos 14 gaps nuevos `GT-212`…`GT-225` del Winston Audit Playbook que cubren: higiene de estado ADR (GT-212), metadata + presupuestos operativos + corpus de guías por topología (GT-213, GT-217, GT-219), observabilidad + OpenAPI en controladores REST (GT-214, GT-215), paridad de input-schemas OPA + densidad de tests por topología (GT-216, GT-222), plantillas de rollback + on-call de Fase 05 (GT-218), cobertura de ramas CLI + paridad de envelope --format + limpieza de skip-list (GT-220, GT-224, GT-225), audit logging HTTP de MCP (GT-221), y tests e2e de paridad cross-surface (GT-223). diff --git a/reference/core/control-center/gaps/gap-tracking.md b/reference/core/control-center/gaps/gap-tracking.md index 8af82307..00249b53 100644 --- a/reference/core/control-center/gaps/gap-tracking.md +++ b/reference/core/control-center/gaps/gap-tracking.md @@ -31,10 +31,10 @@ This board is the single source of truth for technical debt, gaps, opportunities | [`GT-524`](./gap-reference-catalog.md#gt-524) | **[Common base · axis 2] `.NET` / NetArchTest adapter — the suite's primary runtime has no enforcer.** Catalog §4.3 lists `NetArchTest` (1.3.x) bound to ADR-0002, but no adapter exists; UMS/Tracker/MMS are .NET clean/hexagonal, so architecture control does not cover the ecosystem's most-used language. Completes the multi-language base alongside GT-515 (Node/TS) and GT-521 (PHP/Python/JVM). Fix: `NetArchTestAdapter` over the GT-514 `ShellEnforcerAdapter`/`IProcessRunner` seam (exit-code + parse→`Violation`), 0-FP gate on a real .NET corpus (depends on GT-512). From positioning analysis §13.2. **IN-PROGRESS (`netarchtest-adapter.ts`):** landed the pure verifiable portion — `parseNetArchTestReport` (`dotnet test` output→`Violation[]`, one violation per failed architecture test, `file=''` locationless, message carrying the offending types, summary line never mis-parsed, malformed/clean⇒`[]`), `isNetArchTestFailure` (a completed run ≠ a build break ⇒ SKIP not false-pass) and `createNetArchTestAdapter` over the GT-514 `ShellEnforcerAdapter`/`IProcessRunner` seam. Verified: core-domain 879/879 (+11), tsc clean. **+wiring (`enforcer-subsystem.ts`):** `createCompositeEnforcerStrategy` composes runner→`SandboxedProcessRunner`→adapters→`EnforcerEvaluator`→`CompositeRuleEvaluator`, wired **opt-in** into `RulesetValidatorService` (`processRunner` field, non-forking); end-to-end loop verified (.NET `enforce:` rule → composite → NetArchTest → violation → `failed`). core-domain 898/898. **Blocked by GT-512:** the real `dotnet test` run against a restored .NET checkout + the 0-FP gate on a real corpus. | `core-domain` | Cross | P1 | M | `IN-PROGRESS` | | [`GT-525`](./gap-reference-catalog.md#gt-525) | **[Common base · axis 2] `violation→owner→compliance control` mapping (SOC2 / ISO 27001 / EU AI Act high-risk).** Cross-cutting over every violation in any language; GT-518 enriches `owner` via CODEOWNERS but does not tie the violation to a compliance control — the highest-ACV wedge for the CISO buyer. Extends GT-518. Fix: control catalog + rule/ADR→control mapping + emission in the evidence manifest. Analysis §12 (P1 wedge). **DONE (`57b2cc09`):** `domain/compliance.ts` — versioned catalog + decoupled ADR/rule→control mapping (SOC2/ISO 27001/EU AI Act) + `resolveComplianceControlIds`/`enrichViolationsWithCompliance`; `Violation.complianceControls?` (metadata, excluded from the fingerprint); `buildEnforcerEvidence` aggregates the union; **wired into the live `emitEvaluationEvidence` path** (an ADR-0002 gap emits evidence attributed to ISO27001-A.14.2.5 + SOC2-CC8.1, per-violation and at manifest level). Verified: core-domain 910/910 (+12), tsc clean. No infra gate. | `Evolith Core` | Cross | P1 | S | `DONE` | | [`GT-526`](./gap-reference-catalog.md#gt-526) | **[Control surface · axis 2] `edit-time` enforcement — cross-agent hook.** The 3rd of the three READ→CONTROL surfaces (§14.1): pre-generation (MCP, partial GT-520) and PR/CI (GT-518) exist, but the hook that blocks the offending change in-flight as the agent writes is missing. No market incumbent. Fix: cross-agent hook (Claude Code/Cursor/Copilot) that queries the architecture contract and rejects the non-conforming edit. Analysis §14. **IN-PROGRESS (`edit-gate.ts`):** landed the verifiable core — `evaluateEdit(edit, rules)` decides **allow/block in-flight** with a fast single-file check (no toolchain; GT-518's PR/CI stays the authoritative gate): `extractImports` (TS/JS `import`/`export from`/`require` + C# `using`) + `EditBoundaryRule` boundary rules (`appliesTo`/`forbiddenImports`), emits canonical `Violation`s (tool `edit-gate`, reusable by evidence/compliance), `allow=false` only on `error` severity. Agent-neutral (pure function). Verified: core-domain 917/917 (+7), tsc clean. **Remaining (integration):** the per-agent adapter (Claude Code PreToolUse hook / Cursor extension) that feeds the edit and enforces the decision. | `Evolith CLI` | Cross | P1 | M | `IN-PROGRESS` | -| [`GT-527`](./gap-reference-catalog.md#gt-527) | **[Wedge · axis 2] Lock-in-free ownership ingestion connectors: Port / Cortex / OpsLevel + Backstage.** Ingest IDP blueprints and `catalog-info.yaml` as an ownership/service source to enrich violations and ADRs without vendor lock-in; Evolith blocks where they only measure (§13.2). Fix: read-only connectors via ACL normalizing to the canonical ownership shape. **IN-PROGRESS (`domain/ownership.ts`):** landed the pure normalization core — canonical `OwnershipEntry`, `parseBackstageCatalog` (`catalog-info.yaml` kind Component → owner + pathPrefix via `evolith.io/path`/relative `source-location`; drops non-Components/incomplete), `parseBlueprintOwnership` (generic Port/Cortex/OpsLevel: `component`\|`identifier` + `owner`\|`team`), `resolveOwner` (file→owner longest-prefix) and `enrichViolationsWithOwner` (fills `owner` without overwriting, excluded from the fingerprint). **Completes the violation→owner→compliance chain** (composes with GT-525). Verified: core-domain 925/925 (+8), tsc clean. **Remaining (connector/infra):** reading the repo's `catalog-info.yaml` (via the config-parser port) and the read-only Port/Cortex API fetch. **+parallel wave (`63fda478`):** landed `loadBackstageOwnership(yamlText)` in infra-providers (multi-doc YAML parse → `parseBackstageCatalog` → `OwnershipEntry[]`). Verified 4/4. Remaining: the read-only Port/Cortex API fetch. | `Evolith Core` | Cross | P2 | L | `IN-PROGRESS` | +| [`GT-527`](./gap-reference-catalog.md#gt-527) | **[Wedge · axis 2] Lock-in-free ownership ingestion connectors: Port / Cortex / OpsLevel + Backstage.** Ingest IDP blueprints and `catalog-info.yaml` as an ownership/service source to enrich violations and ADRs without vendor lock-in; Evolith blocks where they only measure (§13.2). Fix: read-only connectors via ACL normalizing to the canonical ownership shape. **IN-PROGRESS (`domain/ownership.ts`):** landed the pure normalization core — canonical `OwnershipEntry`, `parseBackstageCatalog` (`catalog-info.yaml` kind Component → owner + pathPrefix via `evolith.io/path`/relative `source-location`; drops non-Components/incomplete), `parseBlueprintOwnership` (generic Port/Cortex/OpsLevel: `component`\|`identifier` + `owner`\|`team`), `resolveOwner` (file→owner longest-prefix) and `enrichViolationsWithOwner` (fills `owner` without overwriting, excluded from the fingerprint). **Completes the violation→owner→compliance chain** (composes with GT-525). Verified: core-domain 925/925 (+8), tsc clean. **Remaining (connector/infra):** reading the repo's `catalog-info.yaml` (via the config-parser port) and the read-only Port/Cortex API fetch. **+parallel wave (`63fda478`):** landed `loadBackstageOwnership(yamlText)` in infra-providers (multi-doc YAML parse → `parseBackstageCatalog` → `OwnershipEntry[]`). Verified 4/4. Remaining: the read-only Port/Cortex API fetch. **DONE (`wave`):** `fetchBlueprintOwnership(client, source)` + injected `BlueprintHttpClient` complete the IDP connectors (Backstage loader + Port/Cortex) over the pure ownership parsers. Verified: infra-providers 78/78. | `Evolith Core` | Cross | P2 | L | `DONE` | | [`GT-528`](./gap-reference-catalog.md#gt-528) | **[Wedge · axis 2] Structurizr / C4 DSL ingestion → executable ADR.** Turn Structurizr/C4 models (architecture intent, today prose/diagram) into `enforce:` rules verifiable against real code (§13.2). Fix: DSL parser + mapping to `NormalizedRule.enforce`; complements GT-516. **IN-PROGRESS (`c4-compiler.ts`):** landed the compiler — `compileC4ToBoundaryRules(model)` turns a normalized `C4Model` (elements with `path`/`importPrefix` + allowed relationships) into GT-526 `EditBoundaryRule`s, deriving the denylist from the model's allowlist (an element may only depend on what it declares; everything else is forbidden), with `ruleId` `C4-`, ADR and severity. **The C4 diagram becomes executable** and feeds the edit-time gate (GT-526) and PR/CI. Verified end-to-end: a `src/domain` edit importing `src/infrastructure` → blocked; core-domain 933/933 (+5), tsc clean. **Remaining (ingestion):** parse the raw Structurizr `.dsl` (or its JSON export) into the normalized `C4Model`. **DONE (`5c66dd69`, parallel wave):** landed `parseStructurizrDsl(dsl)` — parses element defs (`ident = keyword "Name"` + path/import/adr tags) and relationships (`a -> b`), ignoring scaffolding; composes end-to-end with `compileC4ToBoundaryRules` + `evaluateEdit`. Driver-verified: core-domain 950/950. Single-line subset documented. | `Evolith Core` | Cross | P2 | M | `DONE` | -| [`GT-529`](./gap-reference-catalog.md#gt-529) | **[Surround · axis 1] ACL contract + Jira Enterprise integration reference.** Map Jira ideas/epics/stories/approvals/releases to Evolith artifacts preserving origin/identity/timestamps/lineage, with transition safeguards (completing a Jira workflow does not authorize a phase transition). §8.3 / §12. Fix: external work-system ACL + integration guide. **IN-PROGRESS (`domain/external-work-acl.ts`):** landed the pure ACL — `CanonicalWorkItem` with `WorkItemProvenance` (source/externalId/externalKey/url/created/updated), `parseJiraIssue`/`parseJiraIssues` (maps Jira issues preserving provenance; rejects a missing `id` rather than fabricating identity), `mapJiraIssueType` (Epic/Story/Task/Version→canonical kind). **Transition safeguard (§9-6):** `authorizesPhaseTransition: false` by contract + `externalWorkAuthorizesTransition`⇒`false` (completing a Jira workflow does NOT authorize a phase gate). Verified: core-domain 939/939 (+6), tsc clean. **Remaining (connector/infra + doc):** the read-only Jira REST API fetch + the documented integration guide. | `Evolith Core` | Cross | P1 | L | `IN-PROGRESS` | -| [`GT-530`](./gap-reference-catalog.md#gt-530) | **[Surround · axis 1] Langfuse adapter → canonical evidence.** Map Langfuse traces/evaluations/cost/latency/prompt-version/tool-calls to Evolith's evidence model, to avoid rebuilding an LLM telemetry platform. §8.1 / §12. Fix: `LangfuseEvidenceAdapter` via the observability port. **IN-PROGRESS (`domain/observability-evidence.ts`):** landed the pure mapper + portable shape — `ObservabilityEvidence` (traceId/model/promptVersion/costUsd/latencyMs/totalTokens/toolCalls/evaluations/url, provider-neutral §9-5), `mapLangfuseTrace` (aggregates cost/tokens/latency across observations, takes model/prompt from the first GENERATION, collects distinct tool-calls, maps scores→evaluations; a trace with no id⇒null) and the `IObservabilityEvidenceSource` port. Verified: core-domain 945/945 (+6), tsc clean. **Remaining (connector/infra):** the read-only Langfuse API fetch behind the port. | `Evolith Core` | Cross | P2 | L | `IN-PROGRESS` | +| [`GT-529`](./gap-reference-catalog.md#gt-529) | **[Surround · axis 1] ACL contract + Jira Enterprise integration reference.** Map Jira ideas/epics/stories/approvals/releases to Evolith artifacts preserving origin/identity/timestamps/lineage, with transition safeguards (completing a Jira workflow does not authorize a phase transition). §8.3 / §12. Fix: external work-system ACL + integration guide. **IN-PROGRESS (`domain/external-work-acl.ts`):** landed the pure ACL — `CanonicalWorkItem` with `WorkItemProvenance` (source/externalId/externalKey/url/created/updated), `parseJiraIssue`/`parseJiraIssues` (maps Jira issues preserving provenance; rejects a missing `id` rather than fabricating identity), `mapJiraIssueType` (Epic/Story/Task/Version→canonical kind). **Transition safeguard (§9-6):** `authorizesPhaseTransition: false` by contract + `externalWorkAuthorizesTransition`⇒`false` (completing a Jira workflow does NOT authorize a phase gate). Verified: core-domain 939/939 (+6), tsc clean. **Remaining (connector/infra + doc):** the read-only Jira REST API fetch + the documented integration guide. **DONE (`wave`):** `fetchJiraWorkItems(client, jql)` + injected `JiraHttpClient` port land the read-only connector over the ACL; live fetch is the injected client at deploy. Verified: infra-providers 78/78. | `Evolith Core` | Cross | P1 | L | `DONE` | +| [`GT-530`](./gap-reference-catalog.md#gt-530) | **[Surround · axis 1] Langfuse adapter → canonical evidence.** Map Langfuse traces/evaluations/cost/latency/prompt-version/tool-calls to Evolith's evidence model, to avoid rebuilding an LLM telemetry platform. §8.1 / §12. Fix: `LangfuseEvidenceAdapter` via the observability port. **IN-PROGRESS (`domain/observability-evidence.ts`):** landed the pure mapper + portable shape — `ObservabilityEvidence` (traceId/model/promptVersion/costUsd/latencyMs/totalTokens/toolCalls/evaluations/url, provider-neutral §9-5), `mapLangfuseTrace` (aggregates cost/tokens/latency across observations, takes model/prompt from the first GENERATION, collects distinct tool-calls, maps scores→evaluations; a trace with no id⇒null) and the `IObservabilityEvidenceSource` port. Verified: core-domain 945/945 (+6), tsc clean. **Remaining (connector/infra):** the read-only Langfuse API fetch behind the port. **DONE (`wave`):** `LangfuseEvidenceAdapter implements IObservabilityEvidenceSource` over an injected `LangfuseHttpClient`, delegating to `mapLangfuseTrace`. Verified: infra-providers 78/78. | `Evolith Core` | Cross | P2 | L | `DONE` | | [`GT-531`](./gap-reference-catalog.md#gt-531) | **[Surround · axis 1] Cowork/Claude adapter as a bounded governed executor.** Activity executor with permissions/plans/approvals/evidence capture, treating Claude as one of several replaceable executors (§8.2, §9). Extends the agent-runtime epic GT-383…394 (HITL GT-441 / adapters GT-438). **IN-PROGRESS (`cowork-agent.adapter.ts`):** landed `CoworkAgentEngineAdapter` — implements the same `IAgentEnginePort` as stub/hermes/swarms (Claude Cowork as a **replaceable** executor), and is **bounded**: it never proposes a tool outside the governed skill catalog (a Cowork/LLM proposal for a non-existent capability is rejected, not invented). The runtime envelope (approval GT-441 / policy / trace) already governs it; the live Claude call sits behind an injectable `CoworkClient` (no client = deterministic, like the stub). Verified: agent-runtime 92/92, tsc clean, GT-388 surface-freeze updated. **Remaining (connector/infra):** the real `CoworkClient` against the Claude/Cowork API. | `agent-runtime` | Cross | P2 | M | `IN-PROGRESS` | | [`GT-532`](./gap-reference-catalog.md#gt-532) | **[Surround · axis 1] Executive portfolio views + marketplace adapters + per-tenant governance packages.** Enterprise-adoption and ecosystem-scale improvement (§12 P2); mostly Tracker (enterprise value-capture plane). | `Tracker` | Cross | P3 | XL | `PENDING` | | [`GT-486`](./gap-reference-catalog.md#gt-486) | **cli did not emit a parseable ADR-0073 envelope for sdlc-status.** Output could not be parsed into an { success, data\|error, meta } envelope. (Auto-detected by the exploratory test agent on operation `sdlc-status`.) | `Evolith CLI` | Cross | P2 | S | `DONE` | @@ -63,10 +63,10 @@ This board is the single source of truth for technical debt, gaps, opportunities | [`GT-489`](./gap-reference-catalog.md#gt-489) | **cli envelope is missing a boolean success field for architecture-validate.** ADR-0073 requires a boolean top-level success. (Auto-detected by the exploratory test agent on operation `architecture-validate`.) | `Evolith CLI` | Cross | P3 | S | `DONE` | | [`GT-475`](./gap-reference-catalog.md#gt-475) | **Newer write-class MCP tools bypass the GT-158 HITL approval gate** — `evolith-satellite-create`/`-adopt` and `evolith-moscow-create`/`-update`/`-remove` are ABAC `write` but omit `mutative: true`, so the dispatcher's out-of-band `apply`+`approvalToken` second factor (`mcp-tool-dispatch.ts:136-146`) never fires; `satellite-create` even provisions a live GitHub repo unguarded. Fix: set `mutative: true` on write-class tool schemas + a registry-parity test asserting every ABAC `write` tool is `mutative` (`evolith-phase-advance` may instead warrant reclassification to `read` per GT-379). Found by the exploratory test-agent design pass. | `MCP Server` | Cross | P1 | S | `DONE` | | [`GT-476`](./gap-reference-catalog.md#gt-476) | **Gap-consistency guard `08-validate-tracking.mjs` (+5 sync helpers) reference pre-refactor tracking paths → the semantic guard is dormant.** Lines 7-14 point at `reference/core/control-center/gap-tracking.md` / `gap-closure-evidence.json`, but commit `e16120e9` moved them under `gaps/`+`evidence/`; the script `process.exit(1)`s with "Missing tracking artifacts" before validating anything, and its unit test bypasses path resolution so CI stays green. Same stale paths in `sync-tracking-order`/`sync-tables`/`fix-tracking-parity`/`fix-tracking-structural`/`sync-project-board`. The `.harness/` guard scripts are owned by this repository and fixed directly here. Follow-on: re-point the six scripts and re-arm the guard on push/PR. **DONE (`56968194`, parallel wave):** the 6 scripts were already re-pointed; the agent armed the guard in `docs.yml` as a `tracking-guard` job on `pull_request` + `push` to main/develop (gating the heavy publish job to `workflow_dispatch`). Driver-verified: guard green (532/478) + the job runs on PR. | `.harness` | Cross | P2 | S | `DONE` | -| [`GT-477`](./gap-reference-catalog.md#gt-477) | **`gap-tracking` Progress/Progreso counters drift from the real board with no live enforcement.** The line claimed `438/450` while the table held 474 rows (450 done / 7 in-progress / 16 pending / 1 deferred); root cause is the dead guard path ([`GT-476`](./gap-reference-catalog.md#gt-476)), so reconciliation never runs. Counters corrected during the GT-475…GT-484 registration; the systemic fix is to re-point + re-arm `08-validate-tracking.mjs` on push/PR so they cannot drift again. | `Governance` | Cross | P2 | S | `PENDING` | +| [`GT-477`](./gap-reference-catalog.md#gt-477) | **`gap-tracking` Progress/Progreso counters drift from the real board with no live enforcement.** The line claimed `438/450` while the table held 474 rows (450 done / 7 in-progress / 16 pending / 1 deferred); root cause is the dead guard path ([`GT-476`](./gap-reference-catalog.md#gt-476)), so reconciliation never runs. Counters corrected during the GT-475…GT-484 registration; the systemic fix is to re-point + re-arm `08-validate-tracking.mjs` on push/PR so they cannot drift again. **DONE (`56968194`):** GT-476 armed the `parseProgress`-validating guard on pull_request + push, so counter drift reds the PR. | `Governance` | Cross | P2 | S | `DONE` | | [`GT-478`](./gap-reference-catalog.md#gt-478) | **`ArchitecturePlanController` yields a double-`v1` route and skips validation + OpenAPI.** `@Controller('v1/architecture-plans')` (string literal) combined with global URI versioning (`prefix: 'api/v'`, `defaultVersion: '1'`) resolves to `/api/v1/v1/architecture-plans/evaluate`; the body binds to a bare `Partial` (no class-validator DTO → ValidationPipe inert) and carries no `@nestjs/swagger` decorators (omitted from the OpenAPI contract). Fix: `@Controller({ path: 'architecture-plans', version: '1' })` + a validated DTO + `@ApiTags`/`@ApiResponse`. | `Core API` | Cross | P2 | S | `DONE` | | [`GT-479`](./gap-reference-catalog.md#gt-479) | **`surface-parity.e2e-spec.ts` is a false-green cross-surface test.** Each `OperationFixture` declares an `envCommand` field that is never read (only the CLI binary is spawned — no second surface is exercised), and every envelope assertion is wrapped in `if (envelope && envelope.success === …)` with no else, so the suite stays green even when the CLI emits no parseable ADR-0073 envelope (the exact GT-452/GT-474 regression class). The real tri-surface net lives in `src/tests/contract/roundtrip-gate-evaluate.spec.ts`. Fix: wire `envCommand` into a real second-surface equivalence check or delete it and make assertions unconditional. (Also contradicts GT-223's DONE claim of a `surface-parity-fixture.ts` invoking CLI+MCP+REST — no such fixture exists.) | `Evolith CLI` | Cross | P2 | S | `DONE` | -| [`GT-480`](./gap-reference-catalog.md#gt-480) | **Spanish board rows GT-462..474 use the non-canonical status token `HECHO`, unsupported by the guard's `STATUS_MAP`.** GT-417 normalized ES done-statuses to `COMPLETADO` (the canonical token), but the recent closures re-introduced `HECHO` (13 rows). Once the guard path ([`GT-476`](./gap-reference-catalog.md#gt-476)) is fixed, `canonicalStatus('HECHO')` is undefined → "unsupported ES status" + EN(DONE)/ES(HECHO) mismatches. Fix (preferred): normalize the 13 `HECHO` rows back to `COMPLETADO`. | `Governance` | Cross | P2 | XS | `PENDING` | +| [`GT-480`](./gap-reference-catalog.md#gt-480) | **Spanish board rows GT-462..474 use the non-canonical status token `HECHO`, unsupported by the guard's `STATUS_MAP`.** GT-417 normalized ES done-statuses to `COMPLETADO` (the canonical token), but the recent closures re-introduced `HECHO` (13 rows). Once the guard path ([`GT-476`](./gap-reference-catalog.md#gt-476)) is fixed, `canonicalStatus('HECHO')` is undefined → "unsupported ES status" + EN(DONE)/ES(HECHO) mismatches. Fix (preferred): normalize the 13 `HECHO` rows back to `COMPLETADO`. **DONE (`85595fd6`):** 0 `HECHO` tokens remain; guard STATUS_MAP green. | `Governance` | Cross | P2 | XS | `DONE` | | [`GT-481`](./gap-reference-catalog.md#gt-481) | **Dead CLI e2e tests invoked the `mcp` subcommand removed by GT-449 + the CLI e2e suite was red.** mcp refs dropped; then the residual ~12 failures were found to be NOT fixture debt but the `98a20dca` taxonomy split (`reference/`@root vs `rulesets/`@src → `gate.e2e-spec` REPO_ROOT + `gate-status` reference resolution) and 5 `validate`/arch tests asserting an unattainable `passed\|warning` against a bare fixture. Resolved test-only: v1 fixtures, `REPO_ROOT` fix, validate/arch rewritten to the real ADR-0073 contract, gate-status mock-Core. **CLI e2e suite green: 18 suites / 132 tests.** Underlying standalone-CLI reference-path defect stays with GT-451 (F-007). | `Evolith CLI` | Cross | P2 | XS | `DONE` | | [`GT-482`](./gap-reference-catalog.md#gt-482) | **`tools-registration.spec.ts` is stale and non-exhaustive — it can't catch added/removed tools.** Its `expected` list enumerates 28 of the 35 registered tools (omits `evolith-evaluate`, `-composable-validate`, `-agent-run`, the 4 `-topology-*`) and asserts with `toContain`, which is blind to extra tools. Fix: regenerate `expected` from code and assert set equality (`expect(new Set(names)).toEqual(new Set(expected))`). | `MCP Server` | Cross | P3 | XS | `DONE` | | [`GT-483`](./gap-reference-catalog.md#gt-483) | **QA-suite engine + E2E playbooks carry dangling `.bmad-core/agents/` references after the taxonomy migration.** `engine/step-executor.mjs` `AGENT_PROMPTS` tell each specialist it lives at `.bmad-core/agents/.md` (lines 67/76/85/94/103), but commit `e16120e9` moved the personas to `reference/core/foundations/agent-skills/`; same stale path in `e2e-test-playbooks.md:7` (+ `.es.md`). Low impact (informational prompt text; validationScripts still run). Fix: repoint the five template paths + the two playbook lines. | `.bmad-core` | Cross | P3 | XS | `DONE` | @@ -547,7 +547,7 @@ This board is the single source of truth for technical debt, gaps, opportunities | [`GT-246`](./gap-reference-catalog.md#gt-246) | Implement Chaos Mesh/Litmus experiments | `QA` | Cross | P3 | L | `DONE` | -**Progress:** 498 / 532 done · 20 in progress · 12 pending · 2 deferred +**Progress:** 503 / 532 done · 17 in progress · 10 pending · 2 deferred **Wave 2026-06-23 (Winston deep audit III):** Added 14 new gaps `GT-212`…`GT-225` from the Winston Audit Playbook covering: ADR status hygiene (GT-212), topology manifest metadata + operational budgets + guidance corpus (GT-213, GT-217, GT-219), REST controller observability + OpenAPI (GT-214, GT-215), OPA input-schema parity + per-topology test density (GT-216, GT-222), SDLC Phase 05 rollback + on-call templates (GT-218), CLI branch coverage + envelope format coverage + skip-list cleanup (GT-220, GT-224, GT-225), MCP HTTP audit logging (GT-221), and cross-surface parity e2e tests (GT-223). diff --git a/src/packages/infra-providers/src/idp-blueprint.adapter.spec.ts b/src/packages/infra-providers/src/idp-blueprint.adapter.spec.ts new file mode 100644 index 00000000..0a41fe27 --- /dev/null +++ b/src/packages/infra-providers/src/idp-blueprint.adapter.spec.ts @@ -0,0 +1,50 @@ +import type { + BlueprintOwnershipRecord, + OwnershipEntry, +} from '@beyondnet/evolith-core-domain/domain/ownership'; +import type { BlueprintHttpClient } from './idp-blueprint.adapter'; +import { fetchBlueprintOwnership } from './idp-blueprint.adapter'; + +/** A stub client returning canned records — no network. */ +function stubClient(records: BlueprintOwnershipRecord[]): BlueprintHttpClient { + return { listEntities: async () => records }; +} + +describe('fetchBlueprintOwnership', () => { + it('fetches via the injected client and maps to canonical OwnershipEntry[]', async () => { + const client = stubClient([ + // component|identifier, owner|team, optional path + { component: 'billing-service', owner: 'team-payments', path: './services/billing/' }, + { identifier: 'checkout-web', team: 'team-storefront', path: 'apps\\checkout' }, + { identifier: 'notify-worker', owner: 'team-comms' }, + ]); + + const entries = await fetchBlueprintOwnership(client, 'port'); + + const expected: OwnershipEntry[] = [ + { component: 'billing-service', owner: 'team-payments', pathPrefix: 'services/billing', source: 'port' }, + { component: 'checkout-web', owner: 'team-storefront', pathPrefix: 'apps/checkout', source: 'port' }, + { component: 'notify-worker', owner: 'team-comms', pathPrefix: undefined, source: 'port' }, + ]; + expect(entries).toEqual(expected); + }); + + it('drops records with no owner (owner|team both absent)', async () => { + const client = stubClient([ + { component: 'orphan-service', path: 'services/orphan' }, + { component: 'owned-service', owner: 'team-a' }, + ]); + + const entries = await fetchBlueprintOwnership(client, 'cortex'); + + expect(entries).toEqual([ + { component: 'owned-service', owner: 'team-a', pathPrefix: undefined, source: 'cortex' }, + ]); + }); + + it('tags provenance from the source argument', async () => { + const client = stubClient([{ identifier: 'svc', team: 'team-x' }]); + const entries = await fetchBlueprintOwnership(client, 'opslevel'); + expect(entries[0].source).toBe('opslevel'); + }); +}); diff --git a/src/packages/infra-providers/src/idp-blueprint.adapter.ts b/src/packages/infra-providers/src/idp-blueprint.adapter.ts new file mode 100644 index 00000000..811f9f55 --- /dev/null +++ b/src/packages/infra-providers/src/idp-blueprint.adapter.ts @@ -0,0 +1,36 @@ +import type { + BlueprintOwnershipRecord, + OwnershipEntry, +} from '@beyondnet/evolith-core-domain/domain/ownership'; +import { parseBlueprintOwnership } from '@beyondnet/evolith-core-domain/domain/ownership'; + +/** + * READ-ONLY connector for an IDP blueprint export (Port / Cortex / OpsLevel) (GT-527 · axis 2). + * + * This is the thin connector/infra layer: the actual HTTP call to the IDP is INJECTED as a + * {@link BlueprintHttpClient} so the adapter stays pure of network configuration and is fully + * unit-testable with a stub. Once the already-normalized {@link BlueprintOwnershipRecord}s are + * fetched, normalization is delegated to the PURE {@link parseBlueprintOwnership} in + * `@beyondnet/evolith-core-domain`. No lock-in, no writes — a company's existing source of truth + * is read as-is. + */ + +/** Injected port: fetches the raw, already-normalized ownership records from an IDP blueprint. */ +export interface BlueprintHttpClient { + /** Return the blueprint's ownership records (component|identifier + owner|team + optional path). */ + listEntities(): Promise; +} + +/** + * Fetch ownership from an IDP blueprint via the injected {@link BlueprintHttpClient}, then map to + * the canonical {@link OwnershipEntry}[] with the pure {@link parseBlueprintOwnership} (records + * lacking a component/owner are dropped; `path` is normalized). `source` tags provenance + * (`port`, `cortex`, `opslevel`). + */ +export async function fetchBlueprintOwnership( + client: BlueprintHttpClient, + source: string, +): Promise { + const records = await client.listEntities(); + return parseBlueprintOwnership(records, source); +} diff --git a/src/packages/infra-providers/src/index.ts b/src/packages/infra-providers/src/index.ts index 2002ad1e..734ad60f 100644 --- a/src/packages/infra-providers/src/index.ts +++ b/src/packages/infra-providers/src/index.ts @@ -24,3 +24,9 @@ export { NxWorkspaceStrategy } from './architecture/nx-workspace.strategy'; export type { NxWorkspaceStrategyOptions } from './architecture/nx-workspace.strategy'; export { resolveRulesetFilePath } from './architecture/ruleset-file-resolver'; export { loadBackstageOwnership } from './backstage-catalog.loader'; +export { fetchJiraWorkItems } from './jira-work-system.adapter'; +export type { JiraHttpClient } from './jira-work-system.adapter'; +export { LangfuseEvidenceAdapter } from './langfuse-evidence.adapter'; +export type { LangfuseHttpClient } from './langfuse-evidence.adapter'; +export { fetchBlueprintOwnership } from './idp-blueprint.adapter'; +export type { BlueprintHttpClient } from './idp-blueprint.adapter'; diff --git a/src/packages/infra-providers/src/jira-work-system.adapter.spec.ts b/src/packages/infra-providers/src/jira-work-system.adapter.spec.ts new file mode 100644 index 00000000..c37f3502 --- /dev/null +++ b/src/packages/infra-providers/src/jira-work-system.adapter.spec.ts @@ -0,0 +1,80 @@ +import { + fetchJiraWorkItems, + type JiraHttpClient, +} from './jira-work-system.adapter'; +import type { JiraIssue } from '@beyondnet/evolith-core-domain/domain/external-work-acl'; + +/** Two canned issues: one conforming, one WITHOUT an id (non-conforming → dropped by the ACL). */ +const CANNED_ISSUES: JiraIssue[] = [ + { + id: '10001', + key: 'PROJ-1', + self: 'https://jira.example.com/rest/api/3/issue/10001', + fields: { + summary: 'Ship the governance connector', + issuetype: { name: 'Story' }, + status: { name: 'In Progress' }, + created: '2026-07-01T10:00:00.000Z', + updated: '2026-07-10T12:30:00.000Z', + }, + }, + { + // no `id` → no identity/lineage → the ACL rejects it (no fabrication) + key: 'PROJ-2', + self: 'https://jira.example.com/rest/api/3/issue/PROJ-2', + fields: { + summary: 'Ghost issue with no id', + issuetype: { name: 'Task' }, + status: { name: 'Done' }, + }, + }, +]; + +/** Stub client — records the JQL it was asked for and returns the canned batch. No network. */ +class StubJiraHttpClient implements JiraHttpClient { + lastJql?: string; + constructor(private readonly issues: JiraIssue[]) {} + async searchIssues(jql: string): Promise { + this.lastJql = jql; + return this.issues; + } +} + +describe('fetchJiraWorkItems (Jira work-system adapter)', () => { + it('maps via the ACL: preserves provenance, drops the id-less issue, keeps safeguard false', async () => { + const client = new StubJiraHttpClient(CANNED_ISSUES); + + const items = await fetchJiraWorkItems(client, 'project = PROJ ORDER BY created'); + + // it forwarded the JQL to the injected client + expect(client.lastJql).toBe('project = PROJ ORDER BY created'); + + // the id-less issue is dropped → only the conforming one survives + expect(items).toHaveLength(1); + + const [item] = items; + + // provenance preserved (origin, identity, key, url, timestamps) + expect(item.provenance).toEqual({ + source: 'jira', + externalId: '10001', + externalKey: 'PROJ-1', + url: 'https://jira.example.com/rest/api/3/issue/10001', + createdAt: '2026-07-01T10:00:00.000Z', + updatedAt: '2026-07-10T12:30:00.000Z', + }); + + // mapped fields + expect(item.kind).toBe('story'); + expect(item.title).toBe('Ship the governance connector'); + expect(item.status).toBe('In Progress'); + + // the transition safeguard is guaranteed by the ACL — never authorizes a phase transition + expect(item.authorizesPhaseTransition).toBe(false); + }); + + it('returns an empty batch when the client yields no issues', async () => { + const client = new StubJiraHttpClient([]); + await expect(fetchJiraWorkItems(client, 'project = EMPTY')).resolves.toEqual([]); + }); +}); diff --git a/src/packages/infra-providers/src/jira-work-system.adapter.ts b/src/packages/infra-providers/src/jira-work-system.adapter.ts new file mode 100644 index 00000000..6e018ece --- /dev/null +++ b/src/packages/infra-providers/src/jira-work-system.adapter.ts @@ -0,0 +1,36 @@ +/** + * Jira work-system connector adapter (GT-529 · axis 1 — positioning §8.3 / §9-6). + * + * The read-only INFRA seam behind the pure external-work ACL. It fetches already-shaped Jira + * issues through an INJECTED {@link JiraHttpClient} (so this is unit-testable with a stub, free + * of network config) and normalizes them via the pure `parseJiraIssues` ACL — which preserves + * provenance, drops id-less (non-conforming) issues, and keeps `authorizesPhaseTransition` false. + * This adapter adds no mapping of its own; the ACL is the single source of truth. + */ +import { + parseJiraIssues, + type JiraIssue, + type CanonicalWorkItem, +} from '@beyondnet/evolith-core-domain/domain/external-work-acl'; + +/** + * Minimal injected port for the Jira read side. An implementation performs the actual REST + * search (JQL) and returns raw {@link JiraIssue} objects; the adapter stays pure of network + * config and can be exercised with a stub. + */ +export interface JiraHttpClient { + searchIssues(jql: string): Promise; +} + +/** + * Fetch Jira issues for a JQL query and map them to {@link CanonicalWorkItem}s through the pure + * ACL. Read-only: provenance is preserved, id-less issues are dropped, and the transition + * safeguard (`authorizesPhaseTransition === false`) is guaranteed by the ACL — never here. + */ +export async function fetchJiraWorkItems( + client: JiraHttpClient, + jql: string, +): Promise { + const issues = await client.searchIssues(jql); + return parseJiraIssues(issues); +} diff --git a/src/packages/infra-providers/src/langfuse-evidence.adapter.spec.ts b/src/packages/infra-providers/src/langfuse-evidence.adapter.spec.ts new file mode 100644 index 00000000..de77b666 --- /dev/null +++ b/src/packages/infra-providers/src/langfuse-evidence.adapter.spec.ts @@ -0,0 +1,61 @@ +import { LangfuseEvidenceAdapter, LangfuseHttpClient } from './langfuse-evidence.adapter'; +import { LangfuseTrace } from '@beyondnet/evolith-core-domain/domain/observability-evidence'; + +const cannedTrace: LangfuseTrace = { + id: 'trace-abc', + timestamp: '2026-07-13T00:00:00.000Z', + htmlPath: 'https://cloud.langfuse.com/trace/trace-abc', + observations: [ + { + type: 'GENERATION', + name: 'llm-call', + model: 'claude-opus-4-8', + promptName: 'governance-gate', + promptVersion: 3, + usage: { totalTokens: 1234 }, + calculatedTotalCost: 0.0456, + latency: 800, + toolCalls: [{ name: 'search_repo' }, { name: 'read_file' }], + }, + ], + scores: [ + { name: 'faithfulness', value: 0.9 }, + { name: 'verdict', value: 'pass' }, + ], +}; + +const stubClient = (trace: LangfuseTrace | null): LangfuseHttpClient => ({ + getTrace: jest.fn().mockResolvedValue(trace), +}); + +describe('LangfuseEvidenceAdapter (GT-530 connector)', () => { + it('fetches a trace and maps it to canonical ObservabilityEvidence', async () => { + const client = stubClient(cannedTrace); + const adapter = new LangfuseEvidenceAdapter(client); + + const evidence = await adapter.fetchEvidence('trace-abc'); + + expect(client.getTrace).toHaveBeenCalledWith('trace-abc'); + expect(evidence).not.toBeNull(); + expect(evidence!.traceId).toBe('trace-abc'); + expect(evidence!.source).toBe('langfuse'); + expect(evidence!.model).toBe('claude-opus-4-8'); + expect(evidence!.costUsd).toBeCloseTo(0.0456); + expect(evidence!.totalTokens).toBe(1234); + expect(evidence!.toolCalls).toEqual(['search_repo', 'read_file']); + expect(evidence!.evaluations).toEqual([ + { name: 'faithfulness', value: 0.9 }, + { name: 'verdict', value: 'pass' }, + ]); + }); + + it('returns null for an unknown trace id', async () => { + const client = stubClient(null); + const adapter = new LangfuseEvidenceAdapter(client); + + const evidence = await adapter.fetchEvidence('does-not-exist'); + + expect(client.getTrace).toHaveBeenCalledWith('does-not-exist'); + expect(evidence).toBeNull(); + }); +}); diff --git a/src/packages/infra-providers/src/langfuse-evidence.adapter.ts b/src/packages/infra-providers/src/langfuse-evidence.adapter.ts new file mode 100644 index 00000000..060d156c --- /dev/null +++ b/src/packages/infra-providers/src/langfuse-evidence.adapter.ts @@ -0,0 +1,34 @@ +import { + IObservabilityEvidenceSource, + ObservabilityEvidence, + LangfuseTrace, + mapLangfuseTrace, +} from '@beyondnet/evolith-core-domain/domain/observability-evidence'; + +/** + * Injected transport for fetching a Langfuse trace by id. Keeping the network + * concern behind this port keeps {@link LangfuseEvidenceAdapter} pure of HTTP/ + * config so it can be unit-tested with a stub. Returns `null` when the trace + * does not exist. + */ +export interface LangfuseHttpClient { + getTrace(traceId: string): Promise; +} + +/** + * Langfuse connector adapter (GT-530 · axis 1 — positioning §8.1 / §9-5). + * + * The infra/connector step behind {@link IObservabilityEvidenceSource}: fetches + * a raw trace via the injected {@link LangfuseHttpClient}, then maps it to + * Evolith's canonical, portable {@link ObservabilityEvidence} using the pure + * core-domain mapper {@link mapLangfuseTrace}. No mapping logic lives here. + */ +export class LangfuseEvidenceAdapter implements IObservabilityEvidenceSource { + constructor(private readonly client: LangfuseHttpClient) {} + + async fetchEvidence(traceId: string): Promise { + const trace = await this.client.getTrace(traceId); + if (!trace) return null; + return mapLangfuseTrace(trace); + } +}