API DEV TOOLS IMPLEMENTATION PLAN

Documentation for Sessions

API Dev Tools — Implementation Plan (Session 229)

Branch: session-229-230 (concurrent with session-230)
Scope: Backend instrumentation first, then frontend. One unified route with filtering.
Revision: v3 — incorporates v2 re-analysis (forRoot pattern, SSE exclusion, LoggingInterceptor recommendation, WebSocket out-of-scope).


1. Executive Summary

Goal: Build API Dev Tools in gograb-dev so developers can see real-time activity across all 4 APIs, filter by endpoint/user/role/event type, and generate reports.

Order: Backend instrumentation → Frontend. No shortcuts.

User/role: backend-api is the canonical source for users and roles. All APIs log userId (from JWT when present). Frontend fetches user list (with roles) from backend-api and correlates with activity for role-based filtering.


2. Architecture Decisions

2.1 One Route vs Per-API Routes

Decision: One unified route with filter-by-API.

2.2 User/Role Filtering

Decision: backend-api is the canonical source.

Assumption verified: JWT strategy maps sub → userId in req.user (unified-auth.guard.ts → createUserCaller()).

2.3 Shared Backend Library

Decision: Add request activity instrumentation to @libs-api/backend-core.

2.4 Frontend Library Category

Decision: @libs-structural/api-tools — NOT @libs-api.


3. Phase 1: Backend Instrumentation

3.1 Add to @libs-api/backend-core

Artifact Purpose
RequestActivityRecord Interface: { requestId, method, path, statusCode, userId?, role?, durationMs, timestamp, recordType }
RequestActivityStore In-memory ring buffer (configurable size, default 2000). Simple circular array — Node.js is single-threaded, no synchronization needed.
RequestActivityInterceptor NestJS interceptor: captures request/response, extracts userId/role from req.user, pushes to store. Composes with or extends existing LoggingInterceptor — avoid duplicating extraction logic; use hook/callback pattern if LoggingInterceptor can emit data.
RequestActivityModule NestJS module: provides interceptor + controller. Registers GET /dev/activity.
DevActivityController Controller under @Controller('dev') with @Get('activity'). Uses @Roles('developer', 'admin', 'system') decorator (idiomatic NestJS).
DevActivityGuard Guard: only allow in non-production OR when ENABLE_DEV_ACTIVITY=true. Returns 404 otherwise.

Record type (4-tier classification):

Code Range Record Type Rationale
2xx success Normal operation
3xx redirect Informational, rarely relevant
4xx client_error Client-side issues (auth failures, bad requests)
5xx server_error Backend failures — real alerts

Alternative (3-tier): success (2xx–3xx), client_error (4xx), server_error (5xx). Never conflate redirects with errors.

excludePaths: Config to skip logging certain paths. Default: ['/dev/', '/health', '/system/']. Prevents polling noise (frontend polls /dev/activity and /health every 5–10s) from flooding the ring buffer. With 4 APIs × 2 endpoints × 2 polls/min ≈ 96 requests/min of noise; without exclusion, 2000-record buffer fills in ~20 minutes.

SSE long-lived connections: The codebase has 15+ SSE endpoints (Vitto engine, module generation, MCP gateway, deployment, etc.). SSE connections stay open for minutes or hours. Without exclusion, the interceptor would record durationMs: 300000+ and a single statusCode when the connection closes — producing misleading "ultra-slow request" entries. Add SSE paths to excludePaths (e.g., /engine/process, /module-generation/generate, /mcp/, /deployment/status) or detect content-type: text/event-stream and skip recording. Option 1 (path exclusion) is simplest.

Do NOT log: Request/response bodies, query params (could contain tokens). Only: method, path (as-is for dev tools), statusCode, userId, role, duration, timestamp.

3.2 Activity Endpoint Contract

GET /api/dev/activity      (backend-api)
GET /ai-api/dev/activity  (backend-ai-api)
GET /docs-api/dev/activity (backend-docs-api)
GET /vitto-api/dev/activity (backend-vitto-api)

Query params:

