cli: publish JSON Schemas for the machine surface #180
No reviewers
Labels
No labels
priority/P0
priority/P1
priority/P2
release/v0.1.0
status/blocked
status/planned
type/bug
type/design
type/test-gap
type/tracker
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
natsukium/felis!180
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/cli-bridge-schemas-29"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #29.
The CLI's
--format json/--format jsonlobjects and thefelis bridgeprotocol had only serde structs and a reference page, while the protobuf wire andconfig.tomlboth shipped machine-readable schemas. A consumer could not validate its requests or the bridge's outputs on its own.crates/felis-cli/schemas/felis-cli-v1.schema.jsonandfelis-bridge-v1.schema.jsonare rendered from the serde types (cli_schema.rs), so a field's schema and its serialization stay one edit apart and staleness is a failing test. Only the envelope is composed by hand — the CLI and the bridge wrap the same bodies differently, and serde has no type for "which body under which key".error.kindis published as an open string, not an enum, so a validator built from an older bundle still accepts a token minted later.tests/fixtures/schema-invalid/carry what a live run cannot produce, and the request half is also fed to a live bridge so the schema and the binary draw one line.tests/fixtures/bridge/pin what JSON Schema cannot state: one terminal per request id, and nothing after it. They are diffed per request id with session ids, timestamps and prose masked.minimum/maximum(schemars emits an explicit range only up to 16 bits, andformatis non-normative in draft 2020-12), and the spawn-geometry pairing rule is stated as a two-stateoneOfrather thandependentRequired, which mismatched the bridge's null-as-absence reading.Doc cascade:
docs/reference/cli.md(schema files and their$defs),docs/reference/testing.md(the validation layer, fixture layout, regeneration recipes),docs/reference/ipc.md,docs/explanation/architecture/control-surfaces.md(why the bundle is generated rather than written, and whyerror.kindis open),CHANGELOG.md,justfile,skills/felis/SKILL.md,.agents/skills/extend-ipc/SKILL.md.Verified: just check green (fmt, clippy, nextest, deny, proto-compat); reviewed by pi luna + pi sol (round 1) + pi luna + pi sol; docs proofread by Gemini gemini-3.8-flash-high
Follow-ups
sourceandattachmentstrings the runtime rejects, so schema-valid requests can still fail as malformedDeferred
Paramsaccessors with per-op#[derive(Deserialize)]structs so the runtime and the published request schema share one type — a pure refactor: the binary already refuses unknown fields and parameters, and the guard test pins the published property sets to the runtime table.cancelresult ({"canceled": bool}) is declared inline in the bridge document rather than derived from a type — it is the one body no CLI verb produces and it has no serde struct in the codebase.cli_bridge.rs:549asserts the exit code). Tracked by #166; the lower-severity duplicate finding on the same gap is merged into it.{"rows": null}/{"cols": null}) — minor extra edge-case coverage beyond any numbered acceptance criterion; the existing cases already cover the documentedoneOfshape.Two constraints the bundle claimed to state were unenforceable by a consumer's validator. `format` is a non-normative annotation in draft 2020-12 and schemars emits an explicit range only up to 16 bits, so every `u32`, `u64` and `i64` field published a lower bound and no upper one: a `sessions` count of 2^32 or a capture `lines` of 2^64 validated against a document that describes a Rust integer which cannot hold them. The rendered document now carries the width's own `minimum`/`maximum`. `dependentRequired` keys on a property being present, while the bridge reads an explicit `null` as absence. The grammar therefore refused `{"rows": null}`, which the bridge serves on the default grid, and admitted `{"rows": null, "cols": 80}`, which it answers `invalid_request`. The two-state pairing rule is stated directly instead, and the reference no longer promises `malformed_request` for the one cross-field rule the bridge answers otherwise. Refs #29