diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 74e7487a..fa1506ed 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -61,6 +61,13 @@ jobs: - name: Install dependencies run: npm ci + # GT-377 AC-3: stateless-Core architecture boundary guard. Fails the build + # if a *Repository for product/initiative/evidence/decision appears in + # core-domain (Core is a stateless Evaluation Engine — those are context, + # not entities — per ADR-0101), and enforces the inner-layer import rules. + - name: Run core-domain architecture boundary guard (GT-377) + run: npm run lint:boundaries --workspace @evolith/core-domain + # GT-382 follow-up: the core-domain OPA integration test loads the real # compiled policy.wasm. Build it (pinned opa toolchain) so the test runs # end-to-end; it self-skips if the artifact is absent. diff --git a/packages/core-domain/.eslintrc.js b/packages/core-domain/.eslintrc.js deleted file mode 100644 index 7b0ed6a2..00000000 --- a/packages/core-domain/.eslintrc.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * ESLint boundaries configuration for @evolith/core-domain. - * - * Enforces that core-domain is a pure inner layer: - * - NO imports from apps/ (core-api, etc.) - * - NO imports from packages/mcp-server - * - Only intra-layer (domain, application, infrastructure) imports allowed - * - * Layer hierarchy for this package: - * common / types - * └─ domain (entities, value objects, domain services) - * └─ application (use cases) - * └─ infrastructure (adapters) - */ - -'use strict'; - -module.exports = { - root: true, - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 2022, - sourceType: 'module', - }, - plugins: ['@typescript-eslint', 'boundaries'], - extends: ['plugin:boundaries/recommended'], - - settings: { - 'import/resolver': { - node: { extensions: ['.js', '.jsx', '.ts', '.tsx'] }, - }, - - 'boundaries/elements': [ - { type: 'common', pattern: 'src/common/**/*' }, - { type: 'domain', pattern: 'src/domain/**/*' }, - { type: 'application', pattern: 'src/application/**/*' }, - { type: 'infrastructure', pattern: 'src/infrastructure/**/*' }, - { type: 'gates', pattern: 'src/gates/**/*' }, - { type: 'phases', pattern: 'src/phases/**/*' }, - { type: 'tenancy', pattern: 'src/tenancy/**/*' }, - { type: 'providers', pattern: 'src/providers/**/*' }, - { type: 'evidence', pattern: 'src/evidence/**/*' }, - { type: 'evaluation', pattern: 'src/evaluation/**/*' }, - ], - 'boundaries/ignore': [ - 'src/**/*.spec.ts', - 'src/**/*.test.ts', - ], - }, - - rules: { - '@typescript-eslint/no-explicit-any': 'warn', - - 'boundaries/element-types': [ - 'error', - { - default: 'disallow', - rules: [ - // common: no internal imports - { from: 'common', allow: [] }, - - // domain: innermost — only common - { from: 'domain', allow: ['domain', 'common'] }, - - // application: use cases — domain + common - { from: 'application', allow: ['application', 'domain', 'common'] }, - - // infrastructure: adapters — can use all inner layers - { from: 'infrastructure', allow: ['infrastructure', 'application', 'domain', 'common'] }, - - // gates/phases/tenancy/providers/evidence: cross-cutting within this package - { from: 'gates', allow: ['gates', 'domain', 'application', 'common'] }, - { from: 'phases', allow: ['phases', 'domain', 'application', 'common'] }, - { from: 'tenancy', allow: ['tenancy', 'domain', 'common'] }, - { from: 'providers', allow: ['providers', 'infrastructure', 'domain', 'common'] }, - { from: 'evidence', allow: ['evidence', 'domain', 'application', 'common'] }, - - // evaluation: stateless Core Evaluation Engine (GT-377/ADR-0101) — - // canonical contracts + orchestrator; composes domain + application. - { from: 'evaluation', allow: ['evaluation', 'domain', 'application', 'common'] }, - ], - }, - ], - - // External npm packages are allowed everywhere - 'boundaries/no-external': 'off', - }, - - ignorePatterns: [ - 'dist/', - 'node_modules/', - 'coverage/', - '**/*.js', - ], -}; diff --git a/packages/core-domain/eslint.config.mjs b/packages/core-domain/eslint.config.mjs new file mode 100644 index 00000000..ab8dcb76 --- /dev/null +++ b/packages/core-domain/eslint.config.mjs @@ -0,0 +1,117 @@ +// @ts-check +/** + * ESLint flat config for @evolith/core-domain (ESLint 9+). + * + * Migrated from the legacy `.eslintrc.js` + `--no-eslintrc` invocation, which is + * incompatible with ESLint 9 (the `--no-eslintrc` flag was removed and the legacy + * eslintrc loader broke eslint-plugin-boundaries with "Cannot set properties of + * undefined (setting defaultMeta)"). + * + * Enforces two architectural guards: + * + * 1. Layer boundaries — core-domain is a pure inner package. Imports may only + * flow inward across the layer hierarchy: + * + * common / types + * └─ domain (entities, value objects, domain services) + * └─ application (use cases) + * └─ infrastructure (adapters) + * + * 2. Stateless Core (GT-377 / ADR-0101) — Core is a stateless Evaluation Engine. + * `product` / `initiative` / `evidence` / `decision` are CONTEXT, never + * persisted entities, so a `*Repository` for any of them must NEVER appear in + * this package. The `no-restricted-syntax` rule below fails the build (and CI) + * if such an identifier is declared, imported, or referenced. + */ +import boundaries from 'eslint-plugin-boundaries'; +import tseslint from 'typescript-eslint'; +// Shared (CommonJS) so the negative regression test enforces the EXACT same rule. +import guards from './eslint.guards.cjs'; + +const { STATELESS_CORE_REPOSITORY_BAN } = guards; + +export default tseslint.config( + { + ignores: [ + 'dist/**', + 'coverage/**', + 'node_modules/**', + '**/*.js', + '**/*.cjs', + '**/*.mjs', + ], + }, + { + files: ['src/**/*.ts'], + plugins: { + '@typescript-eslint': tseslint.plugin, + boundaries, + }, + languageOptions: { + parser: tseslint.parser, + ecmaVersion: 2022, + sourceType: 'module', + }, + settings: { + 'boundaries/elements': [ + { type: 'common', pattern: 'src/common/**/*' }, + { type: 'domain', pattern: 'src/domain/**/*' }, + { type: 'application', pattern: 'src/application/**/*' }, + { type: 'infrastructure', pattern: 'src/infrastructure/**/*' }, + { type: 'gates', pattern: 'src/gates/**/*' }, + { type: 'phases', pattern: 'src/phases/**/*' }, + { type: 'tenancy', pattern: 'src/tenancy/**/*' }, + { type: 'providers', pattern: 'src/providers/**/*' }, + { type: 'evidence', pattern: 'src/evidence/**/*' }, + { type: 'evaluation', pattern: 'src/evaluation/**/*' }, + ], + 'boundaries/ignore': ['src/**/*.spec.ts', 'src/**/*.test.ts'], + }, + rules: { + '@typescript-eslint/no-explicit-any': 'warn', + + 'boundaries/element-types': [ + 'error', + { + default: 'disallow', + rules: [ + // common: no internal imports + { from: 'common', allow: [] }, + + // domain: innermost — only common + { from: 'domain', allow: ['domain', 'common'] }, + + // application: use cases — domain + common + { from: 'application', allow: ['application', 'domain', 'common'] }, + + // infrastructure: adapters — can use all inner layers + { + from: 'infrastructure', + allow: ['infrastructure', 'application', 'domain', 'common'], + }, + + // gates/phases/tenancy/providers/evidence: cross-cutting within this package + { from: 'gates', allow: ['gates', 'domain', 'application', 'common'] }, + { from: 'phases', allow: ['phases', 'domain', 'application', 'common'] }, + { from: 'tenancy', allow: ['tenancy', 'domain', 'common'] }, + { + from: 'providers', + allow: ['providers', 'infrastructure', 'domain', 'common'], + }, + { from: 'evidence', allow: ['evidence', 'domain', 'application', 'common'] }, + + // evaluation: stateless Core Evaluation Engine (GT-377/ADR-0101) — + // canonical contracts + orchestrator; composes domain + application. + { + from: 'evaluation', + allow: ['evaluation', 'domain', 'application', 'common'], + }, + ], + }, + ], + + // Stateless-Core guard (GT-377 AC-3 / ADR-0101): no business-entity repositories. + 'no-restricted-syntax': ['error', STATELESS_CORE_REPOSITORY_BAN], + }, + }, +); diff --git a/packages/core-domain/eslint.guards.cjs b/packages/core-domain/eslint.guards.cjs new file mode 100644 index 00000000..0997707d --- /dev/null +++ b/packages/core-domain/eslint.guards.cjs @@ -0,0 +1,29 @@ +'use strict'; + +/** + * Architecture guards shared between the ESLint flat config (`eslint.config.mjs`) + * and the regression test (`stateless-core-repository.guard.spec.ts`), so both + * enforce the EXACT same rule — single source of truth for GT-377 AC-3. + * + * CommonJS on purpose: the `.mjs` config imports it statically and the ts-jest + * (CommonJS) test `require`s it, with no ESM/dynamic-import interop hazard. + */ + +/** + * Bans any identifier shaped like a repository for a business entity that Core + * treats as pure context (GT-377 / ADR-0101 — Core is a stateless Evaluation + * Engine). Matches declarations, imports and type references such as + * `ProductRepository`, `IInitiativeRepository`, `EvidenceRepositoryPort`, + * `InMemoryDecisionRepository`, … while leaving legitimate infrastructure + * repositories (`AuditRepository`, `SubscriptionRepository`, `DeliveryRepository`) + * untouched. + * + * @type {{ selector: string, message: string }} + */ +const STATELESS_CORE_REPOSITORY_BAN = { + selector: 'Identifier[name=/(Product|Initiative|Evidence|Decision)Repository/]', + message: + 'GT-377/ADR-0101: Core is a stateless Evaluation Engine. product/initiative/evidence/decision are context, not entities — a *Repository for them must not appear in core-domain.', +}; + +module.exports = { STATELESS_CORE_REPOSITORY_BAN }; diff --git a/packages/core-domain/package.json b/packages/core-domain/package.json index 9f413af4..d4883cc9 100644 --- a/packages/core-domain/package.json +++ b/packages/core-domain/package.json @@ -70,7 +70,7 @@ }, "scripts": { "build": "tsc", - "lint:boundaries": "eslint \"src/**/*.ts\" --no-eslintrc -c .eslintrc.js", + "lint:boundaries": "eslint \"src/**/*.ts\"", "test": "jest --config jest.config.js --runInBand", "test:cov": "jest --config jest.config.js --runInBand --coverage", "test:e2e": "jest --config jest.e2e.config.js --runInBand" diff --git a/packages/core-domain/src/evaluation/stateless-core-repository.guard.spec.ts b/packages/core-domain/src/evaluation/stateless-core-repository.guard.spec.ts new file mode 100644 index 00000000..b17a6ac8 --- /dev/null +++ b/packages/core-domain/src/evaluation/stateless-core-repository.guard.spec.ts @@ -0,0 +1,104 @@ +/** + * GT-377 AC-3 — architecture guard regression test. + * + * Asserts that the stateless-Core repository ban actually fails when a + * `*Repository` for a business entity that Core treats as pure context + * (product / initiative / evidence / decision) appears — and, crucially, does + * NOT fire for legitimate infrastructure repositories. This is the negative test + * proving the guard from `gap-reference-catalog.md` "#### GT-377" AC-3 ("ESLint + * boundary guard fails CI if a `*Repository` for product/initiative/evidence/ + * decision appears") is wired and effective, not merely declared. + * + * It exercises the SAME rule object that ships in `eslint.config.mjs` by + * importing it from the shared `eslint.guards.cjs` (single source of truth), and + * runs it through ESLint's synchronous flat-config `Linter` — no config-file + * dynamic import, so it is jest/ts-jest safe. + * + * The banned identifiers below live ONLY inside string literals (lint input), + * never as real code, so this spec itself stays green under `lint:boundaries`. + */ +import { Linter } from 'eslint'; + +// require (not import) so the runtime object matches what ESLint expects, free of +// ts-jest ESM-interop wrapping (`@typescript-eslint/parser` sets `__esModule`). +const tsParser = require('@typescript-eslint/parser') as Linter.Parser; +const { STATELESS_CORE_REPOSITORY_BAN } = require('../../eslint.guards.cjs') as { + STATELESS_CORE_REPOSITORY_BAN: { selector: string; message: string }; +}; + +const GUARD_RULE = 'no-restricted-syntax'; + +const linter = new Linter({ configType: 'flat' }); + +function guardErrors(code: string): string[] { + const messages = linter.verify(code, { + languageOptions: { + parser: tsParser, + ecmaVersion: 2022, + sourceType: 'module', + }, + rules: { + [GUARD_RULE]: ['error', STATELESS_CORE_REPOSITORY_BAN], + }, + }); + return messages + .filter((m) => m.ruleId === GUARD_RULE && m.severity === 2) + .map((m) => m.message); +} + +describe('GT-377 AC-3 — stateless-Core repository guard', () => { + const BANNED_ENTITIES = ['Product', 'Initiative', 'Evidence', 'Decision']; + + it.each(BANNED_ENTITIES)( + 'FAILS when a %sRepository is imported and referenced', + (entity) => { + const code = [ + `import { ${entity}Repository } from '../infrastructure/persistence';`, + `export function use(repo: ${entity}Repository): void { void repo; }`, + '', + ].join('\n'); + + const errors = guardErrors(code); + + expect(errors.length).toBeGreaterThan(0); + expect(errors.every((m) => m.includes('GT-377'))).toBe(true); + }, + ); + + it.each(BANNED_ENTITIES)( + 'FAILS when a %sRepository interface is declared inline', + (entity) => { + const code = `export interface ${entity}Repository { findById(id: string): unknown; }\n`; + + const errors = guardErrors(code); + + expect(errors.length).toBeGreaterThan(0); + }, + ); + + it.each([ + 'AuditRepository', + 'SubscriptionRepository', + 'DeliveryRepository', + 'createRepository', + 'getRepository', + ])('does NOT flag the legitimate identifier %s (no false positive)', (ident) => { + const code = [ + `import { ${ident} } from '../infrastructure/persistence';`, + `export const ref = ${ident};`, + '', + ].join('\n'); + + expect(guardErrors(code)).toEqual([]); + }); + + it('produces zero guard errors for a clean evaluation module', () => { + const code = [ + "import type { EvaluationContext } from './contracts';", + 'export function evaluate(ctx: EvaluationContext): EvaluationContext { return ctx; }', + '', + ].join('\n'); + + expect(guardErrors(code)).toEqual([]); + }); +}); diff --git a/reference/governance/standards/vision/gap-reference-catalog.es.md b/reference/governance/standards/vision/gap-reference-catalog.es.md index 3fb648ed..54dd8364 100644 --- a/reference/governance/standards/vision/gap-reference-catalog.es.md +++ b/reference/governance/standards/vision/gap-reference-catalog.es.md @@ -56,7 +56,7 @@ Este catálogo explica cada gap: problema, propósito, evidencia, criterios de c - **Criterios de aceptación:** - [x] `evaluation-context.schema.json` / `evaluation-result.schema.json` validan round-trip; `schemaVersion` obligatorio. - [x] `tenantId`/`productId`/`initiativeId` son `string`; `DecisionRecommendation.binding` literal `false`. - - [ ] Guard ESLint falla el CI si aparece un `*Repository` de producto/iniciativa/evidencia/decisión. + - [x] Guard ESLint falla el CI si aparece un `*Repository` de producto/iniciativa/evidencia/decisión. **(MET — Oleada 2026-06-29)** `packages/core-domain/eslint.config.mjs` (flat config, ESLint 9) prohíbe `Identifier[name=/(Product|Initiative|Evidence|Decision)Repository/]` vía `no-restricted-syntax` (regla compartida desde `eslint.guards.cjs`); gateado por el step `Run core-domain architecture boundary guard (GT-377)` en `ci-cd.yml` (`test-core-domain`); test de regresión negativo `src/evaluation/stateless-core-repository.guard.spec.ts` (14 casos) prueba que dispara en las cuatro entidades prohibidas y nunca en `*Repository` legítimo (`AuditRepository`, `createRepository`, …). La rotura previa (override global `ajv:8.17.1` en conflicto con el dep directo `ajv:8.20.0` de los workspaces, dejando a `eslint`/`@eslint/eslintrc` sin `ajv@6`) se corrige con `overrides` acotados en el `package.json` raíz. - **Dependencias:** `GT-376`. #### GT-378 diff --git a/reference/governance/standards/vision/gap-reference-catalog.md b/reference/governance/standards/vision/gap-reference-catalog.md index 77ad1522..289cc85c 100644 --- a/reference/governance/standards/vision/gap-reference-catalog.md +++ b/reference/governance/standards/vision/gap-reference-catalog.md @@ -56,7 +56,7 @@ This catalog explains each gap: problem, purpose, evidence, closure criteria, an - **Acceptance criteria:** - [x] `evaluation-context.schema.json` / `evaluation-result.schema.json` validate round-trip; `schemaVersion` mandatory. - [x] `tenantId`/`productId`/`initiativeId` are `string`; `DecisionRecommendation.binding` literal `false`. - - [ ] ESLint boundary guard fails CI if a `*Repository` for product/initiative/evidence/decision appears. + - [x] ESLint boundary guard fails CI if a `*Repository` for product/initiative/evidence/decision appears. **(MET — Wave 2026-06-29)** `packages/core-domain/eslint.config.mjs` (flat config, ESLint 9) bans `Identifier[name=/(Product|Initiative|Evidence|Decision)Repository/]` via `no-restricted-syntax` (rule shared from `eslint.guards.cjs`); gated by the `Run core-domain architecture boundary guard (GT-377)` step in `ci-cd.yml` (`test-core-domain`); negative regression test `src/evaluation/stateless-core-repository.guard.spec.ts` (14 cases) proves it fires on the four banned entities and never on legit `*Repository` (`AuditRepository`, `createRepository`, …). The prior breakage (global `ajv:8.17.1` override conflicting with workspaces' direct `ajv:8.20.0`, starving `eslint`/`@eslint/eslintrc` of `ajv@6`) is fixed with scoped `overrides` in the root `package.json`. - **Dependencies:** `GT-376`. #### GT-378 diff --git a/reference/governance/standards/vision/gap-tracking.es.md b/reference/governance/standards/vision/gap-tracking.es.md index afef2538..d9b7738a 100644 --- a/reference/governance/standards/vision/gap-tracking.es.md +++ b/reference/governance/standards/vision/gap-tracking.es.md @@ -15,7 +15,7 @@ Este tablero es la única fuente de verdad para deuda técnica, gaps, oportunida |---|---|:---:|:---:|:---:|:---:|:---:| | [`GT-375`](./gap-reference-catalog.es.md#gt-375) | Contratos de evaluación stateless del Core — formalizar `EvaluationContext` (entrada) / `EvaluationResult` (salida): el consumidor (Evolith Tracker) envía contexto y el Core devuelve veredictos/recomendaciones estructurados. Producto/tenant/iniciativa son **solo identificadores de contexto opacos**, nunca entidades del Core; épicas/historias como `ExternalReferenceContext`. El Core emite `Recommendation`/`DecisionRecommendation` no vinculante; el Tracker decide, persiste y audita. Según ADR-0101 (corrige ADR-0100) / UP-002. **Épica paraguas — decompuesta en `GT-376`…`GT-381` (R0–R5).** | `Cross` | Cross | P0 | XL | `EN-PROGRESO` | | [`GT-376`](./gap-reference-catalog.es.md#gt-376) | R0 — Decisión Core stateless evaluator + reconciliación documental (finalizar ADR-0101; corregir ADR-0100 Decisión 1, UP-002 d2/d7; superseder secciones de entidades/repos del diseño previo; `GateDecision`→`CoreGateVerdict`, `'WAIVED'`→`Verdict.WAIVE`) | `Cross` | Cross | P0 | M | `COMPLETADO` | -| [`GT-377`](./gap-reference-catalog.es.md#gt-377) | R1 — Contratos `EvaluationContext`/`EvaluationResult` + Contract Schema Registry (tipos canónicos reutilizando `Verdict`/`PhaseId`; schemas versionados; envelope ADR-0073; guard ESLint que prohíbe `*Repository` para entidades de negocio) | `Core Domain` | Cross | P0 | L | `EN-PROGRESO` | +| [`GT-377`](./gap-reference-catalog.es.md#gt-377) | R1 — Contratos `EvaluationContext`/`EvaluationResult` + Contract Schema Registry (tipos canónicos reutilizando `Verdict`/`PhaseId`; schemas versionados; envelope ADR-0073; guard ESLint que prohíbe `*Repository` para entidades de negocio) | `Core Domain` | Cross | P0 | L | `COMPLETADO` | | [`GT-378`](./gap-reference-catalog.es.md#gt-378) | R2 — Envolver engines existentes tras el contrato (adaptador sobre `satellite-evaluation-pipeline`; Gate/Artifact/Evidence/Ruleset/OPA + Compliance emiten resultados canónicos; compatibilidad de verdict legacy; paridad Native+OPA 0 drift) | `Core Domain` | Cross | P0 | L | `COMPLETADO` | | [`GT-379`](./gap-reference-catalog.es.md#gt-379) | R3 — Engines arquitectónicos (Architecture/Blueprint/Topology/Checkpoint/Recommendation; `DecisionRecommendation` `binding:false`; checkpoint no muta estado) | `Core Domain` | Cross | P1 | L | `COMPLETADO` | | [`GT-381`](./gap-reference-catalog.es.md#gt-381) | R5 — Docs/taxonomía + reconciliación final + integración Tracker (reclasificar artefactos ágiles a `ExternalReferenceContext`; publicar doc canónico del Core Evaluation Engine; Tracker envía contexto/consume resultado/emite `GateDecision`; Core degrada a evaluación-only; paridad CLI/MCP/API) | `Cross` | Cross | P2 | M | `EN-PROGRESO` | @@ -451,6 +451,8 @@ Este tablero es la única fuente de verdad para deuda técnica, gaps, oportunida **Oleada 2026-06-29 (GT-381 R5 — reclasificación de propiedad de artefactos, parcial):** añadida una sección normativa **"Propiedad de artefactos — definiciones del Core vs. work items externos (ADR-0101 / GT-380)"** a `sdlc-evolith-artifact-mapping.md` (EN+ES). Clasifica los work items ágiles (Epics/Functional/Technical/User Stories/Agile Backlog/Story Seeds/Epic Candidate Matrix/Sprints/tareas) como **`ExternalReferenceContext`** propiedad del Tracker/herramientas externas — nunca artefactos de gate del Core — y reconcilia el doc con el ADR-0101 aceptado + los cambios de gate de GT-380 (ningún gate del Core exige una story; las plantillas siguen siendo guía de autoría del Core, las instancias son del Tracker). Resuelve GT-381 AC-1 "zero divergent formats" a nivel de encuadre. `GT-381` sigue **EN-PROGRESO**: falta = reclasificación mecánica de las tablas por fase, "Core degrada sin Tracker" + paridad CLI/MCP/API (BR-008), y la **integración real con el Tracker** (necesita el componente externo). El PR #147 (`vigorous-gauss`, guard ESLint GT-377 AC-3) está ABIERTO pero CONFLICTING vs develop — a mergear cuando esa sesión rebasee + quede verde. +**Oleada 2026-06-29 (GT-377 AC-3 — el guard de boundaries de ESLint enforcea + gatea CI → `GT-377` COMPLETADO):** el guard de repositorios de Core-stateless está vivo. Migré `packages/core-domain` a flat config de ESLint 9 (`eslint.config.mjs`); prohíbe `Identifier[name=/(Product|Initiative|Evidence|Decision)Repository/]` vía `no-restricted-syntax` (regla compartida desde `eslint.guards.cjs` como única fuente de verdad) — acotado por entidad, así los legítimos `AuditRepository`/`createRepository` no se tocan. Test de regresión negativo `src/evaluation/stateless-core-repository.guard.spec.ts` (14 casos) prueba que dispara en las cuatro entidades prohibidas (import + interface inline) y nunca en `*Repository` legítimo. **Cableado en CI**: nuevo step `Run core-domain architecture boundary guard (GT-377)` en `ci-cd.yml` `test-core-domain`; borrado el `.eslintrc.js` muerto. El fix del override de ajv que esto requería (global `ajv`→`8.20.0` + `eslint`/`@eslint/eslintrc`→`ajv@^6.12.6`, la causa real de la nota "lint:boundaries roto" — el ajv global chocaba con el dep directo `ajv:8.20.0` de los workspaces) **ya estaba en main** desde la oleada de flat-config de mcp-server/core-api — idéntico, así que esta integración **no tuvo conflicto en `package.json`/lockfile** (solo `ci-cd.yml` + board). Verificado en CI en-branch: **`Test core-domain` verde incl. el step del guard**; local `npm ci`→`lint:boundaries` **0 errores**, `tsc` verde, core-domain **692/692**. Integra el **PR #147** (`vigorous-gauss`) marcado como pendiente en la oleada GT-381 de arriba. Con AC-1 (schemas) + AC-2 ya en main, **los tres ACs de GT-377 están MET → `GT-377` → `COMPLETADO`** (status de fila actualizado; el re-orden de la fila al bloque de completados queda para el próximo pase de board). + **Ordenamiento:** una sola tabla, ordenada por estado (pendientes luego completados), luego criticidad (`P0` → `P1` → `P2` → `P3`), luego complejidad (`XS` → `S` → `M` → `L` → `XL`). Los IDs `GT-*` enlazan al [Catálogo de Referencia de Gaps](./gap-reference-catalog.es.md); los IDs `MT-A*` enlazan al [plan de implementación Multi-Topology](./multi-topology-reference-corpus-implementation-plan.es.md). --- diff --git a/reference/governance/standards/vision/gap-tracking.md b/reference/governance/standards/vision/gap-tracking.md index 1b7106ca..b8857282 100644 --- a/reference/governance/standards/vision/gap-tracking.md +++ b/reference/governance/standards/vision/gap-tracking.md @@ -15,7 +15,7 @@ This board is the single source of truth for technical debt, gaps, opportunities |---|---|:---:|:---:|:---:|:---:|:---:| | [`GT-375`](./gap-reference-catalog.md#gt-375) | Core stateless evaluation contracts — formalize `EvaluationContext` (input) / `EvaluationResult` (output): consumers (Evolith Tracker) send context, the Core returns structured verdicts/recommendations. Product/tenant/initiative are **opaque context identifiers only**, never Core entities; epics/stories as `ExternalReferenceContext`. The Core emits non-binding `Recommendation`/`DecisionRecommendation`; the Tracker decides, persists and audits. Per ADR-0101 (corrects ADR-0100) / UP-002. **Umbrella epic — decomposed into `GT-376`…`GT-381` (R0–R5).** | `Cross` | Cross | P0 | XL | `IN-PROGRESS` | | [`GT-376`](./gap-reference-catalog.md#gt-376) | R0 — Core stateless evaluator decision + documentation reconciliation (finalize ADR-0101; correct ADR-0100 Decision 1, UP-002 d2/d7; supersede prior design entity/repo sections; `GateDecision`→`CoreGateVerdict`, `'WAIVED'`→`Verdict.WAIVE`) | `Cross` | Cross | P0 | M | `DONE` | -| [`GT-377`](./gap-reference-catalog.md#gt-377) | R1 — `EvaluationContext`/`EvaluationResult` contracts + Contract Schema Registry (canonical types reusing `Verdict`/`PhaseId`; versioned schemas; ADR-0073 envelope; ESLint boundary guard banning `*Repository` for business entities) | `Core Domain` | Cross | P0 | L | `IN-PROGRESS` | +| [`GT-377`](./gap-reference-catalog.md#gt-377) | R1 — `EvaluationContext`/`EvaluationResult` contracts + Contract Schema Registry (canonical types reusing `Verdict`/`PhaseId`; versioned schemas; ADR-0073 envelope; ESLint boundary guard banning `*Repository` for business entities) | `Core Domain` | Cross | P0 | L | `DONE` | | [`GT-378`](./gap-reference-catalog.md#gt-378) | R2 — Wrap existing engines behind the contract (adapter over `satellite-evaluation-pipeline`; Gate/Artifact/Evidence/Ruleset/OPA + Compliance emit canonical results; legacy verdict compatibility; Native+OPA parity 0 drift) | `Core Domain` | Cross | P0 | L | `DONE` | | [`GT-379`](./gap-reference-catalog.md#gt-379) | R3 — Architectural engines (Architecture/Blueprint/Topology/Checkpoint/Recommendation; `DecisionRecommendation` `binding:false`; checkpoint does not mutate state) | `Core Domain` | Cross | P1 | L | `DONE` | | [`GT-381`](./gap-reference-catalog.md#gt-381) | R5 — Docs/taxonomy + final reconciliation + Tracker integration (reclassify agile artifacts to `ExternalReferenceContext`; publish canonical Core Evaluation Engine doc; Tracker sends context/consumes result/emits `GateDecision`; Core degrades to evaluation-only; CLI/MCP/API parity) | `Cross` | Cross | P2 | M | `IN-PROGRESS` | @@ -465,6 +465,8 @@ This board is the single source of truth for technical debt, gaps, opportunities **Wave 2026-06-29 (GT-381 R5 — artifact-ownership reclassification, partial):** added a normative **"Artifact Ownership — Core definitions vs. external work items (ADR-0101 / GT-380)"** section to `sdlc-evolith-artifact-mapping.md` (EN+ES). Classifies agile work items (Epics/Functional/Technical/User Stories/Agile Backlog/Story Seeds/Epic Candidate Matrix/Sprints/tasks) as **`ExternalReferenceContext`** owned by the Tracker/external tools — never Core gate artifacts — and reconciles the doc with the accepted ADR-0101 + the GT-380 gate changes (no Core gate mandates a story; templates remain Core authoring guidance, instances are Tracker-owned). Resolves GT-381 AC-1 "zero divergent formats" at the framing level. `GT-381` stays **IN-PROGRESS**: remaining = mechanical reclassification of the per-phase tables, "Core degrades without the Tracker" + CLI/MCP/API parity (BR-008), and the actual **Tracker integration** (needs the external Tracker component). PR #147 (`vigorous-gauss`, GT-377 AC-3 ESLint guard) is OPEN but CONFLICTING vs develop — to be merged once that session rebases + goes green. +**Wave 2026-06-29 (GT-377 AC-3 — ESLint boundary guard enforces + gates CI → `GT-377` DONE):** the stateless-Core repository guard is live. Migrated `packages/core-domain` to ESLint 9 flat config (`eslint.config.mjs`); it bans `Identifier[name=/(Product|Initiative|Evidence|Decision)Repository/]` via `no-restricted-syntax` (rule shared from `eslint.guards.cjs` as the single source of truth) — entity-scoped, so legit `AuditRepository`/`createRepository` are untouched. Negative regression test `src/evaluation/stateless-core-repository.guard.spec.ts` (14 cases) proves it fires on the four banned entities (import + inline interface) and never on legit `*Repository`. **Wired into CI**: new `Run core-domain architecture boundary guard (GT-377)` step in `ci-cd.yml` `test-core-domain`; deleted dead `.eslintrc.js`. The root ajv-override fix this needed (global `ajv`→`8.20.0` + `eslint`/`@eslint/eslintrc`→`ajv@^6.12.6`, the real cause of the "lint:boundaries broken" note — global ajv conflicted with the workspaces' direct `ajv:8.20.0`) was **already on main** from the mcp-server/core-api flat-config wave — byte-identical, so this integration had **no `package.json`/lockfile conflict** (only `ci-cd.yml` + board). Verified in CI on-branch: **`Test core-domain` green incl. the guard step**; locally `npm ci`→`lint:boundaries` **0 errors**, `tsc` green, core-domain **692/692**. This integrates **PR #147** (`vigorous-gauss`) flagged pending in the GT-381 wave above. With AC-1 (schemas) + AC-2 already on main, **all three GT-377 ACs are MET → `GT-377` → `DONE`** (table row status updated; row re-sort into the completed block left to the next board pass). + **Ordering:** one table, ordered by status (pending then completed), then criticality (`P0` → `P1` → `P2` → `P3`), then complexity (`XS` → `S` → `M` → `L` → `XL`). `GT-*` IDs link to the [Gap Reference Catalog](./gap-reference-catalog.md); `MT-A*` IDs link to the supporting [Multi-Topology implementation plan](./multi-topology-reference-corpus-implementation-plan.md). ---