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.
964 lines
32 KiB
Markdown
964 lines
32 KiB
Markdown
# Phase 03 — Input Schema Defect Rectification
|
||
|
||
## Objective
|
||
|
||
Apply all input-schema fixes to `03-input-schemas.md`. Below is the **complete replacement JSON** for every modified tool; unmodified tools carry over verbatim (rename only, Phase 01). Order of `##` sections is unchanged.
|
||
|
||
## Prerequisites
|
||
|
||
- Phase 01 complete (all `##` section headers already carry the new `read_*`/`write_*` names).
|
||
- Phase 04 consulted for the string↔int enum maps referenced by `request_type`, `status`, `agent`, `notification_type` (the maps themselves are owned by Phase 04; this phase changes the schemas to strings).
|
||
- Source document to mutate: `docs/schema/03-input-schemas.md`.
|
||
|
||
## Specification
|
||
|
||
### 3.1 `read_media` — fix `by_request` enum
|
||
|
||
Only change vs. current doc: `by_request.media.enum` `["movie","tv","artist"]` → `["movie","tv","album"]`. Adapter maps `album`→`GET /api/v2/Search/artist/request/{requestId}` (route segment literally `artist`, but the ID namespace is album requests — keep the **Verify** note; response `ArtistInformation` projects into `media_page`).
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"oneOf": [
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "details" },
|
||
"target": { "$ref": "#/$defs/external" }
|
||
},
|
||
"required": ["action", "target"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "by_request" },
|
||
"media": { "type": "string", "enum": ["movie", "tv", "album"] },
|
||
"request_id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["action", "media", "request_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "movie_localized" },
|
||
"tmdb_id": { "type": "integer", "minimum": 1 },
|
||
"language": { "type": "string", "minLength": 1, "maxLength": 20 }
|
||
},
|
||
"required": ["action", "tmdb_id", "language"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "ratings" },
|
||
"media": { "type": "string", "enum": ["movie", "tv"] },
|
||
"name": { "type": "string", "minLength": 1, "maxLength": 300 },
|
||
"year": { "type": "integer", "minimum": 1870, "maximum": 9999 }
|
||
},
|
||
"required": ["action", "media", "name", "year"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "streaming" },
|
||
"media": { "type": "string", "enum": ["movie", "tv"] },
|
||
"tmdb_id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["action", "media", "tmdb_id"],
|
||
"additionalProperties": false
|
||
}
|
||
],
|
||
"$defs": {
|
||
"external": {
|
||
"oneOf": [
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"media": { "const": "movie" },
|
||
"provider": { "const": "tmdb" },
|
||
"id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["media", "provider", "id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"media": { "const": "movie" },
|
||
"provider": { "const": "imdb" },
|
||
"id": { "type": "string", "minLength": 1, "maxLength": 32, "pattern": "^tt[0-9]+$" }
|
||
},
|
||
"required": ["media", "provider", "id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"media": { "const": "tv" },
|
||
"provider": { "type": "string", "enum": ["tmdb", "tvdb"] },
|
||
"id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["media", "provider", "id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"media": { "type": "string", "enum": ["artist", "album"] },
|
||
"provider": { "const": "musicbrainz" },
|
||
"id": { "type": "string", "minLength": 1, "maxLength": 100 }
|
||
},
|
||
"required": ["media", "provider", "id"],
|
||
"additionalProperties": false
|
||
}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.2 `read_requests` — optional `status` (default `all`), `sort` → `sort_direction`
|
||
|
||
- `status` leaves `required`; gains `"default": "all"` (documentation — adapter applies it).
|
||
- `sort` object removed; flat optional `sort_direction` enum `["asc","desc"]`, `"default": "desc"`. Wire: `sort=requestDate`, `sortOrder=asc|desc` path segments — `request_date` remains the only published sort field.
|
||
- `$defs/sort` deleted; `$defs/page` unchanged.
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"oneOf": [
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "list" },
|
||
"media": { "type": "string", "enum": ["movie", "tv"] },
|
||
"status": {
|
||
"type": "string",
|
||
"enum": ["all", "pending", "processing", "available", "denied", "unavailable"],
|
||
"default": "all"
|
||
},
|
||
"sort_direction": { "type": "string", "enum": ["asc", "desc"], "default": "desc" },
|
||
"page": { "$ref": "#/$defs/page" }
|
||
},
|
||
"required": ["action", "media"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "list" },
|
||
"media": { "const": "album" },
|
||
"status": {
|
||
"type": "string",
|
||
"enum": ["all", "pending", "processing", "available", "denied"],
|
||
"default": "all"
|
||
},
|
||
"sort_direction": { "type": "string", "enum": ["asc", "desc"], "default": "desc" },
|
||
"page": { "$ref": "#/$defs/page" }
|
||
},
|
||
"required": ["action", "media"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "get" },
|
||
"target": {
|
||
"type": "object",
|
||
"properties": {
|
||
"kind": { "type": "string", "enum": ["movie", "tv_parent"] },
|
||
"id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["kind", "id"],
|
||
"additionalProperties": false
|
||
}
|
||
},
|
||
"required": ["action", "target"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "children" },
|
||
"parent_request_id": { "type": "integer", "minimum": 1 },
|
||
"page": { "$ref": "#/$defs/page" }
|
||
},
|
||
"required": ["action", "parent_request_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "search" },
|
||
"media": { "type": "string", "enum": ["movie", "tv", "album"] },
|
||
"query": { "type": "string", "minLength": 1, "maxLength": 200 },
|
||
"page": { "$ref": "#/$defs/page" }
|
||
},
|
||
"required": ["action", "media", "query"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "recent" },
|
||
"page": { "$ref": "#/$defs/page" }
|
||
},
|
||
"required": ["action"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "retry_queue" },
|
||
"page": { "$ref": "#/$defs/page" }
|
||
},
|
||
"required": ["action"],
|
||
"additionalProperties": false
|
||
}
|
||
],
|
||
"$defs": {
|
||
"page": {
|
||
"type": "object",
|
||
"properties": {
|
||
"offset": { "type": "integer", "minimum": 0, "default": 0 },
|
||
"limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 }
|
||
},
|
||
"required": [],
|
||
"additionalProperties": false
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.3 `write_request_create` — `is_4k` optional + `season` selection mode
|
||
|
||
- Movie branch: `is_4k` removed from `required` (`"default": false` documents intent; adapter defaults false → `is4kRequest:false` — note: omitting `is4kRequest` entirely is also acceptable wire-wise since the field is optional upstream; the adapter MUST emit `false` explicitly for deterministic diffs).
|
||
- `selection` gains a third oneOf branch: `mode:"season"` + `season_numbers` array.
|
||
- Wire construction for `season` mode (**Design**): for each `season_numbers` entry the adapter resolves the show's season/episode list via the same upstream call used by `read_media.details` (TV, same provider namespace), then emits `seasons:[{seasonNumber:n, episodes:[{episodeNumber:e},…]}]` with every listed episode; `requestAll/firstSeason/latestSeason` all `false`. If episode metadata yields zero episodes for a requested season → `INVALID_ARGUMENT` with `field:"season_numbers"`. Sending `episodes:[]` as a whole-season shortcut remains **Verify**-gated and is not the default.
|
||
- `overrides` keys already `root_folder_id`/`quality_profile_id` (unchanged — they are the canonical names other tools now align to).
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"oneOf": [
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "movie" },
|
||
"tmdb_id": { "type": "integer", "minimum": 1 },
|
||
"is_4k": { "type": "boolean", "default": false },
|
||
"language": { "type": "string", "minLength": 1, "maxLength": 20 },
|
||
"on_behalf_user_id": { "type": "string", "minLength": 1, "maxLength": 200 },
|
||
"overrides": { "$ref": "#/$defs/overrides" }
|
||
},
|
||
"required": ["action", "tmdb_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "tv" },
|
||
"provider": { "const": "tmdb" },
|
||
"id": { "type": "integer", "minimum": 1 },
|
||
"selection": { "$ref": "#/$defs/selection" },
|
||
"language": { "type": "string", "minLength": 1, "maxLength": 20 },
|
||
"language_profile_id": { "type": "integer", "minimum": 1 },
|
||
"on_behalf_user_id": { "type": "string", "minLength": 1, "maxLength": 200 },
|
||
"overrides": { "$ref": "#/$defs/overrides" }
|
||
},
|
||
"required": ["action", "provider", "id", "selection"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "tv" },
|
||
"provider": { "const": "tvdb" },
|
||
"id": { "type": "integer", "minimum": 1 },
|
||
"selection": { "$ref": "#/$defs/selection" },
|
||
"language_profile_id": { "type": "integer", "minimum": 1 },
|
||
"on_behalf_user_id": { "type": "string", "minLength": 1, "maxLength": 200 },
|
||
"overrides": { "$ref": "#/$defs/overrides" }
|
||
},
|
||
"required": ["action", "provider", "id", "selection"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "album" },
|
||
"musicbrainz_id": { "type": "string", "minLength": 1, "maxLength": 100 },
|
||
"requested_by_alias": { "type": "string", "minLength": 1, "maxLength": 200 }
|
||
},
|
||
"required": ["action", "musicbrainz_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "collection" },
|
||
"collection_id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["action", "collection_id"],
|
||
"additionalProperties": false
|
||
}
|
||
],
|
||
"$defs": {
|
||
"overrides": {
|
||
"type": "object",
|
||
"properties": {
|
||
"root_folder_id": { "type": "integer", "minimum": 1 },
|
||
"quality_profile_id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": [],
|
||
"additionalProperties": false,
|
||
"minProperties": 1
|
||
},
|
||
"selection": {
|
||
"oneOf": [
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"mode": { "type": "string", "enum": ["all", "first_season", "latest_season"] }
|
||
},
|
||
"required": ["mode"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"mode": { "const": "season" },
|
||
"season_numbers": {
|
||
"type": "array",
|
||
"items": { "type": "integer", "minimum": 0 },
|
||
"minItems": 1,
|
||
"maxItems": 200,
|
||
"uniqueItems": true
|
||
}
|
||
},
|
||
"required": ["mode", "season_numbers"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"mode": { "const": "episodes" },
|
||
"seasons": {
|
||
"type": "array",
|
||
"items": {
|
||
"type": "object",
|
||
"properties": {
|
||
"season_number": { "type": "integer", "minimum": 0 },
|
||
"episodes": {
|
||
"type": "array",
|
||
"items": { "type": "integer", "minimum": 1 },
|
||
"minItems": 1,
|
||
"maxItems": 1000,
|
||
"uniqueItems": true
|
||
}
|
||
},
|
||
"required": ["season_number", "episodes"],
|
||
"additionalProperties": false
|
||
},
|
||
"minItems": 1,
|
||
"maxItems": 200,
|
||
"uniqueItems": true
|
||
}
|
||
},
|
||
"required": ["mode", "seasons"],
|
||
"additionalProperties": false
|
||
}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.4 `write_request_moderate` — `is_4k` optional in all four movie branches
|
||
|
||
All four `media:"movie"` branches (`approve`, `deny`, `mark_available`, `mark_unavailable`) drop `is_4k` from `required`; the property stays with `"default": false`. Adapter emits `is4K:false` when omitted. TV/album branches unchanged.
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"oneOf": [
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "approve" },
|
||
"media": { "const": "movie" },
|
||
"request_id": { "type": "integer", "minimum": 1 },
|
||
"is_4k": { "type": "boolean", "default": false }
|
||
},
|
||
"required": ["action", "media", "request_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "deny" },
|
||
"media": { "const": "movie" },
|
||
"request_id": { "type": "integer", "minimum": 1 },
|
||
"is_4k": { "type": "boolean", "default": false },
|
||
"reason": { "type": "string", "minLength": 1, "maxLength": 2000 }
|
||
},
|
||
"required": ["action", "media", "request_id", "reason"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "mark_available" },
|
||
"media": { "const": "movie" },
|
||
"request_id": { "type": "integer", "minimum": 1 },
|
||
"is_4k": { "type": "boolean", "default": false }
|
||
},
|
||
"required": ["action", "media", "request_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "mark_unavailable" },
|
||
"media": { "const": "movie" },
|
||
"request_id": { "type": "integer", "minimum": 1 },
|
||
"is_4k": { "type": "boolean", "default": false }
|
||
},
|
||
"required": ["action", "media", "request_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "approve" },
|
||
"media": { "type": "string", "enum": ["tv", "album"] },
|
||
"request_id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["action", "media", "request_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "deny" },
|
||
"media": { "type": "string", "enum": ["tv", "album"] },
|
||
"request_id": { "type": "integer", "minimum": 1 },
|
||
"reason": { "type": "string", "minLength": 1, "maxLength": 2000 }
|
||
},
|
||
"required": ["action", "media", "request_id", "reason"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "mark_available" },
|
||
"media": { "type": "string", "enum": ["tv", "album"] },
|
||
"request_id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["action", "media", "request_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "mark_unavailable" },
|
||
"media": { "type": "string", "enum": ["tv", "album"] },
|
||
"request_id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["action", "media", "request_id"],
|
||
"additionalProperties": false
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 3.5 `write_request_options` — unified override keys
|
||
|
||
`advanced.options` properties renamed to the canonical `root_folder_id`/`quality_profile_id` (was `root_path_override`/`quality_override`); `language_profile_id` unchanged. Wire mapping: `root_folder_id→rootPathOverride`, `quality_profile_id→qualityOverride`, `language_profile_id→languageProfile`. `tv_root`/`tv_quality` branches already use the canonical names — unchanged.
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"oneOf": [
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "advanced" },
|
||
"media": { "type": "string", "enum": ["movie", "tv"] },
|
||
"request_id": { "type": "integer", "minimum": 1 },
|
||
"options": {
|
||
"type": "object",
|
||
"properties": {
|
||
"root_folder_id": { "type": "integer", "minimum": 1 },
|
||
"quality_profile_id": { "type": "integer", "minimum": 1 },
|
||
"language_profile_id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": [],
|
||
"additionalProperties": false,
|
||
"minProperties": 1
|
||
}
|
||
},
|
||
"required": ["action", "media", "request_id", "options"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "tv_root" },
|
||
"parent_request_id": { "type": "integer", "minimum": 1 },
|
||
"root_folder_id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["action", "parent_request_id", "root_folder_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "tv_quality" },
|
||
"parent_request_id": { "type": "integer", "minimum": 1 },
|
||
"quality_profile_id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["action", "parent_request_id", "quality_profile_id"],
|
||
"additionalProperties": false
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 3.6 `write_request_reprocess` — string `request_type`, optional `is_4k`, unwrap
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"properties": {
|
||
"request_type": { "type": "string", "enum": ["movie", "tv", "album"] },
|
||
"request_id": { "type": "integer", "minimum": 1 },
|
||
"is_4k": { "type": "boolean", "default": false }
|
||
},
|
||
"required": ["request_type", "request_id"],
|
||
"additionalProperties": false
|
||
}
|
||
```
|
||
|
||
Wire: `POST /api/v2/Requests/reprocess/{type}/{requestId}/{is4K}` with `type` = Phase 04 `RequestType` int (`tv`→0, `movie`→1, `album`→2) and `is4K` defaulting false.
|
||
|
||
### 3.7 `write_issue_create` — `request_type` string enum + unwrap
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"properties": {
|
||
"title": { "type": "string", "minLength": 1, "maxLength": 300 },
|
||
"subject": { "type": "string", "minLength": 1, "maxLength": 300 },
|
||
"description": { "type": "string", "minLength": 1, "maxLength": 10000 },
|
||
"category_id": { "type": "integer", "minimum": 1 },
|
||
"request_type": { "type": "string", "enum": ["movie", "tv", "album"] },
|
||
"request_id": { "type": "integer", "minimum": 1 },
|
||
"provider_id": { "type": "string", "minLength": 1, "maxLength": 200 }
|
||
},
|
||
"required": ["title", "description", "category_id", "request_type"],
|
||
"additionalProperties": false,
|
||
"anyOf": [
|
||
{ "required": ["request_id"] },
|
||
{ "required": ["provider_id"] }
|
||
]
|
||
}
|
||
```
|
||
|
||
### 3.8 `read_issues` — `status` string enum replaces `status_code`
|
||
|
||
`list` and `summary` branches: `status` stays **required** (upstream path needs a status) as `enum ["pending","in_progress","resolved","closed"]`. `$defs/status_code` deleted; `$defs/page` unchanged.
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"oneOf": [
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "list" },
|
||
"status": { "type": "string", "enum": ["pending", "in_progress", "resolved", "closed"] },
|
||
"page": { "$ref": "#/$defs/page" }
|
||
},
|
||
"required": ["action", "status"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "summary" },
|
||
"status": { "type": "string", "enum": ["pending", "in_progress", "resolved", "closed"] },
|
||
"page": { "$ref": "#/$defs/page" }
|
||
},
|
||
"required": ["action", "status"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "get" },
|
||
"issue_id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["action", "issue_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "by_request" },
|
||
"request_id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["action", "request_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "by_provider" },
|
||
"provider_id": { "type": "string", "minLength": 1, "maxLength": 200 }
|
||
},
|
||
"required": ["action", "provider_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "provider_summary" },
|
||
"provider_id": { "type": "string", "minLength": 1, "maxLength": 200 }
|
||
},
|
||
"required": ["action", "provider_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "comments" },
|
||
"issue_id": { "type": "integer", "minimum": 1 },
|
||
"page": { "$ref": "#/$defs/page" }
|
||
},
|
||
"required": ["action", "issue_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "counts" }
|
||
},
|
||
"required": ["action"],
|
||
"additionalProperties": false
|
||
}
|
||
],
|
||
"$defs": {
|
||
"page": {
|
||
"type": "object",
|
||
"properties": {
|
||
"offset": { "type": "integer", "minimum": 0, "default": 0 },
|
||
"limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 }
|
||
},
|
||
"required": [],
|
||
"additionalProperties": false
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.9 `write_issue_manage` — `status` string enum
|
||
|
||
`set_status` branch: `status` required, `enum ["pending","in_progress","resolved","closed"]`; `$defs/status_code` deleted. Wire body `{issueId, status:<int>}`.
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"oneOf": [
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "set_status" },
|
||
"issue_id": { "type": "integer", "minimum": 1 },
|
||
"status": { "type": "string", "enum": ["pending", "in_progress", "resolved", "closed"] }
|
||
},
|
||
"required": ["action", "issue_id", "status"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "delete" },
|
||
"issue_id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["action", "issue_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "delete_comment" },
|
||
"comment_id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["action", "comment_id"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "create_category" },
|
||
"value": { "type": "string", "minLength": 1, "maxLength": 200 }
|
||
},
|
||
"required": ["action", "value"],
|
||
"additionalProperties": false
|
||
},
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"action": { "const": "delete_category" },
|
||
"category_id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["action", "category_id"],
|
||
"additionalProperties": false
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 3.10 `write_settings_patch` — string enums in `NotificationTemplates`
|
||
|
||
Replace the `Ombi_Store_Entities_NotificationTemplates` `$def` **only**; every section branch and all other `$defs` carry over byte-identical. Patch keys `notification_type`/`agent` translate to wire `notificationType`/`agent` with int values via Phase 04 before merging.
|
||
|
||
```json
|
||
"Ombi_Store_Entities_NotificationTemplates": {
|
||
"type": "object",
|
||
"properties": {
|
||
"notification_type": {
|
||
"type": "string",
|
||
"enum": [
|
||
"new_request",
|
||
"issue",
|
||
"request_available",
|
||
"request_approved",
|
||
"admin_note",
|
||
"test",
|
||
"request_declined",
|
||
"item_added_to_fault_queue",
|
||
"welcome_email",
|
||
"issue_resolved",
|
||
"issue_comment",
|
||
"newsletter",
|
||
"partially_available",
|
||
"plex_watchlist_token_expired",
|
||
"request_deleted",
|
||
"issue_in_progress",
|
||
"issue_deleted"
|
||
]
|
||
},
|
||
"agent": {
|
||
"type": "string",
|
||
"enum": [
|
||
"email",
|
||
"discord",
|
||
"pushbullet",
|
||
"pushover",
|
||
"telegram",
|
||
"slack",
|
||
"mattermost",
|
||
"mobile",
|
||
"gotify",
|
||
"webhook",
|
||
"whatsapp",
|
||
"ntfy"
|
||
]
|
||
},
|
||
"subject": { "type": "string" },
|
||
"message": { "type": "string" },
|
||
"enabled": { "type": "boolean" }
|
||
},
|
||
"required": [],
|
||
"additionalProperties": false
|
||
}
|
||
```
|
||
|
||
### 3.11 Single-element `oneOf` unwraps (9 tools)
|
||
|
||
Mechanical rule: delete the `oneOf` wrapper; promote the sole branch's `type`/`properties`/`required`/`additionalProperties`/`anyOf` to the schema root; `$defs` unchanged. Complete results:
|
||
|
||
**`write_issue_comment`**:
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"properties": {
|
||
"issue_id": { "type": "integer", "minimum": 1 },
|
||
"comment": { "type": "string", "minLength": 1, "maxLength": 10000 }
|
||
},
|
||
"required": ["issue_id", "comment"],
|
||
"additionalProperties": false
|
||
}
|
||
```
|
||
|
||
**`write_vote`**:
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"properties": {
|
||
"media": { "type": "string", "enum": ["movie", "tv", "album"] },
|
||
"request_id": { "type": "integer", "minimum": 1 },
|
||
"direction": { "type": "string", "enum": ["up", "down"] }
|
||
},
|
||
"required": ["media", "request_id", "direction"],
|
||
"additionalProperties": false
|
||
}
|
||
```
|
||
|
||
**`write_request_delete`**:
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"properties": {
|
||
"target": { "$ref": "#/$defs/target" }
|
||
},
|
||
"required": ["target"],
|
||
"additionalProperties": false,
|
||
"$defs": {
|
||
"target": {
|
||
"oneOf": [
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"kind": { "type": "string", "enum": ["movie", "tv_parent", "tv_child", "album"] },
|
||
"id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["kind", "id"],
|
||
"additionalProperties": false
|
||
}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**`read_settings`**:
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"properties": {
|
||
"section": {
|
||
"type": "string",
|
||
"enum": [
|
||
"custom_page", "ombi", "base_url", "plex", "client_id", "emby",
|
||
"jellyfin", "landingpage", "customization", "default_language",
|
||
"themes", "sonarr", "radarr", "lidarr", "lidarrenabled",
|
||
"authentication", "update", "user_management", "couchpotato",
|
||
"dognzb", "sickrage", "jobs", "issues", "issuesenabled", "vote",
|
||
"voteenabled", "themoviedb", "notifications.email",
|
||
"notifications.email.enabled", "notifications.discord",
|
||
"notifications.telegram", "notifications.pushbullet",
|
||
"notifications.pushover", "notifications.slack",
|
||
"notifications.mattermost", "notifications.twilio",
|
||
"notifications.mobile", "notifications.gotify",
|
||
"notifications.ntfy", "notifications.webhook",
|
||
"notifications.newsletter"
|
||
]
|
||
}
|
||
},
|
||
"required": ["section"],
|
||
"additionalProperties": false
|
||
}
|
||
```
|
||
|
||
**`write_integration_test`**:
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"properties": {
|
||
"service": {
|
||
"type": "string",
|
||
"enum": [
|
||
"discord", "pushbullet", "pushover", "gotify", "ntfy", "webhook",
|
||
"mattermost", "slack", "email", "plex", "emby", "jellyfin",
|
||
"radarr", "sonarr", "couchpotato", "telegram", "sickrage",
|
||
"newsletter", "mobile", "lidarr", "whatsapp"
|
||
]
|
||
},
|
||
"profile_id": { "type": "string", "minLength": 1, "maxLength": 200 }
|
||
},
|
||
"required": ["service", "profile_id"],
|
||
"additionalProperties": false
|
||
}
|
||
```
|
||
|
||
**`write_notification_send`**:
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"properties": {
|
||
"subject": { "type": "string", "minLength": 1, "maxLength": 300 },
|
||
"body": { "type": "string", "minLength": 1, "maxLength": 20000 },
|
||
"bcc": { "type": "boolean" },
|
||
"user_ids": {
|
||
"type": "array",
|
||
"items": { "type": "string", "minLength": 1, "maxLength": 200 },
|
||
"minItems": 1,
|
||
"maxItems": 100,
|
||
"uniqueItems": true
|
||
}
|
||
},
|
||
"required": ["subject", "body", "bcc", "user_ids"],
|
||
"additionalProperties": false
|
||
}
|
||
```
|
||
|
||
**`write_retry_remove`**:
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"properties": {
|
||
"queue_id": { "type": "integer", "minimum": 1 }
|
||
},
|
||
"required": ["queue_id"],
|
||
"additionalProperties": false
|
||
}
|
||
```
|
||
|
||
(Phase 3.7 `write_issue_create` above is also an unwrap — already printed.)
|
||
|
||
### 3.12 Untouched tools — carry over verbatim (rename only)
|
||
|
||
`read_search`, `read_discover`, `read_reference`, `read_request_stats`, `read_votes`, `read_users`, `read_library`, `read_server`, `read_integration`, `write_request_subscribe`, `write_user_preferences`, `write_user_manage`, `write_job_run`, `read_logs`. These keep byte-identical schema bodies; only the `##` heading renames.
|
||
|
||
## Target artefacts
|
||
|
||
- `docs/schema/03-input-schemas.md` — every schema block replaced or unwrapped per §§3.1–3.11; the 14 tools in §3.12 carry over byte-identical (heading already renamed by Phase 01). Section order unchanged.
|
||
|
||
## Verification gates
|
||
|
||
- [ ] `is_4k` appears in no `required` array anywhere in the file.
|
||
- [ ] `request_type_code`/`status_code`/`sort` object/`root_path_override`/`quality_override` absent.
|
||
- [ ] `season_numbers` present with `minItems:1`, `uniqueItems:true`, `minimum:0`.
|
||
- [ ] `by_request` enum is exactly `["movie","tv","album"]`.
|
||
- [ ] No `oneOf` wrapper has exactly one element.
|
||
- [ ] All JSON blocks validate under `Draft202012Validator`; each tool accepts its minimal valid object and rejects `{}` where any field is required.
|
||
- [ ] `read_requests` accepts `{"action":"list","media":"movie"}` (status/sort_direction defaults adapter-side).
|
||
|
||
## Evidence labels
|
||
|
||
- **Documented** — wire mappings cited for `sort`/`sortOrder`, `rootPathOverride`/`qualityOverride`/`languageProfile`, `is4K`/`is4kRequest`, `reprocess` path ints.
|
||
- **Design** — `season_numbers` array semantics and adapter episode expansion (D6); canonical `root_folder_id`/`quality_profile_id` naming; adapter-emitted `is4kRequest:false` for deterministic diffs.
|
||
- **Verify** — `album`→`artist/request` route-segment asymmetry note; `episodes:[]` whole-season shortcut remains Verify-gated.
|