Libertas App data-type design guidelines
For initial declarations, follow the App contract's user-facing and non-user-facing type guidance. This page owns compatibility when those declarations have been published. An unpublished design may replace experimental types, encodings and APIs without retaining predecessor variants or implementing a data converter.
Libertas protocol schemas and persistent-data schemas are published contracts. Every published version of an App must remain backward compatible with the earlier versions of those contracts.
Stable Union identity
Use one invariant Union type for each protocol or persistent-data family for the entire lifetime of the App. The Union's native type name is part of its schema identity and must not contain an App, schema, or release version.
Good stable container names include:
WeatherProtocol;DeviceControlProtocol; andStoredData.
Do not publish container names such as WeatherProtocolV1,
WeatherProtocolV2, StoredDataV3, or Protocol2026. A later App version
must continue to reference the original Union; it must not replace that Union
with a newly versioned container.
This restriction applies to the Union container. A newly appended payload type or variant may have a version-specific name when that is the clearest way to distinguish a new wire shape, although a semantic name is normally easier to maintain.
Append-only evolution
Evolve a published protocol or persistent-data Union only by appending new variants at the end of the existing declaration. A newer App version may add:
- a new protocol request, response, subscription request, or subscription-data payload type; or
- a new persistent-data payload type.
The new type becomes a new variant of the same stable Union. For every previously published variant:
- preserve its declaration position and `Discriminator` (a variant's `Fixed` constrains its payload and never identifies the variant);
- preserve its native variant name;
- preserve its payload's wire shape and meaning; and
- preserve its protocol roles and other compatibility-relevant contract.
Never insert a new variant before an existing one, remove or reorder a variant, reuse an existing discriminator, or silently change an existing payload type. If a payload needs an incompatible shape or meaning, define a new payload/variant and append it.
WeatherProtocol WeatherProtocol
0 ReadCurrent 0 ReadCurrent unchanged
1 CurrentReading version N 1 CurrentReading unchanged
-> 2 SetUnits appended
3 UnitsChanged appended
Do not evolve it by replacing the container:
WeatherProtocolV1 -> WeatherProtocolV2 invalid design
Persistent keys
Each task binds one stable key union and one stable value union under the storage schema contract. The append-only rule applies to both roots after publication. Existing key variants keep their tags, field order, numeric widths, byte bounds and optional slots.
An embedded union reserves its maximum payload width. Appending a variant to it must preserve that width and every existing encoding. If the new variant needs a larger slot, define a new layout and append a new root key variant; never enlarge the embedded slot of an existing published key variant. Reference changes or aliases must not hide an encoding change. The key codec defines byte equality.
Why both rules are required
Union declaration order determines its runtime variant index. Keeping the container's schema identity stable and appending only at the end preserves the identity and index of every existing variant.
For endpoint protocols, configuration-time conformance checks establish only the mandatory client/server contract. Optional protocol variants are checked by the client at runtime against the server declaration before use. Stable append-only indices make that runtime check reliable across App versions.
For persistent data, a newer App version must continue to recognize values written under every older version of the same data schema. Appending a new payload case preserves the older cases while allowing the newer version to write a new data shape.
Backward compatibility is directional: the newer App must understand all older published variants. An older App is not expected to understand a variant first published later. Protocol code therefore treats later optional variants as capabilities and checks support before sending them.
Declaration-order schematics
The language syntax differs, but the schema rule is identical. The following blocks are deliberately non-copyable schematics: they omit payload declarations, required user documentation, schema roles, discriminators, derives, and export annotations so that only stable identity and declaration order are visible. Do not paste them into App source.
// Keep this name and the existing declaration order in every release.
export type WeatherProtocol =
| ReadCurrent
| CurrentReading
| SetUnits; // appended by a later release
// Keep this name and append new persistent payload types at the end.
export type StoredData =
| RecordedReading
| UnitPreference; // appended by a later release
// Keep this name and the existing declaration order in every release.
pub enum WeatherProtocol {
ReadCurrent,
CurrentReading(CurrentReading),
SetUnits(SetUnits), // appended by a later release
}
// Keep this name and append new persistent payload types at the end.
pub enum StoredData {
RecordedReading(RecordedReading),
UnitPreference(UnitPreference), // appended by a later release
}
The TypeScript variants must retain their stable numeric discriminator values. The Rust declaration order supplies the corresponding variant indices. Follow the TypeScript application manual or Rust application manual for the exact protocol-role, data-schema, discriminator, derive, and export annotations. Every real package, type, field, member, variant, List side, and public function must also follow the schema documentation contract.
Review checklist
Before publishing a new App version, compare each protocol and persistent-data Union with the last published version:
- The Union has the same versionless native name and schema identity.
- Every old variant is present at the same position with the same discriminator, payload, meaning, and compatibility contract.
- All new payload types appear only as variants appended after the old ones.
- Existing code still reads every persistent-data variant published earlier.
- Clients check server support before using newly appended optional protocol variants.