Files
ombi-mcp/docs/megaplans/m1/04-enum-translation-layer.md
gronod 722108ebd0 Initial commit: Ombi MCP server design docs and Go skeleton
Schema docs (docs/schema) cover tool mapping, input/output contracts and
endpoint coverage for all 31 MCP tools; megaplan M1 phases 01-03 applied
(namespace rename to read_*/write_*, input schema rectification).
Go module provides config loading, Ombi client, and tool registry
skeleton for implementation.
2026-09-18 15:55:44 +01:00

166 lines
8.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 04 — Semantic Abstraction & Enum Translation Layer
## Objective
Define the bidirectional string↔integer contract between agent-facing enums and Ombi wire constants, replacing the "numeric codes retained pending verification" stance for the four mapped enums.
## Prerequisites
- Phase 01 complete (tool names are `read_*`/`write_*`).
- Consumers of this contract: Phase 03 (`write_issue_create`, `write_request_reprocess`, `read_issues`, `write_issue_manage`, `write_settings_patch` string enums), Phase 05 (`issue`/`retry` output defs), Phase 06 (routing rule #4 rewrite and verification-gap rows).
- Documents mutated: `docs/schema/02-tool-mapping.md` (routing rule #4), `docs/schema/06-verification.md` (enum-labels gap row). The Go package below is the baseline for `internal/translate/enums.go`, published via `docs/schema/08-go-baseline.md` in Phase 06.
## Specification
### T1 — `RequestType` (upstream `Ombi.Store.Entities.RequestType`, `enum:[0,1,2]`)
**Critical**: upstream order is `TvShow=0, Movie=1, Album=2` — NOT positional. Verified against upstream source; still label **Verify** per-instance for version drift.
| MCP string | Upstream int | Used by |
|---|---|---|
| `movie` | 1 | `write_issue_create.request_type`, `write_request_reprocess.request_type`, issue/retry outputs |
| `tv` | 0 | same |
| `album` | 2 | same |
### T2 — `IssueStatus` (upstream `enum:[0,1,2,3]`; frontend confirms Pending=0, InProgress=1, Resolved=2; `closed`→3)
| MCP string | Upstream int | Used by |
|---|---|---|
| `pending` | 0 | `read_issues` list/summary `status`, `write_issue_manage.set_status.status`, issue output `status` |
| `in_progress` | 1 | same |
| `resolved` | 2 | same |
| `closed` | 3 | same |
### T3 — `NotificationAgent` (upstream `enum:[0..11]`, labels from upstream source)
| MCP string | Int | | MCP string | Int |
|---|---|---|---|---|
| `email` | 0 | | `mobile` | 7 |
| `discord` | 1 | | `gotify` | 8 |
| `pushbullet` | 2 | | `webhook` | 9 |
| `pushover` | 3 | | `whatsapp` | 10 |
| `telegram` | 4 | | `ntfy` | 11 |
| `slack` | 5 | | | |
| `mattermost` | 6 | | | |
### T4 — `NotificationType` (upstream `enum:[0..16]`, labels from upstream source)
| MCP string | Int | | MCP string | Int |
|---|---|---|---|---|
| `new_request` | 0 | | `issue_resolved` | 9 |
| `issue` | 1 | | `issue_comment` | 10 |
| `request_available` | 2 | | `newsletter` | 11 |
| `request_approved` | 3 | | `partially_available` | 12 |
| `admin_note` | 4 | | `plex_watchlist_token_expired` | 13 |
| `test` | 5 | | `request_deleted` | 14 |
| `request_declined` | 6 | | `issue_in_progress` | 15 |
| `item_added_to_fault_queue` | 7 | | `issue_deleted` | 16 |
| `welcome_email` | 8 | | | |
### Enums deliberately NOT abstracted (stay numeric, **Verify**)
`VoteType [0,1]` (labels unverified), `RequestSource`, `RequestLimitType [0,1,2]`, legacy `orderType`/`statusType`/`availabilityType` filter ints. Documented in 06-verification.md as remaining gaps.
### Read-side fallback contract (graceful degradation)
For every mapped enum in an upstream response:
- mapped int → emit string label in the `*_label` field name (`status`, `request_type`, `agent`, `notification_type`) **and** the raw int in its `*_code` twin (`status_code`, `request_type_code`, `agent_code`, `notification_type_code`) — preserves provenance;
- unmapped int → omit the label field, emit only `*_code` int, append warning `unmapped upstream enum value <n> for <field>` to `warnings[]`.
Input side: label fields are the only accepted form (`*_code` rejected via `additionalProperties:false`); unknown label → `INVALID_ARGUMENT`.
### Go translation package (baseline for `internal/translate/enums.go` — printed in full)
```go
// Package translate maps agent-facing enum labels to upstream Ombi
// integer constants. All maps are bidirectional.
package translate
import "fmt"
var requestTypeToWire = map[string]int{"movie": 1, "tv": 0, "album": 2}
var requestTypeFromWire = map[int]string{0: "tv", 1: "movie", 2: "album"}
var issueStatusToWire = map[string]int{
"pending": 0, "in_progress": 1, "resolved": 2, "closed": 3,
}
var issueStatusFromWire = map[int]string{
0: "pending", 1: "in_progress", 2: "resolved", 3: "closed",
}
var notificationAgentToWire = map[string]int{
"email": 0, "discord": 1, "pushbullet": 2, "pushover": 3,
"telegram": 4, "slack": 5, "mattermost": 6, "mobile": 7,
"gotify": 8, "webhook": 9, "whatsapp": 10, "ntfy": 11,
}
var notificationAgentFromWire = map[int]string{
0: "email", 1: "discord", 2: "pushbullet", 3: "pushover",
4: "telegram", 5: "slack", 6: "mattermost", 7: "mobile",
8: "gotify", 9: "webhook", 10: "whatsapp", 11: "ntfy",
}
var notificationTypeToWire = map[string]int{
"new_request": 0, "issue": 1, "request_available": 2,
"request_approved": 3, "admin_note": 4, "test": 5,
"request_declined": 6, "item_added_to_fault_queue": 7,
"welcome_email": 8, "issue_resolved": 9, "issue_comment": 10,
"newsletter": 11, "partially_available": 12,
"plex_watchlist_token_expired": 13, "request_deleted": 14,
"issue_in_progress": 15, "issue_deleted": 16,
}
var notificationTypeFromWire = map[int]string{
0: "new_request", 1: "issue", 2: "request_available",
3: "request_approved", 4: "admin_note", 5: "test",
6: "request_declined", 7: "item_added_to_fault_queue",
8: "welcome_email", 9: "issue_resolved", 10: "issue_comment",
11: "newsletter", 12: "partially_available",
13: "plex_watchlist_token_expired", 14: "request_deleted",
15: "issue_in_progress", 16: "issue_deleted",
}
// ErrUnknownLabel is returned for an out-of-enum agent label.
var ErrUnknownLabel = fmt.Errorf("unknown enum label")
func RequestTypeToWire(s string) (int, error) { return lookup(requestTypeToWire, s) }
func IssueStatusToWire(s string) (int, error) { return lookup(issueStatusToWire, s) }
func AgentToWire(s string) (int, error) { return lookup(notificationAgentToWire, s) }
func NotifTypeToWire(s string) (int, error) { return lookup(notificationTypeToWire, s) }
func RequestTypeFromWire(i int) (string, bool) { return lookupRev(requestTypeFromWire, i) }
func IssueStatusFromWire(i int) (string, bool) { return lookupRev(issueStatusFromWire, i) }
func AgentFromWire(i int) (string, bool) { return lookupRev(notificationAgentFromWire, i) }
func NotifTypeFromWire(i int) (string, bool) { return lookupRev(notificationTypeFromWire, i) }
func lookup(m map[string]int, s string) (int, error) {
v, ok := m[s]
if !ok {
return 0, fmt.Errorf("%w: %q", ErrUnknownLabel, s)
}
return v, nil
}
func lookupRev(m map[int]string, i int) (string, bool) {
s, ok := m[i]
return s, ok
}
```
## Target artefacts
- `docs/schema/02-tool-mapping.md` — shared routing rule #4: replace "Numeric-code inputs are deliberately retained… Publish a verified label map later" with the four label maps above (published); remaining numeric enums listed as gaps.
- `docs/schema/06-verification.md` — "Enum labels" gap row updated: T1–T4 marked **published**; `VoteType`/`RequestSource`/`RequestLimitType`/legacy filters remain gaps.
- `internal/translate/enums.go` — baseline specified in full above (consumed by Phase 06's `08-go-baseline.md`).
## Verification gates
- [ ] All four tables printed in both directions (label→int and int→label), 3/4/12/17 entries.
- [ ] `movie`→1, `tv`→0 documented explicitly with the non-positional warning.
- [ ] Input rejection of `*_code` fields and read-side fallback wording present.
- [ ] No tool schema still accepts integer enum codes for the four mapped enums.
## Evidence labels
- **Documented** — upstream enum value ranges (`RequestType [0,1,2]`, `IssueStatus [0,1,2,3]`, `NotificationAgent [0..11]`, `NotificationType [0..16]`) per RAML; frontend confirms Pending/InProgress/Resolved ordering.
- **Design** — the string-label contract, `*_code` twin fields, read-side fallback with `warnings[]`, and input-side `INVALID_ARGUMENT` on unknown labels are intentional MCP contracts backed by upstream-source evidence.
- **Verify** — `closed`→3 (not frontend-confirmed); per-instance version drift on all four maps; `VoteType`/`RequestSource`/`RequestLimitType` label maps remain unverified.