From a9cc013dc83d64a7e964131494123970b2e5ceec Mon Sep 17 00:00:00 2001 From: Alberto Arroyo Raygada Date: Sat, 8 Aug 2026 21:18:16 -0500 Subject: [PATCH 1/4] docs(transparency): make the signing lever discoverable without reading the Helm chart (#146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GT-588's machinery is complete and tested. What was missing was smaller and stopped anyone using it: `Transparency` appeared in ZERO appsettings files, so the only way to learn the lever existed — or what its keys are called — was to read the Helm chart. The section ships DISABLED, which is not a placeholder: it is the state the chart also ships and the state TransparencyWiringTests asserts. Turning signing on changes what the product claims about its own audit trail, and that is a deployment decision, not a default. THE SEEDS ARE NOT HERE, not even as empty strings, and that is deliberate: a blank in a committed file is an invitation to fill it. They are delivered by the deployment through a Secret, and with Enabled=true and no seeds the application REFUSES TO START rather than falling back to a development key — a ledger that looks signed and proves nothing is worse than no ledger. Verified: the JSON parses, the six keys the binder reads are exactly the non-secret ones (no *SeedBase64 key exists in the file), transparency suite 14/14, and DesactivadoPorDefecto_NoDecoraNada still passes — the default decorates nothing, so this adds discoverability and no behaviour. CI green including Deploy (kind + Helm + smoke), which installs the chart for real, and the C#-signs/TypeScript-verifies interop job. NOT verified: that a real deployment reads it. Nothing is deployed anywhere (GT-435/GT-448), the same wall GT-588's remaining criterion sits behind. --- .../Tracker.Presentation/appsettings.json | 63 ++++++++++++++++--- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/src/apps/tracker-api/Tracker.Presentation/appsettings.json b/src/apps/tracker-api/Tracker.Presentation/appsettings.json index 3008d9be..27d4d2a6 100644 --- a/src/apps/tracker-api/Tracker.Presentation/appsettings.json +++ b/src/apps/tracker-api/Tracker.Presentation/appsettings.json @@ -30,13 +30,36 @@ }, "Authorization": { "RolePermissionMap": { - "tracker-admin": [ "tracker:*" ], - "tracker-gate-reader": [ "tracker:gate:read" ], - "tracker-gate-operator": [ "tracker:gate:read", "tracker:gate:evaluate", "tracker:core:evaluate", "tracker:gate-decision:read", "tracker:gate-decision:create", "tracker:gate-decision:decide", "tracker:evidence:read", "tracker:evidence:create" ], - "tracker-api-operator": [ "tracker:initiative:read", "tracker:initiative:create", "tracker:initiative:approve", "tracker:sdlc-execution:read", "tracker:sdlc-execution:create", "tracker:sdlc-execution:advance", "tracker:audit:read", "tracker:audit:create", "tracker:core-transaction:read", "tracker:core-transaction:attach" ] + "tracker-admin": [ + "tracker:*" + ], + "tracker-gate-reader": [ + "tracker:gate:read" + ], + "tracker-gate-operator": [ + "tracker:gate:read", + "tracker:gate:evaluate", + "tracker:core:evaluate", + "tracker:gate-decision:read", + "tracker:gate-decision:create", + "tracker:gate-decision:decide", + "tracker:evidence:read", + "tracker:evidence:create" + ], + "tracker-api-operator": [ + "tracker:initiative:read", + "tracker:initiative:create", + "tracker:initiative:approve", + "tracker:sdlc-execution:read", + "tracker:sdlc-execution:create", + "tracker:sdlc-execution:advance", + "tracker:audit:read", + "tracker:audit:create", + "tracker:core-transaction:read", + "tracker:core-transaction:attach" + ] }, - "UmsPermissionMap": { - } + "UmsPermissionMap": {} }, "CoreApi": { "//ApiKey": "SERVICE credential the BFF presents to the Core as 'Authorization: Bearer ' (Core's global ApiKeyGuard / EVOLITH_API_KEY). Required for non-mock runs. This does NOT contradict ADR-0080: that contract forbids forwarding USER tokens or tenant identity to the Core (repositoryRef + opaque workspaceRef only); a BFF->Core service key is a separate, allowed transport concern. Keep this key.", @@ -45,15 +68,35 @@ "ApiKey": "local-dev-key", "TimeoutMs": 8000, "MockFallback": false, - "RequiredEvaluationKinds": [ "gate" ], - "RequiredOperations": [ "evolith-gate-evaluate", "evolith-phase-artifacts-evaluate" ], - "RequiredCapabilitySurfaces": [ "rest" ], + "RequiredEvaluationKinds": [ + "gate" + ], + "RequiredOperations": [ + "evolith-gate-evaluate", + "evolith-phase-artifacts-evaluate" + ], + "RequiredCapabilitySurfaces": [ + "rest" + ], "LocalWorkspaceRef": "rulesets", "RepositoryUrl": "https://github.com/beyondnetcode/evolith_tracker", "RepositoryRevision": "HEAD", "WorkspaceRefPrefix": "tracker" }, "Cors": { - "Origins": ["http://localhost:4200"] + "Origins": [ + "http://localhost:4200" + ] + }, + "Transparency": { + "//": "GT-588 — firma del expediente (RFC 9943 / SCITT). DESACTIVADA aqui y a proposito: encenderla cambia lo que el producto promete sobre su propia auditoria. Esta seccion existe para que la palanca sea DESCUBRIBLE sin leerse el chart de Helm, que era la unica forma de saber que existia.", + "//seeds": "Las dos semillas Ed25519 NO aparecen aqui, ni siquiera vacias, a proposito: un hueco en un fichero commiteado es una invitacion a rellenarlo. Las entrega el despliegue por Secret (ver product/infra/helm/evolith-tracker-api/values.yaml). Con Enabled=true y sin semillas la aplicacion se NIEGA a arrancar, en vez de caer a una clave de desarrollo que produciria un ledger que parece firmado y no prueba nada.", + "//ledger": "LedgerPath debe caer en un volumen persistente. Un ledger en el filesystem efimero de un pod se borra en cada reinicio, que es exactamente lo que la ficha llama decorativo.", + "Enabled": false, + "LedgerPath": "/var/lib/evolith/transparency/ledger.jsonl", + "Issuer": "evolith-tracker", + "IssuerKeyId": "tracker-issuer", + "TransparencyServiceIssuer": "evolith-tracker-transparency", + "TransparencyServiceKeyId": "tracker-ts" } } From ff77029d7220e901abfd8d02ba275cba56577be5 Mon Sep 17 00:00:00 2001 From: aarroyo Date: Mon, 10 Aug 2026 10:53:39 -0500 Subject: [PATCH 2/4] docs(gaps): add execution handoff for pending lanes --- ...racker-gap-execution-handoff-2026-08-10.md | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 docs/audit/tracker-gap-execution-handoff-2026-08-10.md diff --git a/docs/audit/tracker-gap-execution-handoff-2026-08-10.md b/docs/audit/tracker-gap-execution-handoff-2026-08-10.md new file mode 100644 index 00000000..29e21e90 --- /dev/null +++ b/docs/audit/tracker-gap-execution-handoff-2026-08-10.md @@ -0,0 +1,131 @@ +# Tracker Gap Execution Handoff — 2026-08-10 + +> This is an execution handoff, not a gap registry. The canonical gap board remains +> `docs/audit/tracker-gap-tracking.md` and the canonical catalog remains +> `docs/audit/tracker-gap-reference-catalog.md`. + +## Verified State + +- Branch checked: `develop` at `a9cc013`. +- Gap registry validator: `204` rows/details coherent. +- Status count: `DONE=179`, `PENDING=13`, `DEFERRED=7`, `BLOCKED=3`, `SUPERSEDED=1`, `WONTFIX=1`. +- Current canonical pending count: `13`, not `17` or `19`. +- No canonical `IN-PROGRESS` gaps were found. +- `origin/develop...origin/main`: `develop` has `1` commit not in `main`; `main` has `8` merge commits not in `develop` by graph shape. +- The current working tree is dirty with UMS/auth/docs changes. Treat those as unrelated/user work unless explicitly instructed otherwise. + +## Token Strategy + +Do not start a long implementation wave when context or weekly plan budget is low. + +Safe units: + +1. Pick one small lane. +2. Create or use a clean worktree from `origin/develop`. +3. Implement only that lane. +4. Run the targeted tests plus the gap registry check. +5. Commit and push `develop`. +6. Merge `develop` into `main`. +7. Stop and leave the next lane in a new handoff if token budget is low. + +The safest first implementation is `LV-26`, because it is isolated and small. + +## Pending Gaps After Code Review + +| Gap | Real current reading | Remaining work | +|---|---|---| +| `LV-26` | Small backend bug. | Preserve runtime `401/403` instead of flattening to `502`. | +| `CP-05` | Backend preserves Core intelligence; web does not fully consume it. | Add typed web model/render for `intelligence`: recommendations, risks, actions, signals, kinds. | +| `CP-07` | Core deposits can be attached to SDLC records. | Make attached deposits visible in the phase/gate user flow and robot evidence. | +| `CP-10` | Parity robot exists. | Extend robot to assert visible recommendations and live Core evidence. | +| `CP-16` | Backend ledger projection exists. | Add web surface and export-related ledger events. | +| `CP-12` | Basic phase workspace exists. | Upgrade it into a document desk per gate/artifact/template/version/approval. | +| `CP-03` | Real product gap. | Generate artifact filling wizard/renderer from Core artifact formats. | +| `CP-13` | Real product gap. | Add advanced canonical Markdown editor plus Word-like editing mode. | +| `CP-15` | Gate approvals exist. | Add artifact/version approval by tenant people, teams, and agents. | +| `CP-17` | Real product gap. | Governed DOCX/PDF export from canonical Markdown, with version fingerprint. | +| `CP-18` | Real product gap. | Safe document plugin framework, starting with Mermaid. | +| `CP-11` | Chat exists, model missing. | Define advisory interaction matrix: chat, nudges, on-demand, auto-attach. | +| `CP-09` | Real advisory gap. | Optional Core technical advisory surfaces inside SDLC flow. | + +## Recommended Parallel Lanes + +### Lane A — Quick Closure + +- Scope: `LV-26`. +- Why first: low risk, small diff, improves diagnosis against agent-runtime. +- Likely files: + - `src/apps/tracker-api/Tracker.Presentation/Integration/AgentRuntimeTurnExecutor.cs` + - `src/apps/tracker-api/Tracker.Presentation/Endpoints/Integration/AssistantEndpoints.cs` + - `src/apps/tracker-api/Tracker.Tests/Presentation/Integration/AgentRuntimeGatewayTests.cs` + - Possibly assistant endpoint tests. +- Expected closure: one commit. + +### Lane B — Core Intelligence Visible + +- Scope: `CP-05`, `CP-07`, `CP-10`. +- Why grouped: all depend on the same fact: Core result intelligence must be visible, attachable, and robot-verified. +- Key observation: backend already maps `CoreEvaluationIntelligenceView`; web types still describe advisory data as living in `summary`. +- Likely files: + - `src/apps/tracker-web/src/api/types.ts` + - `src/apps/tracker-web/src/components/screens/md3/CoreVerdictView.tsx` + - `src/apps/tracker-web/src/components/screens/md3/GateSubmissionView.tsx` + - `robosoft/robots/core-sdlc-parity.robot.mjs` + - `robosoft/robots/core-integration.robot.mjs` +- Expected closure: 1 medium commit or 2 small commits. + +### Lane C — Document Workspace Minimum + +- Scope: first slice of `CP-03` and `CP-12`. +- Goal: a usable artifact desk that starts from Core artifact profiles, creates a copy/draft, edits fields/content, saves versions, and submits to gate. +- Keep it minimal before editor/export work. +- Likely files: + - `src/apps/tracker-web/src/components/screens/md3/SdlcPipelineMd3.tsx` + - `src/apps/tracker-web/src/api/types.ts` + - `src/apps/tracker-api/Tracker.Application/Sdlc/PhaseArtifact/*` + - `src/apps/tracker-api/Tracker.Presentation/Endpoints/Sdlc/SdlcPhaseEndpoints.cs` +- Expected closure: larger than Lane B; split if token budget is low. + +### Lane D — Rich Authoring And Export + +- Scope: `CP-13`, `CP-18`, `CP-17`. +- Do after Lane C has a stable artifact desk. +- Recommended order: + 1. Markdown editor and canonical MD persistence. + 2. Mermaid/plugin rendering and sanitization. + 3. DOCX/PDF export from the canonical document. +- This is the largest frontend/document pipeline lane. + +### Lane E — Governance And Advisory Model + +- Scope: `CP-15`, `CP-16`, `CP-11`, `CP-09`. +- Do after artifact workspace and Core intelligence are visible. +- Recommended order: + 1. Artifact/version approval model. + 2. Ledger UI and export events. + 3. Advisory interaction matrix. + 4. Optional technical advisory surfaces. + +## Suggested Next Session Prompt + +Use this prompt to resume: + +```text +Winston, retoma desde docs/audit/tracker-gap-execution-handoff-2026-08-10.md. +Tenemos 13 gaps PENDING canónicos. No uses el working tree actual si sigue sucio: +crea una worktree/rama limpia desde origin/develop. Empieza por Lane A (LV-26), +implementa el fix pequeño, corre tests focalizados y python3 .harness/scripts/check-gap-registry.py. +Si pasa, haz commit, push a develop y merge a main siguiendo la política acordada. +No abras Lane B hasta cerrar LV-26. +``` + +## Verification Commands + +Run before closing any lane: + +```bash +python3 .harness/scripts/check-gap-registry.py +git status --short +``` + +For `LV-26`, also run targeted backend tests around agent runtime and assistant conversation. From 69808cd2b6154789ab60527ca44089e250539d26 Mon Sep 17 00:00:00 2001 From: aarroyo Date: Mon, 10 Aug 2026 11:03:16 -0500 Subject: [PATCH 3/4] fix(agent-runtime): preserve upstream failure status --- docs/audit/tracker-gap-reference-catalog.md | 9 +-- docs/audit/tracker-gap-tracking.md | 4 +- .../Integration/AssistantEndpoints.cs | 34 ++++++---- .../Integration/AgentRuntimeFailure.cs | 43 ++++++++++++ .../Integration/AgentRuntimeTurnExecutor.cs | 2 +- .../Integration/AssistantTurnLedgerTests.cs | 68 ++++++++++++++++++- 6 files changed, 140 insertions(+), 20 deletions(-) create mode 100644 src/apps/tracker-api/Tracker.Presentation/Integration/AgentRuntimeFailure.cs diff --git a/docs/audit/tracker-gap-reference-catalog.md b/docs/audit/tracker-gap-reference-catalog.md index be273506..388398d5 100644 --- a/docs/audit/tracker-gap-reference-catalog.md +++ b/docs/audit/tracker-gap-reference-catalog.md @@ -3944,11 +3944,12 @@ Por tanto, «no hay rate limiting configurado» es **falso**; lo correcto es «e - **Component:** `Backend` · **Module:** Assistant / Agent Runtime integration · **Type:** LV - **Criticality:** P2 · **Complexity:** XS - **Discovery:** Found while running `core-integration` against a live stack for the FIRST time — the robot is excluded from the default RoboSoft list and had never executed. The same run found a real Core defect (the MCP chart's `runAsUser`, beyondnetcode/evolith_arch32#425) and one defect in the robot itself (#139). -- **Proposed fix:** Carry the upstream status and code through the `Result` rather than re-deriving them from a string — e.g. a typed failure carrying `(code, upstreamStatus)`, so `Translate` maps `AgentRuntime.HttpError` + 401 to a 502 whose BODY names the upstream status and code. The HTTP status arguably stays 502 (the caller's own auth did succeed; the failure is upstream), so the defect to fix is the ERASURE, not the number. +- **Resolution / Next step:** Fixed on 2026-08-10. `AgentRuntimeTurnExecutor` now wraps `AgentRuntimeGatewayException` with `AgentRuntimeFailure`, preserving `statusCode`, `code`, and `message` while still returning `Result.Failure` so the failed turn is audited. `AssistantEndpoints.Translate` rehydrates that envelope and returns the runtime-specific status/code/message instead of falling through to `AgentRuntime.Failed`/502. - **Acceptance criteria:** - - [ ] A non-2xx from the runtime yields a response body naming the upstream status and the specific code, not the generic `AgentRuntime.Failed`. - - [ ] A test drives a 401 from a stubbed runtime and asserts the body distinguishes it from an unreachable runtime. -- **Status:** `PENDING` + - [x] A non-2xx from the runtime yields a response body naming the upstream status and the specific code, not the generic `AgentRuntime.Failed`. + - [x] A test drives a 401 from a stubbed runtime and asserts the body distinguishes it from an unreachable runtime. +- **Evidence:** `AssistantTurnLedgerTests.Un401DelRuntime_SeDevuelveComo401ConCodigoEspecifico` drives `/api/v1/assistant/converse` through the real endpoint, verifies HTTP 401 + `AgentRuntime.HttpError`, and checks the failed turn ledger keeps the runtime failure reason. +- **Status:** `DONE` #### LV-25 diff --git a/docs/audit/tracker-gap-tracking.md b/docs/audit/tracker-gap-tracking.md index a5f00c69..aed270b1 100644 --- a/docs/audit/tracker-gap-tracking.md +++ b/docs/audit/tracker-gap-tracking.md @@ -87,7 +87,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-26`](./tracker-gap-reference-catalog.md#lv-26) | **Un 401 del agent-runtime llega al llamante como 502, y con el código específico sobrescrito.** El gateway lanza `AgentRuntimeGatewayException` con el status real; el executor lo aplana a string a propósito —para que el turno fallido quede asentado, y eso está bien— pero `Translate` vuelve a derivar el status desde ese string, no casa con ningún caso y cae en `_ => ("AgentRuntime.Failed", 502)`. Se pierden a la vez el **status de origen** (401) y el **código específico** (`AgentRuntime.HttpError` → genérico). | Un problema de CREDENCIAL se reporta como problema de DISPONIBILIDAD: el 502 manda a comprobar si el runtime está en pie, y estaba en pie respondiendo 401 en 88 ms. | **Observado 2026-08-04:** el robot `core-integration` dio `POST /assistant/converse → 502` contra el stack real; la causa era `AGENT_RUNTIME_API_KEY` distinta entre el secreto del Tracker y la del runtime. Solo el log de tracker-api mostraba el 401. Alineando la clave el paso pasó a verde. | `Backend` | Cross | P2 | XS | `PENDING` | +| [`LV-26`](./tracker-gap-reference-catalog.md#lv-26) | **Hecho — el fallo del agent-runtime conserva status y código específico al cruzar el puerto auditable.** `AgentRuntimeFailure` encapsula `statusCode/code/message`; `AssistantEndpoints.Translate` lo rehidrata y devuelve 401 + `AgentRuntime.HttpError` cuando el runtime responde 401, sin degradarlo a `AgentRuntime.Failed`/502. | Un problema de credencial ya se muestra como credencial, no como disponibilidad del runtime, y el turno fallido sigue quedando en el ledger. | Evidencia: `Un401DelRuntime_SeDevuelveComo401ConCodigoEspecifico` cubre endpoint real + ledger con runtime stub que lanza 401. | `Backend` | Cross | P2 | XS | `DONE` | | [`LV-25`](./tracker-gap-reference-catalog.md#lv-25) | **Hallado por el nuevo harness UI-E2E (Playwright, `apps/tracker-web-e2e`) — Winston 2026-07-24.** Un platform-operator que cambia el tenant operado seguía viendo las listas tenant-scoped del tenant anterior: varias query-keys de react-query no incluyen el tenant (p.ej. `qk.initiatives = ['initiatives']`, `api/hooks.ts:110`) y `setActingTenant` (`store/auth.store.ts:309`) sólo hacía `set({actingTenantId})` sin invalidar el caché → datos del tenant equivocado hasta `staleTime` (30s), o **indefinidamente** al alternar entre dos tenants con datos. El backend SÍ aísla (robosoft `tenant-isolation` 10/10) — es correctness de **caché de cliente**, no fuga de datos. **Fixed:** efecto en `app/app.tsx` que hace `queryClient.invalidateQueries()` al cambiar `actingTenantId` (guarda de primer render), forzando refetch de toda query activa bajo el nuevo scope. Verificado por el propio E2E: switch a «Acme» → 9 iniciativas + drill-down a las 5 compuertas (`05-processes` 5/5). | Un operador de plataforma cambia de organización y la pantalla sigue mostrando las iniciativas de la anterior | Alternar entre dos tenants con datos mostraba las filas del tenant equivocado hasta 30 s | `WEB` | Cross | P2 | S | `DONE` | | [`LV-14`](./tracker-gap-reference-catalog.md#lv-14) | La pantalla de Soporte sigue mostrando datos inventados porque no existe modelo de tickets | La sección se ve completa y funcional, pero lo que muestra no corresponde a nada real del negocio | Resuelto por DESCARTE (decisión PO): Soporte no es contexto acotado ni está en la visión; pantalla huérfana del prototipo retirada por completo (pantalla+nav+ruta+mock+permisos). Web typecheck+build verde | `Backend/WEB` | Local | P3 | M | `DONE` | | [`LV-15`](./tracker-gap-reference-catalog.md#lv-15) | **Hecho — roll-out de [ADR T-034](../adrs/T-034-config-hub-vs-monitor.md)** (Config-hub vs Monitor) completo. Los **monitores** migrados a solo-lectura con la edición movida a zonas inline en Tenant configuration: Gate policies & criteria, Custom fields / artifact schemas (Gate governance 100% solo-lectura) y Tenant intelligence (`intel` solo-lectura). **Connectors (PPM intake)** y **Products** quedan como pantallas de **registro/gestión** — exentas por diseño (ADR T-034 §2.1: los registros no son monitores); sus cards de Config enlazan a su área de gestión ("Manage …"). | | | `WEB` | Cross | P2 | L | `DONE` | @@ -222,7 +222,7 @@ This board is the single source of truth for Tracker technical debt, gaps, oppor | [`GT-480`](./tracker-gap-reference-catalog.md#gt-480) | El job de despliegue corría también para cambios de sólo documentación | ~14 min de CI para publicar dos ficheros markdown | Sale a su propio workflow con `paths-ignore`; lista negra y no blanca, porque la blanca se queda obsoleta en silencio | `Infra` | Cross | P3 | XS | `DONE` | | [`GT-481`](./tracker-gap-reference-catalog.md#gt-481) | El despliegue se comprobaba dos veces sobre el mismo árbol | ~7 min de clúster Kubernetes repetidos sobre contenido idéntico | Deja de correr en push a `main`; y las esperas fijas pasan a sondeo por hecho observable | `Infra` | Cross | P3 | XS | `DONE` | -**Progress:** 179 / 204 done · 13 pending · 0 in progress · 3 blocked · 7 deferred · 1 superseded · 1 wontfix +**Progress:** 180 / 204 done · 12 pending · 0 in progress · 3 blocked · 7 deferred · 1 superseded · 1 wontfix *(Conteos reconciliados contra `python3 .harness/scripts/check-gap-registry.py` el 2026-08-01 al cerrar `CP-01` y `CP-08`: 203 fichas / 203 filas; estados `{PENDING: 17, DONE: 174, DEFERRED: 7, BLOCKED: 3, SUPERSEDED: 1, WONTFIX: 1}`.)* **Wave 2026-06-07 → 2026-06-14 (BMAD audit + coherence):** Items `GAP-*`, `COH-*`, `OPP-*` from the PROMPT MAESTRO functional/technical/documentary audit and the source-coherence analysis (106 items: 81 resolved, 24 open, 1 blocked, 1 deferred at import time). diff --git a/src/apps/tracker-api/Tracker.Presentation/Endpoints/Integration/AssistantEndpoints.cs b/src/apps/tracker-api/Tracker.Presentation/Endpoints/Integration/AssistantEndpoints.cs index 4485060f..0bbc39c2 100644 --- a/src/apps/tracker-api/Tracker.Presentation/Endpoints/Integration/AssistantEndpoints.cs +++ b/src/apps/tracker-api/Tracker.Presentation/Endpoints/Integration/AssistantEndpoints.cs @@ -95,13 +95,15 @@ public static void MapAssistantEndpoints(this IEndpointRouteBuilder app) if (resultado.IsFailure) { - var (code, status) = Translate(resultado.Error); + var translated = Translate(resultado.Error); // El error SIN traducir: `AgentExecution.NotAuditable` y `AgentRuntime.Failed` // ambos salen 503/502 hacia fuera, pero en la traza son dos historias distintas // —el expediente rechazo el asiento, o el runtime no respondio— y colapsarlas al // codigo HTTP borraria justo la que hay que poder buscar. traza.SetGovernanceOutcome(resultado.Error); - return Results.Json(new { code, message = resultado.Error }, statusCode: status); + return Results.Json( + new { code = translated.Code, message = translated.Message }, + statusCode: translated.Status); } traza.SetGovernanceOutcome("ok"); @@ -138,15 +140,23 @@ private static IReadOnlyList GrantedScopes(ITrackerUserContext user) => ? [AgentScope.ReadCorpus] : []; - private static (string Code, int Status) Translate(string error) => error switch + private static (string Code, int Status, string Message) Translate(string error) { - "AgentExecution.UnknownScope" => (error, 400), - "AgentExecution.ScopeNotGranted" => (error, 403), - "AgentExecution.UnattributableTurn" => (error, 400), - // El expediente no admitio el asiento, asi que el turno NO se ejecuto. 503 y no 500: no - // hay nada roto en la peticion, hay una dependencia —la que da la garantia— indisponible. - "AgentExecution.NotAuditable" => (error, 503), - _ when error.StartsWith("AgentTurnAuditor.", StringComparison.Ordinal) => (error, 503), - _ => ("AgentRuntime.Failed", 502), - }; + if (AgentRuntimeFailure.TryParse(error, out var runtimeFailure)) + { + return (runtimeFailure.Code, runtimeFailure.StatusCode, runtimeFailure.Message); + } + + return error switch + { + "AgentExecution.UnknownScope" => (error, 400, error), + "AgentExecution.ScopeNotGranted" => (error, 403, error), + "AgentExecution.UnattributableTurn" => (error, 400, error), + // El expediente no admitio el asiento, asi que el turno NO se ejecuto. 503 y no 500: no + // hay nada roto en la peticion, hay una dependencia —la que da la garantia— indisponible. + "AgentExecution.NotAuditable" => (error, 503, error), + _ when error.StartsWith("AgentTurnAuditor.", StringComparison.Ordinal) => (error, 503, error), + _ => ("AgentRuntime.Failed", 502, error), + }; + } } diff --git a/src/apps/tracker-api/Tracker.Presentation/Integration/AgentRuntimeFailure.cs b/src/apps/tracker-api/Tracker.Presentation/Integration/AgentRuntimeFailure.cs new file mode 100644 index 00000000..a5dad0cf --- /dev/null +++ b/src/apps/tracker-api/Tracker.Presentation/Integration/AgentRuntimeFailure.cs @@ -0,0 +1,43 @@ +namespace Tracker.Presentation.Integration; + +/// +/// Stable string envelope for runtime failures that must cross the governance +/// port as Result.Error without losing their HTTP status. +/// +internal static class AgentRuntimeFailure +{ + private const string Prefix = "AgentRuntimeGateway|"; + + public static string From(AgentRuntimeGatewayException ex) => + $"{Prefix}{ex.StatusCode}|{ex.Code}|{ex.Message}"; + + public static bool TryParse(string? error, out ParsedAgentRuntimeFailure failure) + { + failure = default; + if (string.IsNullOrWhiteSpace(error) || + !error.StartsWith(Prefix, StringComparison.Ordinal)) + { + return false; + } + + var parts = error.Split('|', 4); + if (parts.Length != 4 || !int.TryParse(parts[1], out var statusCode)) + { + return false; + } + + if (statusCode < 400 || statusCode > 599 || + string.IsNullOrWhiteSpace(parts[2])) + { + return false; + } + + failure = new ParsedAgentRuntimeFailure(parts[2], statusCode, parts[3]); + return true; + } +} + +internal readonly record struct ParsedAgentRuntimeFailure( + string Code, + int StatusCode, + string Message); diff --git a/src/apps/tracker-api/Tracker.Presentation/Integration/AgentRuntimeTurnExecutor.cs b/src/apps/tracker-api/Tracker.Presentation/Integration/AgentRuntimeTurnExecutor.cs index 81709e47..be6593aa 100644 --- a/src/apps/tracker-api/Tracker.Presentation/Integration/AgentRuntimeTurnExecutor.cs +++ b/src/apps/tracker-api/Tracker.Presentation/Integration/AgentRuntimeTurnExecutor.cs @@ -51,7 +51,7 @@ public async Task> ExecuteAsync(AgentTurnRequest request, Cancell // asentado con su motivo. Una excepcion que sube hasta el endpoint se llevaria por // delante ese asiento, y un fallo sin rastro es indistinguible de un turno que nunca // se intento — que es justo la diferencia que a alguien le importara. - return Result.Failure($"{ex.Code}: {ex.Message}"); + return Result.Failure(AgentRuntimeFailure.From(ex)); } } } diff --git a/src/apps/tracker-api/Tracker.Tests/Presentation/Integration/AssistantTurnLedgerTests.cs b/src/apps/tracker-api/Tracker.Tests/Presentation/Integration/AssistantTurnLedgerTests.cs index 97e62f3d..fac2cec7 100644 --- a/src/apps/tracker-api/Tracker.Tests/Presentation/Integration/AssistantTurnLedgerTests.cs +++ b/src/apps/tracker-api/Tracker.Tests/Presentation/Integration/AssistantTurnLedgerTests.cs @@ -100,6 +100,43 @@ public async Task LaRespuestaDelRuntime_SeSigueDevolviendoIntacta() var cuerpo = await respuesta.Content.ReadFromJsonAsync(); cuerpo.GetProperty("reply").GetString().Should().Be("respuesta de prueba"); } + + [Fact] + public async Task Un401DelRuntime_SeDevuelveComo401ConCodigoEspecifico() + { + using var factory = AssistantLedgerWebApplicationFactory.WithRuntimeFailure( + "AgentRuntime.HttpError", + "Agent Runtime converse failed with HTTP 401.", + StatusCodes.Status401Unauthorized); + using var client = factory.CreateClient(); + var conversacion = $"conv-{Guid.NewGuid():N}"; + + var respuesta = await client.PostAsJsonAsync("/api/v1/assistant/converse", new + { + message = "runtime auth probe", + conversationId = conversacion, + }); + + respuesta.StatusCode.Should().Be(HttpStatusCode.Unauthorized); + + var cuerpo = await respuesta.Content.ReadFromJsonAsync(); + cuerpo.GetProperty("code").GetString().Should().Be("AgentRuntime.HttpError"); + cuerpo.GetProperty("message").GetString() + .Should().Be("Agent Runtime converse failed with HTTP 401."); + + var asientos = await factory.Ledger.GetByEntityAsync("AgentTurn", conversacion); + var incluyeMotivoRuntime = asientos.Any(a => + { + if (!a.Changes.TryGetValue("failureReason", out var reason)) + { + return false; + } + + return Convert.ToString(reason, System.Globalization.CultureInfo.InvariantCulture)! + .Contains("AgentRuntime.HttpError"); + }); + incluyeMotivoRuntime.Should().BeTrue(); + } } /// @@ -111,6 +148,23 @@ public async Task LaRespuestaDelRuntime_SeSigueDevolviendoIntacta() public sealed class AssistantLedgerWebApplicationFactory : WebApplicationFactory { public InMemoryAuditEntryRepository Ledger { get; } = new(); + private readonly IAgentRuntimeGateway _runtimeGateway; + + public AssistantLedgerWebApplicationFactory() + : this(new StubAgentRuntimeGateway()) + { + } + + private AssistantLedgerWebApplicationFactory(IAgentRuntimeGateway runtimeGateway) + { + _runtimeGateway = runtimeGateway; + } + + public static AssistantLedgerWebApplicationFactory WithRuntimeFailure( + string code, + string message, + int statusCode) => + new(new FailingAgentRuntimeGateway(code, message, statusCode)); protected override void ConfigureWebHost(IWebHostBuilder builder) { @@ -144,7 +198,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) services.AddScoped(); services.RemoveAll(); - services.AddSingleton(); + services.AddSingleton(_runtimeGateway); }); } @@ -164,4 +218,16 @@ public Task ConverseAsync( return Task.FromResult(doc.RootElement.Clone()); } } + + private sealed class FailingAgentRuntimeGateway( + string code, + string message, + int statusCode) : IAgentRuntimeGateway + { + public Task ConverseAsync( + AssistantConverseRequest request, ITrackerUserContext user, CancellationToken cancellationToken) + { + throw new AgentRuntimeGatewayException(code, message, statusCode); + } + } } From 36357aa8310131f924370c2b90ce61c0ea0108cb Mon Sep 17 00:00:00 2001 From: aarroyo Date: Mon, 10 Aug 2026 11:05:01 -0500 Subject: [PATCH 4/4] docs(gaps): refresh execution handoff after lv-26 --- ...racker-gap-execution-handoff-2026-08-10.md | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/docs/audit/tracker-gap-execution-handoff-2026-08-10.md b/docs/audit/tracker-gap-execution-handoff-2026-08-10.md index 29e21e90..96e6296a 100644 --- a/docs/audit/tracker-gap-execution-handoff-2026-08-10.md +++ b/docs/audit/tracker-gap-execution-handoff-2026-08-10.md @@ -6,12 +6,13 @@ ## Verified State -- Branch checked: `develop` at `a9cc013`. +- Branch checked: `develop` at `69808cd`. - Gap registry validator: `204` rows/details coherent. -- Status count: `DONE=179`, `PENDING=13`, `DEFERRED=7`, `BLOCKED=3`, `SUPERSEDED=1`, `WONTFIX=1`. -- Current canonical pending count: `13`, not `17` or `19`. +- Status count: `DONE=180`, `PENDING=12`, `DEFERRED=7`, `BLOCKED=3`, `SUPERSEDED=1`, `WONTFIX=1`. +- Current canonical pending count: `12`, not `17` or `19`. - No canonical `IN-PROGRESS` gaps were found. -- `origin/develop...origin/main`: `develop` has `1` commit not in `main`; `main` has `8` merge commits not in `develop` by graph shape. +- `LV-26` is closed by commit `69808cd` and pushed to `develop`. +- Merge to `main` is represented by PR #147 (`codex/main-merge-20260810` -> `main`); the PR is mergeable but blocked while required checks are queued/protected. - The current working tree is dirty with UMS/auth/docs changes. Treat those as unrelated/user work unless explicitly instructed otherwise. ## Token Strategy @@ -28,13 +29,12 @@ Safe units: 6. Merge `develop` into `main`. 7. Stop and leave the next lane in a new handoff if token budget is low. -The safest first implementation is `LV-26`, because it is isolated and small. +`LV-26` was the first safe implementation and is now complete. The next useful unit is Lane B, but it is medium-sized and should not start if the plan budget is low. ## Pending Gaps After Code Review | Gap | Real current reading | Remaining work | |---|---|---| -| `LV-26` | Small backend bug. | Preserve runtime `401/403` instead of flattening to `502`. | | `CP-05` | Backend preserves Core intelligence; web does not fully consume it. | Add typed web model/render for `intelligence`: recommendations, risks, actions, signals, kinds. | | `CP-07` | Core deposits can be attached to SDLC records. | Make attached deposits visible in the phase/gate user flow and robot evidence. | | `CP-10` | Parity robot exists. | Extend robot to assert visible recommendations and live Core evidence. | @@ -54,12 +54,8 @@ The safest first implementation is `LV-26`, because it is isolated and small. - Scope: `LV-26`. - Why first: low risk, small diff, improves diagnosis against agent-runtime. -- Likely files: - - `src/apps/tracker-api/Tracker.Presentation/Integration/AgentRuntimeTurnExecutor.cs` - - `src/apps/tracker-api/Tracker.Presentation/Endpoints/Integration/AssistantEndpoints.cs` - - `src/apps/tracker-api/Tracker.Tests/Presentation/Integration/AgentRuntimeGatewayTests.cs` - - Possibly assistant endpoint tests. -- Expected closure: one commit. +- Status: `DONE` in commit `69808cd` (`fix(agent-runtime): preserve upstream failure status`). +- Evidence: `AssistantTurnLedgerTests.Un401DelRuntime_SeDevuelveComo401ConCodigoEspecifico` and `python3 .harness/scripts/check-gap-registry.py`. ### Lane B — Core Intelligence Visible @@ -112,11 +108,13 @@ Use this prompt to resume: ```text Winston, retoma desde docs/audit/tracker-gap-execution-handoff-2026-08-10.md. -Tenemos 13 gaps PENDING canónicos. No uses el working tree actual si sigue sucio: -crea una worktree/rama limpia desde origin/develop. Empieza por Lane A (LV-26), -implementa el fix pequeño, corre tests focalizados y python3 .harness/scripts/check-gap-registry.py. -Si pasa, haz commit, push a develop y merge a main siguiendo la política acordada. -No abras Lane B hasta cerrar LV-26. +Tenemos 12 gaps PENDING canónicos. No uses el working tree actual si sigue sucio: +crea una worktree/rama limpia desde origin/develop. LV-26 ya está cerrado en +69808cd; empieza por Lane B (CP-05 + CP-07 + CP-10) sólo si hay presupuesto +para una tarea mediana. Si el presupuesto es bajo, limita la sesión a mapear +tipos/superficies y deja el plan de patch antes de tocar código. +Al cerrar cualquier lane: pruebas focalizadas, python3 .harness/scripts/check-gap-registry.py, +commit, push a develop y actualizar PR #147 hacia main. ``` ## Verification Commands @@ -128,4 +126,4 @@ python3 .harness/scripts/check-gap-registry.py git status --short ``` -For `LV-26`, also run targeted backend tests around agent runtime and assistant conversation. +For Lane B, also run focused frontend typecheck/build or robot tests for the touched surfaces.