docs: GlitchTip read-only error/event tools design (#73)
Add docs/architecture/glitchtip-readonly-tools-design.md: implementation- ready design for the GlitchTip observability boundary's read-only tool set — minimum tools (whoami, list_projects, list_unresolved, get_issue, recent_events, search; GET-only), field-level allowlist projection with an explicit never-returned list (headers, cookies, auth fields, emails, IPs, bodies, locals, raw frames), default fingerprint/release/summary+permalink output, raw access gated behind a distinct approval-only glitchtip.event.read_raw operation, cursor pagination with explicit truncation flags, per-service credential/profile requirements (token by reference, fail closed), Sentry-compat API subset note, fail-closed failure matrix, mocked testing strategy with negative PII assertions, and a readiness checklist. Namespace left to ADR-0001 owner decision #2. Design only; no implementation, no mutation tools, no code behavior changed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,174 @@
|
|||||||
|
# GlitchTip Read-Only Error/Event Tools — Design Notes
|
||||||
|
|
||||||
|
- **Status:** Design (implementation-ready notes; **no implementation in this repo**)
|
||||||
|
- **Issue:** #73 (umbrella: #75; boundary decision: ADR-0001, #71)
|
||||||
|
- **Related:** #74 (GlitchTip→Gitea filing workflow — composes these read tools),
|
||||||
|
#78 (dedup/linking, child of #74), #76 (per-service profile schema)
|
||||||
|
- **Date:** 2026-07-02
|
||||||
|
|
||||||
|
## 1. Purpose and scope
|
||||||
|
|
||||||
|
Define the minimum **read-only** GlitchTip MCP tool set that lets an LLM answer:
|
||||||
|
*"What unresolved errors does project X have (by environment/release), and what
|
||||||
|
is this specific error?"* — with privacy-safe output suitable for LLM context,
|
||||||
|
issue bodies, and audit logs.
|
||||||
|
|
||||||
|
Strictly read-only, per ADR-0001:
|
||||||
|
|
||||||
|
- **No mutation tools** — no resolving/ignoring/assigning issues, no comment
|
||||||
|
posting, no project/team/key administration, no deletes.
|
||||||
|
- **No automatic GlitchTip→Gitea filing** (that is #74's *orchestrated,
|
||||||
|
explicitly-invoked* workflow; it composes these read tools and Gitea write
|
||||||
|
tools — never one dual-credential server).
|
||||||
|
- **This server never holds Gitea write credentials.**
|
||||||
|
|
||||||
|
## 2. Boundary placement (namespace pending)
|
||||||
|
|
||||||
|
These tools belong to the GlitchTip observability boundary of the MCP Control
|
||||||
|
Plane — `glitchtip-mcp` (ADR-0001's recommendation), `observability-mcp`, or
|
||||||
|
folded into `ops-mcp`. **ADR-0001 open owner decision #2 picks the name; this
|
||||||
|
design does not assume it.** Tool names below use the `glitchtip_` prefix for
|
||||||
|
readability and rename mechanically with the decision.
|
||||||
|
|
||||||
|
Fixed regardless of the name (per `tool-boundaries.md`,
|
||||||
|
`credential-isolation.md`):
|
||||||
|
|
||||||
|
- Own server process, own `.env`, GlitchTip credentials only.
|
||||||
|
- No Gitea, Jenkins, or Ops tokens in this runtime; no GlitchTip token
|
||||||
|
anywhere else.
|
||||||
|
|
||||||
|
## 3. API surface note (Sentry compatibility)
|
||||||
|
|
||||||
|
GlitchTip implements a Sentry-compatible REST API (`/api/0/...` — organizations,
|
||||||
|
projects, issues, events). The design targets **GlitchTip's documented subset**
|
||||||
|
only; Sentry-only endpoints must not be assumed. The implementation should pin
|
||||||
|
against a tested GlitchTip version and treat missing endpoints/fields as
|
||||||
|
degraded-but-safe (omit field, never crash).
|
||||||
|
|
||||||
|
## 4. Minimum read-only tool set
|
||||||
|
|
||||||
|
| Tool | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `glitchtip_whoami` | Verify authenticated identity + active profile (mirror of `gitea_whoami`; fail-closed identity proof) |
|
||||||
|
| `glitchtip_list_projects` | Projects visible to the token (org-scoped), with pagination bounds |
|
||||||
|
| `glitchtip_list_unresolved` | Unresolved issues for a project, filterable (§6), sorted by last-seen |
|
||||||
|
| `glitchtip_get_issue` | Safe detail of one issue (fields §5) |
|
||||||
|
| `glitchtip_recent_events` | Recent events for an issue (summaries only, §5) |
|
||||||
|
| `glitchtip_search` | Issue search within a project (query + filters §6) |
|
||||||
|
|
||||||
|
All tools are `GET`-only. No tool issues PUT/POST/DELETE.
|
||||||
|
|
||||||
|
## 5. Privacy: field-level allowlist (the core rule)
|
||||||
|
|
||||||
|
Error events routinely contain PII and secrets (request bodies, cookies,
|
||||||
|
headers, tokens, user emails/IPs, local variables). Therefore: **allowlist
|
||||||
|
projection only — raw event/issue payloads are never passed through.**
|
||||||
|
|
||||||
|
### Issue-level safe fields (`glitchtip_list_unresolved`, `glitchtip_get_issue`, `glitchtip_search`)
|
||||||
|
|
||||||
|
| Field | Notes |
|
||||||
|
|---|---|
|
||||||
|
| `issue_id` | GlitchTip issue ID (dedup key for #78) |
|
||||||
|
| `fingerprint` | When available (dedup key for #78) |
|
||||||
|
| `title` / `culprit` | Error type + short message/transaction — redactor-passed |
|
||||||
|
| `project` | Slug |
|
||||||
|
| `level` | error/warning/… |
|
||||||
|
| `status` | unresolved/… |
|
||||||
|
| `environment` | When filtered/available |
|
||||||
|
| `release` | Version string |
|
||||||
|
| `first_seen` / `last_seen` | ISO-8601 UTC |
|
||||||
|
| `event_count` / `user_count` | Numbers only — never user identities |
|
||||||
|
| `permalink` | GlitchTip web URL (the "link, not dump" principle) |
|
||||||
|
|
||||||
|
### Event-level safe fields (`glitchtip_recent_events`)
|
||||||
|
|
||||||
|
`event_id`, `timestamp`, `level`, `environment`, `release`, redactor-passed
|
||||||
|
`message`, and a **stack summary** only: top N (default 5) frames as
|
||||||
|
`module/filename:function:line` — in-app frames preferred.
|
||||||
|
|
||||||
|
### Redact / omit — never returned
|
||||||
|
|
||||||
|
Request headers; cookies; auth/session fields; user emails, usernames, IPs;
|
||||||
|
request/form bodies; query strings; local variables; full raw stack frames
|
||||||
|
(source context lines); SDK/device metadata beyond platform name; breadcrumbs;
|
||||||
|
any `extra`/`context` blobs.
|
||||||
|
|
||||||
|
Full raw frames or request context require a **separate, explicitly approved**
|
||||||
|
operation (`glitchtip.event.read_raw`) that is absent from default profiles —
|
||||||
|
same pattern as `jenkins.console.read` in the #72 design. Even then, output
|
||||||
|
passes the shared secret redactor; redaction failure ⇒ error, never raw text.
|
||||||
|
|
||||||
|
**Default output = fingerprint / release / summary + permalink.** The
|
||||||
|
permalink carries the human to the full data in GlitchTip's own UI, where its
|
||||||
|
access control applies — the MCP layer does not re-serve raw payloads.
|
||||||
|
|
||||||
|
## 6. Filtering and pagination
|
||||||
|
|
||||||
|
Filters (all optional, combinable): `project` (required for issue/event
|
||||||
|
queries), `environment`, `release`, `fingerprint`, free-text `query`
|
||||||
|
(GlitchTip search syntax, e.g. `is:unresolved`).
|
||||||
|
|
||||||
|
Pagination: cursor-based per the API. Bounds: per-page cap 50; default overall
|
||||||
|
cap 100 items; hard cap `max_pages` (default 10) against runaway loops —
|
||||||
|
mirroring `gitea_auth.api_get_all`. Truncation is **explicit** in the return
|
||||||
|
(`"truncated": true`) — never silent.
|
||||||
|
|
||||||
|
## 7. Credentials and profile requirements
|
||||||
|
|
||||||
|
Per-service profile model (`gitea-execution-profiles.md`, extended by #76):
|
||||||
|
|
||||||
|
- Env/config: `GLITCHTIP_URL`, `GLITCHTIP_ORG`, `GLITCHTIP_TOKEN_SOURCE_NAME`
|
||||||
|
(secret **name** only; value resolved at runtime, never logged/committed).
|
||||||
|
- Profile: e.g. `glitchtip-readonly` with namespaced
|
||||||
|
`allowed_operations: ["glitchtip.read", "glitchtip.event.read"]`
|
||||||
|
(+ `glitchtip.event.read_raw` only with explicit approval);
|
||||||
|
`forbidden_operations: ["glitchtip.issue.mutate", "glitchtip.admin"]`
|
||||||
|
belt-and-braces though no mutating tool exists.
|
||||||
|
- Missing URL/org/token/profile ⇒ **fail closed** before any network call.
|
||||||
|
- Read-only ⇒ no confirmation gates; identity (`glitchtip_whoami`) must work so
|
||||||
|
workflows can prove which account they read as.
|
||||||
|
|
||||||
|
## 8. Failure behavior (fail closed, clear, safe)
|
||||||
|
|
||||||
|
| Condition | Behavior |
|
||||||
|
|---|---|
|
||||||
|
| Unknown project/issue | Explicit `{"found": false, ...}` — no fuzzy matching |
|
||||||
|
| GlitchTip unreachable (DNS/timeout) | `"network error contacting GlitchTip: <redacted reason>"` — mirror `gitea_auth.api_request` conversion |
|
||||||
|
| 502/503/504 | "GlitchTip upstream unavailable" |
|
||||||
|
| 401/403 | "GlitchTip auth failed / insufficient permissions" — no credential echo |
|
||||||
|
| 429 | Honor Retry-After with capped jittered backoff (as `gitea_auth`) |
|
||||||
|
| Malformed JSON | "malformed JSON response from GlitchTip" — no raw-body dump |
|
||||||
|
| Missing profile/creds | Fail closed before any network call (§7) |
|
||||||
|
|
||||||
|
All error text passes the shared secret redactor.
|
||||||
|
|
||||||
|
## 9. Testing strategy (mocked; for the implementing package)
|
||||||
|
|
||||||
|
Mocked-GlitchTip unit tests only, per `docs/developer-testing-guidelines.md`:
|
||||||
|
|
||||||
|
- Assert method is always `GET`; URL/filter/cursor shape correct.
|
||||||
|
- **Projection tests:** response fixtures containing emails, IPs, cookies,
|
||||||
|
headers, request bodies, locals, full frames ⇒ none appear in output
|
||||||
|
(explicit negative assertions per §5's redact list).
|
||||||
|
- Stack summary: top-N frame cap enforced; source-context lines absent.
|
||||||
|
- Pagination: per-page/overall/max-pages caps; explicit `truncated` flag.
|
||||||
|
- Filters: environment/release/fingerprint/query passed through correctly.
|
||||||
|
- Failure matrix of §8 incl. no-token-in-error assertions.
|
||||||
|
- Profile gate: missing/insufficient profile ⇒ no network call
|
||||||
|
(`mock_api.assert_not_called()` pattern).
|
||||||
|
- `read_raw` op absent ⇒ raw-frame request refused without an API call.
|
||||||
|
|
||||||
|
## 10. Implementation-readiness checklist
|
||||||
|
|
||||||
|
Ready to implement once:
|
||||||
|
|
||||||
|
1. ADR-0001 owner decision #2 (namespace/placement) is made — mechanical
|
||||||
|
rename of the `glitchtip_` prefix if needed.
|
||||||
|
2. ADR-0001 owner decision #1 (repo home) is made.
|
||||||
|
3. #76 profile schema exists (or a minimal `glitchtip-readonly` profile is
|
||||||
|
hand-rolled to the same rules).
|
||||||
|
4. A pinned GlitchTip version is chosen for API-subset testing (§3).
|
||||||
|
|
||||||
|
Explicitly **not** unlocked by this document: any GlitchTip mutation, any
|
||||||
|
automatic Gitea filing (#74 designs that as a gated, explicitly-invoked
|
||||||
|
orchestrated workflow), any Gitea credentials in this boundary.
|
||||||
Reference in New Issue
Block a user