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.
156 lines
7.3 KiB
Markdown
156 lines
7.3 KiB
Markdown
# Phase 05 — Output Schema Modularisation
|
||
|
||
## Objective
|
||
|
||
Dismantle the monolithic 1,200-line union in `04-results.md`. Publish **per-tool `outputSchema`** on each `Tool` object (MCP 2025-11-25), each referencing a centralised `$defs` library.
|
||
|
||
## Prerequisites
|
||
|
||
- Phase 01 complete (`operation` enum values already carry the new tool names; this phase replaces the enum with per-tool `const`).
|
||
- Phase 04 complete (string-label enums used by the replacement `issue`/`retry` defs).
|
||
- Locked decision D4: each of the 31 tools declares a distinct `outputSchema` composed from a centralised `$defs` library of the 15 result families; output schemas are **never** embedded in `description` strings.
|
||
- Source document to rewrite: `docs/schema/04-results.md`.
|
||
|
||
## Specification
|
||
|
||
### Structure of the rewritten `04-results.md`
|
||
|
||
1. Keep prose sections `Registration and results`, `Domain projections`, `Errors and partial effects`, `Resources` (update wording: per-tool `outputSchema`, not "the common outputSchema"; operation enum → new names).
|
||
2. **§ Shared `$defs` library** — one JSON block containing every definition (`target`, `identifier`, `reference`, `credit`, `provider`, `episode`, `season`, `media`, `request`, `issue`, `comment`, `group`, `vote`, `user`, `calendar`, `artwork`, `retry`, `paging`, `media_page`, `request_page`, `issue_page`, `comment_page`, `group_page`, `vote_page`, `user_page`, `calendar_page`, `artwork_page`, `retry_page`, `reference_page`, `metric`, `metrics`, `change`, `settings`, `mutation`, `log_file`, `logs`, `error`). All carried verbatim except the two replacements below.
|
||
3. **§ Envelope template** — printed in full below.
|
||
4. **§ Tool→family matrix + composition rule** — printed in full below.
|
||
5. Remove the giant `allOf` if/then cascade and the global `operation` enum (replaced by per-tool `const`).
|
||
|
||
### Envelope template (per-tool `outputSchema`, printed in full)
|
||
|
||
```json
|
||
{
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"properties": {
|
||
"ok": { "type": "boolean" },
|
||
"operation": { "const": "<tool_name>" },
|
||
"data": { "oneOf": [ "<$ref to each allowed family>" ] },
|
||
"error": { "$ref": "#/$defs/error" },
|
||
"warnings": {
|
||
"type": "array",
|
||
"items": { "type": "string" },
|
||
"maxItems": 100
|
||
},
|
||
"truncated": { "type": "boolean" },
|
||
"correlation_id": { "type": "string" }
|
||
},
|
||
"required": ["ok", "operation", "warnings", "truncated", "correlation_id"],
|
||
"additionalProperties": false,
|
||
"allOf": [
|
||
{
|
||
"if": { "properties": { "ok": { "const": true } } },
|
||
"then": { "required": ["data"], "not": { "required": ["error"] } },
|
||
"else": { "required": ["error"] }
|
||
}
|
||
],
|
||
"$defs": { "<transitive closure of the shared library for this tool>" }
|
||
}
|
||
```
|
||
|
||
### Composition rule (deterministic)
|
||
|
||
For each tool: set `operation.const` to the tool name; set `data.oneOf` to its allowed families per the matrix; compute `$defs` = `{error}` ∪ the allowed families ∪ every def reachable by following `$ref`s inside them (e.g. `media_page` → `media` → `identifier`, `target`, `credit`, `provider`, `reference`, `season`, `episode`; `request_page` → `request` → `target`, `identifier`, `season`, `episode`, `paging`). Registration text must state: "`tools/list` advertises `outputSchema` on each Tool object per MCP 2025-11-25; output contracts never appear inside `description`."
|
||
|
||
### Tool→family matrix (identical allowances as before, new names)
|
||
|
||
| Tool | Allowed `data` families |
|
||
|---|---|
|
||
| `read_search` | media_page |
|
||
| `read_discover` | media_page |
|
||
| `read_media` | media_page |
|
||
| `read_reference` | reference_page |
|
||
| `read_requests` | request_page, retry_page |
|
||
| `read_request_stats` | metrics |
|
||
| `read_issues` | issue_page, comment_page, group_page, metrics |
|
||
| `read_votes` | vote_page |
|
||
| `read_users` | user_page, reference_page |
|
||
| `read_library` | media_page, calendar_page, artwork_page |
|
||
| `read_server` | metrics, reference_page |
|
||
| `read_integration` | reference_page, user_page |
|
||
| `read_settings` | settings |
|
||
| `read_logs` | logs |
|
||
| `write_request_create` … all 17 `write_*` | mutation |
|
||
|
||
### Replacement defs (printed in full — enum abstraction per Phase 04)
|
||
|
||
`issue` (labels + code twins):
|
||
|
||
```json
|
||
"issue": {
|
||
"type": "object",
|
||
"properties": {
|
||
"id": { "type": "integer", "minimum": 1 },
|
||
"title": { "type": "string" },
|
||
"subject": { "type": "string" },
|
||
"description": { "type": "string" },
|
||
"category_id": { "type": "integer", "minimum": 1 },
|
||
"status": {
|
||
"type": "string",
|
||
"enum": ["pending", "in_progress", "resolved", "closed"]
|
||
},
|
||
"status_code": { "type": "integer", "enum": [0, 1, 2, 3] },
|
||
"request_type": {
|
||
"type": "string",
|
||
"enum": ["movie", "tv", "album"]
|
||
},
|
||
"request_type_code": { "type": "integer", "enum": [0, 1, 2] },
|
||
"request_id": { "type": "integer", "minimum": 1 },
|
||
"provider_id": { "type": "string" },
|
||
"created_date": { "type": "string" },
|
||
"resolved_date": { "type": "string" },
|
||
"reported_by_user_id": { "type": "string" }
|
||
},
|
||
"required": ["id"],
|
||
"additionalProperties": false
|
||
}
|
||
```
|
||
|
||
`retry` (label + code twin):
|
||
|
||
```json
|
||
"retry": {
|
||
"type": "object",
|
||
"properties": {
|
||
"queue_id": { "type": "integer", "minimum": 1 },
|
||
"request_id": { "type": "integer", "minimum": 1 },
|
||
"request_type": {
|
||
"type": "string",
|
||
"enum": ["movie", "tv", "album"]
|
||
},
|
||
"request_type_code": { "type": "integer", "enum": [0, 1, 2] },
|
||
"title": { "type": "string" },
|
||
"reason": { "type": "string" }
|
||
},
|
||
"required": ["queue_id"],
|
||
"additionalProperties": false
|
||
}
|
||
```
|
||
|
||
`vote` stays numeric (`vote_code` `enum:[0,1]`) — VoteType labels remain **Verify**. `settings` def gains optional leaf naming note (no schema change needed — `change.value` already allows string|number; agent/notification_type leaves emit label strings when mapped, raw ints + warning when unmapped).
|
||
|
||
All other defs carry over byte-identical from the current `04-results.md` (`media`, `request`, `comment`, `group`, `user`, `calendar`, `artwork`, `paging`, all `*_page` shells, `metric`, `metrics`, `change`, `settings`, `mutation`, `log_file`, `logs`, `error`).
|
||
|
||
## Target artefacts
|
||
|
||
- `docs/schema/04-results.md` — restructured per the five steps above: prose updated, shared `$defs` library block (with `issue`/`retry` replaced), envelope template, tool→family matrix, monolithic union removed. Per-tool `outputSchema` composition documented; `operation` becomes `const`.
|
||
|
||
## Verification gates
|
||
|
||
- [ ] `operation` is a `const` per tool; no global `oneOf`-of-15-families union remains at envelope level.
|
||
- [ ] Every tool's `data.oneOf` matches the matrix exactly.
|
||
- [ ] `$defs` in each tool schema is exactly the transitive closure (no unused defs — context budget).
|
||
- [ ] `issue`/`retry` emit `status`/`request_type` label enums plus `*_code` twins.
|
||
- [ ] Docs state `outputSchema` is a Tool field; nothing about embedding contracts in `description`.
|
||
|
||
## Evidence labels
|
||
|
||
- **Documented** — `outputSchema` on the `Tool` object per MCP 2025-11-25 (D4); result-family allowances carried over from the existing spec.
|
||
- **Design** — per-tool `outputSchema` composition, transitive-closure `$defs` rule, `operation` as `const`, `issue`/`retry` label+code twins per Phase 04.
|
||
- **Verify** — `vote` stays numeric pending `VoteType` label verification; unmapped upstream ints emit `*_code` + warning per the Phase 04 fallback contract.
|