ML-161 (and subtasks): research and plan

This commit is contained in:
Claudio Ortolina
2026-05-04 09:17:16 +01:00
parent 4f167c4f8f
commit 50b9023707
5 changed files with 399 additions and 0 deletions
@@ -0,0 +1,59 @@
---
id: ML-161
title: pi access to production errors
status: To Do
assignee: []
created_date: '2026-05-04 08:06'
updated_date: '2026-05-04 08:10'
labels:
- pi
- api
- observability
dependencies:
- ML-162
- ML-163
- ML-164
references:
- 'backlog://document/doc-7'
priority: medium
ordinal: 4000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Errors in production are captured via the `error_tracker` Elixir dependency and accessed via a dedicated dashboard at `/dev/errors`. There is no built-in tooling or endpoint to pull production errors programmatically in a pi session. This task covers the investigation and implementation of the best approach to expose production error data to pi, broken into three subtasks:
1. **Expose production errors via a programmatic API (behind auth)** — Add a JSON API endpoint under `/api/v1/errors` that lists errors and shows individual error details from the error_tracker tables.
2. **Create pi tools to pull errors** — Implement `fetch_production_errors` (list with filtering/pagination) and `fetch_production_error` (single error detail) as pi tools.
3. **Create a pi extension to browse errors** — Build an interactive TUI browsing experience via a pi extension that uses the tools from subtask 2.
### Error data attributes
These are the attributes that subtask implementations must treat as the canonical data model:
**Error (from `error_tracker_errors` table):**
- `id` — UUID, unique error identifier
- `kind` — string, e.g. "error", "throw", "exit"
- `reason` — string, the error message/reason
- `source_line` — string, e.g. "lib/foo.ex:42"
- `source_function` — string, e.g. "MusicLibrary.Foo.bar/2"
- `status` — atom: `:resolved` | `:unresolved`
- `fingerprint` — hex string, deterministic hash of (kind, source_line, source_function)
- `last_occurrence_at` — UTC datetime (microsecond precision)
- `muted` — boolean
- `inserted_at` / `updated_at` — UTC datetime
**Occurrence (from `error_tracker_occurrences` table):**
- `id` — UUID
- `reason` — string, error reason at time of occurrence
- `context` — map, includes `live_view.view`, `request.path`, etc.
- `breadcrumbs` — array of strings
- `stacktrace` — embedded struct with `lines` array (each having: `application`, `module`, `function`, `arity`, `file`, `line`)
- `error_id` — FK to error
- `inserted_at` — UTC datetime
**Aggregated/per-error metadata (computed, not stored):**
- `occurrence_count` — total occurrences for this error
- `first_occurrence_at` — earliest occurrence timestamp
<!-- SECTION:DESCRIPTION:END -->
@@ -0,0 +1,47 @@
---
id: ML-162
title: Expose production errors via JSON API endpoint
status: To Do
assignee: []
created_date: '2026-05-04 08:08'
updated_date: '2026-05-04 08:10'
labels:
- api
- elixir
- backend
dependencies: []
parent_task_id: DRAFT-1
priority: medium
ordinal: 7000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add an API controller and routes under `/api/v1/errors` to expose ErrorTracker data as JSON, behind the existing Bearer token auth.
This subtask covers the server-side work only: controller, JSON serialization, context queries, and routes. The pi tooling and extensions are covered by separate subtasks.
### API design
**`GET /api/v1/errors`** — List errors
- Query params: `status` (resolved/unresolved), `muted` (true/false), `search` (substring match on reason), `limit` (default 50), `offset` (default 0)
- Returns: `{ errors: [...], total: n, limit: n, offset: n }`
- Each error includes: id, kind, reason, source_line, source_function, status, fingerprint, last_occurrence_at, muted, inserted_at, updated_at, occurrence_count, first_occurrence_at
**`GET /api/v1/errors/:id`** — Single error detail
- Returns the error with all its occurrences (with stacktraces), sorted by inserted_at desc
- Each occurrence includes: id, reason, context, breadcrumbs, stacktrace (lines), inserted_at
### Data attributes (canonical — shared with all subtasks)
**Error fields:** id, kind, reason, source_line, source_function, status, fingerprint, last_occurrence_at, muted, inserted_at, updated_at
**Occurrence fields:** id, reason, context, breadcrumbs, stacktrace (with lines), error_id, inserted_at
**Computed:** occurrence_count, first_occurrence_at
### Dependencies
- Uses `MusicLibrary.TelemetryRepo` (already exists)
- Uses the `error_tracker_errors` and `error_tracker_occurrences` tables (already exist)
- Auth via existing `require_api_token` plug (already in use by `/api/v1` pipeline)
<!-- SECTION:DESCRIPTION:END -->
@@ -0,0 +1,51 @@
---
id: ML-163
title: Create pi tools to fetch production errors
status: To Do
assignee: []
created_date: '2026-05-04 08:08'
updated_date: '2026-05-04 08:15'
labels:
- pi
- tool
- typescript
dependencies: []
parent_task_id: DRAFT-1
priority: medium
ordinal: 5000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Create `fetch_production_errors` and `fetch_production_error` pi tools that call the `/api/v1/errors` JSON API endpoint.
These tools are registered via `pi.registerTool()` in a pi extension. They make authenticated HTTP requests to the production server using the same HURL variable pattern established by `fetch_production_logs` (ML-160).
### Tools
**`fetch_production_errors`** — List/filter production errors
- Parameters: `status` (optional: "resolved" | "unresolved"), `muted` (optional: boolean), `search` (optional: string, substring match on reason), `limit` (optional: number, default 50), `offset` (optional: number, default 0)
- Calls `GET /api/v1/errors` with query params
- Returns: formatted list of errors with counts and timestamps
- Truncates output with `truncateTail` (50KB / 2000 lines)
**`fetch_production_error`** — Get a single error with full details
- Parameters: `id` (required: UUID string)
- Calls `GET /api/v1/errors/:id`
- Returns: error details with all occurrences, stacktraces, and context
- Truncates output with `truncateTail`
### Tool guidelines (promptGuidelines)
- Use fetch_production_errors when investigating what errors are occurring in production
- Use fetch_production_error when you need full details on a specific error, including stacktraces and context
- Start with a small limit (e.g., 20) and filter by status or search before fetching large result sets
### Auth
Uses the same HURL variable pattern as `fetch_production_logs`:
- `PI_API_TOKEN` — Bearer token for API auth
- `PI_SERVICE_FQDN_WEB` — Production domain
They need to be configured in the pi environment as local secrets.
<!-- SECTION:DESCRIPTION:END -->
@@ -0,0 +1,79 @@
---
id: ML-164
title: Create pi extension for interactive error browsing
status: To Do
assignee: []
created_date: '2026-05-04 08:08'
updated_date: '2026-05-04 08:10'
labels:
- pi
- extension
- typescript
- tui
dependencies: []
parent_task_id: DRAFT-1
priority: medium
ordinal: 6000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Build a pi extension that provides an interactive TUI for browsing production errors, using the `fetch_production_errors` and `fetch_production_error` tools from the parent task.
This extension gives the user (and LLM) a browseable interface for production errors, accessible via a slash command like `/prod-errors`.
### Extension features
1. **`/prod-errors` command** — Opens an interactive TUI using `ctx.ui.custom()`:
- Lists recent errors (unresolved first, then by last_occurrence_at desc)
- Shows key metadata: reason (truncated), kind, source location, occurrence count, last seen, status badge, muted indicator
- Keyboard navigation: up/down to select, Enter to view details, Escape to close
- Filter toggle: show/hide resolved, show/hide muted (keyboard shortcuts)
- Pagination: load more errors as user scrolls
2. **Error detail view** (Enter on an error):
- Full reason text
- Source location with link-like formatting
- Status, muted, fingerprint
- Timeline of occurrences with timestamps
- Stacktrace display (collapsible per occurrence)
- Context display (request path, LiveView, etc.)
- Breadcrumbs if present
3. **UI patterns** (consistent with existing `/prod-logs` extension):
- Uses `pi.exec("curl", ...)` to call the API
- Reads credentials from `resolveVar()` (same pattern as prod-logs)
- Keyboard shortcuts displayed as hints
- Theme-aware rendering via `ctx.ui.theme`
### TUI component design
```
╔══════════════════════════════════════════════════╗
║ Production Errors [12 errors] ║
╠══════════════════════════════════════════════════╣
║ ▶ [UNRESOLVED] FunctionClauseError ║
║ MusicLibrary.Foo.bar/2 lib/foo.ex:42 ║
║ 23 occurrences · last seen 2h ago ║
║ ──────────────────────────────────────────────── ║
║ [RESOLVED] MatchError ║
║ MusicLibrary.Baz.qux/1 lib/baz.ex:15 ║
║ 5 occurrences · last seen 3d ago ║
║ ──────────────────────────────────────────────── ║
║ [MUTED] KeyError (key :foo not found) ║
║ MusicLibrary.Other.func/3 lib/other.ex:99 ║
║ 1 occurrence · last seen 7d ago ║
║ ──────────────────────────────────────────────── ║
║ ║
║ ↑↓ navigate ↵ details r toggle resolved ║
║ m toggle muted q quit ║
╚══════════════════════════════════════════════════╝
```
### File location
`.pi/extensions/prod-errors/index.ts` (new extension, separate from `prod-logs`)
The prod-logs extension already provides the `resolveVar` pattern and `fetchLogs` function. This extension follows the same conventions but for error_tracker data.
<!-- SECTION:DESCRIPTION:END -->