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.
8.0 KiB
8.0 KiB
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_patchstring enums), Phase 05 (issue/retryoutput 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 forinternal/translate/enums.go, published viadocs/schema/08-go-baseline.mdin 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
*_labelfield name (status,request_type,agent,notification_type) and the raw int in its*_codetwin (status_code,request_type_code,agent_code,notification_type_code) — preserves provenance; - unmapped int → omit the label field, emit only
*_codeint, append warningunmapped upstream enum value <n> for <field>towarnings[].
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)
// 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's08-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
*_codefields 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,
*_codetwin fields, read-side fallback withwarnings[], and input-sideINVALID_ARGUMENTon 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/RequestLimitTypelabel maps remain unverified.