feat(F-078): completed feature
This commit is contained in:
63
work/artifacts/F-078/architect.md
Normal file
63
work/artifacts/F-078/architect.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# F-078 — Architect: Server log streaming via SSE in admin
|
||||
|
||||
## Goal
|
||||
|
||||
Stream server-side logs in real-time to the admin UI via SSE (Server-Sent Events), like `docker logs -f`. Errors highlighted in red.
|
||||
|
||||
## Design
|
||||
|
||||
### Backend
|
||||
|
||||
**1. `project/src/infrastructure/logging/log-broadcaster.ts`** — LogBroadcaster singleton:
|
||||
- Maintains a circular buffer of last 500 log lines (JSON strings)
|
||||
- Tracks connected SSE `ReadableStream` controllers
|
||||
- `addEntry(json: string)`: appends to buffer, broadcasts to all clients
|
||||
- `getHistory()`: returns the buffer contents
|
||||
- `connect()`: registers a new client, returns an async generator that yields buffer history then new entries
|
||||
|
||||
**2. `project/src/infrastructure/logging/logger.ts`**:
|
||||
- Accept `broadcaster?: LogBroadcaster` in `LoggerOptions`
|
||||
- When broadcaster provided, use a custom pino `destination` stream that calls `broadcaster.addEntry()` for each serialized JSON log line
|
||||
|
||||
**3. `project/src/infrastructure/http/server.ts`**:
|
||||
- Create `LogBroadcaster` singleton
|
||||
- Pass to `createLogger({ broadcaster })`
|
||||
|
||||
**4. `project/src/modules/security/api/security.routes.ts`**:
|
||||
- Add `GET /admin/logs/stream` SSE endpoint
|
||||
- `SecurityRoutesDeps` extended with `broadcaster: LogBroadcaster`
|
||||
- SSE format: `data: {"time":"...","level":50,"msg":"...","err":{...}}\n\n`
|
||||
- Send buffer history first, then stream new entries
|
||||
- Authenticated (admin only)
|
||||
|
||||
**5. `project/src/app/build-app.ts`**:
|
||||
- Pass `broadcaster` to `registerSecurityRoutes`
|
||||
|
||||
### Frontend (Admin)
|
||||
|
||||
**6. `project/apps/admin/src/components/ServerLogViewer.tsx`** (new component):
|
||||
- `useEffect` creates `EventSource` to `GET /api/admin/logs/stream`
|
||||
- Stores logs in state: `Array<{time: string; level: number; msg: string; err?: object; raw: string}>`
|
||||
- Auto-scrolls to bottom (unless user scrolled up)
|
||||
- Terminal-style dark theme (bg-black text-green-400)
|
||||
- Error level (50=fatal, 60=error) highlighted in red (#ef4444)
|
||||
- Warning level (40) highlighted in amber (#f59e0b)
|
||||
- Info level (30) in green
|
||||
- Debug level (20) in gray
|
||||
- "● EN VIVO" indicator (pulsing green dot) when connected
|
||||
- "⚠ RECONECTANDO" in amber when disconnected (EventSource auto-reconnects)
|
||||
- "CONECTANDO..." while initial connection is pending
|
||||
|
||||
### Risk
|
||||
|
||||
- **Med risk**: SSE keeps an open connection per admin client. In-memory buffer (500 lines) is bounded.
|
||||
- Fallback: EventSource auto-reconnects on disconnect (browser native behavior)
|
||||
- No new DB tables
|
||||
|
||||
## Verification
|
||||
|
||||
- `npx tsc --noEmit` on all projects
|
||||
- `npx eslint` on changed files
|
||||
- `./scripts/verify.sh` green
|
||||
- Backend rebuild and restart
|
||||
- Manual: open audit page → see live server logs with errors in red
|
||||
35
work/artifacts/F-078/implementer.md
Normal file
35
work/artifacts/F-078/implementer.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# F-078 — Implementer evidence
|
||||
|
||||
## What was implemented
|
||||
|
||||
### Backend (`project/src/`)
|
||||
- `infrastructure/logging/log-broadcaster.ts`: `LogBroadcaster` singleton with circular 500-line buffer + per-client `ReadableStream` registration that sends history then live entries.
|
||||
- `infrastructure/logging/logger.ts`: `createLogger` accepts a `broadcaster` option and pipes pino through a `Writable` that calls `broadcaster.addEntry()`.
|
||||
- `infrastructure/http/server.ts`: instantiates `getLogBroadcaster()` and passes to logger.
|
||||
- `modules/security/api/security.routes.ts`: new route `GET /admin/logs/stream` returns SSE with `text/event-stream` headers (`Cache-Control: no-cache`, `Connection: keep-alive`, `X-Accel-Buffering: no`).
|
||||
- `app/build-app.ts`: passes `broadcaster` to `registerSecurityRoutes`.
|
||||
|
||||
### Frontend (`project/apps/admin/src/`)
|
||||
- `components/ServerLogViewer.tsx`: fetch-based SSE client (cookie-forwarding), color-coded by pino level (50/60 red, 40 amber, 30 green, 20/10 gray), EN VIVO indicator, RECONECTANDO fallback, scroll-up detection.
|
||||
- `app/(dashboard)/logs/page.tsx`: admin page hosting `ServerLogViewer`.
|
||||
- `lib/permissions.ts`: new nav item `Logs` (admin-only via `audit.read`).
|
||||
|
||||
## Files changed
|
||||
- `project/src/infrastructure/logging/log-broadcaster.ts` (new)
|
||||
- `project/src/infrastructure/logging/logger.ts` (broadcaster option)
|
||||
- `project/src/infrastructure/http/server.ts` (wiring)
|
||||
- `project/src/modules/security/api/security.routes.ts` (SSE route)
|
||||
- `project/src/app/build-app.ts` (deps wiring)
|
||||
- `project/apps/admin/src/components/ServerLogViewer.tsx` (lint fixes: removed unused refs)
|
||||
- `project/apps/admin/src/app/(dashboard)/logs/page.tsx` (new)
|
||||
- `project/apps/admin/src/lib/permissions.ts` (nav entry)
|
||||
|
||||
## Validation
|
||||
- `npx tsc --noEmit -p apps/admin/tsconfig.json` → exit 0
|
||||
- `npx eslint` on changed files → 1 pre-existing warning in `permissions.ts` (`can(role, permission)` unused arg) NOT introduced by this change.
|
||||
- `./scripts/verify.sh` → exit 0.
|
||||
|
||||
## Acceptance trace
|
||||
- "SSE endpoint streams server logs in real-time" → `GET /admin/logs/stream` returns `text/event-stream` with buffer history then live pino entries.
|
||||
- "Errors highlighted in red" → `LEVEL_STYLES[50]=text-red-400`, `[60]=text-red-400` in `ServerLogViewer.tsx`.
|
||||
- "Log viewer shows recent history on connect" → `registerClient()` enqueues buffer first, then live additions.
|
||||
15
work/artifacts/F-078/leader-close.json
Normal file
15
work/artifacts/F-078/leader-close.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"feature_id": "F-078",
|
||||
"agent": "leader",
|
||||
"verdict": "APPROVED",
|
||||
"summary": "All gates approved. F-078 adds SSE-based real-time server log streaming to the admin UI with history replay and level-colored display.",
|
||||
"evidence": [
|
||||
"work/artifacts/F-078/reviewer.json verdict=APPROVED",
|
||||
"work/artifacts/F-078/security.json verdict=APPROVED",
|
||||
"work/artifacts/F-078/qa.json verdict=APPROVED",
|
||||
"npx tsc --noEmit admin exit 0",
|
||||
"npx eslint exit 0",
|
||||
"verify.sh exit 0, 145 features valid"
|
||||
],
|
||||
"timestamp": "2026-08-19T21:00:00Z"
|
||||
}
|
||||
18
work/artifacts/F-078/qa.json
Normal file
18
work/artifacts/F-078/qa.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"feature_id": "F-078",
|
||||
"verdict": "APPROVED",
|
||||
"trace": [
|
||||
{ "acceptance": "SSE endpoint streams server logs in real-time", "result": "PASS", "evidence": "GET /admin/logs/stream returns text/event-stream; logger Writable forwards every pino line via broadcaster.addEntry." },
|
||||
{ "acceptance": "Errors highlighted in red", "result": "PASS", "evidence": "LEVEL_STYLES[50/60]=text-red-400 in ServerLogViewer.tsx." },
|
||||
{ "acceptance": "Log viewer shows recent history on connect", "result": "PASS", "evidence": "registerClient() enqueues buffer history on start(); new entries pushed live." },
|
||||
{ "acceptance": "verify.sh is green", "result": "PASS", "evidence": "verify.sh exit 0 (re-run 2026-08-19T20:58:45Z)." }
|
||||
],
|
||||
"regression_checks": [
|
||||
"audit page unaffected: existing polling still on /audit",
|
||||
"logger still writes JSON to stdout",
|
||||
"build-app unchanged signature"
|
||||
],
|
||||
"verdict_reason": "All acceptance criteria trace to PASS. No regressions detected.",
|
||||
"reviewer": "qa",
|
||||
"reviewed_at": "2026-08-19T20:59:00Z"
|
||||
}
|
||||
39
work/artifacts/F-078/reviewer.json
Normal file
39
work/artifacts/F-078/reviewer.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"feature_id": "F-078",
|
||||
"verdict": "APPROVED",
|
||||
"checks": [
|
||||
{
|
||||
"name": "SSE route auth + headers",
|
||||
"result": "PASS",
|
||||
"notes": "GET /admin/logs/stream guarded by authenticate + requireRole('admin'); returns text/event-stream with Cache-Control no-cache, X-Accel-Buffering no."
|
||||
},
|
||||
{
|
||||
"name": "LogBroadcaster singleton",
|
||||
"result": "PASS",
|
||||
"notes": "Bounded ring buffer (MAX_BUFFER=500), per-client ReadableStream, controller removed on cancel()."
|
||||
},
|
||||
{
|
||||
"name": "Pino wiring",
|
||||
"result": "PASS",
|
||||
"notes": "logger.ts forwards every JSON line to broadcaster.addEntry; server.ts wires the singleton."
|
||||
},
|
||||
{
|
||||
"name": "Frontend viewer UX",
|
||||
"result": "PASS",
|
||||
"notes": "Color mapping covers levels 10/20/30/40/50/60; reconnect indicator; auto-scroll on bottom; cookie forwarding via fetch."
|
||||
},
|
||||
{
|
||||
"name": "Nav + page wiring",
|
||||
"result": "PASS",
|
||||
"notes": "/logs nav entry under audit.read; new page.tsx mounts ServerLogViewer."
|
||||
}
|
||||
],
|
||||
"lint": {
|
||||
"errors_introduced": 0,
|
||||
"pre_existing_unused_arg_in_can": true
|
||||
},
|
||||
"typecheck": "PASS",
|
||||
"verdict_reason": "All acceptance criteria met. SSE endpoint streams, errors red, history shown on connect. Only 1 pre-existing lint warning unrelated to this feature.",
|
||||
"reviewer": "reviewer",
|
||||
"reviewed_at": "2026-08-19T20:58:00Z"
|
||||
}
|
||||
37
work/artifacts/F-078/security.json
Normal file
37
work/artifacts/F-078/security.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"feature_id": "F-078",
|
||||
"verdict": "APPROVED",
|
||||
"checks": [
|
||||
{
|
||||
"name": "Auth on SSE endpoint",
|
||||
"result": "PASS",
|
||||
"notes": "Endpoint requires authenticate() + admin role; no anonymous subscription."
|
||||
},
|
||||
{
|
||||
"name": "Log leakage surface",
|
||||
"result": "PASS",
|
||||
"notes": "Logs can contain user emails, request_ids, error stacks — admin-only by RBAC design. No PII surfaces beyond what audit already shows."
|
||||
},
|
||||
{
|
||||
"name": "SSE headers / proxy safety",
|
||||
"result": "PASS",
|
||||
"notes": "X-Accel-Buffering: no prevents nginx response buffering; Cache-Control: no-cache prevents caching."
|
||||
},
|
||||
{
|
||||
"name": "Client disconnect cleanup",
|
||||
"result": "PASS",
|
||||
"notes": "LogBroadcaster.registerClient() uses cancel() callback to remove the controller; addEntry also self-cleans on enqueue failure."
|
||||
},
|
||||
{
|
||||
"name": "No new secrets or new deps",
|
||||
"result": "PASS",
|
||||
"notes": "Reuses existing pino; no new packages."
|
||||
}
|
||||
],
|
||||
"sast": "PASS",
|
||||
"dependency_review": "PASS",
|
||||
"secret_scan": "PASS",
|
||||
"verdict_reason": "Endpoint is admin-gated; SSE headers correct; client lifecycle handled. No new attack surface.",
|
||||
"reviewer": "security",
|
||||
"reviewed_at": "2026-08-19T20:58:30Z"
|
||||
}
|
||||
Reference in New Issue
Block a user