Param Type Description
userId string Filter by userId
role string Filter by role (exact match)
endpoint string Filter by path (substring)
method string GET, POST, etc.
recordType string success, redirect, client_error, server_error
since ISO8601 Only records after this time
limit number Max records (default 100, max 500)
offset number (Optional, known limitation) Pagination offset. MVP: limit-only (most recent). Future: add offset for historical scrolling.

Response:

{
  "api": "backend-api",
  "records": [
    {
      "requestId": "req_xxx",
      "method": "GET",
      "path": "/api/users/me",
      "statusCode": 200,
      "userId": "usr_abc",
      "role": "developer",
      "durationMs": 45,
      "timestamp": "2026-03-17T16:00:00.000Z",
      "recordType": "success"
    }
  ],
  "total": 1
}

3.3 LoggingInterceptor Overlap

Problem: Existing LoggingInterceptor already extracts method, url, statusCode, durationMs, requestId. Duplicating in RequestActivityInterceptor is wasteful.

Solution (choose one):

  1. Extend LoggingInterceptor: Add optional callback/hook that receives the extracted data; RequestActivityStore subscribes. No second extraction.
  2. Compose: RequestActivityInterceptor delegates to LoggingInterceptor for extraction, then pushes to store.
  3. Refactor LoggingInterceptor: Extract a shared RequestMetrics type and extractRequestMetrics(req, res, startTime) utility; both interceptors use it.

Recommended: Option 3. Cleanest approach: doesn't modify LoggingInterceptor's behavior, gives both interceptors a stable contract, and avoids coupling.

3.4 Integration Per API

Each API (backend-api, backend-ai-api, backend-docs-api, backend-vitto-api):

  1. Import RequestActivityModule.forRoot() from @libs-api/backend-core — dynamic module pattern (same as RedisModule.forRoot(), AuthCoreModule.forRoot()).
  2. Pass API-specific config so the controller knows which API it's running in and can populate the response api field:
RequestActivityModule.forRoot({
  apiName: 'backend-api',     // → populates response `api` field
  bufferSize: 2000,           // optional override
  excludePaths: ['/dev/', '/health', '/system/', '/engine/process', '/module-generation/generate', '/mcp/', '/deployment/status'],  // optional; includes SSE paths
})
  1. Register in app module.
  2. Ensure RequestActivityInterceptor runs after auth guards (NestJS lifecycle: Guards → Interceptors → Pipes → Handler). Use APP_INTERCEPTOR.

Path consideration: Controller @Controller('dev') + @Get('activity') under backend-api (prefix api) → /api/dev/activity. Verified: NestJS global prefix applies to all controllers.

3.5 Auth for Dev Activity Endpoint

Decision: Require authentication. Use @Roles('developer', 'admin', 'system') decorator on DevActivityController (idiomatic NestJS; avoid manual in-handler role check like system/explorer).

Defense-in-depth: DevActivityGuard checks NODE_ENV !== 'production' || process.env.ENABLE_DEV_ACTIVITY === 'true'. In production without the flag, return 404. Even if ENABLE_DEV_ACTIVITY leaks to prod, auth still protects.


4. Phase 2: Frontend

4.1 Create @libs-structural/api-tools Library

Category: libs-structural (route-level Angular). NOT libs-api (NestJS backend).

Component Purpose
ApiHealthCardComponent Reusable health card per API (status, uptime, version)
ApiActivityViewComponent Activity list with virtual scroll, filterable
ApiToolsFilterBarComponent Filters: API, endpoint, user, role, event type, record type
ApiToolsService Fetches health + activity from all 4 APIs. RxJS, no Promises. Per-API error isolation: use catchError per observable so one down API doesn't fail the whole forkJoin.
ApiToolsReportService Export filtered activity (CSV, JSON). Client-side generation.
Models ApiHealth, ApiActivityRecord, ApiToolsFilter in models/

4.2 Routes in gograb-dev

Path Component Description
/api-tools ApiToolsOverviewComponent 4 health cards + summary stats
/api-tools/activity ApiToolsActivityComponent Activity feed + filter bar
/api-tools/reports ApiToolsReportsComponent Report config + export

Relationship to /infrastructure: The existing /infrastructure route shows agent/session/pipeline stats. API Tools shows request-level API activity. Complementary, not overlapping. Nav: Infrastructure under "System", API Tools as a new section.

