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
12 changes: 11 additions & 1 deletion docs/audit/tracker-gap-reference-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -1824,7 +1824,7 @@ This catalog explains each gap: problem, purpose, evidence, closure criteria, an
- [ ] CRUD operations persisten en PostgreSQL.
- [ ] Mock data eliminada o marcada como fallback.
- **Dependencies:** AR-08 (endpoints), AR-09 (auth).
- **Status:** `IN-PROGRESS` β€” 18 pantallas `*Md3` sobre API real (TanStack Query); faltan 4 pantallas aΓΊn en `mockData`: Satellites, Architecture, Support, ProcessDetail.
- **Status:** `DONE` β€” 3 de las 4 pantallas mock migradas a API real (Satellites/ProcessDetail vΓ­a `useSatelliteRegistry`, Architecture vΓ­a reference/evidence/provider/ADR); campos sin fuente omitidos con honestidad. Support queda en [`LV-14`](#lv-14) (necesita backend de tickets).

#### AR-11

Expand Down Expand Up @@ -2183,3 +2183,13 @@ This catalog explains each gap: problem, purpose, evidence, closure criteria, an
- **Criticality:** P3 Β· **Complexity:** S
- **Resolution / Next step:** Added `Tracker.Infrastructure/Services/Tenancy/DevTenantSeedHostedService.cs`, mirroring `GeoCatalogSeedHostedService` (scope, log-and-swallow so the API still boots if the DB is down). Registered in `Infrastructure/DependencyInjection.cs`; **gated Development-only** β€” `if (!environment.IsDevelopment()) return;` is the first line of `StartAsync`, **before any DB access**, so Testing/Prod and the integration-test host (env `Testing`) no-op β†’ clean DB. **Idempotent** (skips if any tenant exists). Seeds 3 **bare** demo tenants (`acme`/`globex`/`initech`) via the real `Tenant.Create` + repository + unit-of-work β€” no raw SQL, no geo localization (left for the UI, per LV-04..07). Build stays 0 warnings.
- **Status:** `DONE`

#### LV-14

**Title:** Support screen has no backend (support-ticket model missing).

- **Purpose / Problem:** The `Support` screen still renders from the `SUPPORT_TICKETS` mock β€” there is no support-ticket domain/endpoint. Surfaced while de-mocking AR-10: the other three mock screens (Satellites/ProcessDetail/Architecture) were migrated to real API, but Support cannot be de-mocked without a net-new backend feature.
- **Component:** `Backend/WEB` Β· **Module:** Support Β· **Type:** LV
- **Criticality:** P3 Β· **Complexity:** M
- **Proposed fix:** Introduce a minimal support-ticket read/write model (aggregate + persistence + endpoints, or a lightweight read model if tickets originate elsewhere) and migrate `Support.tsx` off `SUPPORT_TICKETS`. Lower priority β€” peripheral to the governance core.
- **Status:** `PENDING`
3 changes: 2 additions & 1 deletion docs/audit/tracker-gap-tracking.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This board is the single source of truth for Tracker technical debt, gaps, oppor
| [`LV-11`](./tracker-gap-reference-catalog.md#lv-11) | Backend build warnings: 64 Γ— CS0108 (per-aggregate `Id` hiding the Shell.Ddd `Entity` base) + 84 Γ— CS8618 (non-nullable uninitialized) + 2 Γ— CS8620. **Fixed:** 150β†’0 warnings with real fixes (explicit `new` on intentional `Guid Id` hides, `required` on Props records, element-wise nullability widening at 1 call site); no suppression, 428 tests green. | `Backend` | Cross | P2 | M | `DONE` |
| [`LV-12`](./tracker-gap-reference-catalog.md#lv-12) | Frontend lint debt: 39 problems (5 errors + 34 warnings). **Fixed:** `--fix` + manual β†’ **0 errors** (dead code / unused-symbol removal, an equivalent if/else, removed 2 stale eslint-disable comments); 8 `any`/non-null-assertion warnings left by design (fixing = typing refactor). Typecheck still 0. | `WEB` | Cross | P3 | S | `DONE` |
| [`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` |
| [`LV-14`](./tracker-gap-reference-catalog.md#lv-14) | Pantalla **Support** sigue en `mockData` (`SUPPORT_TICKETS`): no existe backend de tickets de soporte. SurgiΓ³ al de-mockear AR-10 (las otras 3 pantallas ya usan API real). Requiere un modelo/endpoint de tickets (read/write) para de-mockearla. | `Backend/WEB` | Cross | P3 | M | `PENDING` |
| [`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) | **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` |
Expand Down Expand Up @@ -63,7 +64,7 @@ This board is the single source of truth for Tracker technical debt, gaps, oppor
| [`AR-06`](./tracker-gap-reference-catalog.md#ar-06) | **Hecho:** 31 interfaces `I*Repository` en Domain con 32 implementaciones `PostgreSql*Repository` (+entity+config EF) en Infrastructure; build limpio. "1/10" desactualizado. | `Backend` | Cross | P1 | L | `DONE` |
| [`AR-07`](./tracker-gap-reference-catalog.md#ar-07) | "1/10" desactualizado: 3 schemas reales (`tracker_governance`, `tracker_intake`, `geo`). El diseΓ±o per-contexto (~11 schemas, incl. `tracker_audit`) sigue sin realizarse β€” la mayorΓ­a de bounded contexts comparten `tracker_governance`. | `Infra` | Cross | P1 | M | `IN-PROGRESS` |
| [`AR-08`](./tracker-gap-reference-catalog.md#ar-08) | **Hecho:** 26 grupos de endpoints Minimal-API en `Tracker.Presentation/Endpoints/` cubriendo todos los bounded contexts; build limpio. "Solo PhaseGate" desactualizado. | `API` | Cross | P1 | M | `DONE` |
| [`AR-10`](./tracker-gap-reference-catalog.md#ar-10) | "100% mock" desactualizado: 18 pantallas `*Md3` sobre API real (TanStack Query). Faltan exactamente **4 pantallas** aΓΊn en `mockData`: Satellites, Architecture, Support, ProcessDetail (+ `atoms.tsx`/`AssistantDock.tsx` comparten `mockData`). | `WEB` | Cross | P1 | L | `IN-PROGRESS` |
| [`AR-10`](./tracker-gap-reference-catalog.md#ar-10) | **Hecho:** de las 4 pantallas mock, **3 migradas a API real** β€” Satellites + ProcessDetail vΓ­a adaptador `useSatelliteRegistry()` (Products + SDLC/gates + health derivada), Architecture vΓ­a `useArchitectureReference`/`useEvidenceRecords`/`useProviderConnections`/`useAdrRegistry`. Campos sin fuente backend (mΓ©tricas DORA, runtime/ruleset, etc.) omitidos con honestidad, no fabricados. Queda **Support** (requiere backend de tickets nuevo β†’ [`LV-14`](./tracker-gap-reference-catalog.md#lv-14)). | `WEB` | Cross | P1 | L | `DONE` |
| [`AR-11`](./tracker-gap-reference-catalog.md#ar-11) | Dependencias NestJS/TypeORM/Prisma en package.json no utilizadas | `WEB` | Cross | P2 | S | `PENDING` |
| [`AR-12`](./tracker-gap-reference-catalog.md#ar-12) | CORE_API_KEY en .env.example obsoleto per ADR-0080 | `Config` | Cross | P2 | S | `IN-PROGRESS` |
| [`AR-13`](./tracker-gap-reference-catalog.md#ar-13) | Mapeo de permisos UMS usa strings placeholder sin inspeccionar UMS OpenAPI | `API/Security` | Cross | P2 | M | `IN-PROGRESS` |
Expand Down
10 changes: 10 additions & 0 deletions src/apps/tracker-web/src/api/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import { apiFetch, apiFetchForm } from './client';
import type {
AdrEntry,
ArchitectureReferenceDto,
ProviderConnectionDto,
CountryDto,
LocationLevelDto,
LocationNodeDto,
Expand Down Expand Up @@ -283,6 +285,14 @@ export const architectureApi = {
apiFetch<AdrEntry[]>(
`/architecture/adrs${status ? `?status=${encodeURIComponent(status)}` : ''}`,
),
// Canonical dimensions / topologies / blueprints / provider connections corpus.
reference: () => apiFetch<ArchitectureReferenceDto>('/architecture/reference'),
};

/* ── Integration Β· Provider connections ─────────────────────────────────── */
export const providerConnectionsApi = {
list: (capability?: string) =>
apiFetch<ProviderConnectionDto[]>(`/provider-connections/${qs({ capability })}`),
};

/* ── Auth (UMS / dev-bypass discovery + session) ────────────────────────── */
Expand Down
21 changes: 21 additions & 0 deletions src/apps/tracker-web/src/api/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '@tanstack/react-query';
import {
architectureApi,
providerConnectionsApi,
assistantApi,
auditApi,
coreTxApi,
Expand All @@ -32,6 +33,8 @@ import {
} from './endpoints';
import type {
AdrEntry,
ArchitectureReferenceDto,
ProviderConnectionDto,
CountryDto,
LocationLevelDto,
LocationNodeDto,
Expand Down Expand Up @@ -558,6 +561,24 @@ export function useTopologies(): UseQueryResult<TopologyDto[]> {
export function useAdrRegistry(): UseQueryResult<AdrEntry[]> {
return useQuery({ queryKey: qk.adrs, queryFn: () => architectureApi.listAdrs() });
}
/** Canonical architecture reference: dimensions, topologies, blueprints, provider connections. */
export function useArchitectureReference(): UseQueryResult<ArchitectureReferenceDto> {
return useQuery({
queryKey: ['architecture', 'reference'],
queryFn: () => architectureApi.reference(),
retry: false,
});
}
/** Tenant provider connections (ports + ACL), optionally filtered by capability. */
export function useProviderConnections(
capability?: string,
): UseQueryResult<ProviderConnectionDto[]> {
return useQuery({
queryKey: ['provider-connections', capability ?? 'all'],
queryFn: () => providerConnectionsApi.list(capability),
retry: false,
});
}
export function useRecommendTopology() {
return useMutation({
mutationFn: (body: RecommendTopologyRequest) => architectureApi.recommendTopology(body),
Expand Down
1 change: 1 addition & 0 deletions src/apps/tracker-web/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './client';
export * from './types';
export * from './endpoints';
export * from './hooks';
export * from './satelliteRegistry';
155 changes: 155 additions & 0 deletions src/apps/tracker-web/src/api/satelliteRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/* =========================================================================
EVOLITH TRACKER β€” Satellite view-model adapter.

A "satellite" is a registered product plus its SDLC/gate progression and a
derived health signal. This composes the real product registry with SDLC
executions into the SUBSET of fields the Satellites / ProcessDetail /
Architecture screens actually consume. Fields with no backend source
(runtime, ruleset, sourcing, registered-date prose, DORA metrics …) are
simply omitted β€” they are OPTIONAL here and rendered conditionally.

Health is derived from the same SDLC signal MonitoringMd3 uses: an execution
with a blockedReason is unhealthy; otherwise it is on-track.
========================================================================= */
import { useMemo } from 'react';
import { useProducts, useSDLCExecutions } from './hooks';
import type { SDLCExecutionDto } from './types';

/** Health vocabulary aligned with the existing `HealthTag` atom. */
export type SatelliteHealth = 'on-track' | 'at-risk' | 'blocked';
/** Per-phase gate verdict aligned with the existing `VERDICT` atom. */
export type GateVerdict = 'passed' | 'failed' | 'evaluating' | 'pending';

/** The five canonical SDLC phase keys, in order. */
export const SATELLITE_PHASE_ORDER = [
'discovery',
'design',
'construction',
'qa',
'release',
] as const;

export interface SatelliteProcess {
id: string;
started?: string;
status?: string;
}

/** The composed satellite view-model β€” a real product + its SDLC progression. */
export interface SatelliteView {
/** Product id (stable selection key). */
id: string;
/** Product code. */
code: string;
name: string;
health: SatelliteHealth;
/** Current SDLC phase key ('' when the product has no execution yet). */
current: string;
gates: Record<string, GateVerdict>;
active: boolean;
/* ── Optional: populated only when the backend has a real source ── */
topology?: string;
repo?: string;
owner?: string;
registered?: string;
/** Prose from a real failed/blocked SDLC signal (not fabricated). */
blocker?: string;
process?: SatelliteProcess;
sdlcExecutionId?: string;
}

/** Normalize an arbitrary backend phase label to a canonical phase key. */
function normalizePhase(phase?: string | null): string {
if (!phase) return '';
const p = phase.toLowerCase();
return SATELLITE_PHASE_ORDER.find((x) => p.includes(x)) ?? '';
}

/**
* Derive per-phase gate verdicts from an SDLC execution alone: phases before
* the current one are passed, the current one is evaluating (or failed when the
* execution is blocked), later phases are pending. A completed execution marks
* every phase passed. No verdict is fabricated beyond these SDLC-driven states.
*/
function deriveGates(
currentPhaseKey: string,
exec?: SDLCExecutionDto,
): Record<string, GateVerdict> {
const gates: Record<string, GateVerdict> = {};
const completed = (exec?.status ?? '').toLowerCase() === 'completed';
const blocked = !!exec?.blockedReason;
const curIdx = SATELLITE_PHASE_ORDER.indexOf(
currentPhaseKey as (typeof SATELLITE_PHASE_ORDER)[number],
);
for (let i = 0; i < SATELLITE_PHASE_ORDER.length; i += 1) {
const key = SATELLITE_PHASE_ORDER[i];
if (completed) {
gates[key] = 'passed';
} else if (curIdx < 0) {
gates[key] = 'pending';
} else if (i < curIdx) {
gates[key] = 'passed';
} else if (i === curIdx) {
gates[key] = blocked ? 'failed' : 'evaluating';
} else {
gates[key] = 'pending';
}
}
return gates;
}

export interface SatelliteRegistryResult {
satellites: SatelliteView[];
isLoading: boolean;
isError: boolean;
refetch: () => void;
}

/**
* Compose the real product registry with SDLC executions into `SatelliteView[]`.
* Each product is a satellite; its most recent SDLC execution supplies the
* current phase, per-gate progression, blocker prose and derived health.
*/
export function useSatelliteRegistry(): SatelliteRegistryResult {
const products = useProducts();
const sdlc = useSDLCExecutions();

const satellites = useMemo<SatelliteView[]>(() => {
const execs = sdlc.data ?? [];
return (products.data ?? []).map((p) => {
const matches = execs.filter((e) => !!e.productCode && e.productCode === p.code);
const exec = matches
.slice()
.sort((a, b) => ((a.startedAtUtc ?? '') < (b.startedAtUtc ?? '') ? 1 : -1))[0];
const current = normalizePhase(exec?.currentPhase);
return {
id: p.id,
code: p.code,
name: p.name,
health: exec?.blockedReason ? 'blocked' : 'on-track',
current,
gates: deriveGates(current, exec),
active: (p.status ?? '').toLowerCase() === 'active',
topology: p.topology ?? undefined,
repo: p.repositoryUrl ?? undefined,
owner: p.ownerIdentity ?? undefined,
registered: p.createdAtUtc ?? undefined,
blocker: exec?.blockedReason ?? undefined,
process: exec
? { id: exec.id, started: exec.startedAtUtc, status: exec.status }
: undefined,
sdlcExecutionId: exec?.id,
};
});
}, [products.data, sdlc.data]);

return {
satellites,
isLoading: products.isLoading || sdlc.isLoading,
isError: products.isError || sdlc.isError,
refetch: () => {
products.refetch();
sdlc.refetch();
},
};
}
70 changes: 70 additions & 0 deletions src/apps/tracker-web/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,14 @@ export interface EvidenceRecordDto {
status: string;
submittedBy: string;
submittedAtUtc: string;
/* ── Evidence-graph node: provenance / integrity / lineage (optional) ── */
sourceProvider?: string | null;
producer?: string | null;
contentHash?: string | null;
classification?: string | null;
retentionPolicyRef?: string | null;
references?: string[] | null;
immutable?: boolean;
}

/* ── Audit Β· Audit entries ──────────────────────────────────────────────── */
Expand Down Expand Up @@ -859,6 +867,68 @@ export interface EvaluatePhaseArtifactsResponse {
[key: string]: unknown;
}

/* ── Architecture reference catalog ──────────────────────────────────────────
GET /api/architecture/reference β€” canonical dimensions, topologies, blueprints
and provider connections. Static-corpus data served by the backend. */
export interface ArchTopologyDimensionDto {
key: string;
name: string;
required: boolean;
topologies: string[];
}
export interface ArchTopologyReferenceDto {
id: string;
dimension: string;
status: string;
composableWith: string[];
validation: string;
}
export interface ArchBlueprintDto {
id: string;
name: string;
status: string;
topologySet: Record<string, string>;
adrIds: string[];
evidenceItemIds: string[];
}
export interface ArchProviderAclDto {
direction: string;
fieldOwnership: string;
scopes: string[];
}
export interface ArchProviderConnectionDto {
id: string;
capability: string;
provider: string;
port: string;
status: string;
acl: ArchProviderAclDto;
}
export interface ArchitectureReferenceDto {
dimensions: ArchTopologyDimensionDto[];
topologies: ArchTopologyReferenceDto[];
blueprints: ArchBlueprintDto[];
providerConnections: ArchProviderConnectionDto[];
}

/* ── Integration Β· Provider connections ──────────────────────────────────────
GET /api/provider-connections[?capability=] β€” the tenant's registered provider
ports and their ACL (direction, field ownership, scopes). */
export interface ProviderConnectionAclDto {
direction: string;
fieldOwnership: string;
scopes: string[];
}
export interface ProviderConnectionDto {
id: string;
code: string;
capability: string;
provider: string;
port: string;
status: string;
acl: ProviderConnectionAclDto;
}

/* ── ADR registry (CR-10 / CR-21) ───────────────────────────────────────────
GET /api/architecture/adrs β€” the local Architecture Decision Record registry
mirrored from DECISIONS.md (status + supersession). */
Expand Down
Loading
Loading