Skip to content

ADR 0216: Rust-Owned Public Schema Authority ​

  • Status: Accepted
  • Date: 2026-08-18
  • Task ID: GOV-408
  • Owners: Qiongli maintainers
  • Decision scope: App IPC, MCP tool contracts, and documented public CLI JSON

Context ​

Qiongli 2 already has three useful but differently owned public contract families. Native Rust produces the App protocol while a large handwritten Zod decoder validates it. MCP v2 is described by checked-in JSON Schemas that the native runtime consumes. Public CLI JSON is serialized by Rust and exercised by integration tests, but it has no complete generated schema set.

Those baselines work, yet none provides a safe rule for the next domain schema. A field can otherwise be changed in one producer, decoder, tool declaration, fixture, or CLI example without an explicit compatibility decision. M2 domain objects must not copy that ambiguity.

Decision drivers ​

  • One semantic owner must define names, types, variants, required fields, and bounds across App, MCP, and CLI consumers.
  • Existing public behavior must remain usable while authority is adopted.
  • Compatibility claims must be explicit and testable rather than inferred from a passing serializer or one permissive decoder.
  • Generated files, installed Plugins, and Host caches must remain outputs, not editable authorities.
  • Adoption must not require a risky rewrite of every established contract in a single pull request.

Decision ​

Rust types own new and changed public schemas ​

For every new or modified public App IPC, MCP, or CLI JSON contract, the Rust domain type is the sole semantic authority. Its field names, serde behavior, variants, optionality, bounds, and version define the supported wire shape.

The authority emits a versioned JSON Schema using Draft 2020-12 and at least one Rust-produced positive golden fixture. Non-trivial bounds or variants also retain the smallest negative fixture or consumer test that would fail if the constraint disappeared.

Generated schemas and fixtures are deterministic checked-in outputs. A regeneration check must reproduce them exactly; editing generated bytes does not change the contract.

Consumers adapt to the generated contract ​

  • TypeScript/Zod remains the App's untrusted-data decoder, but it is an adapter verified against the generated schema and Rust fixtures. It cannot introduce frontend-only fields, defaults, variants, or bounds.
  • MCP tools/list, dispatch validation, the capability registry, Skills, and profile-specific input/output declarations consume the same generated schema artifacts or exact projections explicitly recorded by the contract.
  • Documented public CLI JSON is validated against the generated schema and Rust fixture for the named command/output contract.

Consumer-specific refinements may enforce semantic invariants that JSON Schema cannot express. They must reject invalid data without widening or redefining the generated wire contract.

Existing contracts enter as frozen migration baselines ​

The current App IPC, MCP v2, and public CLI JSON families are recorded in tooling/architecture/public-schema-policy.json with their real authority state. Adoption does not claim that the existing handwritten Zod or MCP schemas are already Rust-generated.

Those baselines may continue unchanged. The first later wire-shape change to a named contract must move that touched contract to the Rust-generated path in the same pull request. No wholesale pre-emptive generator or all-contract migration is required.

Every post-baseline change has one compatibility class ​

Every public schema change records its predecessor and successor versions and exactly one class:

  • additive preserves every previously valid payload and supported consumer. It may add optional information but cannot remove or rename fields, make a field required, add an exhaustively consumed variant, narrow a bound, or change existing meaning.
  • migratable-breaking changes the accepted shape but includes a bounded, tested dual-read, adapter, or data migration path and an explicit old-version support boundary.
  • unsupported-breaking has no safe automatic migration. It requires a new explicit version, fail-closed old/new handling, and a separately named product or release approval gate before old support can be removed.

An initial baseline is not retroactively classified as a change. Green CI, successful serialization, or a version bump alone does not choose a class.

One policy record and existing CI enforce the decision ​

The public-schema policy record inventories exactly the App IPC, MCP tool, and public CLI JSON families. Each future change binds Rust sources, its generated schema, golden fixtures, consumer checks, and any required migration or removal gate.

The standard-library validator runs in the existing required Evaluation Truth workflow. It fails on missing boundary coverage, unknown fields or classes, unsafe paths, broken predecessor chains, absent generated evidence, or missing breaking-change controls. It is governance metadata, not a runtime registry.

Alternatives considered ​

Keep each consumer authoritative ​

Rejected because synchronized handwritten Rust, Zod, MCP, and CLI definitions cannot establish which representation wins after drift.

Make JSON Schema or Zod the semantic source ​

Rejected because the shipped native services already own validation, state, and serialization. Moving semantic ownership outward would create a second domain model and weaken native compile-time review.

Convert every existing schema immediately ​

Rejected because the App and CLI type graphs are broad, and a big-bang migration would mix governance adoption with many unrelated wire changes. The touch-to-migrate rule closes future drift without pretending current baselines have already moved.

Consequences ​

  • M2 and later public domain contracts have one declared source and one compatibility vocabulary.
  • Zod, MCP, and CLI validation remain useful at their trust boundaries but no longer own competing semantics for changed contracts.
  • Existing wire shapes and installed products are unchanged by this decision.
  • The first real schema migration chooses the minimum generator appropriate to its actual Rust type graph; this ADR does not add speculative tooling.
  • Unsupported removals remain product/release decisions rather than outcomes a schema author or CI job can authorize alone.

Security and privacy ​

Generated schemas and fixtures must contain synthetic or redacted values only. They never include credentials, absolute profile paths, research content, private Host output, or authorization tokens.

Schema generation does not widen authority to execute commands, move data, or mutate user state. Existing input validation, redaction, path containment, preview/approval, receipt, and Host trust boundaries remain mandatory.

Rollback ​

Reverting the adoption commit removes the policy gate and ADR registration but does not rewrite payloads, installed Plugins, project data, or private state. Once a later contract ships under this decision, rollback preserves its versioned generated schema and compatibility behavior; a new ADR may supersede the authority policy, but accepted history is not edited in place.

Acceptance tests ​

  1. The current ADR registry contains ADR 0216 exactly once while the frozen ARC-201 inventory remains byte-identical.
  2. The policy validator accepts exactly the App IPC, MCP tools, and public CLI JSON baselines and rejects missing, duplicate, reordered, or unsafe records.
  3. Unknown compatibility classes and post-baseline changes without Rust source, generated Draft 2020-12 schema, golden fixture, and consumer checks fail.
  4. migratable-breaking without a migration path and unsupported-breaking without a separate approval gate fail.
  5. Existing App API, MCP capability, public CLI, Plugin, and Skill tests remain green without a wire-version change.

Follow-up tasks ​

  • Migrate the first touched App IPC contract from the frozen Rust/Zod baseline when its next wire-shape change is approved.
  • Migrate each touched MCP input/output contract from checked-in handwritten schema to Rust generation in that tool's change.
  • Add generated public CLI schemas command-by-command when those output contracts next change; do not create an unused all-CLI generator first.

Qiongli documentation