4.3 Data Flow

4.4 Environment: vittoApiUrl

Gap: environment.ts has backendApiUrl, aiApiUrl, docsApiUrl but not vittoApiUrl. proxy.conf.json already routes /vitto-api → localhost:3003.

Fix: Add vittoApiUrl: VITTO_API_URLS.localProxy to environment.ts. Import VITTO_API_URLS from @libs-foundational/api-config.

4.5 Real-Time


5. Implementation Order

Phase 1 (Backend)

Step Task
1.1 Add RequestActivityRecord, RequestActivityStore to @libs-api/backend-core. Extract shared extractRequestMetrics() utility (Option 3).
1.2 Add RequestActivityInterceptor with excludePaths config (include SSE paths: /engine/process, /module-generation/generate, /mcp/, /deployment/status).
1.3 Add RequestActivityModule.forRoot(), DevActivityController (with @Roles), DevActivityGuard to @libs-api/backend-core.
1.4 Export from backend-core index.ts.
1.5 Integrate into backend-api: RequestActivityModule.forRoot({ apiName: 'backend-api' }).
1.6 Integrate into backend-ai-api: RequestActivityModule.forRoot({ apiName: 'backend-ai-api' }).
1.7 Integrate into backend-docs-api: RequestActivityModule.forRoot({ apiName: 'backend-docs-api' }).
1.8 Integrate into backend-vitto-api: RequestActivityModule.forRoot({ apiName: 'backend-vitto-api' }).
1.9 Verify: each API exposes GET /{prefix}/dev/activity with auth. Test with curl.

Phase 2 (Frontend)

Step Task
2.1 Create @libs-structural/api-tools library (yarn create:lib:proper, category = libs-structural).
2.2 Add models, services, components.
2.3 Add path to config/tsconfig-paths/modules/generated.paths.json, run yarn paths:merge.
2.4 Add vittoApiUrl: VITTO_API_URLS.localProxy to gograb-dev environment.ts.
2.5 Add routes to gograb-dev app.routes.ts.
2.6 Add nav entry for API Tools in gograb-dev layout.
2.7 Implement per-API error isolation in ApiToolsService (catchError per observable).
2.8 Add excludePaths filtering client-side (hide /dev/, /health from activity feed by default).
2.9 Verify: full flow works (health, activity, filters, reports).

6. Risks and Mitigations

Risk Mitigation
In-memory store loses data on restart Acceptable for dev tools. Document. Future: optional Redis backend.
Performance: interceptor on every request Minimal work: push to ring buffer. excludePaths reduces noise.
Sensitive data in path Don't log query string. Path as-is for dev tools.
backend-api users list for filter Add GET /api/dev/users (dev-only, admin role) if none exists.
backend-docs-api, backend-vitto-api auth Verify JWT populates req.user. userId/role may be null if not.
Pagination: no offset Known limitation. limit-only for MVP. Add offset later if needed.

7. Out of Scope (Initial)


8. Assumptions (Verified)

# Assumption Status
1 req.user.userId populated after JWT auth Verified: unified-auth.guard.ts → createUserCaller() maps sub → userId
2 NestJS global prefix applies to imported module controllers Verified: standard NestJS behavior
3 APP_INTERCEPTOR runs after APP_GUARD Verified: Guards → Interceptors → Pipes → Handler
4 backend-api has admin users list Unverified. Add GET /api/dev/users if missing.

9. Verification Checklist


10. Revision History

Version Changes
v1 Initial plan
v2 Senior review: (1) Frontend lib → @libs-structural/api-tools, (2) Record type 4-tier, (3) LoggingInterceptor overlap, (4) excludePaths, (5) vittoApiUrl, (6) Per-API error isolation, (7) Drop thread-safe, (8) Pagination offset note, (9) Naming consistency, (10) @Roles decorator, (11) Step numbering
v3 v2 re-analysis: (1) forRoot({ apiName }) dynamic module pattern, (2) SSE long-lived connections → excludePaths, (3) WebSocket activity capture in Out of Scope, (4) LoggingInterceptor Option 3 recommended