# `wf schema` Command Design ## Status Approved design for implementation planning. ## Purpose `wf schema` gives human and machine clients a public discovery surface for the workflow document models accepted by the product. It should cover draft, raw plan, core workflow, and referenced component shapes without requiring agents to inspect tests or implementation code. The command has two output levels: - a compact machine-readable outline by default; - a complete valid JSON Schema document with `--verbose`. ## Goals 1. Cover every schema definition reachable from workflow draft, raw plan, and core workflow roots. 2. Keep default output compact enough for agent context windows. 3. Keep all output valid JSON. 4. Make verbose output a valid, self-contained JSON Schema document. 5. Provide discoverable aliases for the common root models. 6. Fail clearly and nonzero for unknown schema names. 7. Avoid adding runtime dependencies for trivial in-process caching. ## Non-Goals - Building a general-purpose JSON Schema browser. - Searching arbitrary source-code models outside the workflow document domain. - Producing Python source declarations or generated Pydantic models. - Replacing command-specific help, validation diagnostics, or workflow docs. - Guaranteeing that compact outlines preserve every JSON Schema keyword. ## Command Surface ```text wf schema wf schema list wf schema draft wf schema raw wf schema core wf schema WorkflowDraft wf schema RawWorkflowPlan wf schema NodeUse wf schema InputPathBinding wf schema raw --verbose wf schema NodeUse --verbose ``` `wf schema` with no argument is equivalent to `wf schema list`. The command remains a single Typer command with an optional positional name. `draft`, `raw`, and `core` are aliases, not subcommands. ## Root Models And Aliases | Alias | Canonical model | Purpose | | --- | --- | --- | | `draft` | `WorkflowDraft` | Patch-friendly draft workspace document | | `raw` | `RawWorkflowPlan` | Raw graph accepted by artifact creation | | `core` | `Workflow` | Canonical core workflow graph | Canonical names are also accepted directly. Definitions exposed through the combined root schemas are queryable by their exact canonical names. ## Schema Catalog The implementation builds one cached catalog from: - `TypeAdapter(WorkflowDraft).json_schema()`; - `TypeAdapter(RawWorkflowPlan).json_schema()`; - `TypeAdapter(Workflow).json_schema()`. The catalog records: - canonical root schemas; - aliases; - every reachable `$defs` entry; - description/title metadata; - which roots reference each definition. Use `functools.cache` for catalog construction. The CLI process is short-lived; a TTL cache and the `cachetools` dependency provide no useful behavior. Pydantic's `TypeAdapter.json_schema()` remains the authority for schema generation. Do not reimplement model-to-JSON-Schema conversion, discriminated unions, aliases, defaults, validation constraints, or reference construction. Use the existing `jsonschema` package to validate emitted full schemas. Custom code is limited to catalog indexing, collision checks, and the explicitly non-validating compact presentation layer. If two roots expose different definitions with the same canonical name, catalog construction must fail with a clear internal error rather than silently choose one. Structurally identical definitions may be deduplicated. ## List Output `wf schema` and `wf schema list` emit JSON: ```json { "schemas": [ { "name": "NodeUse", "aliases": [], "kind": "definition", "description": "Concrete use of a reusable node definition." }, { "name": "WorkflowDraft", "aliases": ["draft"], "kind": "root", "description": "Patch-friendly JSON authoring document." } ] } ``` Entries are sorted by canonical name. Search/filtering is deferred until real catalog size or usage demonstrates a need. ## Compact Outline Output The default named-schema output is a JSON **schema outline**, not a JSON Schema document. It must label itself accordingly and must not emit dangling `$ref` values. Example: ```json { "name": "RawWorkflowPlan", "kind": "schema_outline", "type": "object", "description": "Raw authoring plan using core graph models.", "required": [ "name", "input_schema", "state_schema", "output_schema", "start", "nodes", "edges" ], "properties": { "name": {"type": "string"}, "start": {"type": "string"}, "nodes": { "type": "array", "items": { "one_of": [ "NodeUse", "SubgraphNode", "ConditionNode", "ForeachNode", "JoinNode", "EndNode", "InterruptNode" ] } }, "edges": {"type": "array", "items": "Edge"} }, "related": ["Edge", "NodeUse", "OutputBinding"], "full_schema_command": "wf schema raw --verbose" } ``` ### Outline Projection Rules - Preserve `name`, `title`, `description`, object `required`, defaults, enums, constants, and basic validation bounds when present. - Preserve object property names. - Convert local `$ref` values to canonical definition-name strings. - Convert `oneOf` reference unions to `one_of` and `anyOf` reference unions to `any_of` when branches are named references. - Preserve simple inline primitive unions in compact JSON form. - For arrays, summarize the item schema recursively. - Add a sorted `related` list containing definitions referenced by the outline. - Do not recursively inline referenced definitions; callers can query them by name. - Do not claim the outline is directly accepted as a validation schema. The projection should be deterministic so snapshots and prompt provenance are stable across runs when source models do not change. ## Verbose JSON Schema Output `wf schema --verbose` emits a complete, self-contained JSON Schema document through the existing `wf_cli.io.emit_json` helper. For root aliases/canonical root names, emit the complete Pydantic-generated root schema with its `$defs` table. For a component definition such as `NodeUse`, emit a valid root document: ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$ref": "#/$defs/NodeUse", "$defs": { "NodeUse": {"type": "object", "properties": {"node": {"type": "string"}}}, "InputPathBinding": {"type": "object", "properties": {"path": {"type": "string"}}}, "InputValueBinding": {"type": "object", "properties": {"value": true}}, "OutputBinding": {"type": "object", "properties": {"to": {"type": "string"}}} } } ``` The short `$defs` bodies above are representative excerpts, not complete runtime output. The real `$defs` table contains the full combined Pydantic-generated definition catalog. Do not hand-roll transitive reference pruning or JSON Schema reference resolution in the first implementation. The larger verbose payload is an acceptable tradeoff for correctness; `--verbose` is explicitly the unbounded form. All verbose documents must pass `Draft202012Validator.check_schema()` and a validator-backed local-reference resolution test. ## Output And Errors - Use `emit_json`; never pass dictionaries directly to `typer.echo`. - Unknown names raise `typer.BadParameter` and exit nonzero. - Unknown-name messages include close canonical/alias matches when available, using the standard library `difflib.get_close_matches`. - Missing names list the catalog rather than showing an empty command group. - Internal catalog collisions or malformed generated schemas fail loudly with actionable errors. - `--verbose` applies to both aliases and canonical names. Example error: ```text Invalid value: unknown schema 'Node'. Did you mean 'NodeUse'? ``` ## Help Text Help must describe the real single-command shape: ```text Usage: wf schema [OPTIONS] [NAME] Print a compact workflow schema outline, or a full JSON Schema with --verbose. Use `wf schema` or `wf schema list` to discover available names. Common aliases: draft, raw, core. ``` Do not call aliases convenience subcommands. ## Skills And Documentation Update user-facing agent instructions so they use public discovery surfaces in this order: 1. `wf schema` to list available workflow document shapes; 2. `wf schema draft` or `wf schema raw` for compact guidance; 3. `wf schema ` for component details; 4. `--verbose` only when a complete validation schema is necessary; 5. workflow validation commands to check an authored document. Remove the stale statement that `wf schema` is an empty WIP group. Keep test and implementation file paths out of user-facing skills. ## Tests Focused CLI tests must cover: - `wf schema` and `wf schema list` return the same sorted catalog; - catalog output is valid JSON and includes roots, aliases, and components; - aliases and canonical root names resolve to the same model; - compact output is valid JSON and contains no `$ref` keys; - compact references are represented by queryable canonical names; - verbose root output passes JSON Schema validation; - verbose component output has no unresolved local `$ref` values; - `--verbose` works for aliases and canonical component names; - unknown names exit nonzero and offer a close-match suggestion; - help text describes aliases rather than subcommands; - the removed WIP help assertions are replaced with behavioral tests. Run focused verification against the schema command and existing CLI app tests, then run Ruff and basedpyright on modified files. ## Migration The current uncommitted prototype is replaced rather than preserved: - remove `cachetools` from runtime dependencies and the lockfile; - remove TTL caches; - replace `_sonset_sandstorm` with explicit catalog helpers; - replace Python-repr output with JSON; - replace detached `$defs` fragments with compact outlines; - update stale CLI tests and skills. No compatibility behavior is required because the previous `wf schema` surface was explicitly an empty work-in-progress group and the prototype has not been released as a documented contract. ## Acceptance Criteria - Agents can discover all supported workflow schema names without reading code. - Default output is compact, deterministic, valid JSON, and has no dangling references. - `--verbose` output is valid, self-contained JSON Schema. - Unknown names fail nonzero. - No new runtime dependency is needed. - Focused CLI tests, Ruff, and basedpyright pass. - User-facing skills direct agents to `wf schema` instead of tests or source.