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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/audit/tracker-gap-reference-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ This catalog explains each gap: problem, purpose, evidence, closure criteria, an
- **Acceptance criteria:**
- [ ] A user can browse topologies/ADRs/blueprints without selecting a satellite process.
- **Dependencies:** [CR-08](#cr-08), [CR-11](#cr-11), [CR-21](#cr-21), [CR-23](#cr-23).
- **Status:** `IN-PROGRESS` — pantalla + endpoint de arquitectura (topologías/blueprints/evidencia/providers) existen; faltan registro/browser de ADRs y sync respaldado por Core.
- **Status:** `DONE` — navegación de arquitectura completa + registro de ADRs (`GET /api/architecture/adrs`, T-001…T-033 con estado/supersesión) y browser `AdrBrowserMd3`. Diferido: sync respaldado por Core (GAP-004).

#### CR-11

Expand Down Expand Up @@ -366,7 +366,7 @@ This catalog explains each gap: problem, purpose, evidence, closure criteria, an
- **Acceptance criteria:**
- [ ] Each canonical concept has a reachable screen.
- **Dependencies:** [CR-08](#cr-08), [CR-10](#cr-10), [CR-13](#cr-13), [CR-14](#cr-14), [CR-15](#cr-15), [CR-22](#cr-22).
- **Status:** `IN-PROGRESS` — las 5 pantallas canónicas están ruteadas (Gate Decision/Evaluation = `GateEvaluationMd3`); único faltante: un browser de ADRs dedicado.
- **Status:** `DONE` — todas las pantallas canónicas ruteadas; el browser de ADRs dedicado (`AdrBrowserMd3`, API real) cierra el último faltante.

#### CR-22

Expand Down
4 changes: 2 additions & 2 deletions docs/audit/tracker-gap-tracking.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ This board is the single source of truth for Tracker technical debt, gaps, oppor
| [`LV-13`](./tracker-gap-reference-catalog.md#lv-13) | Dev-seed papercut: the DevBypass platform-root tenant (`11111111…`) is a phantom — no seeder creates it, so local dev must create tenants by hand before tenant-scoped screens work. **Fixed:** `DevTenantSeedHostedService` (Development-only gate before any DB access, idempotent, seeds 3 bare demo tenants via `Tenant.Create` + repo — no geo, left for the UI). Testing/Prod no-op. | `Backend/WEB` | Cross | P3 | S | `DONE` |
| [`COH-002`](./tracker-gap-reference-catalog.md#coh-002) | **Decidido (obsoleto):** se ratifica "Evolith no posee backlog / sin descomposición interna" (PRD REQ-DIS-07). Las tablas `backlogs`/`epics`/`user_stories(in_refinement/split)` de `tracker-postgresql-data-design.md` quedan anotadas como OBSOLETAS/no-implementar; no se construye entidad de refinamiento. | `Backend` | Discovery | P0 | L | `DONE` |
| [`COH-004`](./tracker-gap-reference-catalog.md#coh-004) | **Hecho (coherencia):** "TestRun" ya no se usa (solo aparece en docs de auditoría); las specs modelan consistentemente TestCycle+TestExecution(+Defect). El contexto QA (`tracker_qa`) sigue sin construir pero es roadmap Fase 3 — se trackea aparte, no bloquea construcción. | `Backend` | QA | P0 | L | `DONE` |
| [`CR-21`](./tracker-gap-reference-catalog.md#cr-21) | Las 5 pantallas canónicas están ruteadas y accesibles (Gate Decision/Evaluation = `GateEvaluationMd3` ya existe — la nota "pendiente" estaba desactualizada); único faltante: un **browser de ADRs** dedicado (hoy los ADRs son solo tags en blueprints). | `WEB` | Cross | P1 | S | `IN-PROGRESS` |
| [`CR-21`](./tracker-gap-reference-catalog.md#cr-21) | **Hecho:** todas las pantallas canónicas ruteadas — el faltante (browser de ADRs dedicado) ahora existe: `AdrBrowserMd3` (filtro por status, supersesión, links a docs) consumiendo `GET /api/architecture/adrs` por API real, en el grupo Governance de la navegación. | `WEB` | Cross | P1 | S | `DONE` |
| [`GAP-010`](./tracker-gap-reference-catalog.md#gap-010) | **Hecho:** existe `reference/knowledge/domain/PRODUCT_VISION.md` (EN) con navegación bilingüe a `PRODUCT_VISION.es.md`. | `Docs` | Cross | P1 | M | `DONE` |
| [`CR-10`](./tracker-gap-reference-catalog.md#cr-10) | Pantalla + endpoint de navegación de arquitectura (independiente del SDLC) existen para topologías/blueprints/evidencia/providers; faltan registro/browser de ADRs (estado/supersesión) y sync respaldado por Core (el catálogo hoy es estático). | `WEB/Backend` | Cross | P1 | M | `IN-PROGRESS` |
| [`CR-10`](./tracker-gap-reference-catalog.md#cr-10) | **Hecho:** navegación de arquitectura independiente del SDLC completa — topologías/blueprints/evidencia/providers + ahora **registro de ADRs** (`GET /api/architecture/adrs`, 33 entradas T-001…T-033 con estado/supersesión desde `DECISIONS.md`) y su browser `AdrBrowserMd3`. **Diferido:** sync del catálogo respaldado por Core (GAP-004). | `WEB/Backend` | Cross | P1 | M | `DONE` |
| [`CR-14`](./tracker-gap-reference-catalog.md#cr-14) | **Hecho:** `EvidenceRecord` es ahora un nodo de Evidence **Graph** — campos de primera clase `sourceProvider`/`producer`/`contentHash`/`classification`(validada)/`retentionPolicyRef`/`immutable`, aristas de linaje `references[]` con operación de dominio `AddReference` (dedup) + `Seal`, comando/endpoint `POST /{id}/references`, migración aditiva `AddEvidenceGraphFields`. Build 0-warnings, 458 tests (+15). | `Backend` | Cross | P1 | M | `DONE` |
| [`CR-15`](./tracker-gap-reference-catalog.md#cr-15) | **Hecho:** dominio `ProviderConnection` construido — taxonomía capacidad→Port (`ProviderCapabilityCatalog`), VO `ProviderAcl` (dirección validada + scopes), agregado con ciclo de vida `Connect`/`Disconnect`/`MarkError`/`UpdateAcl`, persistencia (tabla `provider_connections` + índices) + migración aditiva `AddProviderConnections`, comandos/queries + grupo de endpoints `provider-connections`. 493 tests (+20). **Diferido:** adaptadores externos concretos (GitHub/Jira/Datadog) tras el seam `IProviderPort` — integración viva. | `Backend/Integration` | Cross | P1 | M | `DONE` |
| [`GAP-019`](./tracker-gap-reference-catalog.md#gap-019) | **Hecho:** requisitos Discovery desglosados a detalle implementable — PRD §2 REQ-DIS-01…13, cada uno con cláusula de aceptación, + catálogo de artefactos por gate (`functional-scope.md` §6). Sin user-stories atómicas, coherente con "sin backlog propio". | `Docs` | Cross | P1 | M | `DONE` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public static WebApplication MapTrackerApiSurface(this WebApplication app)
api.MapEvidenceRecordEndpoints();
api.MapAuditEntryEndpoints();
api.MapArchitectureReferenceEndpoints();
api.MapAdrRegistryEndpoints();
api.MapCoreEvaluationEndpoints();
api.MapCoreEvaluationTransactionEndpoints();
api.MapProviderConnectionEndpoints();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
using Tracker.Presentation.Auth;

namespace Tracker.Presentation.Endpoints.Architecture;

/// <summary>
/// CR-10 / CR-21: exposes the canonical local ADR registry (status + supersession) sourced from
/// the authoritative <c>DECISIONS.md</c> table (rows T-001…T-033). Mirrors the static-catalog idiom
/// of <see cref="ArchitectureReferenceCatalog"/>; today the registry is a curated static mirror.
/// </summary>
public static class AdrRegistryEndpoints
{
public static void MapAdrRegistryEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/architecture")
.WithTags("Architecture");

group.MapGet("/adrs", (string? status) =>
{
var entries = AdrRegistryCatalog.Entries;

if (!string.IsNullOrWhiteSpace(status))
{
entries = entries
.Where(e => string.Equals(e.Status, status, StringComparison.OrdinalIgnoreCase))
.ToList();
}

return Results.Ok(entries);
})
.WithName("GetAdrRegistry")
.RequireTrackerPermission(TrackerPermissions.ArchitectureRead)
.Produces<IReadOnlyList<AdrEntryResponse>>(200)
.Produces(401)
.Produces(403);
}
}

/// <summary>
/// Static mirror of the local ADR registry. Each entry corresponds to a row in <c>DECISIONS.md</c>.
/// <c>Status</c> comes from the ADR document frontmatter when a doc exists (Active/Accepted); rows
/// without a local doc default to <c>"Recorded"</c>. Notes are short one-line summaries, not the
/// verbose source notes.
/// </summary>
internal static class AdrRegistryCatalog
{
// TODO CR-10: Core-backed ADR sync — replace this curated static mirror with a live pull from
// the Core governance registry (evolith.yaml → spec.compliance.adrRegistry / Core BFF) so that
// status, supersession and upstream-ref changes flow in automatically instead of being hand-maintained.
public static readonly IReadOnlyList<AdrEntryResponse> Entries =
[
new("T-001", "Orquestación de Monorepo con Nx", "Adoptar", "ADR-0001", null, "Recorded", null, null,
"Nx monorepo initialized in src/ using npm workspaces."),
new("T-002", "Adopción de Microfrontends en Fase 1", "Sobrescribir", null, "docs/adrs/T-002-microfrontends-fase1.md", "Active", null, null,
"Microfrontend topology in Phase 1 — deliberate deviation from the base topology for UI scalability."),
new("T-003", "Arquitectura Hexagonal (Ports & Adapters)", "Adoptar", "ADR-0002", null, "Recorded", null, null,
"Ports & Adapters; pure domain layer with no external dependencies."),
new("T-004", "TypeScript estricto como lenguaje primario", "Adoptar", "ADR-0003", null, "Recorded", null, null,
"Strict TypeScript (strict: true) as the primary language."),
new("T-005", "TypeORM como ORM (Data Mapper pattern)", "Adoptar", "ADR-0043", null, "Recorded", null, null,
"TypeORM using the Data Mapper pattern (over Active Record)."),
new("T-006", "React con Vite como base del frontend", "Adoptar", "ADR-0044", "docs/adrs/T-006-frontend-vite.md", "Active", null, null,
"React + Vite as the frontend base, aligned to the microfrontend topology."),
new("T-007", "Zustand + TanStack Query (State Management)", "Adoptar", "ADR-0045", null, "Recorded", null, null,
"Zustand for client state, TanStack Query for server state."),
new("T-008", "Convención de nombres de schema PostgreSQL", "Definir", null, "docs/adrs/T-008-schema-naming.md", "Active", null, null,
"Canonical PostgreSQL schema naming (tracker_discovery, tracker_design, …)."),
new("T-009", "REST + OpenAPI 3.0 como única API en Fase 1", "Definir", null, "docs/adrs/T-009-rest-openapi.md", "Active", null, null,
"REST + OpenAPI 3.0 as the sole API in Phase 1; GraphQL out of scope."),
new("T-010", "Framework de agentes configurable por tenant", "Extender", null, "docs/adrs/T-010-agent-framework-per-tenant.md", "Active", null, null,
"Agentic framework is tenant-configurable per phase; Tracker stays framework-agnostic."),
new("T-011", "Estándar de numeración para Iniciativas, ADRs y Specs", "Definir", null, "docs/adrs/T-011-numbering-standard.md", "Active", null, null,
"Numbering standard: INIT-{NNN} initiatives, T-{NNN} local ADRs, ADR-{NNNN} upstream."),
new("T-012", "Contratos de eventos de dominio en libs/shared/", "Definir", null, null, "Recorded", null, null,
"Domain event contracts defined as TypeScript in libs/shared; Pact tests pending."),
new("T-013", "Value Object canónico ExternalReference en Shared Kernel", "Definir", null, null, "Recorded", null, null,
"Canonical ExternalReference value object in the Shared Kernel, unifying shapes across contexts."),
new("T-014", "UC-005 dividido en UC-005a y UC-005b", "Definir", null, null, "Recorded", null, null,
"UC-005 split into UC-005a (planning) and UC-005b (execution)."),
new("T-015", "Core BFF Gateway como único canal de comunicación", "Adoptar", "core/0080", null, "Recorded", null, null,
"Tracker talks to Core only via the BFF Gateway; the api-key model (core/0075) was superseded by core/0080."),
new("T-016", "Capa Anti-Corrupción para Integración PPM (Funnel 0)", "Definir", null, null, "Recorded", null, null,
"Anti-corruption layer (PpmIntakeACL) normalizes external PPM schemas before they reach the domain."),
new("T-017", "Core API Exposure Layer (REST-only)", "Adoptar", "core/0074", null, "Recorded", null, null,
"Core-API is REST-only under /api/v1 (no GraphQL/SSE) plus an MCP gateway."),
new("T-018", "Contrato de Referencia de Repositorio Remoto", "Adoptar", "core/0080", null, "Recorded", "core/0075", null,
"Remote repo reference contract; Core does not authenticate — the BFF is the sole perimeter. Supersedes the api-key model (core/0075)."),
new("T-019", "Separación Dominio/Financiero", "Adoptar", "core/0078", null, "Recorded", null, null,
"ROI/finance separated out of the governance domain."),
new("T-020", "Corpus Multi-Topología Componible", "Adoptar", "core/0079", null, "Recorded", null, null,
"Topology is 5 composable dimensions, not a mono→micro ladder; F1/F2/F3 = progressive-axis maturity."),
new("T-021", "PhaseId Canónico Semántico", "Adoptar", "core/GT-343", null, "Recorded", null, null,
"Canonical semantic PhaseIds (discovery|design|construction|qa|release); f1..f5 are deprecated input aliases."),
new("T-022", "Contrato de Evaluación de Satélite", "Adoptar", "core/0073", null, "Recorded", null, null,
"POST /api/v1/evaluate returns technical evidence; the Tracker owns the gate decision."),
new("T-023", "Distribución OPA-wasm Agnóstica", "Adoptar", "core/0085", null, "Recorded", null, null,
"OPA-wasm rule evaluation via rulesets/opa/policy.wasm; per-rule passed|failed|skipped, degrading gracefully."),
new("T-024", "Colisión de Nombre GateDecision", "Definir", null, null, "Recorded", null, null,
"Tracker's rich GateDecision is namespaced (TrackerGateDecision) to avoid colliding with Core's VO."),
new("T-025", "Gobernanza de IA Agéntica (cluster)", "Referenciar", "core/0081-0089", null, "Recorded", null, null,
"Agentic AI governance cluster referencing Core ADRs; basis for the Tracker's AI Governance surface."),
new("T-026", "Redis solo para soporte operacional", "Definir", null, null, "Recorded", null, null,
"Redis is operational support only (cache/locks/jobs/idempotency); PostgreSQL is the system of record."),
new("T-027", "Circuit breaker en cliente Core API", "Definir", null, null, "Recorded", null, null,
"Circuit breaker on the Core API client (CLOSED→OPEN→HALF_OPEN) to prevent cascade failures."),
new("T-028", "Estrategia PostgreSQL schema-per-context", "Adoptar", "T-008", null, "Recorded", null, null,
"PostgreSQL schema-per-context (10 schemas); no cross-schema FKs, references via UUID only."),
new("T-029", "OpenTelemetry Collector como sink único", "Definir", null, null, "Recorded", null, null,
"OpenTelemetry Collector as the single OTLP sink, decoupling the app from observability backends."),
new("T-030", "K8s overlays via Kustomize (no Helm inicialmente)", "Definir", null, null, "Recorded", null, null,
"Kubernetes overlays via Kustomize (not Helm) for Phase 1 simplicity."),
new("T-031", "Criterios de evaluación de compuertas configurables por tenant", "Definir", null, "docs/adrs/T-031-tenant-gate-evaluation-criteria.md", "Accepted", null, null,
"Tenant-configurable gate evaluation criteria over artifact fields, fail-closed with UNCONFIGURED."),
new("T-032", "Contexto acotado Geo / Datos Maestros", "Definir", null, "docs/adrs/T-032-geo-master-data-context.md", "Accepted", null, null,
"Geo / master-data bounded context replacing free-text regional fields with validated catalogs."),
new("T-033", "Adopción de la Plataforma Shell del Core (.NET)", "Extender", "ADR-0071", "docs/adrs/T-033-shell-platform-adoption.md", "Accepted", null, null,
"Adopts/extends the Core .NET Shell platform (DDD, AOP, factory, bootstrapper).")
];
}

/// <summary>
/// A single local ADR registry entry. <c>Supersedes</c>/<c>SupersededBy</c> capture supersession
/// relationships stated in <c>DECISIONS.md</c> notes; both are <c>null</c> when none applies.
/// </summary>
public sealed record AdrEntryResponse(
string Code,
string Title,
string DecisionType,
string? UpstreamRef,
string? LocalDocLink,
string Status,
string? Supersedes,
string? SupersededBy,
string Note);
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using FluentAssertions;
using Tracker.Presentation.Endpoints.Architecture;
using Xunit;

namespace Tracker.Tests.Presentation.Architecture;

/// <summary>
/// CR-10 / CR-21: guards the local ADR registry catalog that backs <c>GET /api/architecture/adrs</c>.
/// Exercised at the catalog level (rather than via <see cref="Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory{T}"/>)
/// because the endpoint is a pure static mirror of <c>DECISIONS.md</c> with no DB or middleware behaviour
/// to observe, and Serilog's process-global bootstrap logger only tolerates a single host boot per test run.
/// </summary>
public sealed class AdrRegistryEndpointTests
{
[Fact]
public void Registry_IsPopulated_AndIncludesT006Active()
{
AdrRegistryCatalog.Entries.Should().NotBeEmpty();

var t006 = AdrRegistryCatalog.Entries.Single(e => e.Code == "T-006");
t006.Status.Should().Be("Active");
t006.LocalDocLink.Should().Be("docs/adrs/T-006-frontend-vite.md");
t006.DecisionType.Should().Be("Adoptar");
}

[Fact]
public void Registry_CoversDecisionsTable_T001ThroughT033()
{
var codes = AdrRegistryCatalog.Entries.Select(e => e.Code).ToList();

codes.Should().HaveCount(33);
codes.Should().Contain("T-001").And.Contain("T-033");
codes.Should().OnlyHaveUniqueItems();
}

[Fact]
public void Registry_EntriesWithoutLocalDoc_DefaultToRecorded()
{
AdrRegistryCatalog.Entries
.Where(e => e.LocalDocLink is null)
.Should().OnlyContain(e => e.Status == "Recorded");
}
}
6 changes: 6 additions & 0 deletions src/apps/tracker-web/src/api/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
========================================================================= */
import { apiFetch, apiFetchForm } from './client';
import type {
AdrEntry,
CountryDto,
LocationLevelDto,
LocationNodeDto,
Expand Down Expand Up @@ -277,6 +278,11 @@ export const architectureApi = {
method: 'POST',
body,
}),
// CR-10 / CR-21: local ADR registry (status + supersession) from DECISIONS.md.
listAdrs: (status?: string) =>
apiFetch<AdrEntry[]>(
`/architecture/adrs${status ? `?status=${encodeURIComponent(status)}` : ''}`,
),
};

/* ── Auth (UMS / dev-bypass discovery + session) ────────────────────────── */
Expand Down
6 changes: 6 additions & 0 deletions src/apps/tracker-web/src/api/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
tenantsApi,
} from './endpoints';
import type {
AdrEntry,
CountryDto,
LocationLevelDto,
LocationNodeDto,
Expand Down Expand Up @@ -118,6 +119,7 @@ export const qk = {
opportunities: (tenantId?: string) => ['opportunities', tenantId ?? 'all'] as const,
opportunity: (id: string) => ['opportunities', 'detail', id] as const,
topologies: ['architecture', 'topologies'] as const,
adrs: ['architecture', 'adrs'] as const,
tenantIntelligence: (tenantId: string) => ['tenant-intelligence', tenantId] as const,
intakeInitiatives: (tenantId?: string) => ['intake-initiatives', tenantId ?? 'all'] as const,
intakeInitiative: (id: string) => ['intake-initiatives', 'detail', id] as const,
Expand Down Expand Up @@ -552,6 +554,10 @@ export function useAssistantConverse() {
export function useTopologies(): UseQueryResult<TopologyDto[]> {
return useQuery({ queryKey: qk.topologies, queryFn: () => architectureApi.topologies() });
}
/** CR-10 / CR-21: local ADR registry (status + supersession) from DECISIONS.md. */
export function useAdrRegistry(): UseQueryResult<AdrEntry[]> {
return useQuery({ queryKey: qk.adrs, queryFn: () => architectureApi.listAdrs() });
}
export function useRecommendTopology() {
return useMutation({
mutationFn: (body: RecommendTopologyRequest) => architectureApi.recommendTopology(body),
Expand Down
Loading
Loading