Compatibility

This chapter is the 1.0 stability promise: which surfaces are frozen, which are still free to move, and how a change is classified when it comes. Read it before you build anything that outlives one interactive session — a stored result, a parser for the agent envelope, a pipeline pinned to a version.

The promise has two strengths, because the package has two kinds of consumer. A person testing interactively recovers from a renamed argument in a minute. An unattended pipeline — or a directory of saved projects — does not recover from a changed file format. So the data contracts freeze hard at 1.0, and the Python call surface freezes as it is documented.

The data contracts are frozen

A break in any of these corrupts accumulated work or an unattended pipeline, so none of them changes shape without its version string moving. The version strings are the six contracts capabilities() reports — Calling rietx from a program has the table:

  • The schemas (Capabilities.schema_version) — the pydantic models everything else rides on: structure, pattern, instrument, plan, result, history node, project document.

  • The agent envelope. agent.refine_json’s request union and response arms, and its error grammar: ok: false with a structured error whose code is one of the three in agent.ERROR_CODES — a closed list.

  • The .rex project directory (Capabilities.project_format_version).

  • The streaming event ladder (Capabilities.event_schema_version).

  • The report gates and thresholds (Capabilities.report_thresholds_version) and the indexing gates (Capabilities.indexing_thresholds_version).

  • The diagnostic and guard code vocabularies, which are open by design: a new code may appear in any release, and an existing code keeps its meaning. Branch on the codes you know; pass through the ones you do not.

The Python surface freezes as it is documented

The public call surface — every function, class, method and field a caller can reach — is derived from the live package rather than hand-listed, so a new public name cannot slip past it. Every name on it is in exactly one of three buckets:

  • Documented. A name this manual’s Part 1 documents is frozen from the release that documents it.

  • Deferred. The rest of the surface is provisional: it works as it stands today, but a name in this bucket may change in a 1.0.x release. The 1.0.x releases are the documentation road — each chapter that lands promotes its names from provisional to frozen.

  • Excluded with a written reason — documented as a protocol rather than as a type (the cancel= token), or a compile-stage internal.

In practice: if this manual names it, build on it; if not, it still works, but check the release notes before you upgrade.

Everything else is internal. Anything importable outside the derived surface is internal and may change without notice.

Provisional by declaration

The rest of the promise is by declaration rather than by bucket:

  • The GUI as a whole ships as beta, its HTTP routes included. Two things about the wire are nevertheless stated normatively below: the JSON dialect, and that the upload routes carry raw bytes.

  • The .rxt text document. It is rendered in-session and never persisted, so nothing accumulates in it. If it ever becomes a saveable file, its Capabilities.textdoc_format_version starts moving.

  • A series is session-scoped. refine_sequential returns its result and writes one history tree per pattern, but the series itself is not a saved document at 1.0.

How a change is classified

The rule is hybrid: what a change is decides which version moves.

Safe additions move no contract version. A new field with a default — on any schema, in an event’s data dict, a new key in capabilities().features — is not a break. The package version and the release notes carry it.

Closed-vocabulary additions are minor events in their own contract. A new action kind, a new indexing caveat, a new node kind, a new abstention kind, a new event kind: each moves the version string of the contract it belongs to. The classification of actions in rietx.report.apply.RECIPES is part of the report contract, so moving a kind between classes is a minor event too.

Renames, removals and threshold moves are breaking events, and each moves its contract’s version as one.

Two clauses complete the rule:

  • A change to what an existing value means is always a documented event, even when no shape changes. A field that keeps its name and type but answers a different question is the least visible break there is, so it is never silent.

  • Tolerate unknown fields and flags. Safe additions arrive without a contract version move, so validating responses against a pinned copy of the schema is unsupported: a pinned-copy validator breaks on exactly the changes this promise calls safe.

The JSON the package writes

Two facts about the wire are normative even where the routes above are not:

  • Non-finite floats serialise as the strings "Infinity", "-Infinity" and "NaN", everywhere the package writes JSON. A parameter bound of ±inf survives a round-trip; a consumer’s JSON parser must expect the string form.

  • The upload routes carry raw bytes, not JSON. A pattern file’s bytes are the contract — the file is stored byte for byte, and the reader that claimed it is recorded beside it — so no upload is wrapped, encoded or re-serialised.

The name and the formats are separate promises

The brand tokens — the distribution name, the state directory, the agent tool name — track the distribution, and would move together if the package were ever renamed. The format tokens — the .rex suffix, the rxt header word, the instrument-profile tag — name versioned contracts, and do not move because a brand did. A future rename is therefore not a format break: a project written today opens under whatever the package is called when you open it.

What a default promises

One principle decides defaults, and it explains why the same content can default differently on two surfaces: a library primitive is cheap, and a delivery surface is complete. Refinement.fit is called in loops — suites, series, parameter sweeps — so anything that costs a multiple of the fit is opt-in there (stage_reports=True). The agent envelope is read once per fit by a consumer that was not watching, so it carries the complete story by default (evidence is on). A future addition lands under the same rule, on whichever side its cost puts it.